Engineering
我覺得Go在工程上良好的支持,是Go能夠在服務(wù)器領(lǐng)域有一席之地的重要原因。這里說的工程友好包括:
- gofmt保證代碼的基本一致,增加可讀性玄坦,避免在爭論不清楚的地方爭論。
- 原生支持的profiling绘沉,為性能調(diào)優(yōu)和死鎖問題提供了強(qiáng)大的工具支持。
- utest和coverage豺总,持續(xù)集成车伞,為項(xiàng)目的質(zhì)量提供了良好的支撐。
- example和注釋喻喳,讓接口定義更友好合理另玖,讓庫的質(zhì)量更高。
GOFMT規(guī)范編碼
這幾天朋友圈霸屏的文章是碼農(nóng)因?yàn)榇a不規(guī)范問題打擊同事表伦,雖然實(shí)際上槍擊案可能不是因?yàn)榇a規(guī)范谦去,但可以看出大家對(duì)于代碼規(guī)范問題能引發(fā)槍擊是毫不懷疑的。這些年在不同的公司碼代碼蹦哼,和不同的人一起碼代碼鳄哭,每個(gè)地方總有人喜歡糾結(jié)于if ()
中是否應(yīng)該有空格,甚至還大開懟戒纲熏。Go語言從來不會(huì)有這種爭論妆丘,因?yàn)橛?code>gofmt,語言的工具鏈支持了格式化代碼局劲,避免大家在代碼風(fēng)格上白費(fèi)口舌勺拣。
比如,下面的代碼看著真是揪心鱼填,任何語言都可以寫出類似的一坨代碼:
package main
import (
"fmt"
"strings"
)
func foo()[]string {
return []string{"gofmt","pprof","cover"}}
func main() {
if v:=foo();len(v)>0{fmt.Println("Hello",strings.Join(v,", "))}
}
如果有幾萬行代碼都是這樣药有,是不是有扣動(dòng)扳機(jī)的沖動(dòng)?如果我們執(zhí)行下gofmt -w t.go
之后苹丸,就變成下面的樣子:
package main
import (
"fmt"
"strings"
)
func foo() []string {
return []string{"gofmt", "pprof", "cover"}
}
func main() {
if v := foo(); len(v) > 0 {
fmt.Println("Hello", strings.Join(v, ", "))
}
}
是不是心情舒服多了愤惰?gofmt只能解決基本的代碼風(fēng)格問題苇经,雖然這個(gè)已經(jīng)節(jié)約了不少口舌和唾沫,我想特別強(qiáng)調(diào)幾點(diǎn):
- 有些IDE會(huì)在保存時(shí)自動(dòng)gofmt羊苟,如果沒有手動(dòng)運(yùn)行下命令
gofmt -w .
塑陵,可以將當(dāng)前目錄和子目錄下的所有文件都格式化一遍,也很容易的是不是蜡励。 - gofmt不識(shí)別空行令花,因?yàn)榭招惺怯幸饬x的,因?yàn)榭招杏幸饬x所以gofmt不知道如何處理凉倚,而這正是很多同學(xué)經(jīng)常犯的問題兼都。
- gofmt有時(shí)候會(huì)因?yàn)閷?duì)齊問題,導(dǎo)致額外的不必要的修改稽寒,這不會(huì)有什么問題扮碧,但是會(huì)干擾CR從而影響CR的質(zhì)量。
先看空行問題杏糙,不能隨便使用空行慎王,因?yàn)榭招杏幸饬x。不能在不該空行的地方用空行宏侍,不能在該有空行的地方不用空行赖淤,比如下面的例子:
package main
import (
"fmt"
"io"
"os"
)
func main() {
f, err := os.Open(os.Args[1])
if err != nil {
fmt.Println("show file err %v", err)
os.Exit(-1)
}
defer f.Close()
io.Copy(os.Stdout, f)
}
上面的例子看起來就相當(dāng)?shù)钠孑猓?code>if和os.Open
之間沒有任何原因需要個(gè)空行,結(jié)果來了個(gè)空行谅河;而defer
和io.Copy
之間應(yīng)該有個(gè)空行卻沒有個(gè)空行咱旱。空行是非常好的體現(xiàn)了邏輯關(guān)聯(lián)的方式绷耍,所以空行不能隨意吐限,非常嚴(yán)重影響可讀性,要么就是一坨東西看得很費(fèi)勁褂始,要么就是突然看到兩個(gè)緊密的邏輯身首異處诸典,真的讓人很詫異。上面的代碼可以改成這樣崎苗,是不是看起來很舒服了:
package main
import (
"fmt"
"io"
"os"
)
func main() {
f, err := os.Open(os.Args[1])
if err != nil {
fmt.Println("show file err %v", err)
os.Exit(-1)
}
defer f.Close()
io.Copy(os.Stdout, f)
}
再看gofmt的對(duì)齊問題搂赋,一般出現(xiàn)在一些結(jié)構(gòu)體有長短不一的字段,比如統(tǒng)計(jì)信息益缠,比如下面的代碼:
package main
type NetworkStat struct {
IncomingBytes int `json:"ib"`
OutgoingBytes int `json:"ob"`
}
func main() {
}
如果新增字段比較長脑奠,會(huì)導(dǎo)致之前的字段也會(huì)增加空白對(duì)齊,看起來整個(gè)結(jié)構(gòu)體都改變了:
package main
type NetworkStat struct {
IncomingBytes int `json:"ib"`
OutgoingBytes int `json:"ob"`
IncomingPacketsPerHour int `json:"ipp"`
DropKiloRateLastMinute int `json:"dkrlm"`
}
func main() {
}
比較好的解決辦法就是用注釋幅慌,添加注釋后就不會(huì)強(qiáng)制對(duì)齊了宋欺。
Profile性能調(diào)優(yōu)
性能調(diào)優(yōu)是一個(gè)工程問題,關(guān)鍵是測量后優(yōu)化,而不是盲目優(yōu)化齿诞。Go提供了大量的測量程序的工具和機(jī)制酸休,包括Profiling Go Programs
, Introducing HTTP Tracing
,我們也在性能優(yōu)化時(shí)使用過Go的Profiling祷杈,原生支持是非常便捷的斑司。
對(duì)于多線程同步可能出現(xiàn)的死鎖和競爭問題,Go提供了一系列工具鏈但汞,比如Introducing the Go Race Detector
, Data Race Detector
宿刮,不過打開race后有明顯的性能損耗,不應(yīng)該在負(fù)載較高的線上服務(wù)器打開私蕾,會(huì)造成明顯的性能瓶頸僵缺。
推薦服務(wù)器開啟http profiling,偵聽在本機(jī)可以避免安全問題踩叭,需要profiling時(shí)去機(jī)器上把profile數(shù)據(jù)拿到后磕潮,拿到線下分析原因。實(shí)例代碼如下:
package main
import (
"net/http"
_ "net/http/pprof"
"time"
)
func main() {
go http.ListenAndServe("127.0.0.1:6060", nil)
for {
b := make([]byte, 4096)
for i := 0; i < len(b); i++ {
b[i] = b[i] + 0xf
}
time.Sleep(time.Nanosecond)
}
}
編譯成二進(jìn)制后啟動(dòng)go mod init private.me && go build . && ./private.me
容贝,在瀏覽器訪問頁面可以看到各種性能數(shù)據(jù)的導(dǎo)航:http://localhost:6060/debug/pprof/
例如分析CPU的性能瓶頸自脯,可以執(zhí)行go tool pprof private.me http://localhost:6060/debug/pprof/profile
,默認(rèn)是分析30秒內(nèi)的性能數(shù)據(jù)斤富,進(jìn)入pprof后執(zhí)行top可以看到CPU使用最高的函數(shù):
(pprof) top
Showing nodes accounting for 42.41s, 99.14% of 42.78s total
Dropped 27 nodes (cum <= 0.21s)
Showing top 10 nodes out of 22
flat flat% sum% cum cum%
27.20s 63.58% 63.58% 27.20s 63.58% runtime.pthread_cond_signal
13.07s 30.55% 94.13% 13.08s 30.58% runtime.pthread_cond_wait
1.93s 4.51% 98.64% 1.93s 4.51% runtime.usleep
0.15s 0.35% 98.99% 0.22s 0.51% main.main
除了top冤今,還可以輸入web命令看調(diào)用圖,還可以用go-torch看火焰圖等茂缚。
UTest和Coverage
當(dāng)然工程化少不了UTest和覆蓋率,關(guān)于覆蓋Go也提供了原生支持The cover story
屋谭,一般會(huì)有專門的CISE集成測試環(huán)境脚囊。集成測試之所以重要,是因?yàn)殡S著代碼規(guī)模的增長桐磁,有效的覆蓋能顯著的降低引入問題的可能性悔耘。
什么是有效的覆蓋?一般多少覆蓋率比較合適我擂?80%覆蓋夠好了嗎衬以?90%覆蓋一定比30%覆蓋好嗎?我覺得可不一定了校摩,參考Testivus On Test Coverage看峻。對(duì)于UTest和覆蓋,我覺得重點(diǎn)在于:
- UTest和覆蓋率一定要有衙吩,哪怕是0.1%也必須要有互妓,為什么呢?因?yàn)槌霈F(xiàn)故障時(shí)讓老板心里好受點(diǎn)啊,能用數(shù)據(jù)衡量出來裸奔的代碼有多少冯勉。
- 核心代碼和業(yè)務(wù)代碼一定要分離澈蚌,強(qiáng)調(diào)核心代碼的覆蓋率才有意義,比如整體覆蓋了80%灼狰,核心代碼占5%宛瞄,核心代碼覆蓋率為10%,那么這個(gè)覆蓋就不怎么有效了交胚。
- 除了關(guān)鍵正常邏輯份汗,更應(yīng)該重視異常邏輯,異常邏輯一般不會(huì)執(zhí)行到承绸,而一旦藏有bug可能就會(huì)造成問題裸影。有可能有些罕見的代碼無法覆蓋到,那么這部分邏輯代碼军熏,CR時(shí)需要特別人工Review轩猩。
分離核心代碼是關(guān)鍵〉磁欤可以將核心代碼分離到單獨(dú)的package均践,對(duì)這個(gè)package要求更高的覆蓋率,比如我們要求98%的覆蓋(實(shí)際上做到了99.14%的覆蓋)摩幔。對(duì)于應(yīng)用的代碼彤委,具備可測性是非常關(guān)鍵的,舉個(gè)我自己的例子或衡,go-oryx這部分代碼是判斷哪些url是代理焦影,就不具備可測性,下面是主要的邏輯:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if o := r.Header.Get("Origin"); len(o) > 0 {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
if proxyUrls == nil {
......
fs.ServeHTTP(w, r)
return
}
for _, proxyUrl := range proxyUrls {
srcPath, proxyPath := r.URL.Path, proxyUrl.Path
......
if proxy, ok := proxies[proxyUrl.Path]; ok {
p.ServeHTTP(w, r)
return
}
}
fs.ServeHTTP(w, r)
})
可以看得出來封断,關(guān)鍵需要測試的核心代碼斯辰,在于后面如何判斷URL符合定義的規(guī)范,這部分應(yīng)該被定義成函數(shù)坡疼,這樣就可以單獨(dú)測試了:
func shouldProxyURL(srcPath, proxyPath string) bool {
if !strings.HasSuffix(srcPath, "/") {
// /api to /api/
// /api.js to /api.js/
// /api/100 to /api/100/
srcPath += "/"
}
if !strings.HasSuffix(proxyPath, "/") {
// /api/ to /api/
// to match /api/ or /api/100
// and not match /api.js/
proxyPath += "/"
}
return strings.HasPrefix(srcPath, proxyPath)
}
func run(ctx context.Context) error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
......
for _, proxyUrl := range proxyUrls {
if !shouldProxyURL(r.URL.Path, proxyUrl.Path) {
continue
}
代碼參考go-oryx: Extract and test URL proxy彬呻,覆蓋率請(qǐng)看gocover: For go-oryx coverage,這樣的代碼可測性就會(huì)比較好柄瑰,也能在有限的精力下盡量讓覆蓋率有效闸氮。
Note: 可見,單元測試和覆蓋率教沾,并不是測試的事情蒲跨,而是代碼本身應(yīng)該提高的代碼“可測試性”。
另外授翻,對(duì)于Go的測試還有幾點(diǎn)值得說明:
- helper:測試時(shí)如果調(diào)用某個(gè)函數(shù)财骨,出錯(cuò)時(shí)總是打印那個(gè)共用的函數(shù)的行數(shù)镐作,而不是測試的函數(shù)。比如test_helper.go隆箩,如果
compare
不調(diào)用t.Helper()
该贾,那么錯(cuò)誤顯示是hello_test.go:26: Returned: [Hello, world!], Expected: [BROKEN!]
,調(diào)用t.Helper()之后是
hello_test.go:18: Returned: [Hello, world!], Expected: [BROKEN!]`捌臊,實(shí)際上應(yīng)該是18行的case有問題杨蛋,而不是26行這個(gè)compare函數(shù)的問題。 - benchmark:測試時(shí)還可以帶Benchmark的理澎,參數(shù)不是
testing.T
而是testing.B
逞力,執(zhí)行時(shí)會(huì)動(dòng)態(tài)調(diào)整一些參數(shù),比如testing.B.N糠爬,還有并行執(zhí)行的testing.PB. RunParallel
寇荧,參考Benchamrk。 - main: 測試也是有個(gè)main函數(shù)的执隧,參考TestMain揩抡,可以做一些全局的初始化和處理。
- doc.go: 整個(gè)包的文檔描述镀琉,一般是在
package http
前面加說明峦嗤,比如http doc的使用例子。
對(duì)于Helper還有一種思路屋摔,就是用帶堆棧的error烁设,參考前面關(guān)于errors的說明,不僅能將所有堆棧的行數(shù)給出來钓试,而且可以帶上每一層的信息装黑。
注意如果package只暴露了interface,比如go-oryx-lib: aac通過
NewADTS() (ADTS, error)
返回的是接口ADTS
弓熏,無法給ADTS的函數(shù)加Example恋谭;因此我們專門暴露了一個(gè)ADTSImpl
的結(jié)構(gòu)體,而New函數(shù)返回的還是接口硝烂,這種做法不是最好的,讓用戶有點(diǎn)無所適從铜幽,不知道該用ADTS
還是ADTSImpl
滞谢。所以一種可選的辦法,就是在包里面有個(gè)doc.go
放說明除抛,例如net/http/doc.go
文件狮杨,就是在package http
前面加說明,比如http doc的使用例子到忽。
注釋和Example
注釋和Example是非常容易被忽視的橄教,我覺得應(yīng)該注意的地方包括:
- 項(xiàng)目的README.md和Wiki清寇,這實(shí)際上就是新人指南,因?yàn)樾氯巳绻芏敲淳秃苋菀琢私膺@個(gè)項(xiàng)目的大概情況护蝶,很多項(xiàng)目都沒有這個(gè)华烟。如果沒有README,那么就需要看文件持灰,該看哪個(gè)文件盔夜?這就讓人很抓狂了。
- 關(guān)鍵代碼沒有注釋堤魁,比如庫的API喂链,關(guān)鍵的函數(shù),不好懂的代碼段落妥泉。如果看標(biāo)準(zhǔn)庫椭微,絕大部分可以調(diào)用的API都有很好的注釋,沒有注釋怎么調(diào)用呢盲链?只能看代碼實(shí)現(xiàn)了蝇率,如果每次調(diào)用都要看一遍實(shí)現(xiàn),真的很難受了匈仗。
- 庫沒有Example瓢剿,庫是一種要求很高的包,就是給別人使用的包悠轩,比如標(biāo)準(zhǔn)庫间狂。絕大部分的標(biāo)準(zhǔn)庫的包,都有Example火架,因?yàn)闆]有Example很難設(shè)計(jì)出合理的API鉴象。
先看關(guān)鍵代碼的注釋,有些注釋完全是代碼的重復(fù)何鸡,沒有任何存在的意義纺弊,唯一的存在就是提高代碼的“注釋率”,這又有什么用呢骡男,比如下面代碼:
wsconn *Conn //ws connection
// The RPC call.
type rpcCall struct {
// Setup logger.
if err := SetupLogger(......); err != nil {
// Wait for os signal
server.WaitForSignals(
如果注釋能通過函數(shù)名看出來(比較好的函數(shù)名要能看出來它的職責(zé))淆游,那么就不需要寫重復(fù)的注釋,注釋要說明一些從代碼中看不出來的東西隔盛,比如標(biāo)準(zhǔn)庫的函數(shù)的注釋:
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
// ParseInt interprets a string s in the given base (0, 2 to 36) and
// bit size (0 to 64) and returns the corresponding value i.
//
// If base == 0, the base is implied by the string's prefix:
// base 2 for "0b", base 8 for "0" or "0o", base 16 for "0x",
// and base 10 otherwise. Also, for base == 0 only, underscore
// characters are permitted per the Go integer literal syntax.
// If base is below 0, is 1, or is above 36, an error is returned.
//
// The bitSize argument specifies the integer type
// that the result must fit into. Bit sizes 0, 8, 16, 32, and 64
// correspond to int, int8, int16, int32, and int64.
// If bitSize is below 0 or above 64, an error is returned.
//
// The errors that ParseInt returns have concrete type *NumError
// and include err.Num = s. If s is empty or contains invalid
// digits, err.Err = ErrSyntax and the returned value is 0;
// if the value corresponding to s cannot be represented by a
// signed integer of the given size, err.Err = ErrRange and the
// returned value is the maximum magnitude integer of the
// appropriate bitSize and sign.
func ParseInt(s string, base int, bitSize int) (i int64, err error) {
標(biāo)準(zhǔn)庫做得很好的是犹菱,會(huì)把參數(shù)名稱寫到注釋中(而不是用@param這種方式),而且會(huì)說明大量的背景信息吮炕,這些信息是從函數(shù)名和參數(shù)看不到的重要信息腊脱。
咱們?cè)倏碋xample,一種特殊的test龙亲,可能不會(huì)執(zhí)行陕凹,它的主要作用是為了推演接口是否合理悍抑,當(dāng)然也就提供了如何使用庫的例子,這就要求Example必須覆蓋到庫的主要使用場景杜耙。舉個(gè)例子搜骡,有個(gè)庫需要方式SSRF攻擊,也就是檢查HTTP Redirect時(shí)的URL規(guī)則泥技,最初我們是這樣提供這個(gè)庫的:
func NewHttpClientNoRedirect() *http.Client {
看起來也沒有問題浆兰,提供一種特殊的http.Client,如果發(fā)現(xiàn)有Redirect就返回錯(cuò)誤珊豹,那么它的Example就會(huì)是這樣:
func ExampleNoRedirectClient() {
url := "http://xxx/yyy"
client := ssrf.NewHttpClientNoRedirect()
Req, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Println("failed to create request")
return
}
resp, err := client.Do(Req)
fmt.Printf("status :%v", resp.Status)
}
這時(shí)候就會(huì)出現(xiàn)問題簸呈,我們總是返回了一個(gè)新的http.Client,如果用戶自己有了自己定義的http.Client怎么辦店茶?實(shí)際上我們只是設(shè)置了http.Client.CheckRedirect這個(gè)回調(diào)函數(shù)蜕便。如果我們先寫Example,更好的Example會(huì)是這樣:
func ExampleNoRedirectClient() {
client := http.Client{}
//Must specify checkRedirect attribute to NewFuncNoRedirect
client.CheckRedirect = ssrf.NewFuncNoRedirect()
Req, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Println("failed to create request")
return
}
resp, err := client.Do(Req)
}
那么我們自然知道應(yīng)該如何提供接口了贩幻。
其他工程化
最近得知WebRTC有4GB的代碼轿腺,包括它自己的以及依賴的代碼,就算去掉一般的測試文件和文檔丛楚,也有2GB的代碼W蹇恰!趣些!編譯起來真的是非常的耗時(shí)間仿荆,而Go對(duì)于編譯速度的優(yōu)化,據(jù)說是在Google有過驗(yàn)證的坏平,具體我們還沒有到這個(gè)規(guī)模拢操。具體可以參考Why so fast?,主要是編譯器本身比GCC快(5X)舶替,以及Go的依賴管理做的比較好令境。
Go的內(nèi)存和異常處理也做得很好,比如不會(huì)出現(xiàn)野指針顾瞪,雖然有空指針問題可以用recover來隔離異常的影響舔庶。而C或C++服務(wù)器,目前還沒有見過沒有內(nèi)存問題的陈醒,上線后就是各種的野指針滿天飛惕橙,總有因?yàn)橐爸羔樃闼赖臅r(shí)候,只是或多或少罷了孵延。
按照Go的版本發(fā)布節(jié)奏吕漂,6個(gè)月就發(fā)一個(gè)版本亲配,基本上這么多版本都很穩(wěn)定尘应,Go1.11的代碼一共有166萬行Go代碼惶凝,還有12萬行匯編代碼,其中單元測試代碼有32萬行(占17.9%)犬钢,使用實(shí)例Example有1.3萬行苍鲜。Go對(duì)于核心API是全部覆蓋的,提交有沒有導(dǎo)致API不符合要求都有單元測試保證玷犹,Go有多個(gè)集成測試環(huán)境混滔,每個(gè)平臺(tái)是否測試通過也能看到,這一整套機(jī)制讓Go項(xiàng)目雖然越來越龐大歹颓,但是整體研發(fā)效率卻很高坯屿。
Links
由于簡書限制了文章字?jǐn)?shù),只好分成不同章節(jié):
- Overview 為何Go有時(shí)候也叫Golang?為何要選擇Go作為服務(wù)器開發(fā)的語言巍扛?是沖動(dòng)领跛?還是騷動(dòng)?Go的重要里程碑和事件撤奸,當(dāng)年吹的那些牛逼吠昭,都實(shí)現(xiàn)了哪些?
- Could Not Recover 君可知胧瓜,有什么panic是無法recover的矢棚?包括超過系統(tǒng)線程限制,以及map的競爭寫府喳。當(dāng)然一般都能recover蒲肋,比如Slice越界、nil指針劫拢、除零肉津、寫關(guān)閉的chan等。
- Errors 為什么Go2的草稿3個(gè)有2個(gè)是關(guān)于錯(cuò)誤處理的舱沧?好的錯(cuò)誤處理應(yīng)該怎么做妹沙?錯(cuò)誤和異常機(jī)制的差別是什么?錯(cuò)誤處理和日志如何配合熟吏?
- Logger 為什么標(biāo)準(zhǔn)庫的Logger是完全不夠用的距糖?怎么做日志切割和輪轉(zhuǎn)?怎么在混成一坨的服務(wù)器日志中找到某個(gè)連接的日志牵寺?甚至連接中的流的日志悍引?怎么做到簡潔又夠用?
- Interfaces 什么是面向?qū)ο蟮腟OLID原則帽氓?為何Go更符合SOLID趣斤?為何接口組合比繼承多態(tài)更具有正交性?Go類型系統(tǒng)如何做到looser, organic, decoupled, independent, and therefore scalable黎休?一般軟件中如果出現(xiàn)數(shù)學(xué)浓领,要么真的牛逼要么裝逼玉凯。正交性這個(gè)數(shù)學(xué)概念在Go中頻繁出現(xiàn),是神仙還是妖怪联贩?為何接口設(shè)計(jì)要考慮正交性漫仆?
- Modules 如何避免依賴地獄(Dependency Hell)?小小的版本號(hào)為何會(huì)帶來大災(zāi)難泪幌?Go為什么推出了GOPATH盲厌、Vendor還要搞module和vgo?新建了16個(gè)倉庫做測試祸泪,碰到了9個(gè)坑吗浩,搞清楚了gopath和vendor如何遷移,以及vgo with vendor如何使用(畢竟生產(chǎn)環(huán)境不能每次都去外網(wǎng)下載)没隘。
- Concurrency & Control 服務(wù)器中的并發(fā)處理難在哪里拓萌?為什么說Go并發(fā)處理優(yōu)勢占領(lǐng)了云計(jì)算開發(fā)語言市場?什么是C10K升略、C10M問題微王?如何管理goroutine的取消、超時(shí)和關(guān)聯(lián)取消品嚣?為何Go1.7專門將context放到了標(biāo)準(zhǔn)庫炕倘?context如何使用,以及問題在哪里翰撑?
- Engineering Go在工程化上的優(yōu)勢是什么罩旋?為什么說Go是一門面向工程的語言?覆蓋率要到多少比較合適眶诈?什么叫代碼可測性涨醋?為什么良好的庫必須先寫Example?
- Go2 Transition Go2會(huì)像Python3不兼容Python2那樣作嗎逝撬?C和C++的語言演進(jìn)可以有什么不同的收獲浴骂?Go2怎么思考語言升級(jí)的問題?
- SRS & Others Go在流媒體服務(wù)器中的使用宪潮。Go的GC靠譜嗎溯警?Twitter說相當(dāng)?shù)目孔V,有圖有真相狡相。為何Go的聲明語法是那樣梯轻?C的又是怎樣?是拍的大腿尽棕,還是拍的腦袋喳挑?