從 Node 到 Go:一個粗略的比較
在 XO 公司,我們最初使用 Node 和 Ruby 構建相互連接的服務系統。我們享受 Node 帶來的明顯性能優勢,以及可以訪問已有的大型軟體包倉庫。我們也可以輕鬆地在公司內部發布並復用已有的插件和模塊。極大地提高了開發效率,使得我們可以快速編寫出可拓展的和可靠的應用。而且,龐大的 Node 社區使我們的工程師向開源軟體貢獻更加容易(比如 BunnyBus 和 Felicity)。
雖然我在大學時期和剛剛工作的一些時間在使用更嚴謹的編譯語言,比如 C++ 和 C#,而後來我開始使用 JavaScript。我很喜歡它的自由和靈活,但是我最近開始懷念靜態和結構化的語言,因為當時有一個同事讓我對 Go 語言產生了興趣。
我從寫 JavaScript 到寫 Go,我發現兩種語言有很多相似之處。兩者學習起來都很快並且易於上手,都具有充滿表現力的語法,並且在開發者社區中都有很多工作機會。沒有完美的編程語言,所以你應該總是選擇一個適合手頭項目的語言。在這篇文章中,我將要說明這兩種語言深層次上的關鍵區別,希望能鼓勵沒有用過 Go 語言的用戶可以有機會使用 Go 。
大體上的差異
在深入細節之前,我們應該先了解一下兩種語言之間的重要區別。
Go,或稱 Golang,是 Google 在 2007 年創建的自由開源編程語言。它以快速和簡單為設計目標。Go 被直接編譯成機器碼,這就是它速度的來源。使用編譯語言調試是相當容易的,因為你可以在早期捕獲大量錯誤。Go 也是一種強類型的語言,它有助於數據完整,並可以在編譯時查找類型錯誤。
另一方面,JavaScript 是一種弱類型語言。除了忽略驗證數據的類型和真值判斷陷阱所帶來的額外負擔之外,使用弱類型語言也有自己的好處。比起使用介面interfaces和范型generics,柯里化currying和可變的形參個數flexible arity讓函數變得更加靈活。JavaScript 在運行時進行解釋,這可能導致錯誤處理和調試的問題。Node 是一款基於 Google V8 虛擬機的 JavaScript 運行庫,這使它成為一個輕量和快速的 Web 開發平台。
語法
作為原來的 JavaScript 開發者,Go 簡單和直觀的語法很吸引我。由於兩種語言的語法可以說都是從 C 語言演變而來的,所以它們的語法有很多相同之處。Go 被普遍認為是一種「容易學習的語言」。那是因為它的對開發者友好的工具、精簡的語法和固守慣例(LCTT 譯註:慣例優先)。
Go 包含大量有助於簡化開發的內置特性。你可以用標準 Go 構建工具把你的程序用 go build 命令編譯成二進位可執行文件。使用內置的測試套件進行測試只需要運行 go test 即可。 諸如原生支持的並發等特性甚至在語言層面上提供。
Google 的 Go 開發者認為,現在的編程太複雜了,太多的「記賬一樣,重複勞動和文書工作」。這就是為什麼 Go 的語法被設計得如此簡單和乾淨,以減少混亂、提高效率和增強可讀性。它還鼓勵開發人員編寫明確的、易於理解的代碼。Go 只有 25 個保留關鍵字和一種循環(for 循環),而不像 JavaScript 有 大約 84 個關鍵字(包括保留關鍵字字、對象、屬性和方法)。
為了說明語法的一些差異和相似之處,我們來看幾個例子:
- 標點符號: Go 去除了所有多餘的符號以提高效率和可讀性。儘管 JavaScript 中需要符號的地方也不多(參見: Lisp),而且經常是可選的,但我更加喜歡 Go 的簡單。
// JavaScript 的逗號和分號nfor (var i = 0; i < 10; i++) {n console.log(i);n}nn//JavaScript 中的標點n
// Go 使用最少數量標點nfor i := 0; i < 10; i++ {n fmt.Println(i)n}nn// Go 中的標點n
- 賦值:由於 Go 是強類型語言,所以你在初始化變數時可以使用 := 操作符來進行類型推斷,以避免重複聲明,而 JavaScript 則在運行時聲明類型。
// JavaScript 賦值nvar foo = "bar";nn// JavaScript 中的賦值n
// Go 的賦值nvar foo string //不使用類型推導nfoo = "bar"nfoo := "bar" //使用類型推導nn// Go 的賦值n
- 導出:在 JavaScript 中,你必須從某個模塊中顯式地導出。 在 Go 中,任何大寫的函數將被默認導出。
const Bar = () => {};nmodule.exports = {n Barn}nn// JavaScript 中的導出n
// Go 中的導出npackage foo // 定義包名nfunc Bar (s string) string {n// Bar 將被導出n}nn// Go 中的導出n
- 導入:在 JavaScript 中 required 庫是導入依賴項和模塊所必需的,而 Go 則利用原生的 import 關鍵字通過包的路徑導入模塊。另一個區別是,與 Node 的中央 NPM 存儲庫不同,Go 使用 URL 作為路徑來導入非標準庫的包,這是為了從包的源碼倉庫直接克隆依賴。
// Javascript 的導入nvar foo = require(foo);nfoo.bar();nn// JavaScript 的導入n
// Go 的導入nimport (n "fmt" // Go 的標準庫部分n "github.com/foo/foo" // 直接從倉庫導入n)nfoo.Bar()nn// Go 的導入n
- 返回值:通過 Go 的多值返回特性可以優雅地傳遞和處理返回值和錯誤,並且通過傳遞引用減少了不正確的值傳遞。在 JavaScript 中需要通過一個對象或者數組來返回多個值。
// Javascript - 返回多值nfunction foo() {n return {a: 1, b: 2};n}nconst { a, b } = foo();nn// JavaScript 的返回n
// Go - 返回多值nfunc foo() (int, int) {n return 1, 2n}na, b := foo()nn// Go 的返回n
- 錯誤處理:Go 推薦在錯誤出現的地方捕獲它們,而不是像 Node 一樣在回調中讓錯誤冒泡。
// Node 的錯誤處理nfoo(bar, function(err, data) {n // 處理錯誤n}nn// JavaScript 的錯誤處理n
//Go 的錯誤處理nfoo, err := bar()nif err != nil {n // 用 defer、 panic、 recover 或 log.fatal 等等處理錯誤.n}nn// Go 的錯誤處理n
- 可變參數函數:Go 和 JavaScript 的函數都支持傳入不定數量的參數。
function foo (...args) {nconsole.log(args.length);n}nfoo(); // 0nfoo(1, 2, 3); // 3nn// JavaScript 中的可變參數函數n
func foo (args ...int) {nfmt.Println(len(args))n}nfunc main() {n foo() // 0n foo(1,2,3) // 3n}nn// Go 中的可變參數函數n
社區
當比較 Go 和 Node 提供的編程範式哪種更方便時,兩邊都有不同的擁護者。Node 在軟體包數量和社區的大小上完全勝過了 Go。Node 包管理器(NPM),是世界上最大的軟體倉庫,擁有超過 410,000 個軟體包,每天以 555 個新軟體包的驚人速度增長。這個數字可能看起來令人吃驚(確實是),但是需要注意的是,這些包許多是重複的,且質量不足以用在生產環境。 相比之下,Go 大約有 13 萬個包。
Node 和 Go 包的數量
儘管 Node 和 Go 歲數相仿,但 JavaScript 使用更加廣泛,並擁有巨大的開發者和開源社區。因為 Node 是為所有人開發的,並在開始的時候就帶有一個強壯的包管理器,而 Go 是特地為 Google 開發的。下面的Spectrum 排行榜顯示了當前流行的的頂尖 Web 開發語言。
Web 開發語言排行榜前 7 名
JavaScript 的受歡迎程度近年來似乎保持相對穩定,而 Go 一直在保持上升趨勢。
編程語言趨勢
性能
如果你的主要關注點是速度呢?當今似乎人們比以前更重視性能的優化。用戶不喜歡等待信息。 事實上,如果網頁的載入時間超過 3 秒,40% 的用戶會放棄訪問您的網站。
因為它的非阻塞非同步 I/O,Node 經常被認為是高性能的語言。另外,正如我之前提到的,Node 運行在針對動態語言進行了優化的 Google V8 引擎上。而 Go 的設計也考慮到速度。Google 的開發者們通過建立了一個「充滿表現力而輕量級的類型系統;並發和垃圾回收機制;強制地指定依賴版本等等」,達成了這一目標。
我運行了一些測試來比較 Node 和 Go 之間的性能。這些測試注重於語言提供的初級能力。如果我準備測試例如 HTTP 請求或者 CPU 密集型運算,我會使用 Go 語言級別的並發工具(goroutines/channels)。但是我更注重於各個語言提供的基本特性(參見 三種並發方法 了解關於 goroutines 和 channels 的更多知識)。
我在基準測試中也加入了 Python,所以無論如何我們對 Node 和 Go 的結果都很滿意。
循環/算術
迭代十億項並把它們相加:
var r = 0;nfor (var c = 0; c < 1000000000; c++) {n r += c;n}nn// Noden
package mainnfunc main() {nvar r intnfor c := 0; c < 1000000000; c++ {n r += cn}n}nn// Gon
sum(xrange(1000000000))nn// Pythonn
結果
這裡的輸家無疑是 Python,花了超過 7 秒的 CPU 時間。而 Node 和 Go 都相當高效,分別用了 900 ms 和 408 ms。
修正:由於一些評論表明 Python 的性能還可以提高。我更新了結果來反映這些變化。同時,使用 PyPy 大大地提高了性能。當使用 Python 3.6.1 和 PyPy 3.5.7 運行時,性能提升到 1.234 秒,但仍然不及 Go 和 Node 。
I/O
遍歷一百萬個數字並將其寫入一個文件。
var fs = require(fs);nvar wstream = fs.createWriteStream(node);nnfor (var c = 0; c < 1000000; ++c) {n wstream.write(c.toString());n}nwstream.end();nn// Noden
package mainnnimport (n "bufio"n "os"n "strconv"n)nnfunc main() {n file, _ := os.Create("go")n b := bufio.NewWriter(file)n for c := 0; c < 1000000; c++ {n num := strconv.Itoa(c)n b.WriteString(num)n }n file.Close()n}nn// Gon
with open("python", "a") as text_file:nfor i in range(1000000):n text_file.write(str(i))nn// Pythonn
結果
Python 以 7.82 秒再次排名第三。 這次測試中,Node 和 Go 之間的差距很大,Node 花費大約 1.172 秒,Go 花費了 213 毫秒。真正令人印象深刻的是,Go 大部分的處理時間花費在編譯上。如果我們將代碼編譯,以二進位運行,這個 I/O 測試僅花費 78 毫秒——要比 Node 快 15 倍。
修正:修改了 Go 代碼以實現緩存 I/O。
冒泡排序
將含有十個元素的數組排序一千萬次。
function bubbleSort(input) {n var n = input.length;n var swapped = true;n while (swapped) {n swapped = false;n for (var i = 0; i < n; i++) {n if (input[i - 1] > input [i]) {n [input[i], input[i - 1]] = [input[i - 1], input[i]];n swapped = true;n }n }n }n}nfor (var c = 0; c < 1000000; c++) {n const toBeSorted = [1, 3, 2, 4, 8, 6, 7, 2, 3, 0];n bubbleSort(toBeSorted);n}nn// Noden
package mainnvar toBeSorted [10]int = [10]int{1, 3, 2, 4, 8, 6, 7, 2, 3, 0}nfunc bubbleSort(input [10]int) {n n := len(input)n swapped := truen for swapped {n swapped = falsen for i := 1; i < n; i++ {n if input[i-1] > input[i] {n input[i], input[i-1] = input[i-1], input[i]n swapped = truen }n }n }n}nfunc main() {n for c := 0; c < 1000000; c++ {n bubbleSort(toBeSorted)n }n}nn// Gon
def bubbleSort(input):n length = len(input)n swapped = Truen while swapped:n swapped = Falsen for i in range(1,length):n if input[i - 1] > input[i]:n input[i], input[i - 1] = input[i - 1], input[i]n swapped = Truenfor i in range(1000000):n toBeSorted = [1, 3, 2, 4, 8, 6, 7, 2, 3, 0]n bubbleSort(toBeSorted)nn//Pythonn
結果
像剛才一樣,Python 的表現是最差的,大約花費 15 秒完成了任務。 Go 完成任務的速度是 Node 的 16 倍。
判決
Go 無疑是這三個測試中的贏家,而 Node 大部分表現都很出色。Python 也表現不錯。要清楚,性能不是選擇編程語言需要考慮的全部內容。如果您的應用不需要處理大量數據,那麼 Node 和 Go 之間的性能差異可能是微不足道的。 有關性能的一些其他比較,請參閱以下內容:
- Node Vs. Go
- Multiple Language Performance Test
- Benchmarks Game
結論
這個帖子不是為了證明一種語言比另一種語言更好。由於各種原因,每種編程語言都在軟體開發社區中佔有一席之地。 我的意圖是強調 Go 和 Node 之間的差異,並且促進展示一種新的 Web 開發語言。 在為一個項目選擇語言時,有各種因素需要考慮,比如開發人員的熟悉程度、花費和實用性。 我鼓勵在決定哪種語言適合您時進行一次徹底的底層分析。
正如我們所看到的,Go 有如下的優點:接近底層語言的性能,簡單的語法和相對簡單的學習曲線使它成為構建可拓展和安全的 Web 應用的理想選擇。隨著 Go 的使用率和社區活動的快速增長,它將會成為現代網路開發中的重要角色。話雖如此,我相信如果 Node 被正確地實現,它正在向正確的方向努力,仍然是一種強大而有用的語言。它具有大量的追隨者和活躍的社區,使其成為一個簡單的平台,可以讓 Web 應用在任何時候啟動和運行。
資料
如果你對學習 Go 語言感興趣,可以參閱下面的資源:
- Golang 網站
- Golang Wiki
- Golang Subreddit
via: https://medium.com/xo-tech/from-node-to-go-a-high-level-comparison-56c8b717324a#.byltlz535
作者:John Stamatakos 譯者:trnhoe 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出
推薦閱讀: