10 個技巧,讓你在2017 年成為更好的 Node 開發(fā)者

本文轉載自:眾成翻譯
譯者:wleonardo
鏈接:http://www.zcfy.cc/article/2349
原文:https://www.sitepoint.com/10-tips-to-become-a-better-node-developer/

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

本文是由我們的客座作者Azat Mardan寫的绊袋。SitePoint的客座帖子希望可以給你帶來web社區(qū)里面著名的作者和演講家的有趣的內(nèi)容。

在2012年铸鹰,我加入了Storify并開始使用Node作為我的主要語言癌别。從那以后,我從來沒來回過頭來覺得我錯過了Python蹋笼,Ruby展姐,Java或PHP,這些我過去10年在web開發(fā)過程中使用的語言剖毯。

Storify提供給我一個很有趣的工作圾笨,因為Storify和其他的公司不太一樣,Storify之前(可能到現(xiàn)在也是)所有的代碼都是由JavaScript編寫的逊谋。而大多數(shù)公司擂达,特別是大公司,例如PayPal涣狗,Walmart(沃爾瑪)或者Capital One(第一資本)谍婉,只是在某一些特定的部分使用了Node舒憾。通常,他們使用Node作為API接口或者用在業(yè)務流程層穗熬,這樣做是很好的镀迂。但是作為一個軟件工程師,沒有什么比得上完全沉浸在Node環(huán)境中開發(fā)唤蔗。

下面我將會列出10個建議探遵,這些建議可以幫助你在2017年成為一個更好的Node開發(fā)者。這些建議是來自于我在日常開發(fā)中看到和學習到的和一些編寫最流行的Node和npm 模塊的開發(fā)者們妓柜。 下面是我們將要介紹的內(nèi)容:

  1. 避免復雜性 — 組織你的代碼在使其盡可能的小直到它們看上去太小了箱季,,然后使它們變得更小棍掐。

  2. 使用異步編程 — 避免使用同步代碼藏雏。

  3. 避免require阻塞 — 把你所有的require聲明都放在文件的頂部,因為require是同步的作煌,會阻塞代碼運行掘殴。

  4. 了解require緩存 — 這是一個特性,也是導致bug的原因粟誓。

  5. 始終檢查錯誤 — 錯誤不是足球奏寨,任何時候都不要拋出錯誤或者跳過錯誤檢查。

  6. 只在同步代碼中使用try…catch — 在異步代碼中try...catch是沒有作用的鹰服。V8引擎針對try...catch無法進行優(yōu)化病瞳。

  7. 返回callbacks或者使用if … else — 返回一個callback只是為了確保不繼續(xù)執(zhí)行。

  8. 監(jiān)聽錯誤事件 — 幾乎所有的Node的類/對象都有event emitter(觀察者模式)并且會廣播error事件悲酷,確保你監(jiān)聽了它們套菜。

  9. 了解你的npm — 使用-S或者-D來安裝模塊來代替--save或者--save-dev`。

  10. 在package.json中使用精確的版本號: npm在使用-S來安裝模塊時會自動使用默認的版本號舔涎,你需要手動修改去鎖定版本號笼踩。除非是開源模塊,否者不要相信你的項目中的SemVer(語義化版本標準)亡嫌。

  11. 加分 — 使用不同的依賴嚎于。把項目在開發(fā)階段需要的東西放在 devDependencies 中,記得使用 npm i --production挟冠。多余的依賴越多于购,出現(xiàn)問題的風險就越大。

好的知染,接下來讓我們一個個單獨地去了解上面的每一點肋僧。

避免復雜性

讓我看一眼npm的創(chuàng)造者Isaac Z. Schlueter寫的一寫模塊,例如,use-strict嫌吠,這個模塊是用來在Javascript中強制使用嚴格模式止潘,這個模塊僅僅只有三行代碼:

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

所以我們?yōu)槭裁匆苊鈴碗s性呢? 一個起源于美國海軍的著名短語:KEEP IT SIMPLE STUPID(或者是“Keep it simple, stupid”)。這就是原因辫诅。事實說明凭戴,人類大腦在任何一個時間只能在其工作記憶中保持五到七個項目。

把你的代碼模塊化成一個更加小的部分炕矮,你和其他的開發(fā)者會更加好的理解它么夫。你也可以更加好的去測試它。如下例子肤视,

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)

我相信大多數(shù)人都會喜歡第二個例子档痪,特別是當名字就解釋了自己的作用。當日邢滑,在你編寫代碼的時候腐螟,你可能認為你知道代碼是如何運行的。甚至你想要展示你把幾個功能連接在一起寫在同一行中是多么的機智困后。但是遭垛,這樣你是寫了一段愚蠢的代碼。如果你思考的很復雜去寫這代碼操灿,那么今后你再去看這段代碼將會很難去理解。保證你的代碼簡單泵督,特別是在Node的異步代碼中趾盐。

當然也會有left-pad 事件,但是其實它只是影響了依賴于left-pad模塊的項目而且11分鐘后就發(fā)布了替代品小腊。代碼的最小化帶來的好處超過了它餓缺點救鲤。npm已經(jīng)改變了發(fā)布策略,任何重要的項目都應該使用緩存或私有的源(作為臨時解決方案)。

使用異步編程

在Node中同步代碼只要很小的一部分秩冈。這些代碼大多數(shù)都是用于命令行工具或者其他與web應用無關的腳本本缠。Node開發(fā)者大多數(shù)都是編寫web應用,因此使用異步代碼可以避免阻塞現(xiàn)場入问。

例如丹锹,當你在編寫一個數(shù)據(jù)庫的腳本或者是一個不需要控制并行的任務時,下面這種寫法可能是可以的:

let data = fs.readFileSync('./acconts.json')
db.collection('accounts').insert(data, (results))=>{
  fs.writeFileSync('./accountIDs.json', results, ()=>{process.exit(1)})
})

但是當你創(chuàng)建一個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()})
    })
  })
})

這個區(qū)別在于你是否需要編寫一個并發(fā)(通常是長期運行)或者非并發(fā)(短期運行)的系統(tǒng)楣黍。根據(jù)經(jīng)驗來說,總是要在Node中使用異步代碼棱烂。

避免require阻塞

Node有一個使用了CommonJS模塊格式的簡單的模塊加載系統(tǒng)租漂。它是基于require函數(shù),require函數(shù)可以很方便的在不同的文件中引入模塊。和AMD/requirejs不同哩治,Node/CommonJS的模塊加載時同步的秃踩。require的工作方式是:引入一個模塊或者一個文件export的內(nèi)容:

`const react = require('react')`

但是大多數(shù)的開發(fā)者并不知道require是會被緩存的。因此业筏,只要解析的文件名(resolved filename)沒有劇烈的變化(比如npm模塊不存在的情況)憔杨,模塊的代碼只會被執(zhí)行并存入變量中一次(在當前進程中)。這是一個很好的優(yōu)化驾孔。當然芍秆,即使有了緩存,你最好還是把你的require聲明寫在開頭翠勉。下面這段代碼妖啥,它在路由中真正使用到了axios模塊的時候才加載。當請求發(fā)送的時候/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會被緩存

我在上面一節(jié)已經(jīng)提到了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')
}

從中我們了解到有一些代碼只會運行一次怀读,你可以使用這個特性來優(yōu)化你的代碼。

始終檢查錯誤

Node不是Java骑脱。在Java中菜枷,你可以拋出錯誤,因為如果發(fā)生了錯誤那么你會希望應用不在繼續(xù)執(zhí)行叁丧。在Java中啤誊,你可以在外層僅僅使用一個簡單的try...catch就可以處理多個錯誤。

但是在Node中并不是這樣的拥娄。自從Node使用了事件循環(huán)和異步執(zhí)行后蚊锹,任何的錯誤發(fā)生時都會與錯誤處理器(例如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的返回內(nèi)容包裹在try...catch中牡昆,那么我們將沒有辦法去處理請求的錯誤。Node的開發(fā)者通過在返回的參數(shù)里面加上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是并行的。但是如果你不夠細心也會因為這個特性產(chǎn)生bug摊沉。 為了安全起見狐史,應該要使用return來終止代碼的繼續(xù)執(zhí)行:

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

這樣可以避免一些因為代碼邏輯的處理不當導致一些不應該執(zhí)行的內(nèi)容(或者錯誤)被執(zhí)行。

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

請確保使用return去阻止代碼的繼續(xù)執(zhí)行。

監(jiān)聽 error 事件

Node中幾乎所有的類/對象都有事件分發(fā)器(觀察家模式)并且會廣播 error 事件骏全。 這是一個很好的特性苍柏,可以使開發(fā)者在這些討厭的錯誤造成巨大后果之前捕捉到它們。

養(yǎng)成一個通過.on()來創(chuàng)建error事件監(jiān)聽的好習慣:

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和前端的開發(fā)者知道在安裝模塊的時候使用--save會在安裝模塊的同時姜贡,會在package.json保存一條含有模塊版本信息的條目试吁。當然,還有--save-dev可以用于安裝devDependencies(在生成環(huán)境中不需要的模塊)楼咳。但是你知道用-S-D是否可以代替--save--save-dev么熄捍?答案是可以的。

當你安裝模塊的時候母怜,你需要刪除-S-D自動為你模塊的版本號添加的^標簽余耽。否者當你使用npm install(或者npm i)安裝模塊的時候,就會自動拉取最新的鏡像(版本號的第二位數(shù)字)苹熏。例如v6.1.0就是v6.2.0的一個鏡像分支碟贾。

npm團隊推薦使用semver,但是你最好不要這樣轨域。npm團隊認為開源開發(fā)者會遵守semver所以他們在npm安裝時自動加上了^袱耽。沒有人可以去保證,所以最好是鎖定你的版本號干发。更好的辦法是使用shrinkwrapnpm shrinkwrap會生成一個包含依賴的具體版本的文件朱巨。

結束語

這篇文章是兩部分的第一部分,我們已經(jīng)提到了很多方面枉长,從使用callbacks和異步代碼蔬崩,到核查錯誤和鎖定依賴。希望你們可以從中學習到一些新的搀暑,或者有用的信息。敬請期待即將推出的第二部分跨琳。

同時自点,告訴我你的想法。我是否遺漏了什么脉让?你是否有不一樣的做法桂敛?在下面的評論區(qū)告訴我你的想法吧。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末溅潜,一起剝皮案震驚了整個濱河市术唬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滚澜,老刑警劉巖粗仓,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡借浊,警方通過查閱死者的電腦和手機塘淑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚂斤,“玉大人存捺,你說我怎么就攤上這事∈镎簦” “怎么了捌治?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纽窟。 經(jīng)常有香客問我肖油,道長,這世上最難降的妖魔是什么师倔? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任构韵,我火速辦了婚禮,結果婚禮上趋艘,老公的妹妹穿的比我還像新娘疲恢。我一直安慰自己,他們只是感情好瓷胧,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布显拳。 她就那樣靜靜地躺著,像睡著了一般搓萧。 火紅的嫁衣襯著肌膚如雪杂数。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天瘸洛,我揣著相機與錄音揍移,去河邊找鬼。 笑死反肋,一個胖子當著我的面吹牛那伐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播石蔗,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼罕邀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了养距?” 一聲冷哼從身側響起诉探,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棍厌,沒想到半個月后肾胯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竖席,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年阳液,在試婚紗的時候發(fā)現(xiàn)自己被綠了怕敬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡帘皿,死狀恐怖东跪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鹰溜,我是刑警寧澤虽填,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站曹动,受9級特大地震影響斋日,放射性物質發(fā)生泄漏。R本人自食惡果不足惜墓陈,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一恶守、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贡必,春花似錦兔港、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至利花,卻和暖如春科侈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炒事。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工臀栈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挠乳。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓权薯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親欲侮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容