爲什麼 Java 開發者會對 Node.js 和 JavaScript 如此激動?

直到最後一口氣,在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死亡的人的影響。

為什麼 Java 開發者會對 Node.js 和 JavaScript 如此激動?

杜克獎頒是頒給哪些表現超越的員工。我在運行野馬迴歸競賽後獲得了這個獎項,這個發現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 開發者會對 Node.js 和 JavaScript 如此激動?

“兼容性問題”是個非常酷的口號,它表示 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語言沒有幫助程序員正確地表達異步執行。

出現了幾個庫,它們有望簡化異步執行。另一個掩蓋複雜性的例子創建了更多的複雜性。

舉個例子:

const async = require(‘async’);
const fs = require(‘fs’);
const cat = function(filez, fini) {
async.eachSeries(filez, function(filenm, next) {
fs.readFile(filenm, ‘utf8’, function(err, data) {

if (err) return next(err);
process.stdout.write(data, ‘utf8’, function(err) {
if (err) next(err);
else next();
});
});
},
function(err) {
if (err) fini(err);
else fini();
});
};
cat(process.argv.slice(2), function(err) {
if (err) console.error(err.stack);
});

這個示例應用程序是一個非常便宜的模仿Unix cat命令。異步庫非常適合簡化異步執行的順序。但它的使用需要一堆模板代碼來模糊程序員的意圖。

這裡有一個循環。它不是作為循環編寫的,並且它不使用自然循環結構。此外,錯誤和結果不會落在自然的地方,但是不方便地被困在回調函數內。在ES2015 / 2016功能登陸Node.js之前,這是我們能做的最好的事情。

Node.js 10.x中的等式如下:

const fs = require(‘fs’).promises;
async function cat(filenmz) {
for (var filenm of filenmz) {
let data = await fs.readFile(filenm, ‘utf8’);
await new Promise((resolve, reject) => {
process.stdout.write(data, ‘utf8’, (err) => {
if (err) reject(err);
else resolve();
});
});
}
}

cat(process.argv.slice(2)).catch(err => {
console.error(err.stack);
});

使用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 開發者會對 Node.js 和 JavaScript 如此激動?

在協助創建了 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 開發者會對 Node.js 和 JavaScript 如此激動?

Java 指環曾在 Java ONE 大會分發。這種指環含有一個帶有完整 Java 實現的芯片。其主要作用是在 JavaONE 大會上解鎖我們安裝在大廳的計算機。

為什麼 Java 開發者會對 Node.js 和 JavaScript 如此激動?

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來繼續這個項目。實事求是地評估你的真實需求,並做出相應的選擇吧。

本文中的所有譯文僅用於學習和交流目的,轉載自oschina翻譯完成於 09-08。參與翻譯 (6人) : 邊城, 硅谷課堂, liyue李月, 子影, ZICK_ZEON, Tocy

如果我們的工作有侵犯到您的權益,請及時聯繫我們。


分享到:


相關文章: