皇帝的新衣:Node.js

edithfang發表於2014-06-22
現在有很多人非難Node.js(例如著名的Node.js iscancer),但是反對者往往誤解其中所傳達的資訊並用一些無關的觀點進行反駁。更麻煩的是現在有兩類人在使用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 Steele1975)),CPS本來是用來應對遞迴時棧的增長,不過在Node.js裡是應對語言不直接支援併發的問題。
是的,Node.js能在一個執行緒裡高效地處理大量連線,但是它不是第一個也不是唯一個能這樣做的執行時系統,看看Vert.x、Erlang、StacklessPython、GHC、Go……
更重要的是,大部分人都用Node.js來實現最小化的可行產品(MVP),因為他們覺得這樣可以為未來的大量使用者提供一個更快的網站。(當然載入500K的Backbone.js和其他各種各樣的庫算不上高效能,不過不用介懷的。)
Node.js讓併發變的簡單!
JavaScript沒有內建任何的和併發相關的語言特性,也不支援超程式設計,Node.js也不能化腐朽為神奇。你只好手工管理全部的延續,或者藉助(很多不同的)庫來把JavaScript的語法應用到極致。(順帶一提,我覺得shoud.js既可怕又方便。)這就像是現代版本的因為語言裡面沒有for迴圈,所以只好用GOTO語句。
來看一下對比吧。
在node.js裡,你可能要寫這樣的函式:

太美了我都不敢看。換Q promise試試:


好看多了,但還是很笨。一個副作用:如果你忘記在鏈式呼叫的最後加上".done()",Q會把你的異常都吞了,而且還有其他不太明顯的問題。當然了,大部分Node.js的庫都不用Q,所以你是要老老實實地用回撥。如果take2不返回Q promise會怎樣?


上面的程式碼是錯的,你看出問題了嗎?而且,我們還忘了異常處理,修改版:


錯誤處理和業務交織在一起,這還有趣嗎?
在Go,你的程式碼會寫成這樣:

或者加上錯誤處理:


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 installband-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()
一個Python程式會這麼做:
1.raise Exception("it broke");
這是一個Go程式設計師
1.我要把err加到返回型別宣告,還要在return語句加一個返回值
Noed.js中有很多東西會阻礙你實現MVP。MVP不是擔心能不能快40ms返回一個HTTP響應或者你的DigitalOcean機子能同時處理多少連線。你沒有時間去成為一個併發程式設計的專家(而且明顯你現在也不是,如果是的話你就不會用Node了)。
* 這裡有一篇關於從Python換到Node.js的文章。最有價值的一句:“這種推遲的程式設計模式更難理解和除錯。如果開發者不能完全理解Twised,他就會犯很多無知的錯誤,最後會死的很慘”。所以他們換到另一個困難的系統,如果你不能理解它然後又犯了些無知的錯誤,它一樣會微妙地掛掉。
我熱愛Node.js!Node.js就是生活!
這些觀點不代表也我的公司和同事,也沒有經過他們的複審。你也可以把全部文字都加上標籤。
本文轉載自:http://ourjs.com/detail/53a2fa0a41a7309c42000007
相關閱讀
評論(1)

相關文章