10 个技巧,让你在2017 年成为更好的 Node 开发者

A person in a traditional cross-legged meditation pose, focusing on becoming a better Node developer

本文是由我们的客座作者Azat Mardan写的。SitePoint引入客座帖子的目的是希望能给你带来web社区里著名作者和演讲者的有趣内容。

在2012年,我加入了Storify并开始使用Node作为我的主要语言。从那以后,我从未回首过去并觉得我错过了Python,Ruby,Java以及PHP,这些在过去10年里,我在web开发过程中使用的语言。

Storify提供给我一个很有趣的工作,因为Storify和其他的公司不太一样,Storify之前(可能到现在也是)所有的代码都是由JavaScript编写的。而大多数公司,特别是大公司,例如PayPal,Walmart(沃尔玛)或者Capital One(第一资本),只是在某一些特定的部分使用了Node。通常,他们使用Node作为API接口或者用在业务流程层,这样做是很好的。但是作为一个软件工程师,没什么比得上能够完全沉浸在Node环境里。

下面我将列出10条建议,这些建议可以帮助你在2017年成为一个更好的Node开发者。其中一些建议是我在日常实践中所学到的,另一些是从那些写了最流行的Node和npm模块的人们身上学到的。 下面是我们将要介绍的内容:

  1. 避免复杂性 — 尽可能将你的代码块拆到最小,要小到极致。

  2. 使用异步编程 — 像躲避瘟疫般避免使用同步代码。

  3. 避免require阻塞 — 把你所有的require声明都放在文件的顶部,因为require是同步的,会阻塞代码运行。

  4. 了解require缓存 — 了解它则可以利用它,否则它可能会带来bug。

  5. 始终检查错误 — 错误不是足球,任何时候都不要抛出错误或者跳过错误检查。

  6. 只在同步代码中使用try…catch — 在异步代码中try...catch是没有作用的。V8引擎针对try...catch无法进行优化。

  7. 返回callbacks或者使用if … else — 返回一个callback只是为了确保不继续执行。

  8. 监听错误事件 — 几乎所有的Node的类/对象都有event emitter(观察者模式)并且会广播error事件,确保你监听了它们。

  9. 了解你的npm — 使用-S或者-D来安装模块来代替--save或者--save-dev`。

  10. 在package.json中使用精确的版本号: npm在使用-S来安装模块时会自动使用默认的版本号,你需要手动修改去锁定版本号。除非是开源模块,否者不要相信你的项目中的SemVer(语义化版本标准)。

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

好的,接下来让我们一个个单独地去了解上面的每一点。

避免复杂性

让我看一眼npm的创造者Isaac Z. Schlueter写的一些模块,例如,use-strict,这个模块是用来在Javascript中强制使用严格模式,这个模块仅仅只有三行代码:

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

所以我们为什么要避免复杂性呢? 一个起源于美国海军的著名短语:KEEP IT SIMPLE STUPID(或者是“Keep it simple, stupid”)。这就是原因。事实说明,人类大脑在任何一个时间只能在其工作记忆中保持五到七个项目。

把你的代码模块化成一个更加小的部分,你和其他的开发者会更加好的理解它。你也可以更加好的去测试它。如下例子,

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)

我相信大多数人都会喜欢第二个例子,特别是光看名字就能了解其作用。当日,在你编写代码的时候,你可能认为你知道代码是如何运行的。甚至你想要展示你把几个功能连接在一起写在同一行中是多么的机智。但是,这样你是写了一段愚蠢的代码。如果你思考的很复杂去写这代码,那么今后你再去看这段代码将会很难去理解。保证你的代码简单,特别是在Node的异步代码中。

当然也会有left-pad 事件,但是其实它只是影响了依赖于left-pad模块的项目而且11分钟后就发布了替代品。代码的最小化带来的好处超过了它的缺点。npm已经改变了发布策略,任何重要的项目都应该使用缓存或私有的源(作为临时解决方案)。

使用异步编程

在Node中同步代码只要很小的一部分。这些代码大多数都是用于命令行工具或者其他与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中使用异步代码。

避免require阻塞

Node有一个使用了CommonJS模块格式的简单的模块加载系统。它是基于require函数,require函数可以很方便的在不同的文件中引入模块。和AMD/requirejs不同,Node/CommonJS的模块加载时同步的。require的工作方式是:引入一个模块或者一个文件export的内容:

`const react = require('react')`

但是大多数的开发者并不知道require是会被缓存的。因此,只要解析的文件名(resolved filename)没有剧烈的变化(比如npm模块不存在的情况),模块的代码只会被执行并存入变量中一次(在当前进程中)。这是一个很好的优化。当然,即使有了缓存,你最好还是把你的require声明写在开头。下面这段代码,它在路由中真正使用到了axios模块的时候才加载。当请求发送的时候/connect会因为需要加载模块所以会变得慢。

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))
})

知道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')
}

从中我们了解到有一些代码只会运行一次,你可以使用这个特性来优化你的代码。

始终检查错误

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在同步代码中是可以被用的。前面的代码片段可以被更好的重构为:

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

如果我们无法将request的返回内容包裹在try...catch中,那么我们将没有办法去处理请求的错误。Node的开发者通过在返回的参数里面加上error来解决了这个问题。因此,我们需要在每一个回调中手动去处理错误。你可以去检查这些错误(判断error不是null),然后展示错误信息给用户或者展示在客户端上并且记录它, 或者你可以通过调用 callback ,给它传 error 参数,将错误传回给上一级调用栈(如果你在调用栈之上有另一个回调函数)。

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)
  }
}))

返回回调或者使用if … else

Node是并行的。但是如果你不够细心也会因为这个特性产生bug。 为了安全起见,应该要使用return来终止代码的继续执行:

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

这样可以避免一些因为代码逻辑的处理不当导致一些不应该执行的内容(或者错误)被执行。

let error = true
if (error) callback(error)
console.log('I will run. Not good!')

请确保使用return去阻止代码的继续执行。

监听 error 事件

Node中几乎所有的类/对象都有事件分发器(观察者模式)并且会广播 error 事件。 这是一个很好的特性,可以使开发者在这些讨厌的错误造成巨大后果之前捕捉到它们。

养成一个通过.on()来创建error事件监听的好习惯:

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)
})

了解你的npm

很多的Node和前端的开发者知道在安装模块的时候使用--save会在安装模块的同时,会在package.json保存一条含有模块版本信息的条目。当然,还有--save-dev可以用于安装devDependencies(在生成环境中不需要的模块)。但是你知道用-S-D是否可以代替--save--save-dev么?答案是可以的。

当你安装模块的时候,你需要删除-S-D自动为你模块的版本号添加的^标签。否者当你使用npm install(或者npm i)安装模块的时候,就会自动拉取最新的镜像(版本号的第二位数字)。例如v6.1.0就是v6.2.0的一个镜像分支。

npm团队推荐使用semver,但是你最好不要这样。npm团队认为开源开发者会遵守semver所以他们在npm安装时自动加上了^。没有人可以去保证,所以最好是锁定你的版本号。更好的办法是使用shrinkwrapnpm shrinkwrap会生成一个包含依赖的具体版本的文件。

结束语

这篇文章是两部分的第一部分,我们已经提到了很多方面,从使用callbacks和异步代码,到核查错误和锁定依赖。希望你们可以从中学习到一些新的,或者有用的信息。敬请期待即将推出的第二部分。

同时,告诉我你的想法。我是否遗漏了什么?你是否有不一样的做法?在下面的评论区告诉我你的想法吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容