Go 錯(cuò)誤處理:用 select-case 來解決這個(gè)歷史難題?
大家好,我是煎魚。
日???Go 社區(qū)的一些新動(dòng)態(tài),發(fā)現(xiàn)大家對于錯(cuò)誤處理的新提案是很積極。上次分享了一篇想要用 switch-case 來解決現(xiàn)狀的新提案,不少同學(xué)認(rèn)為不可行。
沒想到 Go 社區(qū)的同學(xué)腦洞還是很大的,這幾天又整出來個(gè) select-case 的新提案的方式來解決錯(cuò)誤處理。
今天基于此給大家分享一下社區(qū)里的新腦洞。
快速背景
本節(jié)的背景主要是給不了解的同學(xué)拉通一下。如果已經(jīng)知道的可以跳過本節(jié)。新提案的提出背景,與之前的類似。
社區(qū)內(nèi)的 Go 開發(fā)者很多嫌棄 if err != nil 的錯(cuò)誤處理方式過于繁瑣,紛紛提出各種改進(jìn)方式和新提案。截至目前暫無大改進(jìn)被通過。
具體演示代碼如下:
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
}
// 和煎魚一起煎個(gè)魚...
}
要寫比較多的判斷和返回錯(cuò)誤的邏輯,并且這些代碼比正式的調(diào)用代碼還要多。所以也常被人戲稱一個(gè) Go 工程里 80% 都是 if err != nil 等錯(cuò)誤檢查代碼。
新提案
本次新提案是由 @bjorndm 提出的 《proposal: Go 2: add trap on direct assignment with select block[1]》:
圖片
提出者本身使用編程語言的經(jīng)驗(yàn)比較豐富,用過:C, Ruby, Pascal, Basic, Java, Shell 等。本次提出該提案的原因是某些 shell 中 trap 語句的啟發(fā)。
抽象了一下,提案內(nèi)容如下:
- 功能上是要擴(kuò)展 select 關(guān)鍵字的語法,允許在 select 關(guān)鍵字和其代碼塊之間放一個(gè)單獨(dú)的變量,這會在變量上安裝一個(gè) “陷阱”(類似觸發(fā)器)。
- 這個(gè) “陷阱” 是關(guān)鍵點(diǎn),當(dāng)任何值被賦給該變量時(shí)將會觸發(fā)。然后在 select 代碼塊的主體中,case 語句可用于檢查變量的值。
從原作者的描述來看,提案內(nèi)容比較生硬。我們結(jié)合演示代碼來看就知道,他是想構(gòu)思什么新語法來使用 select-case 達(dá)到錯(cuò)誤處理的目的了。
演示代碼如下:
func CanFail(name string) error {
var err error
select err {
case err != nil:
return fmt.Errorf("CanFail: %w", err)
}
fin, err := os.Open(name)
buf, err := io.ReadAll(fin)
return nil
}
結(jié)合新提案的語法,由于 select 代碼塊中是一個(gè)變量,符合新語法 “陷阱” 的場景。
因此 err 變量被安裝了 “陷阱”,當(dāng)后面的 os.Open 和 io.ReadAll 等方法賦值給 err 變量時(shí),就能觸發(fā) select 子句的 case 檢查。
最終以此達(dá)到簡化 if err != nil 的目的。也可以滿足 Go1 兼容性保障,達(dá)到向前和向后兼容,不需要新增關(guān)鍵字。
總結(jié)
截止目前我們已經(jīng)看過了許多 Go 錯(cuò)誤處理的腦洞新提案。本提案是期望利用 select-case 的特性結(jié)構(gòu)來做擴(kuò)展,以此達(dá)到向前兼容的目的。
從編譯和運(yùn)行上,作者認(rèn)為代價(jià)是比較小的,只需要在內(nèi)部替換成類似 switch 的效果就可以了。
參考資料
[1]proposal: Go 2: add trap on direct assignment with select block: https://github.com/golang/go/issues/66161