轉(zhuǎn)眼加入螞蟻已經(jīng)三個(gè)多月,這期間主要維護(hù)一 Go 寫的服務(wù)器。雖然用的時(shí)間不算長(zhǎng),但還是積累了一些心得體會(huì),這里總結(jié)歸納一下,供想嘗試 Go 的同學(xué)參考。
本文會(huì)依次介紹 Go 的設(shè)計(jì)理念、開發(fā)環(huán)境、語(yǔ)言特性。本文在談及語(yǔ)言特性的時(shí)也會(huì)討論一些 Go 的不足之處,旨在給讀者提供一個(gè)全面的視角。
簡(jiǎn)介
一般來(lái)說(shuō),編程語(yǔ)言都會(huì)有一個(gè) slogan 來(lái)表示它們的特點(diǎn)。比如提到 Clojure,一般會(huì)想到這么幾個(gè)詞匯:lisp on
JVM、immutable、persistent;Java 的話我能想到的是企業(yè)級(jí)開發(fā)、中規(guī)中矩。對(duì)于 Go ,官網(wǎng)介紹到:
Go is an open source programming language that makes it easy to build simple,
reliable, and efficient software.
提取幾個(gè)關(guān)鍵詞:open(開放)、simple(簡(jiǎn)潔)、reliable(可靠)、efficient(高效)。這也可以說(shuō)是它的設(shè)計(jì)目標(biāo)。除了上面這些口號(hào)外,初學(xué)者還需要知道
Go 是一門命令式的靜態(tài)語(yǔ)言(是指在編譯時(shí)檢查變量類型是否匹配),與 Java 屬于同一類別。
Imperative Functional
Dynamic Python/Ruby/Javascript Lisp/Scheme/Clojure
Static Java/C++/Rust/Go OCaml/Scala/Haskell
由于 Hello World <https://play.golang.org/> 太簡(jiǎn)潔,不具備展示 Go 的特點(diǎn),所以下面展示一段訪問(wèn)
httpbin,打印 response 的完整代碼。
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { //
http://httpbin.org/#/Anything/get_anything r, err :=
http.Get("http://httpbin.org/anything?hello=world") if err != nil { panic(err)
} defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil {
panic(err) } fmt.Printf("body = %s\n", string(body)) }
上面的代碼片段包括了 Go 的主要組成:包的聲明與引用、函數(shù)定義、錯(cuò)誤處理、流程控制、defer
<https://tour.golang.org/flowcontrol/12>。
開發(fā)環(huán)境
通過(guò)上面的代碼片段,可以看出 Go 語(yǔ)言
simple(簡(jiǎn)潔)的特點(diǎn),所以找一個(gè)最熟悉的文本編輯器,一般通過(guò)配置插件,都可以達(dá)到快速開發(fā)的目的。很久之前我就已經(jīng)把所有文本編輯放到 Emacs
上,這里介紹下我的配置。
除了 go-mode <https://github.com/dominikh/go-mode.el> 這個(gè) major mode,為了配置像
源碼跳轉(zhuǎn)、API 自動(dòng)補(bǔ)全、查看函數(shù)文檔等現(xiàn)代 IDE 必備功能,需要安裝以下命令
go get -u github.com/rogpeppe/godef go get -u github.com/stamblerre/gocode #
for go-eldoc/company-go go get -u golang.org/x/tools/cmd/goimports go get -u
github.com/kisielk/errcheck go get -u github.com/lukehoban/go-outline # for
go-imenu
然后再按照 setup-go.el
<https://github.com/jiacai2050/dotfiles/blob/master/.emacs.d/customizations/setup-go.el>
里的配置,就擁有了一個(gè)功能完備的開發(fā)環(huán)境。
不像 Java 語(yǔ)言需要運(yùn)行時(shí),Go 支持直接將整個(gè)項(xiàng)目 build 成一個(gè)二進(jìn)制文件,方便部署,而支持交叉編譯
<https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5>,不過(guò)在開發(fā)時(shí),直接 go
run XXX.go 更為便利,截止到 Go 1.12,還不支持 REPL
<https://stackoverflow.com/questions/8513609/does-go-provide-repl>,官方有提供在線版的
Playground <https://play.golang.org/> 供分享、調(diào)試代碼。
我個(gè)人的習(xí)慣是建一個(gè) go-app 項(xiàng)目,每個(gè)要測(cè)試的邏輯放到一個(gè) test 里面去,這樣就可以使用 go test -v -run XXX
來(lái)運(yùn)行。之所以不選用go run,是因?yàn)橐粋€(gè)目錄下只允許有一個(gè) main 的 package,多個(gè) IDE 會(huì)提示錯(cuò)誤。
數(shù)據(jù)類型
一般編程語(yǔ)言,數(shù)據(jù)類型 <https://go101.org/article/type-system-overview.html>分為基本的與復(fù)雜的兩類。
基本的一般比較簡(jiǎn)單,表示一個(gè)值,Go 里面就有 string, bool, int8, int32(rune), int64, float32,
float64, byte(uint8) 等基本類型
復(fù)雜類型一般表示多個(gè)值或具有某些高級(jí)用法,Go 里面有:
* pointer Go 里只支持取地址 & 與間接訪問(wèn) * 操作符,不支持對(duì)指針進(jìn)行算術(shù)操作
* struct 類似于 C 語(yǔ)言里面的 struct,Java 里面的對(duì)象
* function 函數(shù)在 Go 里是一等成員
* array 大小固定的數(shù)組
* slice 動(dòng)態(tài)的數(shù)組
* map 哈希表
* chan 用于在多個(gè) goroutine 內(nèi)通信
* interface 類似于 Java 里面的接口,但是與 Java 里的用法不一樣
下面將重點(diǎn)介紹 Go 里特有或用途最廣的數(shù)據(jù)類型。
struct/interface
Go 里面的 struct 類似于 Java 里面的 Object,但是并沒有繼承,僅僅是對(duì)數(shù)據(jù)的一層包裝(抽象)。相對(duì)于其他復(fù)雜類型,struct 是值類型
,也就是說(shuō)作為函數(shù)參數(shù)或返回值時(shí),會(huì)拷貝一份值,值類型分配在 stack 上,與之相對(duì)的引用類型,分配在 heap 上。
初學(xué)者一般會(huì)有這樣的誤區(qū),認(rèn)為傳值比傳引用要慢,實(shí)則不然,具體涉及到 Go 如何管理內(nèi)存
<https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-stacks-and-pointers.html>
,這里暫不詳述,感興趣到可以閱讀:
* The Top 10 Most Common Mistakes I’ve Seen in Go Projects
<https://itnext.io/the-top-10-most-common-mistakes-ive-seen-in-go-projects-4b79d4f6cd65>
* pointer_test.go
<https://gist.github.com/teivah/a32a8e9039314a48f03538f3f9535537>
這個(gè)測(cè)試在筆者機(jī)器上運(yùn)行結(jié)果: BenchmarkByPointer-8 20000000 86.7 ns/op BenchmarkByValue-8
50000000 31.9 ns/op
所以一般推薦直接使用值類型的 struct,如果確認(rèn)這是瓶頸了,可以再嘗試改為引用類型(&struct)
如果說(shuō) struct 是對(duì)狀態(tài)的封裝,那么 interface 就是對(duì)行為的封裝,相當(dāng)于對(duì)外的契約(contract)。而且 Go 里面有這么一條最佳實(shí)踐
<https://www.reddit.com/r/golang/comments/cf1lda/having_trouble_understanding_how_to_properly_use/eu7r4f3>
Accept interfaces, return concrete structs. (函數(shù)的參數(shù)盡量為 interface,返回值為 struct)
這樣的好處也很明顯,作為類庫(kù)的設(shè)計(jì)者,對(duì)其要求的參數(shù)盡量寬松,方便使用,返回具體值方便后續(xù)的操作處理。一個(gè)極端的情況,可以用 interface{}
表示任意類型的參數(shù),因?yàn)檫@個(gè)接口里面沒有任何行為,所以所有類型都是符合的。又由于 Go 里面不支持范型
<https://dev.to/deanveloper/go-2-draft-generics-3333>,所以interface{}是唯一的解決手段。
相比較 Java 這類面向?qū)ο蟮恼Z(yǔ)言,接口需要顯式(explicit)繼承(使用 implements 關(guān)鍵字),而在 Go 里面是隱式的(implicit)
<https://golang.org/doc/faq#implements_interface>
,新手往往需要一段時(shí)間來(lái)體會(huì)這一做法的巧妙,這里舉一例子來(lái)說(shuō)明:
Go 的 IO 操作涉及到兩個(gè)基礎(chǔ)類型:Writer/Reader ,其定義如下:
type Reader interface { Read(p []byte) (n int, err error) } type Writer
interface { Write(p []byte) (n int, err error) }
自定義類型如果實(shí)現(xiàn)了這兩個(gè)方法,那么就實(shí)現(xiàn)了這兩個(gè)接口,下面的 Example 就是這么一個(gè)例子:
type Example struct { } func (e *Example) Write(p byte[]) (n int, err error) {
} func (e *Example) Read(p byte[]) (n int, err error) { }
由于隱式繼承過(guò)于靈活,在 Go 里面可能會(huì)看到如下代碼
<https://stackoverflow.com/questions/17994519/golang-interface-compliance-compile-type-check>
:
var _ blob.Fetcher = (*CachingFetcher)(nil)
這是通過(guò)將 nil 強(qiáng)轉(zhuǎn)為 *CachingFetcher,然后在賦值時(shí),指定 blob.Fetcher 類型,保證 *CachingFetcher 實(shí)現(xiàn)了
blob.Fetcher 接口。
作為接口的設(shè)計(jì)者,如果想實(shí)現(xiàn)者顯式繼承一個(gè)接口,可以在接口中額外加一個(gè)方法
<https://golang.org/doc/faq#guarantee_satisfies_interface>。比如:
type Fooer interface { Foo() ImplementsFooer() }
這樣,實(shí)現(xiàn)者必須實(shí)現(xiàn) ImplementsFooer 方法才能說(shuō)是繼承了 Fooer 接口。所以說(shuō)隱式繼承有利有弊,需要開發(fā)者自己去把握。
map/slice
Map/Slice 是 Go 里面最常用的兩類數(shù)據(jù)結(jié)構(gòu),屬于引用類型。在語(yǔ)言 runtime 層面實(shí)現(xiàn),僅有的兩個(gè)支持范型的結(jié)構(gòu)。
Slice 是長(zhǎng)度不固定的數(shù)組,類似于 Java 里面的 List
<https://docs.oracle.com/javase/8/docs/api/java/util/List.html>。
// map 通過(guò) make 進(jìn)行初始化 // 如果提前知道 m 大小,建議通過(guò) make 的第二個(gè)參數(shù)指定,避免后期的數(shù)據(jù)移動(dòng)、復(fù)制 m :=
make(map[string]string, 10) // 賦值 m["zhangsan"] = "teacher" //
讀取指定值,如不存在,返回其類型的默認(rèn)值 v := m["zhangsan"] // 判斷指定 key 知否在 map 內(nèi) v, ok :=
m["zhangsan"] // slice 通過(guò) make 進(jìn)行初始化 s := make([]int) // 增加元素 s = append(s, 1)
// 也可以通過(guò) make 第二個(gè)參數(shù)指定大小 s := make([]int, 10) for i:=0;i<10;i++ { s[i] = i } //
也可以使用三個(gè)參數(shù)的 make 初始化 slice // 第二個(gè)參數(shù)為初始化大小,第三個(gè)為最大容量 // 需要通過(guò) append 增加元素 s :=
make([]int, 0 ,10) s = append(s, 1)
chan/goroutine
作為一門新語(yǔ)言,Goroutine 是 Go 借鑒 CSP 模型 <https://golang.org/doc/faq#csp>
提供的并發(fā)解決方案,相比傳統(tǒng) OS 級(jí)別的線程,它有以下特點(diǎn) <https://stackoverflow.com/a/27794268/2163429>:
* 輕量,完全在用戶態(tài)調(diào)度(不涉及OS狀態(tài)直接的轉(zhuǎn)化)
* 資源占用少,啟動(dòng)快
* 目前,Goroutine 調(diào)度器不保證公平(fairness),搶占(pre-emption)也支持的非常有限,一個(gè)空的 for{}
可能會(huì)一直不被調(diào)度出去。
一般可以使用 chan/select 來(lái)進(jìn)行 Goroutine 之間的調(diào)度。chan 類似于 Java 里面的 BlockingQueue
<https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/BlockingQueue.html>
,且能保證 Goroutine-safe,也就是說(shuō)多個(gè) Goroutine 并發(fā)進(jìn)行讀寫是安全的。
chan 里面的元素默認(rèn)為1個(gè),也可以在創(chuàng)建時(shí)指定緩沖區(qū)大小,讀寫支持堵塞、非堵塞兩種模式,關(guān)閉一個(gè) chan 后,再寫數(shù)據(jù)時(shí)會(huì) panic。
// chan 與 slice/map 一樣,使用 make 初始化 ch := make(chan int, 2) // blocking read v
:= <-ch // nonblocking read, 需要注意 default 分支不能省略,否則會(huì)堵塞住 select { case v:=<-ch:
default: } // blocking write ch <- v // nonblocking write select { case ch<-v:
default: }
chan 作為 Go 內(nèi)一重要數(shù)據(jù)類型,看似簡(jiǎn)單,實(shí)則暗藏玄妙,用時(shí)需要多加留意,這里不再展開敘述,后面打算專門寫一篇文章去介紹,感興趣的可以閱讀下面的文章:
* Curious Channels <https://dave.cheney.net/2013/04/30/curious-channels>
* Prosumer <https://github.com/jiacai2050/prosumer> 基于 buffered chan
實(shí)現(xiàn)的生產(chǎn)者消費(fèi)者,核心點(diǎn)在于關(guān)閉 chan 只意味著生產(chǎn)者不能再發(fā)送數(shù)據(jù),消費(fèi)者無(wú)法獲知 chan 是否已經(jīng)關(guān)閉,需要用其他方式去通信。
語(yǔ)言特性
Go 相比 Java 來(lái)說(shuō),語(yǔ)言特性真的是少太多。推薦 Learn X in Y minutes
<https://learnxinyminutes.com/docs/go/> 這個(gè)網(wǎng)站,快速瀏覽一遍即可掌握 Go 的語(yǔ)法。Go 的簡(jiǎn)潔程度覺得和
JavaScript 差不多,但卻是一門靜態(tài)語(yǔ)言,具有強(qiáng)類型,這兩點(diǎn)又讓它區(qū)別于一般的腳本語(yǔ)言。
代碼風(fēng)格
Go 遵循約定大于配置(convention over configuratio)的設(shè)計(jì)理念,比如在構(gòu)建一個(gè)項(xiàng)目時(shí),直接 go build
一個(gè)命令就搞定了,不需要什么 Makefile、pom.xml 等配置文件。下面介紹幾個(gè)常用的約定:
* 一個(gè)包內(nèi)函數(shù)、變量的可見性是通過(guò)首字母大小寫確定的。大寫表示可見。
* 一般 { 放在行末,否則 Go 編輯器會(huì)自動(dòng)插入一個(gè)逗號(hào)
<https://golang.org/doc/effective_go.html#semicolons>,導(dǎo)致編譯錯(cuò)誤
* 一個(gè)文件夾內(nèi),只能定義一個(gè)包
* 變量、函數(shù)命名盡量簡(jiǎn)短
<https://dave.cheney.net/practical-go/presentations/qcon-china.html#_identifier_length>
,標(biāo)準(zhǔn)庫(kù)里面經(jīng)常可以看到一個(gè)字母的變量
由于以上種種約定,在看別人代碼時(shí)很舒服,有種 Python 的感覺。另外建議在編輯器中配置 goimports
<https://godoc.org/golang.org/x/tools/cmd/goimports> 來(lái)自動(dòng)化格式代碼。
錯(cuò)誤處理
Go 內(nèi)沒有 try catch 機(jī)制,而且已經(jīng)明確拒絕了這個(gè) Proposal
<https://github.com/golang/go/issues/32437>,而是通過(guò)返回值的方式來(lái)處理。
f, err := os.Open(filename) if err != nil { return …, err // zero values for
other results, if any }
Go 的函數(shù)一般通過(guò)返回多值的方式來(lái)傳遞 error(且一般是第二個(gè)位置),實(shí)際項(xiàng)目中一般使用 pkg/errors
<https://github.com/pkg/errors> 去處理、包裝 err。
依賴管理
Go 的依賴管理,相比其他語(yǔ)言較弱。
在 Go 1.11 正式引入的 modules <https://blog.golang.org/using-go-modules> 之前,項(xiàng)目必須放在
$GOPATH/src/xxx.com/username/project 內(nèi),這樣 Go 才能去正確解析項(xiàng)目依賴,而且 Go 社區(qū)沒有統(tǒng)一的包托管平臺(tái),不像
Java 中 maven 一樣有中央倉(cāng)庫(kù)的概念,而是直接引用 Git 的庫(kù)地址,所以在 Go 里,一般會(huì)使用
github.com/username/package 的方式來(lái)表示。
go get 是下載依賴但命令,但一個(gè)個(gè)去 get 庫(kù)不僅僅繁碎,而且無(wú)法固化依賴版本信息,所以 dep
<https://github.com/golang/dep> 應(yīng)運(yùn)而生,添加新依賴后,直接運(yùn)行 dep ensure 就可以全部下下來(lái),而且會(huì)把當(dāng)前依賴的
commit id 記錄到 Gopkg.lock 里面,這就能解決版本不固定的問(wèn)題。
但 modules 才是正路,且在 1.13 版本會(huì)默認(rèn)開啟,所以這里只介紹它的用法。
# 首先導(dǎo)出環(huán)境變量 export GO111MODULE=on # 在一個(gè)空文件夾執(zhí)行 init,創(chuàng)建一個(gè)名為 hello 的項(xiàng)目 go mod init
hello # 這時(shí)會(huì)在當(dāng)前文件夾內(nèi)創(chuàng)建 go.mod ,內(nèi)容為 module hello go 1.12 # 之后就可以編寫 Go 文件,添加依賴后,執(zhí)行
go run/ # 依賴會(huì)自動(dòng)下載,并記錄在 go.mod 內(nèi),版本信息記錄在 go.sum
更多用法可以參考官方示例,這里只是想說(shuō)明目前 Go 內(nèi)的工具鏈大部分已經(jīng)支持,但是 godoc 還不支持
<https://github.com/golang/go/issues/26827>。
GC
Go 也是具有垃圾回收 <https://blog.golang.org/ismmkeynote>的語(yǔ)言,但相比于 JVM,Go GC 可能顯得及其簡(jiǎn)單,從
Go 1.10 開始,Go GC 采用 Concurrent Mark & Sweep (CMS) 算法,且不具有分代、compact
特性。讀者如果對(duì)相關(guān)名詞不熟悉,可以閱讀:
* https://engineering.linecorp.com/en/blog/go-gc/
<https://engineering.linecorp.com/en/blog/go-gc/>
而且 Go 里面調(diào)整 GC 的參數(shù)只有一個(gè) GOGC,表示下面的比率
新分配對(duì)象 / 上次 GC 后剩余對(duì)象
默認(rèn) 100,表示新分配對(duì)象達(dá)到之前剩余對(duì)象大小時(shí),進(jìn)行 GC。GOGC=off 可以關(guān)閉 GC,SetGCPercent
<https://golang.org/pkg/runtime/debug/#SetGCPercent> 可以動(dòng)態(tài)修改這個(gè)比率。
在啟動(dòng)一個(gè) Go 程序時(shí),可以設(shè)置 GODEBUG=gctrace=1 來(lái)打印 GC 日志,日志具體含義可參考 pkg/runtime
<https://golang.org/pkg/runtime/#hdr-Environment_Variables>,這里不再贅述。對(duì)調(diào)試感興趣的可以閱讀:
* https://new.blog.cloudflare.com/go-dont-collect-my-garbage/
<https://new.blog.cloudflare.com/go-dont-collect-my-garbage/>
總結(jié)
Go 最初由 Google 在 2007 為解決軟件復(fù)雜度、提升開發(fā)效率的一試驗(yàn)品,到如今不過(guò)十二年,但無(wú)疑已經(jīng)家喻戶曉
<https://hackernoon.com/major-programming-trends-to-prepare-for-in-2019-169987cc75f4>
,成為云時(shí)代的首選 <https://thenewstack.io/go-the-programming-language-of-the-cloud/>
。其面向接口的特有編程方式,也非常靈活,兼具動(dòng)態(tài)語(yǔ)言的簡(jiǎn)潔與靜態(tài)語(yǔ)言的高效,推薦大家嘗試一下。Go Go Go!
擴(kuò)展閱讀
* 03-包與依賴管理.md
<https://github.com/overnote/golang/blob/master/01-Go初識(shí)/03-包與依賴管理.md>
* I Love Go; I Hate Go
<http://dtrace.org/blogs/ahl/2016/08/02/i-love-go-i-hate-go/>
* The Go Programming Language Specification#Receive operator
<https://golang.org/ref/spec#Receive_operator>
* 王垠:對(duì) Go 語(yǔ)言的綜合評(píng)價(jià) <http://www.yinwang.org/blog-cn/2014/04/18/golang>
* https://github.com/golang/go/wiki/CodeReviewComments
<https://github.com/golang/go/wiki/CodeReviewComments>
* https://golang.org/doc/faq <https://golang.org/doc/faq>
熱門工具 換一換

感谢您访问我们的网站,您可能还对以下资源感兴趣:
调教肉文小说-国产成本人片免费av-空姐av种子无码-在线观看免费午夜视频-综合久久精品激情-国产成人丝袜视频在线观看软件-大芭区三区四区无码-啊啊好爽啊啊插啊用力啊啊-wanch视频网-国产精品成人a免费观看