這個(gè)新 Go 錯(cuò)誤處理提案,能解決問題不?
大家好,我是煎魚。
Go 語言的一大特色就是它的錯(cuò)誤機(jī)制,因此基本上所有的錯(cuò)誤處理提案或討論我都會有所查看和學(xué)習(xí),開拓不同的思考視野和解決方法。
今天分享的是 @Cristo García[1] 所提出的提案《Simple Error Handling for Go 2[2]》,略有修改,和煎魚一起學(xué)習(xí)和討論吧!
Go 必須仍然是 Go
這一個(gè)提案的核心觀點(diǎn)是 Go 必須仍然是 Go,這意味著對于錯(cuò)誤處理的改造需要滿足如下原則:
增加盡可能少的語法。
- 盡可能明確方便。
- 本文中的 “我“ 均指代提案作者 @Cristo García,并非正在學(xué)習(xí)的煎魚。
原想法
原提案作者 @PeterRk 提出了以下思想:
func getDivisorFromDB(key string) (uint, error) {
//...
}
func GetDivisor(key string) (uint, error) {
exit := func(err error) (uint, error) {
return 1, fmt.Errorf("fail to get divisor with key \"%s\": %v", key, err)
}
divisor := check(getDivisorFromDB(key), exit)
//...
return divisor, nil
}
使用示例:
divisor := check(getDivisorFromDB(key), exit)
等同于現(xiàn)有的:
divisor, err := getDivisorFromDB(key)
if err != nil {
return exit(err) //return err
}
注意看 check 函數(shù),第二個(gè)參數(shù)的 exit 函數(shù)是它 if err != nil 后的回調(diào)方法,用于出現(xiàn) err 時(shí)的錯(cuò)誤處理。
提案作者認(rèn)為這是一個(gè)正確的方向,我們可以改進(jìn)它(言外之意:現(xiàn)在的還不夠好)。
問題是什么
原有的這個(gè)想法,有如下兩個(gè)問題:
- 包含不明確的返回語句。
- 有時(shí)抽象是不必要的,并且使代碼更難閱讀。
新想法
為此新的想法需要解決以上兩個(gè)問題,@Cristo García 期望達(dá)到更好的效果。通過對語法的簡單修改,我們新增 or 關(guān)鍵字。
可以得到以下示例:
divisor, err := getDivisorFromDB(key) or return exit(err)
新增加的 or 關(guān)鍵字將會檢測最后返回的值(必須是錯(cuò)誤類型)是否與 nil 不同。若不同,將執(zhí)行右邊的函數(shù)。
我們也可以省略 return,代碼將繼續(xù)執(zhí)行。它將像在常規(guī) Go 代碼中一樣被丟棄,這樣該函數(shù)就更可重用。
如下示例:
func GetDivisor(key string) (divisor uint, err error) {
divisor, err = getDivisorFromDB(key) or return
return
}
也就是 or return 語句后不跟任何東西,是可以的,會默認(rèn)拋棄掉。
特殊場景:defer
本節(jié)只是為了辯論,但我們可以借此機(jī)會為 defer 添加錯(cuò)誤檢查,看看能不能做一些什么,得到新的處理方式。
核心思路:如果我們能不把返回的錯(cuò)誤保存在一個(gè)變量中,并在 defer 中使之或得到觸發(fā),那么會非常的有意思。
如下示例 1:
defer f.Close() or return errHdl("", fmt.Errorf("couldn't close file"))
不主動顯式聲明變量,若返回值是錯(cuò)誤類型且不等于 nil,則自動調(diào)用 or return 右側(cè)的函數(shù)并進(jìn)行處理。
如下示例 2:
defer err := f.Close() or return errHdl("couldn't close file", err)
定義接受錯(cuò)誤的變量 err 變量,能通過 or return 的語法直接傳參進(jìn)入函數(shù) errHdl 的入?yún)⒅斜皇褂谩?/p>
結(jié)果
新增了新的 or return 語法后再與原有的錯(cuò)誤處理機(jī)制進(jìn)行對比,看看如何。
新的:
func Foo(path string) ([]byte, error) {
errHdlr := func(reason string, err error) ([]byte, error) {
return nil, fmt.Errorf("foo %s %w", reason, err)
}
f, err := os.Open(path) or return errHdlr("couldn't open file", err)
defer f.Close() or return errHdl("", fmt.Errorf("couldn't close file"))
result, err := io.ReadAll(f) or return errHdlr("couldn't read from file " + path, err)
return result, nil
}
舊的:
func Foo(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("foo %s %w", "couldn't open file", err)
}
result, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("foo %s %w", "couldn't read from file " + path, err)
}
err = f.Close()
if err != nil {
return nil, fmt.Errorf("foo %s %w", "couldn't close the file " + path, err)
}
return result, nil
}
這是一個(gè)非常簡單的例子,但我們已經(jīng)可以看到其好處。正在閱讀代碼的程序員甚至可以把注意力放在左邊而忽略錯(cuò)誤處理。
在使用 gofmt 格式化代碼后,也比較美觀。
如下示例:
f, err := os.Open(path) or return errHdlr("couldn't open file", err)
defer f.Close() or return errHdl("", fmt.Errorf("couldn't close file"))
result, err := io.ReadAll(f) or return errHdlr("couldn't read from file " + path, err)
對的很齊。
總結(jié)
在這一個(gè)新提案中,作者正在做意見征集的階段。其主要是推行了 or 關(guān)鍵字和變量可傳遞至右側(cè)函數(shù)等多種思路(前段時(shí)間我還分享了個(gè)左側(cè)函數(shù)和表達(dá)式的提案)。
該作者的目的是想盡可能的方便,并且不寫以往被大家吐槽的 if err != nil,實(shí)現(xiàn)更加的簡潔。
你覺得這個(gè)提案怎么樣呢?歡迎在評論區(qū)交流和討論。
參考資料
[1]Cristo García: https://gist.github.com/GGCristo
[2]Simple Error Handling for Go 2: https://gist.github.com/GGCristo/27c33308a07c1be216542f1005792c2b