本文轉載自:眾成翻譯
譯者: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和異步代碼,到核查錯誤和鎖定依賴。希望你們可以從中學習到一些新的,或者有用的信息。敬請期待即將推出的第二部分。
同時,告訴我你的想法。我是否遺漏了什么?你是否有不一樣的做法?在下面的評論區告訴我你的想法吧。