來自:開源中國 協作翻譯
原文:Why is a Java guy so excited about Node.js and JavaScript?
連結:https://blog.sourcerer.io/why-is-a-java-guy-so-excited-about-node-js-and-javascript-7cfc423efb44
譯者:邊城, 矽谷課堂, liyue李月, 子影, ZICK_ZEON, Tocy
直到最後一口氣,在Sun Microsystems的Java SE團隊工作10年以上的人難道不應該流出Java位元組碼並實體化抽象介面麼?對於這位前Java SE團隊成員來說,2011年學習Node.js平臺是一股清流。在2009年1月從Sun被解僱之後(就在Oracle收購之前),我學習了Node.js並且迷上了它.
怎麼迷上了?自2010年以來,我撰寫了大量關於Node.js程式設計文章。即,Node.js Web開發的四個版本,以及關於Node.js程式設計的其他書籍和眾多教程部落格文章。這些文章很多時間解釋了Node.js和JavaScript語言的進步。
在Sun Microsystems工作期間,我相信一切皆java。我出席過JavaONE會議,共同開發了java.awt.Robot類,運行了Mustang回歸競賽(Java 1.6版本的漏洞發現競賽),幫助推出了OpenJDK之前的“Distributions License for Java”回答Linux發行版分發JDK版本,後來在啟動OpenJDK專案中扮演了一個小角色。在此過程中,我在java.net(一個現已解散的網站)上釋出了一個部落格,每週寫1-2次,討論Java生態系統中的事件約6年。一個重要的主題是保護Java免受那些預測Java死亡的人的影響。
杜克獎頒是頒給哪些表現超越的員工。我在執行野馬回歸競賽後獲得了這個獎項,這個發現bug競賽有利於Java 1.6版本釋出。
Java元碼發生了什麼?我在這裡的目的是解釋這個純粹的Java倡導者是如何成為一個純粹的Node.js / JavaScript倡導者。
我並沒有完全脫離Java。在過去的3年中,我編寫了大量的Java/Spring/Hibernate程式碼。當我完全享受這項工作的時候——我在太陽能行業工作過,做過一些讓人精神振奮的事情,比如編寫關於千瓦小時的資料庫查詢——用Java程式設計已經失去了它的光彩。
基於 Spring 的兩年程式設計經驗帶給我一個非常深刻的教訓:掩蓋複雜性並不能造就簡單,那隻會讓事件變得更複雜。
TL;DR
-
Java 中充斥著樣板程式碼,他們掩蓋了程式員的真實意圖
-
Spring & Spring Boot 帶來的教訓:掩蓋複雜只會變得更複雜
-
Java EE 就是個”設計委員會“專案,它涵蓋了企業應用開發的一切事務,複雜無比
-
Spring 的程式設計體驗非常棒,直到有一天,一個莫名其妙的異常從一個你一點也不瞭解的深層子系統中冒出來,讓你花了至少 3 天來搞明白遇到了什麼問題
-
零程式碼框架的會帶來怎樣的代價?
-
像 Eclipse 這樣的 IDE 非常強大,但它也揭示了 Java 的複雜性
-
Node.js 是從輕量事件驅動架構中提煉出來的產物
-
JavaScript 社群似乎很樂意拋開樣板程式碼,讓程式員們的思想自由閃耀
-
用於解決回呼地獄的 async/await 函式就是一個去除樣板,擁抱創造的例子
-
使用 Node.js 程式設計讓人身心愉悅
-
JavaScript 缺乏像 Java 那樣嚴格的型別檢查,這有利有弊,因為寫程式碼變得容易了,但卻需要更多測試來保證其正確性
-
npm/yarn 包管理系統非常棒,非常好用,比可惡的 Maven 不知道好到哪兒去了
-
Java 和 Node.js 都有出色的效能。並不是不像有人說的那樣,因為 JavaScript 慢,所以 Node.js 效能低下
-
Node.js 的效能利益於 Google 為了給 Chrome 提速而投入開發的 V8
-
瀏覽器之間的激烈競爭使得 JavaScript 一年比一年強大,這是 Node.js 的福音
Java已經成為一個負擔,使用Node.js是充滿快樂的
有些工具或物件是設計師花了數年打磨和改進的結果。他們嘗試了不同的想法,刪除了不必要的屬性,最終得到了一個物件,該物件的屬性恰好合適。通常這些物件有一種強大的簡單性,非常吸引人。Java不是那種系統。
Spring是開發基於java的web應用程式的流行框架。Spring(特別是Spring Boot)的核心目的是使用預先配置的Java EE堆疊。Spring程式員不需要連線所有servlet、資料持久化、應用伺服器,誰知道還有什麼,就可以獲得完整的系統。相反,Spring負責處理所有這些細節,而您則專註於編碼。例如,JPA Repository類用“findUserByFirstName”之類的名稱來綜合資料庫查詢方法——您不需要編寫任何程式碼,只需將以這種方式命名的方法新增到儲存庫定義中,Spring就會處理剩下的問題。
這是一個很棒的故事,也是一次很好的經歷,直到它沒有了。
當您得到關於“傳遞給持久化的分離物體”的Hibernate PersistentObjectException時,這是什麼意思?這花費了好幾天的時間——冒著過於簡化的風險——這意味著到達REST端點的JSON具有帶值的ID欄位。Hibernate,多載,想要控制ID值,並丟擲這個令人困惑的異常。有成千上萬同樣令人困惑和遲鈍的異常訊息。在Spring堆疊中一個接一個的子系統中,就像一個復仇女神坐在那裡等著你犯最小的錯誤,然後用一個應用程式崩潰異常來攻擊你。
然後是巨大的堆疊跟蹤。他們在幾個螢幕上展示了很多抽象的方法這個和那個。Spring顯然正在制定實現程式碼內容所需的配置。這個抽象級別顯然需要相當多的邏輯來查詢所有內容並執行請求。長堆疊跟蹤並不一定是壞事。相反,它指出了一個癥狀:記憶體/效能開銷成本到底是多少?
零程式設計的情況下,“findUserByFirstName” 會怎樣執行?框架必須解析方法名,猜測程式員的意圖,構造一些類似於抽象語法樹一樣的東西,生成 SQL 等等。這會帶來什麼樣的開銷?難道這樣程式員就不需要寫程式碼了嗎?
有過數次這樣的經歷後,你需要花數周時間去學習原本不需要學習的深奧知識,然後得出和我一樣的結論:掩蓋複雜性並不會讓事情變得簡單,只會讓事情變得更複雜。
重點關註下 Node.js
“相容性問題”是個非常酷的口號,它表示 Java 平臺的關鍵價值主張是完全向後相容。我們對此非常認真,甚至把它像上圖一樣印在 T 恤上。保持這種程度的相容性可能會是個非常沉重的負擔,而有時候避免使用陳舊無用的方法,本身很有效。
Node.js 的另一面…
相比於 Spring 和 Java EE 的異常複雜,Node.js 是一股清流。首先是 Ryan Dahl 在開發 Node.js 核心平臺所用的設計美學。Dahl 的經驗是在重量級複雜系統中使用執行緒。他尋求不同的東西,並花了幾年時間打磨和改進了一系列核心思想,並將之在 Node.js 上實現。最終完成一個輕量級系統,一個執行執行緒,巧妙地使用 JavaScript 匿名函式進行非同步回呼,以及一個巧妙地實現非同步性的執行時庫。最初的標的是高吞吐量的事件處理,並將事件傳遞到回呼函式。
然後還是由於 JavaScript 語言本身。JavaScript 程式員似乎具有去除樣板程式碼的審美,因此程式員的意圖是可以清晰地發揮作用的。
對比Java和JavaScript的一個例子是偵聽器函式的實現。在Java中。偵聽器需要建立抽象介面類的具體實體。這需要大量的空話來掩蓋正在發生的事情。在樣板檔案的面紗後面,程式員的意圖是什麼?
在JavaScript中,一個使用簡單的匿名函式——閉包。您沒有搜尋正確的抽象介面。相反,您只需編寫所需的程式碼,而不需要過多的冗餘。
另一個學習:大多數程式語言都模糊了程式員的意圖,使得理解程式碼變得更加困難。
這個點指向Node.js。但有一個警告,我們必須處理:回呼地獄。
解決方案有時也會帶來問題
在JavaScript中,非同步編碼長期存在兩個問題。一個是Node.js中所謂的“回呼地獄”。很容易陷入深度巢狀回呼函式的陷阱,在這種情況下,每一層巢狀都會使程式碼複雜化,從而使錯誤和結果處理變得更加困難。另一個相關的問題是JavaScript語言沒有幫助程式員正確地表達非同步執行。
出現了幾個庫,它們有望簡化非同步執行。另一個掩蓋複雜性的例子建立了更多的複雜性。
舉個例子:
這個示例應用程式是一個非常便宜的模仿Unix cat命令。非同步庫非常適合簡化非同步執行的順序。但它的使用需要一堆模板程式碼來模糊程式員的意圖。
這裡有一個迴圈。它不是作為迴圈編寫的,並且它不使用自然迴圈結構。此外,錯誤和結果不會落在自然的地方,但是不方便地被困在回呼函式內。在ES2015 / 2016功能登陸Node.js之前,這是我們能做的最好的事情。
Node.js 10.x中的等式如下:
使用async / await函式重寫上一個示例。它是相同的非同步結構,但是使用普通的迴圈結構編寫。錯誤和結果以自然的方式報告。它更容易閱讀,編碼和理解程式員的意圖。
唯一的問題就是process.stdout.write沒有提供Promise介面,因此如果沒有包含Promise,就不能在非同步函式中乾凈利用。
回呼問題並沒有透過複雜性來解決。相反,語言和正規化的變化透過臨時解決方案解決了這些問題和強加給我們的過度措辭。使用非同步函式,我們的程式碼變得更加美觀。
雖然這最初是針對Node.js的,但優秀的解決方案將其轉換為Node.js和JavaScript。
透過定義明確的型別和介面來假裝一切盡在掌握
我認為 Java 提倡透過強型別檢查來保障大型應用開發這一說法就是作死。這一準則提出來的時候大家還在開發單體系統(沒有微服務,也沒有 Docker 等)。因為 Java 有嚴格的型別檢查,Java 編譯器可以避免編譯不好的程式碼,以此幫助你避免多種缺陷的發生。
相對而言,JavaScript 擁有弱型別。道理很簡單:既然程式員們不知道得到的是什麼型別的物件,他們又怎麼知道能幹什麼?
Java 強型別帶來的是死板。程式員們不斷地透過編寫程式碼和其它一些事情來保證一切都確確實實正確。他們花時間極其精確地、死板地編寫程式碼,希望透過更早的發現並修複錯誤,以便節約時間。
還有一個大問題,人們或多或少需要使用一個龐大而複雜的 IDE。簡單的程式員編輯器完全不夠用。要想保持程式員的思路清晰(除了披薩),需要大量的工具,比如下拉顯示物件中的有效欄位,描述方法的引數,輔助構建物件,輔助重構,以及其它由 Eclipse、NetBeans 和 IntelliJ 提供的各種工具。
還有……不要讓我用 Maven,那個工具簡直可以用恐怖來形容。
JavaScript 不需要宣告變數型別,型別轉換基本不會用到。因此,程式碼讀起來非常清晰,不過卻存在潛藏錯誤的風險。
在這一點上,同否贊同 Java 的作法由你決定。我在十年前認為獲取更多確定性的開銷是值得的。但現在我認為那樣做的代價太大,還是用 JavaScript 的方式做事要容易得多。
透過小測試剔除模組的缺陷
Node.js 鼓勵程式員把程式拆分成較小的單元 —— 模組。這看起來是件小事,但它在一定程度上讓標題描述的事情變成為可能。
模組有如下特點:
-
自包含 —顧名思義,它會把相關的程式碼打包到一個單元中
-
強邊界— 模組內的程式碼不會受到其它程式碼侵入
-
精確匯出 —預設情況下,模組中的程式碼和資料並不對外開放,只有指定的函式和資料才會匯出
-
精確匯入 —模組會宣告其依賴哪些模組
-
潛在獨立性 —模組很容易公開釋出到 npm 庫,也可以私有釋出到任何地方,以便在應用程式之間共享
-
更容易推斷 — 閱讀更少程式碼,可以更容易搞明白共目的
-
更容易測試 — 小模組更容易進行單元測試在判斷其是否正確
所有這些決定了 Node.js 易於定義良好的範圍,以及測試。
JavaScript令人擔憂的是缺少強型別程式碼檢查,這會很容易造成一些錯誤。在一個小的,邊界清晰的焦點模組中,受影響的程式碼範圍主要受限於該模組。這使得大多數關註點很小,且可以安全的整合要該模組中。
解決問題的另一個解決辦法是增強測試。
你必須花費一些生產所得收益(這對於 JavaScript 編碼來說是很容易的)去增強測試。你的測試規範必須能捕捉到編譯器可能已經捕捉到的錯誤。你會測試你的程式碼嗎?
對於那些想要在 JavaScript 中使用靜態檢查型別的人,可以去看看 TypeScript 。我從沒用過這個語言,但是聽別人說過關於它的一些很棒的事情。它直接與 JavaScript 相容,且添加了許多有用的型別檢查和其它特性。
這裡的重點是 Node.js 和 JavaScript 。
包管理
我光是考慮 Maven 就會得了中風,並且根本無法思考直接寫出關於它的任何東西。據說一個人要麼愛 Maven ,要麼鄙視它,沒有中間立場。
問題在於 Java 生態系統沒有一個具有凝聚力的包管理系統。Maven 包存在並且工作得相當好,據說也可以在 Gradle 中工作。但它並不像 Node.js 的包管理系統那樣有用/可用/強大。
在 Node.js 的世界裡,有兩個優秀的包管理系統緊密協作。首先,npm 和 npm 儲存庫是唯一的工具。
使用 npm ,我們有一個很好的樣式來描述包的依賴。依賴關係可以是嚴格的(確切地說是版本1.2.3)或者指定為“*”,表示使用的是最新版本。Node.js 社群已經將數十萬個包釋出到了 npm 儲存庫中。使用 npm 儲存庫之外的包就和使用 npm 儲存庫的包一樣簡單。
npm 庫不僅服務於 Node.js,也服務於前端工程師,這再好不過了。以前我們使用像 Bower 這樣的工具進行包管理。Bower 已經不再推薦使用,現在的前端開發者會在 npm 中去尋找前端 JavaScript 庫。很多前端工具鏈,比如 Vue.js CLI 和 Webpack,都是 Node.js 寫的。
yarn 是另一個 Node.js 的包管理系統,它可以從 npm 庫下載所需要的包,使用與 npm 相同的配置檔案。重要的是,yarn 執行更快。
npm 庫可以透過 npm 或 yarn 來訪問,它是 Node.js 輕鬆易用的有力保障。
在協助建立了 java.awt.Robot 之後,我受到啟發建立了這一工具。官方的 Duke 吉祥物完全由曲線繪製,RoboDuke 除了傳動部分和肘部分,其它部分都是直線。
效能
有時候 Java 和 JavaScript 都被認為執行緩慢。
它們都需要編譯器將原始碼轉換為由虛擬機器(VM)執行的位元組碼。VM 經常會進一步將位元組碼編譯成原生代碼,並使用各種最佳化技術。
無論 Java 還是 JavaScript 有都巨大的理由來快速執行。對於 Java 和 Node.js 來說,快速的服務端程式碼就是這樣的需求。而在瀏覽器中,JavaScript 需要更好的客戶端應用效能 —— 下一節 RIA 中會對此詳述。
Sun/Oracle JDK 使用 HotSpot,一個具有多重位元組碼編譯策略的超強虛擬機器。它的名字代表著它會檢查頻繁執行的程式碼並逐步加強最佳化以執行更多程式碼段。HtoSpot 會除錯最佳化,產生非常快的程式碼。
對於 JavaScript 來說,我們常常會想:我們要怎樣讓瀏覽器中的 JavaScript 跑得動各種複雜的應用程式?當真辦公檔案處理套件不能用 JavaScript 在瀏覽器上實現嗎?我們讓事實來說話。我現在正用 Google Docs 寫這篇文章,效能真心不錯。瀏覽器上的 JavaScript 效能每年都是飛速進步。
Node.js 直接受益於這種趨勢,因為它使用 Chrome 的 V8 引擎。
Peter Marshall 說到一個例子,Google V8 工程師的主要工作就是提高 V8 的效能。Marshall 的工作是處理 Node.js 的效能問題。他詳述了為什麼要從 Crankshaft 虛擬機器切換到 Turbofan 虛擬機器。
-
https://youtu.be/YqOhBezMx1o
機器學習這一領域涉及到大量數學知識,而數學科學家樣常用 R 和 Python。包括機器學習在內的若干領域依賴於快速數值計算。由於各種原因,JavaScript 在這方面的較為落後,但目前 JavaScript 在數值計算方面的標準庫正在開發中。
-
https://youtu.be/1ORaKEzlnys
另一個影片演示了在 JavaScript 中使用 TensorFlow,它使用了 TensorFLow.js 庫。這個庫的 API 與 TensorFlow Python 相似,可以匯入預訓練模組。它可以用於分析實時影片以識別訓練過的物件,而且可以完全在瀏覽器中執行。
-
https://youtu.be/YB-kfeNIPCE
在另一場談話中,IBM 的 Chris Baily 談到了 Node.js 的效能和伸縮性相關的問題,尤其是與 Docker/Kunbernetes 部署相關的問題。他從一組基準測試開始,表明 Node.js 在 I/O 吞吐量、程式啟動時間和記憶體佔用等方面顯著優於 Spring Boot。此外,隨著 V8 效能的提升,Node.js 每一個發行版都得到了令人矚目的提升。
-
https://youtu.be/Fbhhc4jtGW4
影片中,Bailey 認為不應該在 Node.js 中執行計算程式碼。理解他為什麼這麼認識非常重要。因為單執行緒模型長時間執行計算會阻塞事件執行。我在《Node.js Web 開發》一書中談到這一問題,並展示了三種處理辦法:
-
演演算法重構 —— 檢測演演算法中較慢的問題,透過重構提速
-
透過事件除錯拆分計算,以便 Node.js 定期傳回執行執行緒
-
將計算移到後端服務中去
如果 JavaScript 的改進仍不滿足於你的應用需求,還有兩個辦法在 Node.js 中直接整合原生代碼。最直接的方法是 Node.js 的原生代碼模組。Node.js 的工具鏈中有有個 node-gyp 用於處理對原生代碼的連結。下麵的影片演示了將 Rust 整合到 Node.js 中:
-
https://youtu.be/Pfbw4YPrwf4
WebAssembly 可以將其它語言編譯成執行速度非常快的 JavaScript 子集。WebAssembly 是可以在 JavaScript 引擎中執行的便攜格式。下麵這個影片很好地概述了這一技術,並演示了在 Node.js 中執行 WebAssembly。
-
https://youtu.be/hYrg3GNn1As
豐富的因特網應用(Rich Internet Applications,RIA)
十年前軟體業熱衷於使用快速(時間上的)JavaScript 引擎實現 RIA,這讓桌面應用的地位受到威脅。
這故事要追溯到 20 年前。Sun 和 Netscape(網景)達成了在 Netscape Navigator(網景瀏覽器)中使用 Java Applet 的協議。JavaScript 在某種程度上被設計為 Java Applet 的指令碼語言。當時希望在 Java 在服務端有 Servlet,而在客戶端用 Applet,兩端使用相同的程式語言,在和諧的環境中開發。但最終由於種種原因未能成型。
十年前 JavaScript 自己也開始能實現足夠複雜而強大的應用。因此 RIA 作為一個可以幹掉 Java 的客戶端應用平臺而流行起來。
現在我們開始看到 RIA 理念成為現實。隨著 Node.js 出現,前後端都可以使用 JavaScript,20 年前的美好願景得以實現。
一些實體:
-
Google Docs (寫文章的東西)看起來和原來的 Office 套件一樣,但它基於瀏覽器執行
-
像 React、Angular 和 Vue 這樣的強大框架,讓使用 HTML/CSS 技術的瀏覽器應用開發變得簡單
-
Electron 整合了 Node.js 和 Chrominum 瀏覽器,可以支援跨平臺的桌面應用開發。很多有名的應用,比如 Visual Studio Code、Atom、GitKraken,以及 Postman 等,都是基於 Electron 來編寫的。它們的表現都很不錯。
-
由於 Electron/NW.js 使用瀏覽器引擎,像 React/Angular/Vue 這樣的框架得以應用於桌面應用開發。比如這個示例:
-
https://blog.sourcerer.io/creating-a-markdown-editor-previewer-in-electron-and-vue-js-32a084e7b8fe
Java 桌面應用平臺並不是因為 JavaScript RIA 而消亡,而是因為 Sun Microsystems 公司在客戶端技術方面的疏忽。Sun 把精力放在企業使用者要求的高速服務端效能上。真正導致 Java Applet 消亡的是幾年前 Java 外掛和 Java Web Start 中發現的一個嚴重的安全漏洞。這個漏洞引起了全世界的警惕,讓大家直接了當的不再使用 Java Applet 和 Java Web Start 應用。
不過仍然可以開發其他型別的 Java 桌面應用,因此 NetBeans 和 Eclipse 這兩大 IDE 仍在激烈競爭。不過 Java 在這方面的工作沒多大進展,除了開發工具外,很少有基於 Java 的應用。
JavaFX 是個例外。
JavaFX 在 10 年前被 Sun 用於 iPhone。它準備在手機上透過 Java 平臺支援開發介面豐富的 GUI 應用,並以此排擠 Flash 和 iOS 應用開發。當然這件事情最終沒有發生。JavaFX 仍在使用,卻不是按照它宣傳的那樣。
這一領域所有讓人感到興奮的東西都與 React、Vue.js 這些框架相關。
這種情況之下,JavaScript 和 Node.js 從很大程度上來說是勝利者。
Java 指環曾在 Java ONE 大會分發。這種指環含有一個帶有完整 Java 實現的晶片。其主要作用是在 JavaONE 大會上解鎖我們安裝在大廳的計算機。
Java 指環介紹。
總結
如今,開發伺服器端程式碼有很多選擇。我們不再侷限於“P語言”(Perl、PHP、Python)和Java,再加上有Node.js, Ruby, Haskell, Go, Rust等等這些程式語言的存在。因此,我們要享受選擇過多帶來的尷尬。
為什麼這個寫Java程式碼的傢伙會轉向Node。很明顯,我更喜歡用Node.js程式設計時那種自由的感覺。Java成為一個負擔,用Node.js。沒有這樣的負擔。然而假設我再次被僱傭寫Java程式碼,我當然還會幹,也單單因為別人花錢僱了我所以我不得不乾。
每個應用程式都有其特定的需求。就因為一個人喜歡Node.js就總使用他當然是不正確的。選擇一種語言或框架而不是另一種語言或框架一定有技術原因。例如,我最近做的一些工作涉及到XBRL檔案。因為最好的XBRL庫是在Python中實現的,所以有必要學習Python來繼續這個專案。實事求是地評估你的真實需求,並做出相應的選擇吧。
●編號790,輸入編號直達本文
●輸入m獲取文章目錄
演演算法與資料結構
更多推薦《18個技術類公眾微信》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。