Go語言規(guī)范context?類型的key用法示例解析
引言
現(xiàn)在團(tuán)隊(duì)里幾乎所有的代碼都需要經(jīng)過 Code Review(代碼審查)之后,才允許合入主分支。作為 CR 負(fù)責(zé)人之一,在 CR 中看到了不少不適合的問題,也看到了不少值得學(xué)習(xí)的點(diǎn)。筆者決定從今天開始,一點(diǎn)一滴地記錄這些做法、經(jīng)驗(yàn)、教訓(xùn),以饗讀者。
這系列文章,我都會(huì)先拋出一句話規(guī)范說明,然后解釋問題的背景,最后再給出正確的規(guī)范。
一句話規(guī)范
- 當(dāng)使用
context.Context
類型保存 KV 對時(shí), key 不能使用原生類型,而應(yīng)該使用派生類型
問題背景
我們知道,可以利用 context.Context
類型來存一些自定義的鍵值對——當(dāng)然了,需要保存新的 context 對象:
ctx = context.WithValue(ctx, someKey, someValue)
很快就可以注意到 key 的參數(shù)類型是 any
,也就是 Go 1.17 之前的 interface{}
。“可能是為了能夠使用 int 吧?” ——初學(xué)者很可能會(huì)這么想。
在實(shí)際的 CR 中,可以看到很多人都使用 string 類型作為 key,比如這是一個(gè)非常典型的例子:
ctx = context.WithValue(ctx, "openid", userOpenID)
存在問題
如果你使用了 VSCode,并且一鍵安裝了 Go 開發(fā)工具包,那么 VSCode 大概率也安裝了 golangci-lint
工具。此時(shí),上面的這段代碼會(huì)喜提一個(gè) warning:
SA1029: should not use built-in type string as key for value; define your own type to avoid collisions (staticcheck)
告警信息雖然是英文,但很容易理解:
- 不應(yīng)該使用內(nèi)建類型作為 KV 中 key 的類型,而應(yīng)該使用自定義的類型來避免沖突。
為什么要這么做呢?很簡單,現(xiàn)代軟件都是團(tuán)隊(duì)開發(fā)的,多模塊相互耦合,互相協(xié)作。在一個(gè) ctx
對象的整個(gè)生命周期中,它需要經(jīng)過多個(gè)邏輯 / 模塊的洗禮,每一個(gè)模塊都可能使用 ctx 來存儲(chǔ)相應(yīng)信息。
假設(shè) user 模塊,它使用 ctx 類型緩存了用戶的 openid 字段。這個(gè)邏輯沒什么問題。然后這個(gè) ctx(和代碼邏輯)繼續(xù)往后走,大家約定,就使用這個(gè) "openid"
來存儲(chǔ)。
有一天,來了一個(gè)緊急需求,比如說要做一個(gè)群聊功能,盡可能復(fù)用老代碼減少開發(fā)?;蛟S group 模塊就利用了 user 模塊的代碼。好巧不巧,從其他團(tuán)隊(duì)過來支援的開發(fā)同學(xué),也使用了 "openid"
這個(gè) key,來存儲(chǔ)群主的 openid。結(jié)果繞了一圈,這位同學(xué)發(fā)現(xiàn):咦怎么這群主的 openid 老變成別人的 openid?擱這群主輪流做是吧?
解決方法
可能有人覺得:那我把 key 的定義統(tǒng)一收集起來規(guī)定不就行了?解決一個(gè)問題總有上中下策,這是個(gè)辦法,但是一個(gè)下下下策。軟件工程主打一個(gè)分而治之,在沒有必要的情況下,盡可能避免集中式的管理。
最上策的解決方法簡單而言,就是使用自定義的類型作為 key 的類型。我們看看下面的代碼:
type myString string func main() { ctx := context.Background() ctx = context.WithValue(ctx, "openid", "不是群主") ctx = context.WithValue(ctx, myString("openid"), "群主") fmt.Println(ctx.Value("openid")) fmt.Println(ctx.Value(myString("openid"))) }
兩行輸出:
不是群主
群主
這就非常清晰了,盡管底層類型相同(都是 string 類型),但是經(jīng)過 type
定義之后,Go 是作為完全不同的 key 來處理的。針對具體類型自定義 key 類型之后,很好地解決了同名 key 沖突的問題。
不過呢,如果你并不需要使用同一個(gè) key 類型,存儲(chǔ)多個(gè)不同 value,那么上面的模式還只能說是中策,真正的上策是這么做的:
type myString struct{} func main() { ctx := context.Background() ctx = context.WithValue(ctx, "openid", "不是群主") ctx = context.WithValue(ctx, myString{}, "群主") fmt.Println(ctx.Value("openid")) fmt.Println(ctx.Value(myString{})) }
我還記得我當(dāng)年第一次看到這個(gè)模式的時(shí)候,我就是上圖的這個(gè)表情。是的,struct{}
類型也可以作為 KV 的 key 類型——當(dāng)然了,也應(yīng)該定義為自定義類型。
使用 struct{}
的好處可是大大的多:首先,這個(gè)類型在 Go 中原則上是不占內(nèi)存空間和 gc 開銷的,可以提升性能;其次,這少了開發(fā)者額外 “寫一個(gè) key” 的時(shí)間(類型往往可以通過 IDE 快速補(bǔ)全),大大提高了敲代碼的速度呀。
典型例子
使用 context WithValue 方法,有一個(gè)很典型的例子,就是在 ctx 中存入一個(gè) trace ID,用于跟蹤整個(gè)調(diào)用鏈。那么,我們可以包裝一個(gè) traceid
包,比較規(guī)范的寫法是這樣的:
// Package traceid 用于在 context 中維護(hù) trace ID package traceid import "context" // WithTraceID 往 context 中存入 trace ID func WithTraceID(ctx context.Context, traceID string) context.Context { return context.WithValue(ctx, traceIDKey{}, traceID) } // TraceID 從 context 中提取 trace ID func TraceID(ctx context.Context) string { v := context.Value(ctx, traceIDKey{}) id, _ := v.(string) return id } type traceIDKey struct{}
以上就是Go語言規(guī)范context 類型的key用法示例解析的詳細(xì)內(nèi)容,更多關(guān)于Go context類型key的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于Golang標(biāo)準(zhǔn)庫flag的全面講解
這篇文章主要介紹了關(guān)于Golang標(biāo)準(zhǔn)庫flag的全面講解,這個(gè)庫的代碼量只有1000行左右,卻提供了非常完善的命令行參數(shù)解析功能,更多相關(guān)內(nèi)容需要的朋友可以參考一下2022-09-09Go語言題解LeetCode268丟失的數(shù)字示例詳解
這篇文章主要為大家介紹了Go語言題解LeetCode268丟失的數(shù)字示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12jenkins配置golang?代碼工程自動(dòng)發(fā)布的實(shí)現(xiàn)方法
這篇文章主要介紹了jenkins配置golang?代碼工程自動(dòng)發(fā)布,jks是個(gè)很好的工具,使用方法也很多,我只用了它簡單的功能,對jenkins配置golang相關(guān)知識感興趣的朋友一起看看吧2022-07-07Prometheus Go client library使用方式詳解
這篇文章主要為大家介紹了Prometheus Go client library使用方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11詳解Go中g(shù)in框架如何實(shí)現(xiàn)帶顏色日志
當(dāng)我們在終端上(比如Goland)運(yùn)行g(shù)in框架搭建的服務(wù)時(shí),會(huì)發(fā)現(xiàn)輸出的日志是可以帶顏色的,那這是如何實(shí)現(xiàn)的呢?本文就來和大家簡單講講2023-04-04