本文转载自:众成翻译
译者:wleonardo
链接:http://www.zcfy.cc/article/2349
原文:https://www.sitepoint.com/10-tips-to-become-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 模块的开发者们。 下面是我们将要介绍的内容:
避免复杂性 — 组织你的代码在使其尽可能的小直到它们看上去太小了,,然后使它们变得更小。
使用异步编程 — 避免使用同步代码。
避免require阻塞 — 把你所有的require声明都放在文件的顶部,因为require是同步的,会阻塞代码运行。
了解require缓存 — 这是一个特性,也是导致bug的原因。
始终检查错误 — 错误不是足球,任何时候都不要抛出错误或者跳过错误检查。
只在同步代码中使用try…catch — 在异步代码中
try...catch
是没有作用的。V8引擎针对try...catch
无法进行优化。返回callbacks或者使用if … else — 返回一个callback只是为了确保不继续执行。
监听错误事件 — 几乎所有的Node的类/对象都有event emitter(观察者模式)并且会广播
error
事件,确保你监听了它们。了解你的npm — 使用
-S
或者-D
来安装模块来代替--save或者
--save-dev`。在package.json中使用精确的版本号: npm在使用
-S
来安装模块时会自动使用默认的版本号,你需要手动修改去锁定版本号。除非是开源模块,否者不要相信你的项目中的SemVer(语义化版本标准)。加分 — 使用不同的依赖。把项目在开发阶段需要的东西放在 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
),然后展示错误信息给用户或者展示在客户端上并且记录它, or passing it back up the call stack by calling the callback with error
(if you have the callback and another function up the call stack).
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安装时自动加上了^
。没有人可以去保证,所以最好是锁定你的版本号。更好的办法是使用shrinkwrap:npm shrinkwrap
会生成一个包含依赖的具体版本的文件。
结束语
这篇文章是两部分的第一部分,我们已经提到了很多方面,从使用callbacks和异步代码,到核查错误和锁定依赖。希望你们可以从中学习到一些新的,或者有用的信息。敬请期待即将推出的第二部分。
同时,告诉我你的想法。我是否遗漏了什么?你是否有不一样的做法?在下面的评论区告诉我你的想法吧。