Go 官方錯誤處理討論:用 ? 替代 if err != nil 可以不?
大家好,我是煎魚。
對于 Go 這一門編程語言,截至目前較大爭議話題仍在 if err != nil 在 Go 應用里所帶來的各種繁雜代碼,引起了社區(qū)很多正反方的探討。
原本以為 Go 核心團隊已經擺爛了。但最近老大哥 @Ian Lance Taylor 提出了一個新提案[1](后轉為討論[2]),引起了大量的社區(qū)交流:
圖片
背景:為什么還要關注這件事?
在 Go 核心團隊的視野中,現(xiàn)階段對于處理 Go 錯誤處理的動力,個人理解至少來源于以下幾個主要原因,不斷地推動他們看到這件事。
第一是:社區(qū)中用代碼反饋最多的,也就是在現(xiàn)在的 Go 應用代碼中,存在較多的 if err != nil 的相關代碼:
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
}
}
一堆的 if err != nil 代碼。
第二是:在歷年的 Go 開發(fā)人員調查報告中提示 “錯誤處理被列為人們今天使用的最大特定挑戰(zhàn)”。
如下圖所示:
圖片
在調查結果中,有提到:“在封閉問題中,回答最多的是學習如何有效地編寫 Go(15%)和錯誤處理的冗長(13%)?!?/p>
第三是:在 Go issues 中存在大量的 Go 錯誤處理的各類提案,例如:
圖片
- 《proposal: Go 2: onerr return[3]》
- 《proposal: Go 2: add or err: statement after function calls for error handling[4]》
- 《proposal: Go 2: Use ?variable simplify handling of multiple-return-values[5]》
太多太多太多這類提案了。我也分享過很多腦洞很大的社區(qū)錯誤新提案。
新提案:? 新語法
這種新語法的部分靈感來源于:Rust 的問號運算符。
? 將會吸收函數返回的錯誤,并在錯誤不為 nil 時自動返回,這與之前的 try 的提議類似。
它的不同之處在于:? 是一個顯式語法元素,而不是對預先聲明函數的調用,而且 ? 只能出現(xiàn)在語句的末尾,不能出現(xiàn)在表達式的中間。
基本介紹
例如,原本的 Go 錯誤處理代碼如下:
r, err := SomeFunction()
if err != nil {
return fmt.Errorf("something failed: %v", err)
}
新提案引入新的 ? 語法,將其改寫為如下:
r := SomeFunction() ? {
return fmt.Errorf("something failed: %v", err)
}
當然。如果是另外一種原本的寫法:
if err := SomeFunction2(); err != nil {
return fmt.Errorf("something else failed: %v", err)
}
也依然可以改寫為:
SomeFunction2() ? {
return fmt.Errorf("something else failed: %v", err)
}
? 這個用法將會吸收函數的錯誤結果。它引入了一個新塊,如果錯誤結果不為 nil,則執(zhí)行該塊塊。
在新塊中,fmt.Errorf 里的標識符 err 指的是上述吸收的錯誤結果。也就是當塊跟在 ? 后面時,它會隱式聲明一個新的 err 變量。
完整例子
func Run() error {
Start() ? // returns error from Start if not nil
Wait() ? // returns error from Wait if not nil
return nil
}
func CopyFile(src, dst string) error {
r := os.Open(src) ? {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer r.Close()
w := os.Create(dst) ? {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
io.Copy(w, r) ? {
w.Close()
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
w.Close() ? {
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
}
func MustOpen(n string) *os.File {
f := os.Open(n) ? {
panic(err)
}
return f
}
func TestFileData(t *testing.T) {
f := os.Open("testfile") ? {
t.Fatal(err)
}
...
}
func CreateIfNotExist(name string) error {
f, err := os.OpenFile(name, os.O_EXCL|os.O_WRONLY, 0o666)
if errors.Is(err, fs.ErrExist) {
return nil
}
err ? // returns err if it is not nil
// write to f ...
}
社區(qū)爭論
Go 錯誤處理機制這事,一向是正反雙方都有。有支持繼續(xù)保持的,也有反對的。
其中一位社區(qū)同學 @Michael Fridman 直接掏出了 Go 創(chuàng)始人 @Rob Pike 的演講 PPT 語錄:
該位同學表示:這一提議的優(yōu)點有限 -- 它增加了另一種做事的方式,并使代碼更難閱讀(具有諷刺意味的是)。
其真心希望谷歌不會不惜一切代價發(fā)展 Go 語言,因為這將損害 Go 的長期用戶對 Go 的喜愛。
并且表示:“讓我們保持 Go 的簡單和無趣”
總結
今天我們給大家?guī)砹?Go 核心團隊的錯誤處理新提案,老大哥 @Ian Lance Taylor 基于 Rust 的問號操作符作為靈感,設計出了 Go 的問號操作符和塊的聯(lián)動機制。
關于 Go 的錯誤處理機制,我經歷很多。見到各種人做封裝,還有使用 pnaic+recover 來做 error 機制的。各種方法都有。誰都不服誰。
但是目前 Go1 還是陷入了一個非常尷尬的境地,有人希望改,有人不希望改。無論如何都難以 “討好” 全部人。
接下來就看新上任的 Go 核心團隊負責人的魄力如何了。能否像以前 rsc 力推 module 一樣背負罵名且用力推進。
畢竟,@Ian Lance Taylor 能提出這個提案和討論。很有可能就是內部先討論過的了。
參考資料
[1]提案: https://github.com/golang/go/issues/71203
[2]討論: https://github.com/golang/go/discussions/71460
[3]proposal: Go 2: onerr return: https://github.com/golang/go/issues/32848
[4]proposal: Go 2: add or err: statement after function calls for error handling: https://github.com/golang/go/issues/33029
[5]proposal: Go 2: Use ?variable simplify handling of multiple-return-values: https://github.com/golang/go/issues/33074