Node.js 编码规约
Node.js 规约主要包含编码风格、安全规约、最佳实践等几个部分,目的是给业务同学提供研发过程中的实质性规范和指导。其中编码风格 follow eslint-config-egg。
支持的 Node.js 版本
仅支持 Current, Active LTS 和 Maintenance LTS 版本。不支持 Unstable 和 EOL 版本。不建议在任何生产项目中使用奇数 (19, 21, 23) 版本,因为他们的生命周期太短,只有六个月。
👉🏻 了解更多
1 编码风格
- 1.1 推荐 使用 Node.js 内置的全局变量。eslint: node/prefer-global
// bad
const { Buffer } = require('buffer')
const b = Buffer.alloc(16)
// good
const b = Buffer.alloc(16)
// bad
const { URL } = require('url')
const u = new URL(s)
// good
const u = new URL(s)
// bad
const { URLSearchParams } = require('url')
const u = new URLSearchParams(s)
// good
const u = new URLSearchParams(s)
// bad
const { TextEncoder } = require('util')
const u = new TextEncoder(s)
// good
const u = new TextEncoder(s)
// bad
const { TextDecoder } = require('util')
const u = new TextDecoder(s)
// good
const u = new TextDecoder(s)
// bad
const process = require('process')
process.exit(0)
// good
process.exit(0)
// bad
const console = require('console')
console.log('hello')
// good
console.log('hello')- 1.2 推荐 使用模块内支持的
promisesAPI。eslint: node/prefer-promises
Node.js 从 v11.14.0 开始支持 require('dns').promises 和 require('fs').promises API。
// bad
const dns = require('dns')
const fs = require('fs')
function lookup(hostname) {
dns.lookup(hostname, (error, address, family) => {
// ...
})
}
function readData(filePath) {
fs.readFile(filePath, 'utf8', (error, content) => {
// ...
})
}
// good
const { promises: dns } = require('dns')
const { promises: fs } = require('fs')
async function lookup(hostname) {
const { address, family } = await dns.lookup(hostname)
// ...
}
async function readData(filePath) {
const content = await fs.readFile(filePath, 'utf8')
// ...
}- 1.3 推荐 如无特殊需求,模块引用声明放在文件顶端,注意引用顺序。eslint: import/order
如无特殊需求(如动态 require),模块引用声明需要放在文件顶端。引用顺序如无特殊需求,按以下顺序来引入依赖:node 内置模块、npm 包、本地文件或其他,几类文件代码块之间各空一行,每类文件代码块中的引用顺序按照字典排序,如有解构引用情况,字典序以解构的第一个为准,解构内部按照字典排序。
// bad
// bad
import fs from 'fs'
import Car from './models/car'
import moment from 'moment'
import mongoose from 'mongoose'
import http from 'http'
import { Bar, Foo } from 'tool'
import note from 'note'
// good
import fs from 'fs'
import Car from './models/car'
const Car = require('./models/car')
const moment = require('moment')
const mongoose = require('mongoose')
const fs = require('fs')
const http = require('http')
const { Foo, Bar } = require('tool')
const note = require('note')
// good
const fs = require('fs')
const http = require('http')
const { Bar, Foo } = require('tool')
const moment = require('moment')
const mongoose = require('mongoose')
const note = require('note')
const Car = require('./models/car')- 1.4 推荐 抛出异常时,使用原生
Error对象。eslint: no-throw-literal
// bad
throw 'error'
throw 0
throw undefined
throw null
const err = new Error()
throw `an ${err}`
const err = new Error()
throw `${err}`
// good
throw new Error()
throw new Error('error')
const err = new Error('error')
throw err
try {
throw new Error('error')
} catch (err) {
throw err
}1.5 推荐 线上环境尽量不要使用 fs/child_process 模块的 sync 方法,如 fs.readFileSync()、cp.execSync() 等。
这样会阻塞 Node.js 应用的进程,导致不能继续处理新的请求,或当前正在处理的请求超时。推荐使用 require('fs').promises 方式或使用 mz。
// bad
const fs = require('fs')
function test() {
fs.readFileSync('./somefile', 'utf-8')
}
// good
const { promises: fs } = require('fs')
async function test() {
await fs.readFile('./somefile', 'utf-8')
}
// good
const fs = require('mz/fs')
async function test() {
await fs.readFile('./somefile', 'utf-8')
}2 安全规约
- 2.1 强制 在客户端隐藏错误详情。
错误提示有可能会暴露出敏感的系统信息,容易被利用去做进一步的攻击。
- 2.2 强制 隐藏或伪造技术栈和框架标识。
隐藏或伪造 X-Powered-By 响应头,应用广泛的框架多有公开的漏洞,防止标识露出被恶意利用。
- 2.3 强制 JSONP 跨域接口必须严格校验访问来源。
配置域名白名单,防止通过 JSONP 接口获取到敏感信息的风险。
- 2.4 强制 禁止使用从参数或明文 cookie 中获取的用户标识进行敏感信息查询输出。
防止未授权访问/越权访问。
- 2.5 强制 防止 SQL 注入。
含有用户输入内容的 SQL 语句必须使用预编译模式。若用户输入无法使用预编译模式(输入为表名/字段名等内容),需要对用户输入进行转义/过滤之后再拼接到 SQL 中。
- 2.6 推荐 定期检查过期依赖和依赖漏洞升级。
检测依赖,对于有漏洞或者过期的依赖要及时升级或替换。
- 2.7 推荐 用户上传文件不允许至服务器本地,需要上传到 OSS 等服务。
任意文件上传漏洞,防止用户上传恶意文件,入侵服务器。
- 2.8 推荐 服务端 URL 重定向需要设置白名单。
若需要对用户输入内容作为目标 URL 进行重定向,需要对其进行域名白名单校验,不允许跳转至白名单外的域名。
- 2.9 推荐 对接口入参严格校验。
使用 jsonschema 或 joi 校验入参,减少意外输入造成的程序报错或崩溃,同时也能减少脏数据形成。
3 最佳实践
- 3.1 推荐 应用不应该有状态。
使用外部数据存储。保证即使结束某个应用实例也不会影响数据和服务。
- 3.2 推荐 尽量不要用 Node.js 应用去托管前端静态文件。
应该把前端静态文件放到 CDN,当静态文件的访问量很大的时候,可能会阻塞其他服务的执行。
- 3.3 推荐 把 CPU 密集型任务委托给反向代理。
Node.js 应用不合适做 CPU 密集型任务(例如 gzip,SSL),请尽量把这类任务代理给 nginx 或其他服务。
- 3.4 推荐 使用
async/await,尽量避免使用回调函数。
async/await 可以让你的代码看起来更简洁,可以规避掉回调地狱的问题,并且使异常处理也变得清晰简单。
- 3.5 推荐 使用
util.promisify处理回调函数,使其返回Promise。
const util = require('util')
const fs = require('fs')
const stat = util.promisify(fs.stat)
async function callStat() {
const stats = await stat('.')
console.log(`This directory is owned by ${stats.uid}`)
}3.6 推荐 使用 Node.js 原生
Promise,而不是三方库如bluebird。3.7 推荐 在类方法中返回
this方便链式调用。
class Jedi {
jump() {
this.jumping = true
return this
}
setHeight(height) {
this.height = height
return this
}
}
const luke = new Jedi()
luke.jump().setHeight(20)- 3.8 推荐 使用 阿里云 Node.js 性能平台 作为应用的性能监控工具。
阿里云 Node.js 性能平台提供 Node.js 应用性能监控、管理及报警,性能快照远程截取与调优, 安全与依赖更新提示,异常日志与慢 HTTP 日志等功能,能有效帮助开发者监控和排查 Node.js 应用性能问题。
配套工具
- eslint-config:本规约配套的 ESLint 规则包,可使用
eslint-config/node或eslint-config/typescript/node引入本文介绍的规则