Errors
錯(cuò)誤處理是現(xiàn)實(shí)中經(jīng)常碰到的竟贯、難以處理好的問(wèn)題扶踊,下面會(huì)從下面幾個(gè)方面探討錯(cuò)誤處理:
- 為什么Go沒(méi)有選擇異常,而是返回錯(cuò)誤碼(error)嫉沽? 因?yàn)楫惓DP秃茈y看出有沒(méi)有寫(xiě)對(duì)择诈,錯(cuò)誤碼方式也不容易械蹋,相對(duì)會(huì)簡(jiǎn)單點(diǎn)。
- Go的error有什么問(wèn)題羞芍,為何Go2草案這么大篇幅說(shuō)error改進(jìn)哗戈? 因?yàn)镚o雖然是錯(cuò)誤碼但還不夠好,問(wèn)題在于啰嗦荷科、繁雜唯咬、缺失關(guān)鍵信息。
- 有哪些好用的error庫(kù)畏浆,如何和日志配合使用胆胰? 推薦用庫(kù)pkg/errors;另外刻获,避免日志和錯(cuò)誤混淆蜀涨。
- Go的錯(cuò)誤處理最佳實(shí)踐是什么? 配合日志使用錯(cuò)誤。錯(cuò)誤需要帶上上下文勉盅、堆棧等信息。
錯(cuò)誤和異常
我們總會(huì)遇到非預(yù)期的非正常情況顶掉,有一種是符合預(yù)期的草娜,比如函數(shù)返回error并處理,這種叫做可以預(yù)見(jiàn)到的錯(cuò)誤痒筒,還有一種是預(yù)見(jiàn)不到的比如除零宰闰、空指針、數(shù)組越界等叫做panic簿透,panic的處理主要參考Defer, Panic, and Recover移袍。
錯(cuò)誤處理的模型一般有兩種,一般是錯(cuò)誤碼模型比如C/C++和Go老充,還有異常模型比如Java和C#葡盗。Go沒(méi)有選擇異常模型,因?yàn)殄e(cuò)誤碼比異常更有優(yōu)勢(shì)啡浊,參考文章Cleaner, more elegant, and wrong 以及Cleaner, more elegant, and harder to recognize觅够。看下面的代碼:
try {
AccessDatabase accessDb = new AccessDatabase();
accessDb.GenerateDatabase();
} catch (Exception e) {
// Inspect caught exception
}
public void GenerateDatabase()
{
CreatePhysicalDatabase();
CreateTables();
CreateIndexes();
}
這段代碼的錯(cuò)誤處理有很多問(wèn)題巷嚣,比如如果CreateIndexes
拋出異常喘先,會(huì)導(dǎo)致數(shù)據(jù)庫(kù)和表不會(huì)刪除,造成臟數(shù)據(jù)廷粒。從代碼編寫(xiě)者和維護(hù)者的角度看這兩個(gè)模型窘拯,會(huì)比較清楚:
Really Easy | Hard | Really Hard |
---|---|---|
Writing bad error-code-based code Writing bad exception-based code |
Writing good error-code-based code |
Writing good exception-based code |
錯(cuò)誤處理不容易做好,要說(shuō)容易那說(shuō)明做錯(cuò)了坝茎;要把錯(cuò)誤處理寫(xiě)對(duì)了涤姊,基于錯(cuò)誤碼模型雖然很難,但比異常模型簡(jiǎn)單嗤放。
Really Easy | Hard | Really Hard |
---|---|---|
Recognizing that error-code-based code is badly-written Recognizing the difference between bad error-code-based code and not-bad error-code-based code. |
Recognizing that error-code-base code is not badly-written |
Recognizing that exception-based code is badly-written Recognizing that exception-based code is not badly-written Recognizing the difference between bad exception-based code and not-bad exception-based code |
如果使用錯(cuò)誤碼模型砂轻,非常容易就能看出錯(cuò)誤處理沒(méi)有寫(xiě)對(duì),也能很容易知道做得好不好斤吐;要知道是否做得非常好搔涝,錯(cuò)誤碼模型也不太容易。
如果使用異常模型和措,無(wú)論做的好不好都很難知道庄呈,而且也很難知道怎么做好。
Errors in Go
Go官方的error介紹派阱,簡(jiǎn)單一句話(huà)就是返回錯(cuò)誤對(duì)象的方式诬留,參考Error handling and Go,解釋了error是什么,如何判斷具體的錯(cuò)誤文兑,顯式返回錯(cuò)誤的好處盒刚。文中舉的例子就是打開(kāi)文件錯(cuò)誤:
func Open(name string) (file *File, err error)
Go可以返回多個(gè)值,最后一個(gè)一般是error绿贞,我們需要檢查和處理這個(gè)錯(cuò)誤因块,這就是Go的錯(cuò)誤處理的官方介紹:
if err := Open("src.txt"); err != nil {
// Handle err
}
看起來(lái)非常簡(jiǎn)單的錯(cuò)誤處理,有什么難的呢籍铁?騷等涡上,在Go2的草案中,提到的三個(gè)點(diǎn)Error Handling拒名、Error Values和Generics泛型吩愧,兩個(gè)點(diǎn)都是錯(cuò)誤處理的,這說(shuō)明了Go1中對(duì)于錯(cuò)誤是有改進(jìn)的地方增显。
再詳細(xì)看下Go2的草案雁佳,錯(cuò)誤處理:Error Handling中,主要描述了發(fā)生錯(cuò)誤時(shí)的重復(fù)代碼同云,以及不能便捷處理錯(cuò)誤的情況甘穿。比如草案中舉的這個(gè)例子No Error Handling: CopyFile,沒(méi)有做任何錯(cuò)誤處理:
package main
import (
"fmt"
"io"
"os"
)
func CopyFile(src, dst string) error {
r, _ := os.Open(src)
defer r.Close()
w, _ := os.Create(dst)
io.Copy(w, r)
w.Close()
return nil
}
func main() {
fmt.Println(CopyFile("src.txt", "dst.txt"))
}
還有草案中這個(gè)例子Not Nice and still Wrong: CopyFile梢杭,錯(cuò)誤處理是特別啰嗦温兼,而且比較明顯有問(wèn)題:
package main
import (
"fmt"
"io"
"os"
)
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return err
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return err
}
defer w.Close()
if _, err := io.Copy(w, r); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
return nil
}
func main() {
fmt.Println(CopyFile("src.txt", "dst.txt"))
}
當(dāng)io.Copy
或w.Close
出現(xiàn)錯(cuò)誤時(shí),目標(biāo)文件實(shí)際上是有問(wèn)題武契,那應(yīng)該需要?jiǎng)h除dst文件的募判。而且需要給出錯(cuò)誤時(shí)的信息,比如是哪個(gè)文件咒唆,不能直接返回err届垫。所以Go中正確的錯(cuò)誤處理,應(yīng)該是這個(gè)例子Good: CopyFile全释,雖然啰嗦繁瑣不簡(jiǎn)潔:
package main
import (
"fmt"
"io"
"os"
)
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
if _, err := io.Copy(w, r); err != nil {
w.Close()
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
if err := w.Close(); err != nil {
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
return nil
}
func main() {
fmt.Println(CopyFile("src.txt", "dst.txt"))
}
具體應(yīng)該如何簡(jiǎn)潔的處理錯(cuò)誤装处,可以讀Error Handling,大致是引入關(guān)鍵字handle和check浸船,由于本文重點(diǎn)側(cè)重Go1如何錯(cuò)誤處理妄迁,就不展開(kāi)分享了。
明顯上面每次都返回的fmt.Errorf
信息也是不夠的李命,所以Go2還對(duì)于錯(cuò)誤的值有提案登淘,參考Error Values。大規(guī)模程序應(yīng)該面向錯(cuò)誤編程和測(cè)試封字,同時(shí)錯(cuò)誤應(yīng)該包含足夠的信息黔州。Go1中判斷error具體是什么錯(cuò)誤有幾種辦法:
- 直接比較耍鬓,比如返回的是
io.EOF
這個(gè)全局變量,那么可以直接比較是否是這個(gè)錯(cuò)誤流妻。 - 可以用類(lèi)型轉(zhuǎn)換type或switch牲蜀,嘗試來(lái)轉(zhuǎn)換成具體的錯(cuò)誤類(lèi)型,看是哪種錯(cuò)誤绅这。
- 提供某些函數(shù)來(lái)判斷是否是某個(gè)錯(cuò)誤涣达,比如
os.IsNotExist
判斷是否是指定錯(cuò)誤。 - 當(dāng)多個(gè)錯(cuò)誤被糅合到一起時(shí)君躺,只能用
error.Error()
返回的字符串匹配峭判,看是否是某個(gè)錯(cuò)誤开缎。
在復(fù)雜程序中棕叫,有用的錯(cuò)誤需要包含調(diào)用鏈的信息。例如奕删,考慮一次數(shù)據(jù)庫(kù)寫(xiě)俺泣,可能調(diào)用了RPC,RPC調(diào)用了域名解析完残,最終是沒(méi)有權(quán)限讀/etc/resolve.conf
文件伏钠,那么給出下面的調(diào)用鏈會(huì)非常有用:
write users database: call myserver.Method: \
dial myserver:3333: open /etc/resolv.conf: permission denied
Errors Solutions
由于Go1的錯(cuò)誤值沒(méi)有完整的解決這個(gè)問(wèn)題,才導(dǎo)致出現(xiàn)非常多的錯(cuò)誤處理的庫(kù)谨设,比如:
- 2017, 12, upspin.io/errors熟掂,帶邏輯調(diào)用堆棧的錯(cuò)誤庫(kù),而不是執(zhí)行的堆棧扎拣,引入了
errors.Is
赴肚、errors.As
和errors.Match
。 - 2015.12, github.com/pkg/errors二蓝,帶堆棧的錯(cuò)誤誉券,引入了
%+v
來(lái)格式化錯(cuò)誤的額外信息比如堆棧。 - 2014.10, github.com/hashicorp/errwrap刊愚,可以wrap多個(gè)錯(cuò)誤踊跟,引入了錯(cuò)誤樹(shù),提供Walk函數(shù)遍歷所有的錯(cuò)誤鸥诽。
- 2014.2, github.com/juju/errgo商玫,Wrap時(shí)可以選擇是否隱藏底層錯(cuò)誤。和
pkg/errors
的Cause返回最底層的錯(cuò)誤不同牡借,它只反饋錯(cuò)誤鏈的下一個(gè)錯(cuò)誤决帖。 - 2013.7, github.com/spacemonkeygo/errors,是來(lái)源于一個(gè)大型Python項(xiàng)目蓖捶,有錯(cuò)誤的hierarchies地回,自動(dòng)記錄日志和堆棧,還可以帶額外的信息。打印錯(cuò)誤的消息比較固定刻像,不能自己定義畅买。
- 2019.09,Go1.13標(biāo)準(zhǔn)庫(kù)擴(kuò)展了error细睡,支持了Unwrap谷羞、As和Is,但沒(méi)有支持堆棧信息溜徙。
Go1.13改進(jìn)了errors湃缎,參考如下實(shí)例代碼:
package main
import (
"errors"
"fmt"
"io"
)
func foo() error {
return fmt.Errorf("read err: %w", io.EOF)
}
func bar() error {
if err := foo(); err != nil {
return fmt.Errorf("foo err: %w", err)
}
return nil
}
func main() {
if err := bar(); err != nil {
fmt.Printf("err: %+v\n", err)
fmt.Printf("unwrap: %+v\n", errors.Unwrap(err))
fmt.Printf("unwrap of unwrap: %+v\n", errors.Unwrap(errors.Unwrap(err)))
fmt.Printf("err is io.EOF? %v\n", errors.Is(err, io.EOF))
}
}
運(yùn)行結(jié)果如下:
err: foo err: read err: EOF
unwrap: read err: EOF
unwrap of unwrap: EOF
err is io.EOF? true
從上面的例子可以看出:
- 沒(méi)有堆棧信息,主要是想通過(guò)Wrap的日志來(lái)標(biāo)識(shí)堆棧蠢壹,如果全部Wrap一層和堆棧差不多嗓违,不過(guò)對(duì)于沒(méi)有Wrap的錯(cuò)誤還是無(wú)法知道調(diào)用堆棧。
- Unwrap只會(huì)展開(kāi)第一個(gè)嵌套的error图贸,如果錯(cuò)誤有多層嵌套蹂季,取不到最里面的那個(gè)error,需要多次Unwrap才行疏日。
- 用
errors.Is
能判斷出是否是最里面的那個(gè)error偿洁。
另外,錯(cuò)誤處理往往和log是容易混為一談的沟优,因?yàn)橛龅藉e(cuò)誤一般會(huì)打日志涕滋,特別是在C/C++中返回錯(cuò)誤碼一般都會(huì)打日志記錄下,有時(shí)候還會(huì)記錄一個(gè)全局的錯(cuò)誤碼比如linux的errno挠阁,而這種習(xí)慣宾肺,造成了error和log混淆造成比較大的困擾【槲ǎ考慮以前寫(xiě)了一個(gè)C++的服務(wù)器爱榕,出現(xiàn)錯(cuò)誤時(shí)會(huì)在每一層打印日志,所以就會(huì)形成堆棧式的錯(cuò)誤日志坡慌,便于排查問(wèn)題黔酥,如果只有一個(gè)錯(cuò)誤,不知道調(diào)用上下文洪橘,排查會(huì)很困難:
avc decode avc_packet_type failed. ret=3001
Codec parse video failed, ret=3001
origin hub error, ret=3001
這種比只打印一條日志origin hub error, ret=3001
要好跪者,但是還不夠好:
- 和Go的錯(cuò)誤一樣,比較啰嗦熄求,有重復(fù)的信息渣玲。如果能提供堆棧信息,可以省去很多需要手動(dòng)寫(xiě)的信息弟晚。
- 對(duì)于應(yīng)用程序可以打日志忘衍,但是對(duì)于庫(kù)逾苫,信息都應(yīng)該包含在error中,不應(yīng)該直接打印日志枚钓。如果底層的庫(kù)都要打印日志铅搓,那會(huì)導(dǎo)致底層庫(kù)都要依賴(lài)日志庫(kù),這是很多庫(kù)都有日志打印函數(shù)供調(diào)用者重寫(xiě)搀捷。
- 對(duì)于多線(xiàn)程星掰,看不到線(xiàn)程信息,或者看不到業(yè)務(wù)層ID的信息嫩舟。對(duì)于服務(wù)器來(lái)說(shuō)氢烘,有時(shí)候需要知道這個(gè)錯(cuò)誤是哪個(gè)連接的,從而查詢(xún)這個(gè)連接之前的上下文信息家厌。
改進(jìn)后的錯(cuò)誤日志變成了在底層返回播玖,而不在底層打印在調(diào)用層打印,有調(diào)用鏈和堆棧像街,有線(xiàn)程切換的ID信息黎棠,也有文件的行數(shù):
Error processing video, code=3001 : origin hub : codec parser : avc decoder
[100] video_avc_demux() at [srs_kernel_codec.cpp:676]
[100] on_video() at [srs_app_source.cpp:1076]
[101] on_video_imp() at [srs_app_source:2357]
從Go2的描述來(lái)說(shuō)晋渺,實(shí)際上這個(gè)錯(cuò)誤處理也還沒(méi)有考慮完備镰绎。從實(shí)際開(kāi)發(fā)來(lái)說(shuō),已經(jīng)比較實(shí)用了木西。
總結(jié)下Go的error畴栖,錯(cuò)誤處理應(yīng)該注意的點(diǎn):
- 凡是有返回錯(cuò)誤碼的函數(shù),必須顯式的處理錯(cuò)誤八千,如果要忽略錯(cuò)誤吗讶,也應(yīng)該顯式的忽略和寫(xiě)注釋。
- 錯(cuò)誤必須帶豐富的錯(cuò)誤信息恋捆,比如堆棧照皆,發(fā)生錯(cuò)誤時(shí)的參數(shù),調(diào)用鏈給的描述等等沸停。特別要強(qiáng)調(diào)變量膜毁,我看過(guò)太多日志描述了一對(duì)常量,比如"Verify the nonce, timestamp and token of specified appid failed"愤钾,而這個(gè)消息一般會(huì)提到工單中瘟滨,然后就是再問(wèn)用戶(hù),哪個(gè)session或request甚至?xí)r間點(diǎn)能颁?這么一大堆常量有啥用呢杂瘸,關(guān)鍵是變量,關(guān)鍵是變量吶伙菊。
- 盡量避免重復(fù)的信息败玉,提高錯(cuò)誤處理的開(kāi)發(fā)體驗(yàn)敌土,糟糕的體驗(yàn)會(huì)導(dǎo)致無(wú)效的錯(cuò)誤處理代碼比如拷貝和漏掉關(guān)鍵信息。
- 分離錯(cuò)誤和日志运翼,發(fā)生錯(cuò)誤時(shí)返回帶完整信息的錯(cuò)誤纯赎,在調(diào)用的頂層決定是將錯(cuò)誤用日志打印,還是發(fā)送到監(jiān)控系統(tǒng)南蹂,還是轉(zhuǎn)換錯(cuò)誤犬金,或者忽略。
Best Practice
推薦用github.com/pkg/errors這個(gè)錯(cuò)誤處理的庫(kù)六剥,基本上是夠用的晚顷,參考Refine: CopyFile,可以看到CopyFile中低級(jí)重復(fù)的代碼已經(jīng)比較少了:
package main
import (
"fmt"
"github.com/pkg/errors"
"io"
"os"
)
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return errors.Wrap(err, "open source")
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return errors.Wrap(err, "create dest")
}
nn, err := io.Copy(w, r)
if err != nil {
w.Close()
os.Remove(dst)
return errors.Wrap(err, "copy body")
}
if err := w.Close(); err != nil {
os.Remove(dst)
return errors.Wrapf(err, "close dest, nn=%v", nn)
}
return nil
}
func LoadSystem() error {
src, dst := "src.txt", "dst.txt"
if err := CopyFile(src, dst); err != nil {
return errors.WithMessage(err, fmt.Sprintf("load src=%v, dst=%v", src, dst))
}
// Do other jobs.
return nil
}
func main() {
if err := LoadSystem(); err != nil {
fmt.Printf("err %+v\n", err)
}
}
改寫(xiě)的函數(shù)中疗疟,用
errors.Wrap
和errors.Wrapf
代替了fmt.Errorf
该默,我們不記錄src和dst的值,因?yàn)樵谏蠈訒?huì)記錄這個(gè)值(參考下面的代碼)策彤,而只記錄我們這個(gè)函數(shù)產(chǎn)生的數(shù)據(jù)栓袖,比如nn
。
import "github.com/pkg/errors"
func LoadSystem() error {
src, dst := "src.txt", "dst.txt"
if err := CopyFile(src, dst); err != nil {
return errors.WithMessage(err, fmt.Sprintf("load src=%v, dst=%v", src, dst))
}
// Do other jobs.
return nil
}
在這個(gè)上層函數(shù)中店诗,我們用的是
errors.WithMessage
添加了這一層的錯(cuò)誤信息裹刮,包括src
和dst
,所以CopyFile
里面就不用重復(fù)記錄這兩個(gè)數(shù)據(jù)了庞瘸。同時(shí)我們也沒(méi)有打印日志捧弃,只是返回了帶完整信息的錯(cuò)誤。
func main() {
if err := LoadSystem(); err != nil {
fmt.Printf("err %+v\n", err)
}
}
在頂層調(diào)用時(shí)擦囊,我們拿到錯(cuò)誤违霞,可以決定是打印還是忽略還是送監(jiān)控系統(tǒng)。
比如我們?cè)谡{(diào)用層打印錯(cuò)誤瞬场,使用%+v
打印詳細(xì)的錯(cuò)誤买鸽,有完整的信息:
err open src.txt: no such file or directory
open source
main.CopyFile
/Users/winlin/t.go:13
main.LoadSystem
/Users/winlin/t.go:39
main.main
/Users/winlin/t.go:49
runtime.main
/usr/local/Cellar/go/1.8.3/libexec/src/runtime/proc.go:185
runtime.goexit
/usr/local/Cellar/go/1.8.3/libexec/src/runtime/asm_amd64.s:2197
load src=src.txt, dst=dst.txt
但是這個(gè)庫(kù)也有些小毛病:
-
CopyFile
中還是有可能會(huì)有重復(fù)的信息贯被,還是Go2的handle
和check
方案是最終解決眼五。 - 有時(shí)候需要用戶(hù)調(diào)用
Wrap
,有時(shí)候是調(diào)用WithMessage
(不需要加堆棧時(shí))刃榨,這個(gè)真是非常不好用的地方(這個(gè)我們已經(jīng)修改了庫(kù)弹砚,可以全部使用Wrap不用WithMessage,會(huì)去掉重復(fù)的堆棧)枢希。
Links
由于簡(jiǎn)書(shū)限制了文章字?jǐn)?shù)桌吃,只好分成不同章節(jié):
- Overview 為何Go有時(shí)候也叫Golang?為何要選擇Go作為服務(wù)器開(kāi)發(fā)的語(yǔ)言?是沖動(dòng)苞轿?還是騷動(dòng)茅诱?Go的重要里程碑和事件逗物,當(dāng)年吹的那些牛逼,都實(shí)現(xiàn)了哪些瑟俭?
- Could Not Recover 君可知翎卓,有什么panic是無(wú)法recover的?包括超過(guò)系統(tǒng)線(xiàn)程限制摆寄,以及map的競(jìng)爭(zhēng)寫(xiě)失暴。當(dāng)然一般都能recover,比如Slice越界微饥、nil指針逗扒、除零、寫(xiě)關(guān)閉的chan等欠橘。
- Errors 為什么Go2的草稿3個(gè)有2個(gè)是關(guān)于錯(cuò)誤處理的矩肩?好的錯(cuò)誤處理應(yīng)該怎么做?錯(cuò)誤和異常機(jī)制的差別是什么肃续?錯(cuò)誤處理和日志如何配合黍檩?
- Logger 為什么標(biāo)準(zhǔn)庫(kù)的Logger是完全不夠用的?怎么做日志切割和輪轉(zhuǎn)始锚?怎么在混成一坨的服務(wù)器日志中找到某個(gè)連接的日志刽酱?甚至連接中的流的日志?怎么做到簡(jiǎn)潔又夠用疼蛾?
- Interfaces 什么是面向?qū)ο蟮腟OLID原則肛跌?為何Go更符合SOLID艺配?為何接口組合比繼承多態(tài)更具有正交性察郁?Go類(lèi)型系統(tǒng)如何做到looser, organic, decoupled, independent, and therefore scalable?一般軟件中如果出現(xiàn)數(shù)學(xué)转唉,要么真的牛逼要么裝逼皮钠。正交性這個(gè)數(shù)學(xué)概念在Go中頻繁出現(xiàn),是神仙還是妖怪赠法?為何接口設(shè)計(jì)要考慮正交性麦轰?
- Modules 如何避免依賴(lài)地獄(Dependency Hell)?小小的版本號(hào)為何會(huì)帶來(lái)大災(zāi)難砖织?Go為什么推出了GOPATH款侵、Vendor還要搞module和vgo?新建了16個(gè)倉(cāng)庫(kù)做測(cè)試侧纯,碰到了9個(gè)坑新锈,搞清楚了gopath和vendor如何遷移,以及vgo with vendor如何使用(畢竟生產(chǎn)環(huán)境不能每次都去外網(wǎng)下載)眶熬。
- Concurrency & Control 服務(wù)器中的并發(fā)處理難在哪里妹笆?為什么說(shuō)Go并發(fā)處理優(yōu)勢(shì)占領(lǐng)了云計(jì)算開(kāi)發(fā)語(yǔ)言市場(chǎng)?什么是C10K、C10M問(wèn)題贵白?如何管理goroutine的取消马澈、超時(shí)和關(guān)聯(lián)取消?為何Go1.7專(zhuān)門(mén)將context放到了標(biāo)準(zhǔn)庫(kù)窟坐?context如何使用海渊,以及問(wèn)題在哪里?
- Engineering Go在工程化上的優(yōu)勢(shì)是什么哲鸳?為什么說(shuō)Go是一門(mén)面向工程的語(yǔ)言切省?覆蓋率要到多少比較合適?什么叫代碼可測(cè)性帕胆?為什么良好的庫(kù)必須先寫(xiě)Example朝捆?
- Go2 Transition Go2會(huì)像Python3不兼容Python2那樣作嗎?C和C++的語(yǔ)言演進(jìn)可以有什么不同的收獲懒豹?Go2怎么思考語(yǔ)言升級(jí)的問(wèn)題芙盘?
- SRS & Others Go在流媒體服務(wù)器中的使用。Go的GC靠譜嗎脸秽?Twitter說(shuō)相當(dāng)?shù)目孔V儒老,有圖有真相。為何Go的聲明語(yǔ)法是那樣记餐?C的又是怎樣驮樊?是拍的大腿,還是拍的腦袋片酝?