從 Node 到 Go:一個粗略的比較
在 XO 公司,我們最初使用 Node 和 Ruby 構建相互連接的服務系統。我們享受 Node 帶來的明顯性能優勢,以及可以訪問已有的大型軟體包倉庫。我們也可以輕鬆地在公司內部發布並復用已有的插件和模塊。極大地提高了開發效率,使得我們可以快速編寫出可拓展的和可靠的應用。而且,龐大的 Node 社區使我們的工程師向開源軟體貢獻更加容易(比如 BunnyBus 和 Felicity)。
雖然我在大學時期和剛剛工作的一些時間在使用更嚴謹的編譯語言,比如 C++ 和 C#,而後來我開始使用 JavaScript。我很喜歡它的自由和靈活,但是我最近開始懷念靜態和結構化的語言,因為當時有一個同事讓我對 Go 語言產生了興趣。
我從寫 JavaScript 到寫 Go,我發現兩種語言有很多相似之處。兩者學習起來都很快並且易於上手,都具有充滿表現力的語法,並且在開發者社區中都有很多工作機會。沒有完美的編程語言,所以你應該總是選擇一個適合手頭項目的語言。在這篇文章中,我將要說明這兩種語言深層次上的關鍵區別,希望能鼓勵沒有用過 Go 語言的用戶可以有機會使用 Go 。
大體上的差異
在深入細節之前,我們應該先了解一下兩種語言之間的重要區別。
Go,或稱 Golang,是 Google 在 2007 年創建的自由開源編程語言。它以快速和簡單為設計目標。Go 被直接編譯成機器碼,這就是它速度的來源。使用編譯語言調試是相當容易的,因為你可以在早期捕獲大量錯誤。Go 也是一種強類型的語言,它有助於數據完整,並可以在編譯時查找類型錯誤。
另一方面,JavaScript 是一種弱類型語言。除了忽略驗證數據的類型和真值判斷陷阱所帶來的額外負擔之外,使用弱類型語言也有自己的好處。比起使用 介面 和 范型 , 柯里化 和 可變的形參個數 讓函數變得更加靈活。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 的逗號和分號
for (var i = 0; i < 10; i++) {
console.log(i);
}
JavaScript 中的標點
// Go 使用最少數量標點
for i := 0; i < 10; i++ {
fmt.Println(i)
}
Go 中的標點
- 賦值:由於 Go 是強類型語言,所以你在初始化變數時可以使用
:=
操作符來進行類型推斷,以避免重複聲明,而 JavaScript 則在運行時聲明類型。
// JavaScript 賦值
var foo = "bar";
JavaScript 中的賦值
// Go 的賦值
var foo string //不使用類型推導
foo = "bar"
foo := "bar" //使用類型推導
Go 的賦值
- 導出:在 JavaScript 中,你必須從某個模塊中顯式地導出。 在 Go 中,任何大寫的函數將被默認導出。
const Bar = () => {};
module.exports = {
Bar
}
JavaScript 中的導出
// Go 中的導出
package foo // 定義包名
func Bar (s string) string {
// Bar 將被導出
}
Go 中的導出
- 導入:在 JavaScript 中
required
庫是導入依賴項和模塊所必需的,而 Go 則利用原生的import
關鍵字通過包的路徑導入模塊。另一個區別是,與 Node 的中央 NPM 存儲庫不同,Go 使用 URL 作為路徑來導入非標準庫的包,這是為了從包的源碼倉庫直接克隆依賴。
// Javascript 的導入
var foo = require('foo');
foo.bar();
JavaScript 的導入
// Go 的導入
import (
"fmt" // Go 的標準庫部分
"github.com/foo/foo" // 直接從倉庫導入
)
foo.Bar()
Go 的導入
- 返回值:通過 Go 的多值返回特性可以優雅地傳遞和處理返回值和錯誤,並且通過傳遞引用減少了不正確的值傳遞。在 JavaScript 中需要通過一個對象或者數組來返回多個值。
// Javascript - 返回多值
function foo() {
return {a: 1, b: 2};
}
const { a, b } = foo();
JavaScript 的返回
// Go - 返回多值
func foo() (int, int) {
return 1, 2
}
a, b := foo()
Go 的返回
- 錯誤處理:Go 推薦在錯誤出現的地方捕獲它們,而不是像 Node 一樣在回調中讓錯誤冒泡。
// Node 的錯誤處理
foo('bar', function(err, data) {
// 處理錯誤
}
JavaScript 的錯誤處理
//Go 的錯誤處理
foo, err := bar()
if err != nil {
// 用 defer、 panic、 recover 或 log.fatal 等等處理錯誤.
}
Go 的錯誤處理
- 可變參數函數:Go 和 JavaScript 的函數都支持傳入不定數量的參數。
function foo (...args) {
console.log(args.length);
}
foo(); // 0
foo(1, 2, 3); // 3
JavaScript 中的可變參數函數
func foo (args ...int) {
fmt.Println(len(args))
}
func main() {
foo() // 0
foo(1,2,3) // 3
}
Go 中的可變參數函數
社區
當比較 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;
for (var c = 0; c < 1000000000; c++) {
r += c;
}
Node
package main
func main() {
var r int
for c := 0; c < 1000000000; c++ {
r += c
}
}
Go
sum(xrange(1000000000))
Python
結果
這裡的輸家無疑是 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');
var wstream = fs.createWriteStream('node');
for (var c = 0; c < 1000000; ++c) {
wstream.write(c.toString());
}
wstream.end();
Node
package main
import (
"bufio"
"os"
"strconv"
)
func main() {
file, _ := os.Create("go")
b := bufio.NewWriter(file)
for c := 0; c < 1000000; c++ {
num := strconv.Itoa(c)
b.WriteString(num)
}
file.Close()
}
Go
with open("python", "a") as text_file:
for i in range(1000000):
text_file.write(str(i))
Python
結果
Python 以 7.82 秒再次排名第三。 這次測試中,Node 和 Go 之間的差距很大,Node 花費大約 1.172 秒,Go 花費了 213 毫秒。真正令人印象深刻的是,Go 大部分的處理時間花費在編譯上。如果我們將代碼編譯,以二進位運行,這個 I/O 測試僅花費 78 毫秒——要比 Node 快 15 倍。
修正:修改了 Go 代碼以實現緩存 I/O。
冒泡排序
將含有十個元素的數組排序一千萬次。
function bubbleSort(input) {
var n = input.length;
var swapped = true;
while (swapped) {
swapped = false;
for (var i = 0; i < n; i++) {
if (input[i - 1] > input [i]) {
[input[i], input[i - 1]] = [input[i - 1], input[i]];
swapped = true;
}
}
}
}
for (var c = 0; c < 1000000; c++) {
const toBeSorted = [1, 3, 2, 4, 8, 6, 7, 2, 3, 0];
bubbleSort(toBeSorted);
}
Node
package main
var toBeSorted [10]int = [10]int{1, 3, 2, 4, 8, 6, 7, 2, 3, 0}
func bubbleSort(input [10]int) {
n := len(input)
swapped := true
for swapped {
swapped = false
for i := 1; i < n; i++ {
if input[i-1] > input[i] {
input[i], input[i-1] = input[i-1], input[i]
swapped = true
}
}
}
}
func main() {
for c := 0; c < 1000000; c++ {
bubbleSort(toBeSorted)
}
}
Go
def bubbleSort(input):
length = len(input)
swapped = True
while swapped:
swapped = False
for i in range(1,length):
if input[i - 1] > input[i]:
input[i], input[i - 1] = input[i - 1], input[i]
swapped = True
for i in range(1000000):
toBeSorted = [1, 3, 2, 4, 8, 6, 7, 2, 3, 0]
bubbleSort(toBeSorted)
Python
結果
像剛才一樣,Python 的表現是最差的,大約花費 15 秒完成了任務。 Go 完成任務的速度是 Node 的 16 倍。
判決
Go 無疑是這三個測試中的贏家,而 Node 大部分表現都很出色。Python 也表現不錯。要清楚,性能不是選擇編程語言需要考慮的全部內容。如果您的應用不需要處理大量數據,那麼 Node 和 Go 之間的性能差異可能是微不足道的。 有關性能的一些其他比較,請參閱以下內容:
結論
這個帖子不是為了證明一種語言比另一種語言更好。由於各種原因,每種編程語言都在軟體開發社區中佔有一席之地。 我的意圖是強調 Go 和 Node 之間的差異,並且促進展示一種新的 Web 開發語言。 在為一個項目選擇語言時,有各種因素需要考慮,比如開發人員的熟悉程度、花費和實用性。 我鼓勵在決定哪種語言適合您時進行一次徹底的底層分析。
正如我們所看到的,Go 有如下的優點:接近底層語言的性能,簡單的語法和相對簡單的學習曲線使它成為構建可拓展和安全的 Web 應用的理想選擇。隨著 Go 的使用率和社區活動的快速增長,它將會成為現代網路開發中的重要角色。話雖如此,我相信如果 Node 被正確地實現,它正在向正確的方向努力,仍然是一種強大而有用的語言。它具有大量的追隨者和活躍的社區,使其成為一個簡單的平台,可以讓 Web 應用在任何時候啟動和運行。
資料
如果你對學習 Go 語言感興趣,可以參閱下面的資源:
via: https://medium.com/xo-tech/from-node-to-go-a-high-level-comparison-56c8b717324a#.byltlz535
作者:John Stamatakos 譯者:trnhoe 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive