新語言不斷誕生,舊的語言也試圖持續進化,目的之一,是能當個不被時代拋棄的現代程式語言。近來Go語言吸引了我,不妨就從相對來說極為年輕這門語言來看看,作為一門現代程式語言要具備些什麼?
語言本身的特性
想瞭解語言的特性,最好的方式就是瞭解誕生原因,Go語言的起源,可參考〈Go at Google: Language Design in the Service of Software Engineering〉。
這篇文章是根據Rob Pike於2012年10月25日在Tucson, Arizona舉行的SPLASH 2012大會上主題演講修改後撰寫而成,正如標題提示:「Go是為軟體工程服務而設計的語言」。
Joshua Bloch說過:「既然寫正確的程式那麼難,我們就應該盡力取得幫助。所以,能減少bug的所有東西都是好的。這就是我是靜態型別語言和靜態分析的信徒的原因。」
因此,既然是為軟體工程而設計的語言,Go作為一門靜態定型、需明確進行型態轉換的強型別語言,並不令人意外,當然,為了在提供型態資訊與語法簡潔性方面取得平衡,型態推斷(Type inference)是必須得考量的功能。
現代程式語言最大特徵之一是混合了多種典範,吸收了過往語言的特徵,以Apple的Swift語言為例,它在剛發表後的一段日子裡,JavaScript開發者說它像JavaScript,Ruby開發者說它像Ruby,你總能看到所熟悉語言的影子在裡頭,現代語言的特性就是如此,彼此之間會越來越像,作為一門以實用為目的之語言,只要對開發有利,並不用堅持某個典範的純粹性。
因而在Go中,你可以看到C的struct,Java的interface,函數式的典型特徵一級函式等特徵。實際上,Go不是物件導向語言,沒有物件,沒有繼承,然而在精神的實現上,使用了struct作為資料與邏輯的聯結(就實現方式上,感覺又有點像Python了),以組合、has-a的關係來消弭程式碼重複的問題,就操作上,又滿像是物件導向語言了。
因應多核處理器普及,並行(Concurrency)程式設計的支援是現代語言的目標,Go就直接在語法上支援並行程式設計,只要在函式前放一個go關鍵字,就可以建立可與其他函式並行運作的goroutine,之間的通訊,則透過channel來實現。
慣例方面的特性
對我來說,在學習各種語言的過程中,總有個重要的部份必須釐清,那就是語言使用過程中各式各樣的慣例。
這類慣例多半是在語言逐漸被廣為使用之後,自然而然由使用者建立的非正式規範,重視使用者的語言會在文件方面為相關慣例留下記錄,用以管理複雜度,形成一致的文化(<用慣例管理複雜度,形成一致文化>),這各式各樣的慣例,可能是命名、程式碼格式、註解、專案結構、測試等。
一開始我對Go語言並不感興趣,覺得它不過就是把Google中重要的三個語言C、Java、Python,加上些許其他語言的特性摻雜而成。不過,某日逛到Go官網,〈How to Write Go Code〉這篇文章吸引了我,因為裡頭Workspaces開頭就提到,go工具一開始就是為了公開檔案庫(repositories)中的開放原始碼而設計,不管你要不要發布程式碼,都得遵守它既定的環境設置規範,無從選擇。
這很有趣,畢竟Java走了一段很長的時間,從Ant、Maven到Gradle,才在專案結構的慣例上有了些規範,Go一開始就這麼做,你的原始碼必須放在src目錄(依package放在各自子目錄),編譯出來的套件會放在pkg目錄,執行檔就放在bin中(也可以自定義GOBIN環境變數),對於相依套件,也可以透過GOPATH來管理。
在命名慣例上,Go也將之融入語言之中,套件中大寫為開頭的名稱,就是import之後可公開引用的名稱,相對於Java之類的語言,使用public等關鍵字來標示是清楚了許多;在程式碼格式上,Go也內建了一些規範,像是C家族語言經常爭論的{}置放方式,在Go中沒得選擇,一定得是右上置{與左下置}的方式,Go在格式方面非常重視,還針對慣例格式寫了個gofmt工具。
gofmt存在的理由很簡單,不希望開發者為了格式、風格而無止盡地爭論,避免程式碼審查(code view)時,過多的時間耗費在格式審查而不是程式碼本身,就如〈Go at Google〉中寫的,gofmt解決許多開發者的問題:「我要用什麼格式寫程式?」gofmt是Go提供的工具之一,也可以與編輯器等結合,例如,在vim-go中結合gofmt,就可以自動於存檔時處理格式問題。
工具與文件上的特性
談到工具,現代程式語言在工具的完備性上也有所重視,比方說最常見的是程式庫、套件等的管理,例如,Ruby從1.9開始內建了RubyGems,Python 3.4之後預設就有pip可以使用,若是JavaScript,使用的是著名的npm套件管理器,而Go可以透過內建的go get指令,來下載套件,有趣的是,go get可以支援svn、hg、git、bzr等檔案庫的安裝,只要有安裝相對應的版本控制程式。
談到了版本控制,不禁會想到多環境的版本控制,對我來說,這也是現代語言應具備的功能之一,像是Python中的virtualenv這類的功能,從Python 3.4開始也開始內建,而由於GOPATH的存在,在Go也可達到這類目的,或者是安裝第三方的gvp與搭配gpm(類似Python的pip),至於Go語言本身多版本的並存需求,則可透過gvm(類似Ruby的rvm)。
Go一開始就內建了測試機制,執行go test的話,會自動讀取套件目錄中的*_test.go來編譯、運行測試;在文件的支援上,有著godoc(或go doc)這樣的工具,可以直接查閱套件、函式等的文件說明,實際上,它們讀取了原始碼中的註解內容,概念上類似於Python的Docstring或者是Java的Javadoc,不過更為簡單,沒有特定的語言結構與標記,如同〈Godoc: documenting Go code〉談到的,目的是希望註解本身就是文件,即便在沒有godoc的情况下,也希望能從程式碼註解中直接獲得文件說明。
實際上,Go還提供了許多工具滿足各種需求,可以在官方網站的〈Commands〉中(https://golang.org/cmd/),找到工具說明文件。而Go的殺手級應用案例之一就是Docker,在〈Docker and Go: why did we decide to write Docker in Go?〉這篇談到,Docker要採用Go開發的五個原因之一,就是go有著各式內建工具可滿足開發流程中的需求。
文件的豐富,無疑地,也是現代語言必須具備的要素之一,現在的開發者不可避免地必須在多個語言之間轉換時,官方網站文件的完整性絕對是必要的考量,特別是像〈Effective Go〉這樣的文件。
這是一篇推崇Go的文章嗎?與其說推崇,不如說我最近確實被Go引起了興趣,學習不同的程式語言向來是我的興趣之一,而這次的興趣,是來自於它內建了許多我認為現代語言該具有的元素,無論是語言本身的語法特性,專案、命名、格式等方面的慣例特性,或者是套件、相依性、多環境管理、測試、文件等開發過程應具備的工具等特性。
這不表示你現在就應該開始投資Go,而只是以它為例,表示在評估、學習或運用一門現代語言的同時,或許也要將這類特性考量在內,無論這類特性是內建在語言本身,或者是來自於周遭自然形成的生態系,學習語言一開始只是秀個「Hello, World」,來表示語言有多簡單,也不再足夠(只是強調「Hello, World」就是如此簡單的示範,也不見得是正確的),就算是學習(古老的)C語言,或許也可參考《21世紀C語言》這類的書籍,才能對如何撰寫出效率,具現代風格的程式做更多的瞭解。