1. 程式人生 > >GO是更好的程式語言嗎?

GO是更好的程式語言嗎?

引言

團隊有專案考慮用GO重寫,所以花了些時間調研GO。

第一次接觸GO是2年前,17年3月份,全職鑽研一週,彼時C++中毒太深,內心排斥其他程式語言,看其他語法總覺得有點怪,而且有“C/C++能做任何事,故無用其他語言之必要”的思想在作祟。

人都有思維定勢,受限於自己的經驗和認知,我亦不能例外,但好在我意識到這一點,所以在調研過程中,努力摒棄成見,儘量擺脫慣性,查閱關於GO的各種(包括核心設計師)文章,傾聽擁躉和批評者的不同聲音,結合自己的思考和分析,力求客觀公正去評價GO。

GO語言簡介

GO是Google開發的一種靜態、強型別、編譯型、併發型,並具有垃圾回收功能的類C程式語言。2009以開源專案的形式釋出,2012年釋出1.0穩定版本,距今已經十年了。

發明一種新的程式語言,首先得找到必要性,不然肯定會被質疑重複造輪子,方法嘛?無非是先找某種語言的一些茬,吐槽一番,複雜、笨拙、低效,太TM沙雕了,不能忍,勞資要立刻馬上分分鐘擼出一種新的程式語言,完美解決所有問題,不然對不起我卓爾不群的智商。

GO的故事也很套路,G公司的Pike大牛聽完C++0x的演講,回到辦公室,開始編譯C++,等待編譯過程中,轉過椅子面向Robert,討論語言的問題,然後拉上Ken爺爺一起合計,群嘲之後,受不了C++某些沙雕設計,還沒等編譯完成,三個老男人便一拍即合,決定一起搞點change the world的偉大事情,於是乎,GO誕生了。

GO語言之父Pike提到:GO語言是以C為原型,以C++為目標而設計的,希望C++程式設計師能以GO作為替代品。因為他覺得C++忒複雜了,要解救程式設計師於水火。

雖然GO以C++為目標而設計,但尷尬的是,Pike坦承GO並沒有吸引來多少C++程式設計師,反而是吸引了不少Python、Ruby程式設計師。這、這、這、這。。。

GO核心團隊

G公司不差錢不缺人,GO團隊更是群星薈萃、大咖雲集,不廢話,直接上圖:

核心設計師Pike和Ken都是出身自貝爾實驗室,Ken之於Pike,亦師亦友,共同發明了UTF-8,還基情四射地結對程式設計過,感情好的穿一條褲子。

Pike是Unix先驅,貝爾實驗室最早跟Ken、Dennis一起開發Unix的猛人,Plan9 OS的靈魂人物。大鬍子Ken爺爺則是Unix之父,和Dennis一起發明了C語言,殿堂骨灰級程式設計師,早已是名滿天下。

技術實力毋容置疑,不過這哥倆都是玩Kernel的,經歷相同,理念相近,分歧會比較少,他們也都坦承C用得最多最熟,所以註定了GO的類C特性,不過這會不會導致GO設計上的思維火花不足,對OOP以及現代程式設計思想的支援不足,亦未可知。

GO的哲學

哲學是難分對錯的,GO有GO的哲學,有它的取捨和審美,不一定每個人都認同,我覺得還挺有道理的,羅列如下:

少即是多

GO信奉Less Is More,大道至簡,臆測是喬幫主的信徒。

世界是並行的

世間萬物是並行發生的,所以GO遵照這個規律,對併發的原生支援讓GO更易於描述並行世界。

世界是物質組成的

微觀世界由小的粒子組合成大的粒子;巨集觀世界由小的物體組合成大的物體。繼承只能描述現實世界的一小部分,使用繼承是不全面的;GO的設計選擇的是組合,這個和現實世界比較吻合的設計,表現力更強。

世界是標準化的

硬體是標準化的,軟體也應如此,GO的介面是DUCK模型,介面是非侵入式的。

正交性

GO的多個特性都是正交性的,正交性是保持事物穩定和簡單的最好設計。

二八定律

80%程式碼只使用20%特性,增加語言特性,並不能提升效率,反而會增加複雜性,提高犯錯率,加重程式設計師心智負擔。

統一格式化

C++語法自由自在,於是乎一群吊絲為tab or space、大括號要不要換行等諸如此類的格式問題吵得不可開交。GO設計師認為,都是吃飽了撐的,你們太愚蠢了。

於是GO規定左大括號{不能換行放置,沒有為什麼,對著幹直接編譯不過。

GO編譯器內建工具gofmt強制原始碼格式化。對不起,沒有選項,我的地盤聽我的,把精力focus到真正重要的事情上來,停止無意義的爭吵。

這其實也是一種哲學:給你(我認為)最好的,而不是給你選擇。就像iPhone一樣,使用者太笨了,他們根本不知道自己需要什麼,就讓幫主替你安排好一切吧。

不過GO強加個人喜好的一刀切做法,也招致批評和厭惡。有比較剛的程式設計師,直接因為大括號不讓換行而拋棄GO。

作為一個經歷過各種妖媚程式碼格式要求的程式設計師,我發出了槓鈴般的笑聲。

GO的特色

GO是介於C與C++之間的語言,比C抽象層次高,比C++抽象層次低。

因為是一門新的程式語言,站在巨人的肩膀,博採眾長,規避了一些已知的問題,開發了一些優秀的特徵,相比C/C++,GO的核心特徵包括以下幾個方面:

  1. 原生併發,以東尼·霍爾的通訊順序程序(CSP)為基礎的goroutine,適合現代多核機器
  2. 垃圾回收,非常高效(請來世界頂級記憶體管理專家設計)
  3. 強大的標準庫,對網路程式設計等的良好支援
  4. CGO提供了GO呼叫C機制,擴充套件了GO的能力邊界
  5. 內嵌關聯陣列
  6. 非侵入式的介面設計
  7. 簡單清晰的語法,以及強編碼規則,好處可能遠超想象

GO vs C/C++

GO與C語法詳細對比

效能對比

雖然GO號稱兼備C++的執行效率和PHP的開發效率,但benchmarks好像打臉了,從資料上看,GO的執行效率接近卻略低於Java。

研發效率

我樂觀預計GO的研發效率上優於C/C++,特別是*nix環境下。

流行度

GO獲得TIOBE 2016年度最佳,2017年10月獲得第10,歷史最高排名。
GO誕生10年,雖然背靠Google,但依然沒有挺進程式語言第一陣營 ,屬於外圍三線。
近一年多流行度排名有所下滑,鐵打的Java、C/C++,流水的程式語言。

工程化水平

知名專案:
Docker:大名鼎鼎的開源應用容器引擎
K8S:容器編排管理系統的事實標準
...

GO更適合開發伺服器端大型軟體,高效能分散式系統領域,網路程式設計,併發程式設計,被譽為雲時代的C語言。

GO成為雲端計算時代流行起來,促進了雲端計算的發展,Google用GO的多,今日頭條、Uber等公司也用GO對業務進行了徹底的重構,golang.org YouTube.com也在使用GO開發。

美國市值TOP20有一半在使用GO,國外很多初創公司選擇GO,國內關注高,但還未得到廣泛應用,應用上呈現國外熱國內冷的特點

Go語言目前所面臨的最大問題在於,還沒有足夠的經驗來證明GO是否真的是一個成功的產品,缺少足夠多超大型應用的實踐。

總體而言,GO的工程化水平低於C/C++和Java等第一梯隊語言。

爭議和不足

GO最初聲稱為了解決Google的問題而設計,為了幫助人們閱讀、除錯和維護軟體而生,但目前為止,難言圓滿。

GO的異常處理經常被吐槽,GC提高了安全性卻失去了控制力,組合代替繼承真的好嗎?包管理做的好嗎?

摒棄先入為主的觀念影響,重新客觀審視GO語言,我覺得在語言設計層面,GO確實更自然、更簡約。比如摒棄行尾的分號,比如if/for不需要圓括號包裹條件,放空內心去想,好像真的更合理。

GO抖掉了C++的諸多包袱,讓程式簡單,也更容易理解(特別是相對於C),但是隨著GO的發展,語法也有可能變重,比如GO 2.0版又把它之前批評的泛型引入了,當初GO批評別人的點又有可能反過來被別人批評。

而關於語法層面是否真的更簡潔,也是有爭議的,三目運算子不支援+強制大括號讓一行C程式碼變成多行GO真的更簡單了嗎?比如程式語言專家莊曉立(Liigo)在CSDN上有吐槽的文章,仔細讀來,也有一定道理,我貼一個連結,可以參考一下。
原文連結:https://blog.csdn.net/liigo/article/details/23699459

另外GO是G公司的,雖然目前開源,但會不會哪天也像Oracle一樣,窮瘋了便開始薅羊毛,Oracle Java JDK已經開始割韭菜了,所以GO智慧產權的風險依然存在,而C/C++已經是宇宙人類的了,世界性的標準化組織在控制管理,風險無窮逼近於零。

小結

GO在一些點確實有突破,比如讓併發程式設計更容易、執行更高效,比如垃圾回收讓程式更安全,比如基於訊息(Channel)程式設計的支援,比如內嵌關聯結構,這些都很贊,也很重要。程式語言發展這麼多年,任何突破都是艱難和寶貴的。

Goroutine是GO的殺手鐗,經過GO改造後的系統很可能有更高併發量和IO吞吐率。

GO跟C非常像,這並不奇怪,因為設計師都是C/C++語言大師,C/C++程式設計師很容易切換到GO,但Java程式設計師轉GO可能要困難一些。

另一個隱患就是在Java佔主導的生態中,GO顯得比較小眾,跟其他中介軟體的融合也存在潛在風險,引入複雜性甚至混亂。

回到標題的問題,GO是更好的語言嗎?GO是理想的程式語言嗎?說實話,我不知道,而且我的觀點也不重要,這似乎是一個哲學問題。

是否要選擇GO作為專案開發語言,我認為不應該被GO聲稱的優勢迷惑,因為你去google任何一門語言,都能收穫一堆優點,PR會自然而然的對缺點選擇性忽視。

但也不能因循守舊,而應該仔細辨別,你度量什麼便得到什麼。GO是否適合你的專案,GO的新特點和優勢對你的專案是否真有必要,是否真有幫助,能給你的專案帶來什麼好處?比如你寫一個單機遊戲程式可能GO網路庫對專案毫無意義,所得收益跟你付出新學一門語言的成本相比如何?同時,它的缺陷是什麼?你是否全面理解?

有時候,它或許就像一位花枝招展的姑娘,待你拋棄一切去擁抱它的時候,你會發現,它的美好只是存在於你的幻想中,當然也有另一種可能,它真的非常好,很適合你,恭喜你,熱情的擁抱它吧。

GO有它適應場景,比如適合網路程式、雲應用、微服務、高效能分散式、大型多人協同,可能在開發效率上有非常大的提升,清晰度上也有提高,可能是理想的首選。

或許我會嘗試用GO開發新專案或者改寫老專案,誰知道呢?這取決於權衡折中,取決於領導決斷,也取決於我的心情。

附錄

一段GO的示例程式碼,品味一下GO的STYLE

package main

import (
    "fmt"
    "go/ast"
    "go/importer"
    "go/parser"
    "go/token"
    "go/types"
    "log"
)

const hello = `package main
import "fmt"
func main() {
        fmt.Println("Hello, world")
}
`

//!+
func PrintDefsUses(fset *token.FileSet, files ...*ast.File) error {
    conf := types.Config{Importer: importer.Default()}
    info := &types.Info{
        Defs: make(map[*ast.Ident]types.Object),
        Uses: make(map[*ast.Ident]types.Object),
    }
    _, err := conf.Check("hello", fset, files, info)
    if err != nil {
        return err // type error
    }

    for id, obj := range info.Defs {
        fmt.Printf("%s: %q defines %v\n",
            fset.Position(id.Pos()), id.Name, obj)
    }
    for id, obj := range info.Uses {
        fmt.Printf("%s: %q uses %v\n",
            fset.Position(id.Pos()), id.Name, obj)
    }
    return nil
}

//!-

func main() {
    // Parse one file.
    fset := token.NewFileSet()
    f, err := parser.ParseFile(fset, "hello.go", hello, 0)
    if err != nil {
        log.Fatal(err) // parse error
    }
    if err := PrintDefsUses(fset, f); err != nil {
        log.Fatal(err) // type error
    }
}