10 个技巧,2017 年成为更好的 Node 开发者 已翻译 100%

oschina 投递于 2017/01/16 15:08 (共 10 段, 翻译完成于 01-17)
阅读 3241
收藏 64
5
加载中

我会在这篇文章中简述 10 个技巧,帮助你在 2017 年成为更好的 Node 开发者。这些技巧有一些是我在实践中学习和领悟的,还有一些从优秀的 Node 和 npm 模块作者那里借鉴而来。本文的内容包含如下一些:

  1. 避免过于复杂 — 把代码按尽可能小的块来组织。

  2. 使用异步代码 — 避免像灾难一样的同步代码。

  3. 避免阻塞请求 — 把所有 require 语句放在文件的顶部,因为它们是同步的,会阻塞执行中的程序。

  4. 了解 require 会被缓存 — 这会在你的代码中起到正面或负面的作用。

  5. 总是检查错误 — 错误不是足球,不要抛弃错误,也不要跳过错误检查。

  6. 只在同步代码中使用 try…catch — try...catch 对异步代码无效,而且 V8 也不能像优化普通代码一样优化 try...catch。

  7. 返回回调或使用 if … else — 确定返回回调阻止继续执行程序。

  8. 监听错误事件 — 几乎所有 Node 的类/对象都从 EventEmitter 继承(观察者模式),并触发错误事件。请确认监听了这些事件。

  9. 了解 npm — 安装模块时使用 -S 或 -D,而不是 --save 或 --save-dev

  10. 在 package.json 中使用确定的版本:npm 会在你使用 -S 的时候选用默认版本,这很愚蠢。所以你需要手工修改版本号。不要相信 semver[译者注:语义化版本标准],但在开源模块中需要这样做。

  11. 加分 — 使用不同的依赖。把项目在开发阶段需要的东西放在 devDependencies 中,记得使用 npm i --production。多余的依赖越多,出现问题的风险就越大。

我们应该分别看待上述每一条,不是吗?

Viyi
Viyi
翻译于 2017/01/17 10:29
1

避免过于复杂

Isaac Z. Schlueter,是 npm 的创造者,我们来看看他写的代码。例如,用 use-strict 在模块中强制实施 JavaScript 严格模式,只需要三行代码:

var module = require('module')
module.wrapper[0] += '"use strict";'
Object.freeze(module.wrap)

为什么要避免复杂性呢?美国海军传说中,有一句短语很著名:化繁为简,返璞归真。(或者“保持简单,蠢货!”)。因为事实证明,人的大脑在同一时刻只能记忆 5 到 7 项内容。

把代码模块化为更小的部分,你和其它开发者才能更好的理解它。这样你也可以更好的测试它。看看这个例子:

app.use(function(req, res, next) {
  if (req.session.admin === true) return next()
  else return next(new Error('Not authorized'))
}, function(req, res, next) {
  req.db = db
  next()
})

或者这个:

const auth = require('./middleware/auth.js')
const db = require('./middleware/db.js')(db)

app.use(auth, db)

我相信大多数人会更喜欢第2个例子,尤其是在名称可以自解释的时候。当然,如果你在写代码,你会想知道它的工作方式。有时,你可能会耍耍小聪明,将多个方法在同一行里进行链式调用。不过,请按最简单的方式编写代码。因为在时隔 6 个月,或在你喝醉或极度兴奋的时候,再来看你写的这些代码,可能你自己都难以理解,更不用说那些不了解它的算法和复杂性的同事了。简单做事,尤其是在使用 Node 的异步方式的时候。

有一种 left-pad 事件,不过它只影响了依赖公共注册表的项目,并在 11 分钟之后重新发布。最小化所带来的益处远大于其缺点。而且,npm 已经修改了它的发布策略,任何重要的项目都应该使用缓存或私有注册中心(作为临时解决方案)。

Viyi
Viyi
翻译于 2017/01/17 10:50
0

使用异步代码

同步代码确实在 Node 中有一个(低的)位置。 它主要用于编写 CLI 命令或与 Web 应用程序无关的其他脚本。Node 开发者主要构建 Web 应用程序,因此他们使用异步代码,以避免阻塞线程。

例如,如果我们只是构建数据库脚本,而不是用来处理并发/并行任务的系统,这样就行了:

let data = fs.readFileSync('./acconts.json')
db.collection('accounts').insert(data, (results))=>{
  fs.writeFileSync('./accountIDs.json', results, ()=>{process.exit(1)})
})

但是在构建 Web 应用时下面写法会更佳:

app.use('/seed/:name', (req, res) => {
  let data = fs.readFile(`./${req.params.name}.json`, ()=>{
    db.collection(req.params.name).insert(data, (results))=>{
      fs.writeFile(`./${req.params.name}IDs.json`, results, ()={res.status(201).send()})
    })
  })
})

区别在于你是否在写并发(通常是长时间运行)还是非并发(短时间运行)的系统。 根据经验,我们总是在 Node 中使用异步代码。

Tocy
Tocy
翻译于 2017/01/16 17:30
0

避免阻塞请求

Node 有一个简单的模块加载系统,使用 CommonJS 模块规范。它内建的 require 函数很容易把另外单独存放的的模块包含进来。与 AMD/requirejs 不同,Node/CommonJS 采用同步的方式加载模块。require工作方式是:导入在模块或文件中导出的内容

const react = require('react')

大多数开发知道 require 有缓存。因此,只要解析出来的文件名没什么变化(在 npm 模块中是没有的),模块中的代码会只执行一次并将结果保存在一个变量中(同一进程内)。这个优化非常棒。然而,在有缓存的情况下,你仍然最好把 require 语句放在前面。看看下面的代码,在真正进入路由的时候才加载 axios 模块。/connect 路由出乎预料的慢,因为它在导入模块的时候才开始请求文件[译者注:IO 操作比 CPU 运算慢很多]:

app.post('/connect', (req, res) => {
  const axios = require('axios')
  axios.post('/api/authorize', req.body.auth)
    .then((response)=>res.send(response))
})

更好更高效的方式是服务器启动后就加载模块,而不是在路由中:

const axios = require('axios')
const express = require('express')
app = express()
app.post('/connect', (req, res) => {
  axios.post('/api/authorize', req.body.auth)
    .then((response)=>res.send(response))
})
Viyi
Viyi
翻译于 2017/01/17 11:01
0

了解 require 会被缓存

我在上一节提到过 require 缓存,但有趣的是,我们可以有 module.exports 之外的代码。例如,

console.log('I will not be cached and only run once, the first time')

module.exports = () => {
  console.log('I will be cached and will run every time this module is invoked')
}

知道一些代码可能仅运行一次,你可以使用这种此功能作为优势。

Tocy
Tocy
翻译于 2017/01/17 09:04
0

总是检查错误

Node 不是 Java。在 Java 中,你可以抛出错误,因为多数时候你会在这些错误发生时中止应用程序的执行。在 Java 中,你可以通过一个单独的 try ... catch 处理多个错误。

在 Node 中不是这样。Node 使用事件循环和异步执行,错误发生时会不属于与处理代码(比如 try...catch)不同的上下文中。下面的作法在 Node 中无效:

try {
  request.get('/accounts', (error, response)=>{
    data = JSON.parse(response)
  })
} catch(error) {
  // Will NOT be called
  console.error(error)
}

不过 try...catch 仍然可以用于同步的 Node 代码。对上面的代码进行重构之后就好多了:

request.get('/accounts', (error, response)=>{
  try {
    data = JSON.parse(response)
  } catch(error) {
    // Will be called
    console.error(error)
  }
})

如果我们不能将 request 调用放在 try...catch 块中,我们就不能处理来自 request 的错误。Node 开发者采用提供包含 error 参数的回调来解决这个问题。这样你需要在每个回调中手工处理错误。你需要检查 error(确保它不是 null),然后将相应的错误消息显示给用户或者客户端,并记录下来。也可以通过调用栈中的 callback 往回传(如果你有回调,而且调用栈上还有另一个函数)。

request.get('/accounts', (error, response)=>{
  if (error) return console.error(error)
  try {
    data = JSON.parse(response)
  } catch(error) {
    console.error(error)
  }
})

你还可以使用 okay 库。你可以像下面的代码那样使用它来避免在无数的回调中手工检查错误(你好,回调地狱)。

var ok = require('okay')

request.get('/accounts', ok(console.error, (response)=>{
  try {
    data = JSON.parse(response)
  } catch(error) {
    console.error(error)
  }
}))
Viyi
Viyi
翻译于 2017/01/17 11:16
0

返回回调或使用 if … else

Node 是并发的。所以,如果不加注意,它可能会变成一个错误。 为了安全起见,我们使用 return 语句终止执行:

let error = true
if (error) return callback(error)
console.log('I will never run - good.')

确保返回一个回调,以防止继续执行。

总长
总长
翻译于 2017/01/16 23:40
0

监听错误事件

几乎所有的 Node 类/对象都扩展了事件发射器(观察者模式)并抛出错误事件。在错位被破坏之前,这给开发人员提供了捕获错误并处理的机会。

使用 .on() 为错误创建事件侦听器是个好习惯:

var req = http.request(options, (res) => {
  if (('' + res.statusCode).match(/^2\d\d$/)) {
    // Success, process response
  } else if (('' + res.statusCode).match(/^5\d\d$/))
    // Server error, not the same as req error. Req was ok.
  }
})

req.on('error', (error) => {
  // Can't even make a request: general error, e.g. ECONNRESET, ECONNREFUSED, HPE_INVALID_VERSION
  console.log(error)
})
Tocy
Tocy
翻译于 2017/01/17 09:09
0

了解 npm

很多 Node 开发者甚至前端开发者都知道 --save(npm install 的参数)可以安装一个模块并在 package.json 中记录模块的版本。另外,还有 --save-dev,用于在 devDependencies 添加记录(记录那些不需要在发布时的模块)。不过你知道可以用 -S 和 -D 代替 --save 和 --save-dev 吗?你可以尝试这样做。

在 package.json 中使用准确的版本号

在安装模块的时候,去删除 -S  和 -D 为你添加的那些 ^ 记号。它们非常危险,因为它们允许 npm install(或简写为 npm i)从 npm 库中拉取最新的小版本(语义化的版本号中的第2个数)。比如从 v6.1.0 到 v6.2.0 就是一个小版本发布。

npm 团队信任 semver,但你不能。他们加上 ^ 符号是因为他们相信开源作者不会在小版本中引入破坏性的修改。然而明眼人都知道它是不可靠的。你应该锁定版本号,最好使用 shrinkwrap:npm shrinkwrap 创建一个包含依赖的具体版本的新文件。

Viyi
Viyi
翻译于 2017/01/17 11:27
0

结语

以上是文章的第一部分。 我们已经涵盖了很多东西,从使用回调函数和异步代码,到检查错误和锁定依赖。 我希望你在这里找到了一些新的或有用的东西。

总长
总长
翻译于 2017/01/16 23:45
0
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(9)

c
coolcao
好像大家都没真正理解作者说的同步代码的问题,因为node的优势就是异步调用,而不是同步,因此作者说避免同步代码,使用异步最大化node性能。
青木_
青木_
现在不都是 await + async么
jayU
jayU

引用来自“young7”的评论

内容比较大路货,估计是早期作品。现在对付异步调用最好的方式是Promise + co,等es7的await async出来之后估计连co都可以扔掉
co 已扔
xxx2xxx
xxx2xxx
不清楚 同步代码 怎么灾难了。

异步代码回调地狱才是灾难; 代码里充斥着各种为了实现同步方式写异步代码的语法糖才是灾难吧 😄
刘学炜
刘学炜
同步代码怎么了?异步才是灾难啊
young7
young7
内容比较大路货,估计是早期作品。现在对付异步调用最好的方式是Promise + co,等es7的await async出来之后估计连co都可以扔掉
颜风
颜风
先跑起来再说,
AutoPlus
AutoPlus
|>>>>>>>>>>>>>>>>>>>>>>>> Isaac Z. Schlueter,是 npm 的创造者,我们来看看他写的代码。例如,用 use-strict 在模块中强制实施 JavaScript 严格模式,只需要三行代码:为什么要避免复杂性呢?美国海军传说中,有一句短语很著名:化繁为简,返璞归真。(或者“保持简单,蠢货!”)。因为事实证明,人的大脑在同一时刻只能记忆 5 到 7 项内容。>>>>>>>>>>>>>>>>>>>>>>>> | 对于这句话,反对的不能再反对了。只是写的代码简洁毫无价值 --- 你用了别人的库,别人又用了你的库,... 。这就像个反应堆,没完没了,如果最初的那段代码写的非常糟糕,那么后面的简洁毫无价值。 简洁应该是算法的清晰可推导,一个不懂编程的人,看到你的代码也能大致理解你的意图,而不是 “只需要三行代码”。
边城
边城
我只能说:不完全赞同原作者的观点
返回顶部
顶部