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