每个Node开发者应该遵循的最佳实践
所有项目都应遵循最佳实践以提高效率和质量
Node.js作为一种用于后端开发的异步和事件驱动的语言,在开发者中很受欢迎。它的语法即使对初学者来说也足够容易理解。
无论你是初学者还是使用Node的经验丰富的开发者,在开发面向生产的应用程序时,遵循Node.js编程的最佳实践是必须的。
在这篇文章中,我们将指出每个Node开发者应该遵循的10个最佳实践。在作为一个程序员应该遵循的一般最佳编码实践之上,这些将帮助你最大限度地利用Node的独特功能。
将准确的软件包版本保存到package.json中
在开发过程中,我们使用以下命令向应用程序添加一个新的包:
npm install express --save
它将软件包及其安装版本保存到package.json文件中,像这样:
"express": "^4.17.1",
在这里,使用圆点符号(^)表示一个版本在">=4.17.1 <5.0.0 "区域内的软件包被应用程序接受。(如果你想知道更多关于像caret这样的符号如何定义版本范围,请阅读SemVar文档)。
当别人使用我们创建的package.json文件来设置应用环境和安装依赖项(使用npm install命令)时,这就成了问题。
在这种情况下,npm安装的不是我们用于开发的包的版本,而是其他用户安装依赖项时的最新版本。如果最新版本与我们在最初执行时使用的版本不兼容,就会导致错误。特别是,如果该项目是开源的,我们需要注意这种可能性。
我们可以使用两种方法来防止这种软件包版本的冲突。
一种方法是在每次安装时使用--save-exact ,并将包保存到package.json文件中:
npm install express --save --save-exact
或者使用以下命令对npmrc文件进行一次配置,以便在默认情况下,所安装的每个软件包的接受版本都是其确切的版本:
npm config set save-exact=true
这两种方法都是将每一个新安装的软件包保存到package.json文件中,没有前面的关健词:
"express": "4.17.1",
现在,每次别人使用package.json文件安装依赖项时,npm都会安装文件中提到的确切版本,而不是最新版本。
使用一个工具,在每次修改代码后重新启动你的应用程序
当你还在开发阶段时,如果你不使用额外的工具,你将不得不停止并重新启动你的应用程序,以应用对代码库的简单修改。编码一段时间后,这将成为一个麻烦,不会让你完全集中在任务上。
作为一个解决方案,你可以使用一个工具来监控应用程序的代码库,并在每次做出改变时自动重新启动应用程序。
一些可用于Node的流行代码监控包是:
- Nodemon。每当代码中加入新的变化时,Nodemon就会自动重启应用程序。你可以通过在命令行上用nodemon代替node来初始化Nodemon(例如,用nodemon app代替node app。)
- 永远。Forever也提供了Nodemon提供的自动重启功能,但有额外的配置选项。这些选项包括设置一个工作目录,以及将通常在stdout上打印的日志写到一个文件中。
- PM2。PM2是另一个你可以使用的进程管理工具。与其他两个相比,它允许更多的控制和功能来管理生产中运行的进程。
你可以使用这些工具,特别是Forever和PM2,甚至在生产环境中使用。它将保证应用程序在运行时出错后无问题地恢复。它还将确保在其运行的服务器发生故障时,应用程序能尽快重新启动。
使用风格指南
当一些开发人员在同一个项目上工作时,不可避免地,他们的代码的风格会有差异。因为,不幸的是,程序员是一群非常有主见的人。
如果你的代码风格与之前从事你目前正在进行的项目的开发人员不同,你很可能会受到诱惑,按照自己的喜好重新设置大括号的位置,或者用制表符替换空格。而你最终会在这个平凡无用的任务上花费数小时,这对你的心灵平静是相当必要的。
尽管程序员们喜欢在空闲时间争论什么是Node编程的最佳风格,但这种选择大多是主观的。所以,为了你的心态平和和工作效率,我们建议你在项目开始前选择并同意一个特定的风格指南。你甚至可以找到其他程序员使用的现有风格指南。
我们可以使用一些工具来确保选定的风格指南的规则在整个程序中得到执行。我们可以用ESLint来验证写好的代码,用Prettier来实现自动格式化的目的。
使用async/await来代替回调
当Node刚被引入时,它的异步性是通过使用回调来维持的。但正如任何Node开发者从经验中知道的那样,当回调被一个接一个地嵌套时,不需要很长时间就会失去控制。这就是我们所说的回调地狱。在这一点上,你的代码将是相当难读的。
然而,随着ES6中async/await的引入(以及之前的承诺),Node已经为这个问题提供了解决方案。因此,作为一个Node开发者,你有责任利用这个新的引入,而不是在有办法避免的情况下再陷入回调地狱。
这是回调地狱的一个温和版本:
function callbackHellExample(err, result, cb) {
if (err) { cb(err) }
functionA(result, (resultA) => {
functionB(resultA, (resultB) => {
cb(resultB)
})
})
}
我们可以使用async/await轻松地避免遇到这样的情况。
async function asyncAwaitExample(err, result) {
if (err) { return err }
let resultA = await functionA(result)
let resultB = await functionB(resultA)
return resultB
}
作为Node编程的最佳实践,总是尝试用async和await来代替回调函数。
使用适当的日志工具
首先,console.log不是一个合适的日志工具。忘了把它用于你的应用程序的每一个日志目的。在许多限制中,console.log并没有为日志提供足够的配置选项,包括根据需要过滤它们的选项。
Node有几个专门的日志框架,你可以在你的应用程序中使用。Winston、Bunyan和Pino是这类工具的几个例子。你可以测试并选择一个适合你的应用程序的需求。
除了简单的记录外,它们还可以让你选择定义记录级别,如错误、警告、信息和调试。有了专门的日志工具,你还可以根据情况只打开必要级别的日志,即使应用程序处于生产状态。
在使用你选择的工具创建一个日志系统时,不要忘记遵循最佳的日志实践。
总是尝试使用const而不是let
尽可能使用const来声明一个变量而不是let。
当你使用const来声明一个变量时,你不能在第一次赋值后给它重新赋值。这可以防止你在多个场合重复使用同一个变量。由于 const 迫使你用适当的名字声明新的变量,而不重复使用现有的变量,所以它使你的代码更干净、更清晰。
但在某些情况下,你可能希望能够灵活地改变变量的值。那么,你可以使用let来声明变量,但前提是你已经正确地把它确定为一个可以改变其值的变量。例如,你必须使用let关键字在for循环内声明增量变量。
在ES6中引入let之前,Node中使用var来声明变量。一些开发者出于习惯,仍然在他们的代码中使用var。但作为Node的最佳实践,不要再在你的代码中使用var了。
同样重要的是,在使用它来声明变量之前,要注意这些关键字的作用域,无论是const还是let。
使用代码覆盖率工具来跟踪未测试的代码
我们没有把测试你的Node应用程序列为最佳实践之一,因为这是不言而喻的事情。然而,使用代码覆盖工具是你作为一个Node开发者绝对应该遵循的最佳实践之一。
我们可以使用代码覆盖工具来确定程序中的测试覆盖水平。这样的工具可以识别测试覆盖率是否有下降,并突出测试不匹配。
Istanbul/NYC是一个很好的工具,你可以用于这个目的。它提供彩色的代码覆盖率报告,帮助你轻松识别你所写的单元测试没有覆盖的区域。你可以轻松地设置NYC,使其与你喜欢的测试框架一起工作。
首先,从npm安装该包作为开发依赖:
npm install nyc --save-dev
然后,添加脚本来运行NYC并生成代码覆盖率报告:
"scripts": {
"test": "mocha",
"coverage: "nyc npm run test"
}
现在你可以使用npm coverage命令来生成报告。
阅读我上面链接的文档,了解如何配置NYC以适应你的应用程序。
使用===运算器而不是===运算器
在Node中,你可以使用===操作符和===操作符来检查两个变量是否相等。虽然第一个运算符更为严格,但第二个运算符也接受较弱的相等。
对于===运算符,两个变量必须是相同的类型,而且在数值上相似:
1 === 1 //true
1 === "1" //false
false === 0 //false
0 === “” //false
“” === false //false
null === undefined //false
NaN === null //false
NaN === undefined //false
NaN === NaN //false
第二个运算符,==,如果两个变量的值相等,即使它们的类型不同,也会被接受为相等。在进行比较之前,它将两个变量转换为一个共同的类型(类型强制):
1 == 1 //true
1 == "1" //true
false == 0 //true
0 == “” //true
“” == false //true
null == undefined //true
NaN == null //false
NaN == undefined //false
NaN == NaN //false
正如你在上面的例子中所看到的,使用==运算符得到的答案与我们通常期望的不同。这是因为Javascript在类型协整时的行为与我们的直觉相反。如果你能记住所有奇怪的情况,并在使用==运算符编写程序时注意到这些情况,那就不是问题。
但是你可以通过使用严格的平等运算符===来进行比较,从而轻松省去对这种不直观的行为的担心。
不要从Node中提供你的前端资产
Node在本质上只使用一个线程来服务所有向服务器发出的请求。我们必须注意最大限度地提高这个单线程的效率,因为,否则会对应用程序的性能产生很大影响。
如果我们使用Node服务器来发送静态内容,如HTML、CSS文件和图片,它们会占用单线程。这不公平地影响了应用程序的动态内容,因为它们是真正利用了应用程序的内部组件和逻辑。由于动态和静态内容之间的执行时间分配不平衡,我们的应用程序就会大大减慢速度。
为了避免这种不平衡,你可以将静态内容从Node服务器上移走,并使用一个专门的中间件,如Nginx、S3或CDN,将它们提供给用户。然后,Node就有机会提供动态内容,这实际上是利用了它的内部逻辑,并防止静态内容导致的性能下降。
保持你的应用程序无状态
将你需要存储的任何类型的数据,如用户会话、用户数据、缓存,尽管应用在外部数据存储中。不要在应用程序本身存储任何信息。
简而言之,保持你的应用程序无状态。你应该能够每天杀死你的服务器并重新启动它而不影响它对用户的服务。作为这种做法的替代方案,你可以使用AWS Lambda这样的无服务器平台,该平台默认会施加无状态行为。
保持服务器的无状态,使应用程序能够在任何最终的系统故障中生存下来,而不损害其服务和性能。
总结
在这篇文章中,我们讨论了作为一个Node开发者应该遵循的10个最佳编码实践。这些实践帮助你编写更好的代码,以确保你的应用程序的最佳性能。
需要注意的是,这些是你应该遵循的实践,以及其他适用于任何编程语言的最佳编码实践。在你第一次尝试时,你可能无法使用所有这些做法。选择你认为最重要的一两个,然后从那里开始。最终,你将能够在编写Node.js代码时遵循所有这些做法。
谢谢你的阅读!