皇帝的新衣:Node.js
現在有很多人非難Node.js(例如著名的Node.js is cancer),但是反對者往往誤解其中所傳達的信息并用一些無關的觀點進行反駁。更麻煩的是現在有兩類人在使用Node.js,第一類人需要一個高并發的服務器來同時處理大量的連接(例如HTTP代理、Websocket聊天服務器等等),第二類人是重度依賴于JavaScript,他們在瀏覽器、服務器、數據庫甚至洗衣機上都用JS。
我想在這里對那些關于Node.js的奇怪的、有誤導性的觀點一一進行反駁。
Node.js超級快!
這還不夠準確,我們把它分成兩個獨立的聲明:
a. 跑在V8上的JavaScript很快!
V8的開發者值得你去稱贊,因為V8讓JavaScript快的讓人難以置信。多快?從測試比賽上看只比Java慢1到5倍(沒錯是“慢”)。如果你仔細看他們的測試,你會發現V8自帶了一個很好的正則表達式引擎。結論?Node.js最適合用來完成需要大量正則表達式、CPU繁重的工作。
如果我們把那個測試比賽當作信條,那什么語言/實現通常會比JavaScript/V8快呢?一看,就是一些開發效率很低的語言:Java、Go、 Erlang(HiPE)、Clojure、F#、Haskell(GHC)、OCaml、Lisp(SBCL),都是不能用來寫服務器的。
更好的是Node.js不需要使用多核,因為解釋器是單線程的(評論肯定會說你可以同時跑多個Node.js進程,而其他語言都不可以這么做)。
b. Node.js是非阻塞的!并發性很好!事件驅動!
有些時候,我很懷疑人們是否真的知道他們自己在說些啥。Node.js在這點甚是奇葩,因為你完全沒得到輕量級線程所帶來的便捷,而且還要自己完成輕量級線程已經幫你做好的事。因為JavaScript對任何種類的并發都沒有直接支持,結果就是一堆使用回調的庫函數。編程語言研究者會發現這是蹩腳版的延續傳遞風格(continuation-passing style (Sussman and Steele 1975)),CPS本來是用來應對遞歸時棧的增長,不過在Node.js里是應對語言不直接支持并發的問題。
是的,Node.js能在一個線程里高效地處理大量連接,但是它不是第一個也不是唯一個能這樣做的運行時系統,看看Vert.x、Erlang、Stackless Python、GHC、Go……
更重要的是,大部分人都用Node.js來實現最小化的可行產品(MVP),因為他們覺得這樣可以為未來的大量用戶提供一個更快的網站。(當然加載500K的Backbone.js和其他各種各樣的庫算不上高性能,不過不用介懷的。)
Node.js讓并發變的簡單!
JavaScript 沒有內建任何的和并發相關的語言特性,也不支持元編程,Node.js也不能化腐朽為神奇。你只好手工管理全部的延續,或者借助(很多不同的)庫來把 JavaScript的語法應用到極致。(順帶一提,我覺得shoud.js既可怕又方便。)這就像是現代版本的因為語言里面沒有for循環,所以只好用 GOTO語句。來看一下對比吧。
在node.js里,你可能要寫這樣的函數:
function dostuff(callback) { task1(function(x) { task2(x, function(y) { task3(y, function(z) { if (z < 0) { callback(0); } else { callback(z); }); }); }); }太美了我都不敢看。換Q promise試試:
function dostuff() { return task1() .then(task2) .then(task3) .then(function(z) { if (z < 0) { return 0; } else { return z; }); }好看多了,但還是很笨。一個副作用:如果你忘記在鏈式調用的最后加上".done()",Q會把你的異常都吞了,而且還有其他不太明顯的問題。當然了,大部分Node.js的庫都不用Q,所以你是要老老實實地用回調。如果take2不返回Q promise會怎樣?
function dostuff() { return task1() .then(function(x) { var deferred = Q.defer(); task2(x, deferred.resolve); return deferred; }) .then(task3) .then(function(z) { if (z < 0) { return 0; } else { return z; } }) .done(); }上面的代碼是錯的,你看出問題了嗎?而且,我們還忘了異常處理,修改版:
function dostuff() { return task1() .then(function(x) { var deferred = Q.defer(); task2(x, function(err, res) { if (err) { deferred.reject(err); } else { deferred.resolve(res); } }); return deferred.promise; }, function(e) { console.log("Task 1 failed."); }) .then(task3, function(e) { console.log("Task 2 failed."); }) .then(function(z) { if (z < 0) { return 0; } else { return z; } }, function(e) { console.log("Task 3 failed."); }) .done(); }錯誤處理和業務交織在一起,這還有趣嗎?
在Go,你的代碼會寫成這樣:
func dostuff() int { z := task3(task2(task1()))) if z < 0 { return 0 } return z }或者加上錯誤處理:
func dostuff() int, err { x, err := task1(); if err != nil { log.Print("Task 1 failed.") return 0, err } y, err := task2(x); if err != nil { log.Print("Task 2 failed.") return 0, err } z, err := task3(y); if err != nil { log.Print("Task 3 failed.") return 0, err } if z < 0 { return 0; } return z; }Go版和Node.js版的實現的功能基本等價,除了Go還處理了等待和控制權轉讓。在Node.js里,我們必須手工管理延續因為我們要和內建的控制流對著干。
噢,在這實現這些東西之前,你還要學會不要錯誤地使用process.nextTick,不然你的API的用戶會很不爽。在這個講究“精益”和MVP的時代,誰有時間去學這些建立在讓人難以理解的運行時上的抽象滲漏問題。
又順帶一提,Q是很慢的(至少網上是這么說的)。看看這個測試,它對比了21種處理異步調用的方式的性能。
難怪人們喜歡Node.js,它給了你輕量級線程的性能以及x86匯編的清晰和可用性。
當人們指出Node.js手工處理控制流很麻煩,反對者就會說:“用函數庫去處理,例如async.js”。于是你開始用庫函數去并行執行一堆任務或者組合兩個函數,這其實就你在任何多線程語言里所做的事,只是更糟糕而已。
LinkedIn遷移到Node.js,服務器從30臺減到3臺!
引用Hacker News上的一句:“我把垃圾車換成了摩托,現在我開車快多了!”。PayPal 和沃爾瑪換到Node.js之后也得到很好的收益。當然,他們是在對比兩個完全不同的東西來讓Node.js看起來更好。在他們好到難以置信的故事里,他們從一個龐大的企業級代碼庫換到一個重頭開始寫的Node.js應用。這有理由不變快嗎?他們換到幾乎任何其他東西都會得到性能提升。
在Linkedin的案例里,他們之前的代理都跑在并發度為1的Mongrel上。就像從用1個手指敲QWERTY鍵盤切換到10個手指敲Dvorak鍵盤,然后認為這全歸功于Dvorak鍵盤布局更好。
這是一個經典的夸大的廣告:真實的故事被誤解,扭曲地去讓不知情的人產生誤解。
Node.js可以再利用你的JavaScript專業知識!
為了更準確,我們也要把它分成兩點:
a. 前端開發者也能進行后端開發!
以前JavaScript被用在哪里?主要是瀏覽器端的前端代碼,讓按鈕加上動畫,把JSON變成精美的用戶界面等等。在后端用JavaSctipt,你可以你的UI開發者去hack后端關鍵的網絡代碼,因為兩邊都是JS,沒什么東西要學的!(對吧?)直到他們發現不能像平常那樣return(因為并發),不能不能像平常那樣throw/catch(因為并發),而且全部東西都是基于回調的,會返回Q promise,會返回原生的promise、genator、pipe或其他的奇怪東西,因為這是Node.js。(記得告訴他們要檢查類型聲明)
你要相信前端開發者學習后端開發的能力。如果不同語言就是一個障礙,那么要明白怎樣正確結合各種回調/promise/generator也不是一件簡單的事。
b. 我們可以在前端和后端共享代碼!
那你就要把服務器端能使用的語言特性限制到瀏覽器所支持的特性。例如你的代碼不能用JS 1.7的generator,直到瀏覽器也支持,而且我們也知道等它普及還要好幾年。事實上,如果不遠離瀏覽器的JS,Node的根本就沒辦法從本質上得到提高。Node.js有很多坑需要用庫去填,但是因為它和一個叫JavaScript的語言綁定在一起,它不能直接在語言層面上出處理這些問題。
這是很尷尬的情況,語言本來就沒給你到來太多東西,而你又不能改變這個語言,所以你只好一直npm install band-aid。
通過執行某些編譯步驟來把新的語言特性轉換到舊的語言,這種情況可以得到改善,你也就可以在服務器寫新的代碼,同時可以運行在正常的JavaScript 上。你的選擇可能是95%都是JavaScript的新語言(TypeSctipt、CoffectSctipt)或者完全不是JavaSctipt的語言(ClojureSctipt)。
更值得擔心的是這意味這你實際上是混淆前端和后臺的職責。事實上,你的后臺成為了是一個囊括驗證、處理等等功能的JSON API,而且這個API會有多個消費者(包括第三方)。例如,當你要造個iPhone和Android應用,你必須決定是用Obj-C、Java或者C# 實現一個原生應用還是用Phonegap/Cordova把你的Backbone.js/Angular.js單頁面應用包裝起來。根據不同的部署平臺,這時在服務器和客戶端共享的代碼可能會成為不利因素。
NPM很好用!
我覺得NPM已經到了一個“不壞”的狀態,這已經領先于很多包管理工具。就像是大多數的生態系統,NPM上有多個實現同樣功能的包。例如你需要一個庫來向Android推送通知。在NPM,你能找到:gcm、node-gcm、node-gcm-service、dpush、gcm4node、libgcm和ngcm,更不用提那些支持多個推送服務的庫。哪個可靠?哪個已經停止開發?最后,你選了下載量最大的那個(為什么結果不能按流行度排序吶?)。
NPM過去經常宕機,看著很多公司突然間就不能部署代碼也是一件好玩的事。現在NPM上線時間已經好了很多,但是誰知道它會不會打斷你的部署進程。
過去,我們成功部署代碼而且不用在部署階段引入對一個新生的、由志愿者運行的、從頭實現的倉庫的依賴。我們甚至在本地保存一份函數庫的代碼。
我倒不擔心NPM,某種程度上它是生態系統的一部分而不是語言的一部分,而且通常情況都能滿足要求。
用Node.js時我效率更高!敏捷!快速!MVP!
似乎Node.js程序員心里都有一個奇怪的二分法:你在用mod_php或者可怕的JavaEE,所以是又大又慢的;你在用Node.js,所以是精益和快速的。這可能就是為什么你很少看到有人吹他怎樣從Python換到Node.js*。自然,如果你來自一個到處都是 AbstractFactoryFactorySingletonBean的過度工程的系統,Node.js的缺乏結構反而是清新的。但只是因為這樣就說 Node.js更高效是錯誤的——因為他們無視了全部的坑。一個Node.js新人可能會這么做:
- 這個函數可能失敗,我要拋一個異常,所以我會寫 throw new Error("it broke");
- 那個異常沒有被我的try-catch捕獲!
- process.on("uncaughtException")又好像可以
- 但是得到不是想象中堆棧軌跡,StackOverflow說這可能違背了最佳實踐
- 也許我要試一下domain?
- 哦,回調通常以錯誤作為第一個參數,我要回去改改我的函數調用
- 有人告訴我應該用promise
- 把例子看了十來二十遍,我覺得應該可以了
- 不過它還是吃了我的異常。不,我還要在最后加上.done() </ol>
- raise Exception("it broke"); </ol>
- 我要把err加到返回類型聲明,還要在return語句加一個返回值 </ol>
- 這里有一篇關于從Python換到Node.js的文章。最有價值的一句:“這種推遲的編程模式更難理解和調試。如果開發者不能完全理解Twised,他就會犯很多無知的錯誤,最后會死的很慘”。所以他們換到另一個困難的系統,如果你不能理解它然后又犯了些無知的錯誤,它一樣會微妙地掛掉。
我熱愛Node.js!Node.js就是生活!
如果你組織Node.js活動時需要演講者,我隨時都進行付費的演講演出。詳細請用郵箱聯系我。
這些觀點不代表也我的公司和同事,也沒有經過他們的復審。你也可以把全部文字都加上<sarcasm>標簽。
發布 funnuy 2014-06-19 原文 notes.ericjiang.com
一個Python程序會這么做:
這是一個Go程序員:
Noed.js中有很多東西會阻礙你實現MVP。MVP不是擔心能不能快40ms返回一個HTTP響應或者你的DigitalOcean機子能同時處理多少連接。你沒有時間去成為一個并發編程的專家(而且明顯你現在也不是,如果是的話你就不會用Node了)。
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!