GO語言系列(一):初識GO語言
原創前言
本專欄全方面解讀軟件領域相關知識,偏向技術深度內容,主要覆蓋編程語言、系統架構、開源框架、技術管理等,又分為多個主題,每個主題包含多篇文章。
本文是專欄的第一篇文章,也是GO語言系列的第一篇文章,今天我想從方方面面講下我對于GO語言的大致印象,后續文章會深入介紹各個特性、編程技巧。
介紹
從歷史說起,Go語言的作者是Robert Griesemer、Rob Pike和Ken Thompson,其中Ken Thompson以在UNIX和C語言開發中的巨大貢獻為程序員所熟知。目前為止有哪些軟件是用Go語言編寫的呢?容器軟件Docker、基礎軟件ETCD和Kubernetes,數據庫軟件TiDB和InfluxDB、消息系統NSQ、緩存組件GroupCache。
可以看到,幾乎在基礎架構軟件的每一個領域,都涌現了由Go語言編寫的新軟件,這些軟件的市場占有率持續攀高。除了作為基礎架構軟件的語言之外,Go語言作為服務器端通用語言的機會也越來越多,從Beego、Gorilla等Go語言Web框架的熱門程度也可以看出一些發展趨勢。
示例程序
我們通過一個簡單的示例程序看看GO的編碼風格:
- Package main
- import "fmt"
- func main(){
- fmt.Println("hello,world");
- }
如何運行上述代碼呢?GO語言是編譯型語言,GO的工具鏈將程序的源文件轉變成機器相關的原生指令(二進制),最基礎的工具是run命令,它可以將一個或者多個GO源文件(以.go為后綴)進行編譯、鏈接,鏈接后就開始運行生成的可執行文件,看一下實際的操作:
- $go run helloworld.go
打印:hello,world
上面的編譯、鏈接、運行,都是一次性工作,也就是說下次運行go run命令時,內部流程會全部重做。我們可以通過go build命令生成二進制程序,隨后就可以任意調用了,如下所示:
- $go build helloworld.go
- $./helloworld
- hello,world
這里我們提到了編譯型語言,什么是編譯型語言?如果編譯型語言編寫的程序需要被機器認識,它需要經過編譯和鏈接兩個步驟,編譯是把源代碼編譯成機器碼,鏈接是把各個模塊的機器碼和依賴庫串聯起來生成可執行文件。
我們來看看編譯型語言的優缺點,由于預編譯過程的存在,對代碼可以進行優化,也只需要一次編譯,運行時效率也會較高,并且可以脫離語言環境獨立運行,缺點是修改后的整個模塊需要編譯。
相對編譯型語言,解釋型語言只會在運行程序的時候才逐行翻譯。那么什么是鏈接?準確地說是鏈接和裝入,即在編譯后執行這兩個步驟,程序才能在內存中運行。鏈接是通過連接器完成的,它將多個目標文件鏈接成一個完整的、可加載的、可執行的目標文件,整個過程包括了符號解析(將目標文件內的應用符號和該符合的定義聯系起來)和將符號定義與存儲器的位置聯系起來兩個步驟。
命名規范
GO語言中的函數、常量、變量、類型、語句、標簽、包的名稱有較統一的命名規則,名稱的開頭是一個字母或下劃線,后面可以是任意數量的字符、數字或下劃線,注意,GO語言是區分大小寫的,并且關鍵字不可以作為名稱。當遇到由單詞組成的名稱時,GO程序員一般使用“駝峰式”的風格。
說到這點,我們來看看Java的命名規范。以$為例,Oracle官網建議不要使用$或者_開始作為變量命名,并且建議在命名中完全不要使用“$”字符,原文是“The convention,however,is to always begin your variable names with a letter,not ‘$’ or ‘_’”。對于這一條,騰訊的看法是一樣的,百度認為雖然類名可以支持使用“$”符號,但只在系統生成中使用(如匿名類、代理類),編碼不能使用。
這類問題在StackOverFlow上有很多人提出,主流意見為大家不需要過多關注,只需要關注原先的代碼是否存在”_”,如果存在就繼續保留,如果不存在則盡量避免使用。也有一位提出盡量不適用”_”的原因是低分辨率的顯示器,肉眼很難區分”_”(一個下劃線)和”__”(兩個下劃線)。
我個人覺得可能是由于受C語言的編碼規范所影響。因為在C語言里面,系統頭文件里將宏名、變量名、內部函數名用_開頭,因此當你#include系統頭文件時,這些文件里的名字都有了定義,如果與你用的名字沖突,就可能引起各種奇怪的現象。綜合各種信息,建議不要使用”_”、”$”、空格作為命名開始,以免不利于閱讀或者產生奇怪的問題。
對于類名,俄羅斯Java專家Yegor Bugayenko給出的建議是盡量采用現實生活中實體的抽象,如果類的名字以“-er”結尾,這是不建議的命名方式。他指出針對這一條有一個例外,那就是工具類,例如StringUtils、FileUtils、IOUtils。對于接口名稱,不要使用IRecord、IfaceEmployee、RedcordInterface,而是使用現實世界的實體命名。
當然,上述都是針對Java的,與GO無關,GO語言受C語言的影響更多。
變量概述
GO語言包括四種主要的聲明方式:變量(var)、常量(const)、類型(type)和函數(func)。我們來聊聊變量相關的幾點感受:
1. var聲明創建一個具體類型的變量,然后給它附加一個名稱,并且設置它的初始值,每一個聲明有一個通用的形式:var name type = expression。多說一句,GO語言允許空字符串,不會報空指針錯誤。
2. 可以采用name:=expression方式聲明變量,注意:=表示聲明,=表示賦值。
如果一個變量生命為var x int,表達式&x(x的地址)獲取一個指向整形變量的指針,它的類型是整形指針(*int)。如果值叫做p,我們可以說p指向x,或者p包含x的地址。p指向的變量寫成*p。表達式*p獲取變量的值(此例為整形),因為*p代表一個標量,所以它也可以出現在賦值操作符左邊,用于更新變量的值。
- x:=1
- p:=&x//p是整形指針,指向x
- fmt.Println(*p)//輸出“1”
- *p=2//等同于x=2
- fmt.Println(x)//輸出“2”
注意,相較于Java的NULL,GO表示指針類型的零值是nil。
3. 使用內置的new函數創建變量,表達式new(T)創建一個未命名的T類型變量,初始化為T類型的零值,并返回其地址(地址類型為*T)。使用new創建的變量和取其地址的普通局部變量沒有什么區別,只是不需要引入(或聲明)一個虛擬的名字,通過new(T)就可以直接在表達式中使用。
- func newInt() *int{
- return new(int)
- }
等同于:
- func newInt() *int{
- var dummy int
- return &dummy
- }
gofmt工具
GO語言提供了很多工具,例如gofmt,它可以將代碼格式化,我們來看看具體是怎么實現的。
Gofmt會讀取程序并且進行格式化,例如gofmt filename命令,它會打印格式化后的代碼。我們來看一個示例程序(程序名demo.go):
- package main
- import "fmt"
- // this is demo to format code
- // with gofmt command
- var a int=2;
- var b int=5;
- var c string= `hello world`;
- func print(){
- fmt.Println("Value for a,b and c is : ");
- fmt.Println(a);
- fmt.Println((b));
- fmt.Println(c);
- }
運行gofmt demo.go之后,輸出的代碼如下:
- package main
- import "fmt"
- // this is demo to format code
- // with gofmt command
- var a int = 2
- var b int = 5
- var c string = `hello world`
- func print() {
- fmt.Println("Value for a,b and c is : ")
- fmt.Println(a)
- fmt.Println((b))
- fmt.Println(c)
- }
垃圾回收
對于高級語言的垃圾回收器,如何知道一個變量是否應該被回收?基本思路是每一個包級別的變量,以及每一個當前執行函數的局部變量,可以作為追溯變量的路徑的源頭,通過指針和其他方式的引用可以找到變量。如果變量的路徑不存在,那么標量變得不可訪問,因此它不會影響任何其他的計算過程。
因為變量的生命周期是通過它的是否可達來確定的,所以局部變量可以在包含它的循環的一次迭代之外繼續存在。
GO語言的垃圾回收器設計的目標就是非阻塞式回收器,GO1.5實現了10毫秒內的回收(注意,根據實驗證明,這種說法只有在GC有足夠CPU時間的情況下才能成立)。從設計原理上來看,Go的回收器是一種并發的、三基色的、標記并清除回收器,它的設計想法是由Dijkstra在1978年提出的,目標是跟現代硬件的屬性和現代軟件的低延遲需求非常匹配。
總結
綜上所述,每一門新的語言的出現都是有原因的,一般來說是兩大原因:
1. 出現了當前主流語言無法解決的復雜場景或具體問題;
2. 需要性價比更高的語言。
我想,除了貝爾實驗室會做一些完全出于個人情懷的東西以外,沒有哪家會隨便布局無出路的新技術吧。正如Rob Pike所說,“復雜性是以乘積方式增長的”,為了解決某個問題,一點點地將系統的某個部分變得更加復雜,不可避免地也給其他部分增加了復雜性。
在不斷要求增加系統功能、選項和配置,以及快速發布的壓力之下,簡單性往往被忽視了。要實現簡單性,就要求在項目的一開始就濃縮思想的本質,并在項目的整個生命周期制定更具體的準則,以分辨出哪些變化是好的,哪些是壞的或致命的。
只要足夠努力,好的變化就既可以實現目的,又能夠不損害Fred Brooks所謂軟件設計上的“概念完整性”。壞的變化就做不到這一點,致命的變化則會犧牲簡單性而換取方便性。但是,只有通過設計上的簡單性,系統才能在增長過程中保持穩定、安全和自洽。Go語言不僅包括語言本身及其工具和標準庫,也保持了極端簡單性的行為文化。
今天的文章僅僅是初步印象介紹,我們下篇文章見。
【本文為51CTO專欄作者“周明耀”原創稿件,轉載請聯系原作者】