你對 Go 錯誤處理的 4 個誤解!
今天煎魚帶大家瞭解幾個 Go 語言的錯誤處理中,大家最關心,也是最容易被誤解、被嫌棄的問題:
-
爲什麼不支持 try-catch?
-
爲什麼不支持全局捕獲的機制?
-
爲什麼要這麼設計錯誤處理?
-
未來的錯誤處理機制會怎麼樣?
落寞的 try-catch
在 Go1 時,大家知道基本不可能支持。於是打起了 Go2 的主意。爲什麼 Go 就不能支持 try-catch 組合拳?
上一年宣發了 Go2 的構想,所以在 2020 年就有小夥伴乘機提出過類似 《proposal: Go 2: use keywords throw, catch, and guard to handle errors[1]》的提案,這可是其他語言都支持的,Go 語言怎麼了?
下面來自該提案的演示,Go1 的錯誤處理:
type data struct {}
func (d data) bar() (string, error) {
return "", errors.New("err")
}
func foo() (data, error) {
return data{}, errors.New("err")
}
func do () (string, error) {
d, err := foo()
if err != nil {
return "", err
}
s, err := d.bar()
if err != nil {
return "", err
}
return s, nil
}
新提案所改造的方式:
type data struct {}
func (d data) bar() string {
throw "", errors.New("err")
}
func foo() (d data) {
throw errors.New("err")
return
}
func do () (string, error) {
catch err {
return "", err
}
s := foo().bar()
return s, nil
}
不過答覆非常明確,@davecheney 在底下回復 “以最強烈的措辭,不(In the strongest possible terms, no)”。這可讓人懵了圈,爲什麼這麼硬呢?
其實 Go 官方早在《Error Handling — Problem Overview[2]》提案早已明確提過,Go 官方在設計上會有意識地選擇使用顯式錯誤結果和顯式錯誤檢查。
結合《language: Go 2: error handling meta issue[3]》可得知,要拒絕 try-catch 關鍵字的主要原因是:
-
會涉及到額外的流程控制,因爲使用 try 的複雜表達式,會導致函數意外返回。
-
在表達式層面上沒有流程控制結構,只有 panic 關鍵字,它不只是從一個函數返回。
說白了,就是設計理念不合,加之實現上也不大合理。在以往的多輪討論中早已被 Go 團隊拒絕了。
反之 Go 團隊倒是一遍遍在回答這個問題,已經不大耐煩了,直接都整理了 issues 版的 FAQ 了。
想捕獲所有 panic
在 Go 語言中,有一個點,很多新同學會不一樣碰到的。那就是在 goroutine 中如果 panic 了,沒有加 recover 關鍵字(有時候也會忘記),就會導致程序崩潰。
又或是以爲加了 recover 就能保障一個 goroutine 下所派生出來的 goroutine 所產生的 panic,一勞永逸。
但現實總是會讓人迷惑,我經常會看到有同學提出類似的疑惑:
來自 Go 讀者交流羣
這時候,有其他語言經驗的同學中,又有想到了一個利器。能不能設置一個全局的錯誤處理 handler。
像是 PHP 語言也可以有類似的方法:
set_error_handler();
set_exception_handler();
register_shutdown_function();
顯然,Go 語言中並沒有類似的東西。歸類一下,我們聚焦以下兩個問題:
-
爲什麼 recover 不能捕獲更上層的 panic?
-
爲什麼 Go 沒有全局的錯誤處理方法?
源碼層面
如果是講設計的話,其實只是通過 Go 的 GMP 模型和 defer+panic+recver 的源碼剖析就能知道了。
本質上 defer+panic 都是掛載在 G 上的,可查看我以前寫的《深入理解 Go panic and recover[4]》,你會有更多深入的理解。
設計思想
在本文中我們不能僅限於源碼,需要更深挖,Go 設計者他的思想是什麼,爲什麼就是不支持?
在 Go issues 中《proposal: spec: allow fatal panic handler[5]》、《No way to catch errors from goroutines automatically[6] 》分別的針對性探討過上述問題。
Go 團隊的大當家 @Russ Cox 給出了明確的答覆:Go 語言的設計立場是錯誤恢復應該在本地完成,或者完全在一個單獨的進程中完成。
這就是爲什麼 Go 語言不能跨 goroutines 從 panic 中恢復,也不能從 throw 中恢復的根本原因,是語言設計層面的思想所決定。
在源碼剖析時,你所看到的整套 GMP+defer+panic+recover 的機制機制,就是跟隨着這個設計思想去編寫和發展的。
設計思想決定源碼實現。
建議方式
從 Go 語言層面去動搖這個設計思想,目前來看可能性很低。至少 2021 年的現在沒有看到改觀。
整體上會建議提供公共的 Go 方法去規避這種情況。參考 issues 所提供的範例如下:
recovery.SafeGo(logger, func() {
method(all parameters)
})
func SafeGo(logger logging.ILogger, f func()) {
go func() {
defer func() {
if panicMessage := recover(); panicMessage != nil {
...
}
}()
f()
}()
}
是不是感覺似曾相識?
每家公司的內部庫都應該有這麼一個工具方法,規避偶爾忘記的 goroutine recover 所引發的奇奇怪怪問題。
也可以參照建議,利用一個單獨的進程(Go 語言中是 goroutine)去統一處理這些 panic,不過這比較麻煩,較少見。
未來會如何
Go 社區對 Go 語言未來的錯誤處理機制非常關心,因爲 Go1 已經米已成炊,希望在 Go2 上解決錯誤處理機制的問題。
期望 Go2 核心要處理的包含如下幾點(#40432):
-
對於 Go2,我們希望使錯誤檢查更加輕便,減少專門用於錯誤檢查的 Go 程序代碼的數量。我們還想讓寫錯誤處理更方便,減少程序員花時間寫錯誤處理的可能性。
-
錯誤檢查和錯誤處理都必須保持明確,即在程序文本中可見。我們不希望重複異常處理的陷阱。
-
現有的代碼必須繼續工作,並保持與現在一樣的有效性。任何改變都必須與現有的代碼相互配合。
爲此,許多人提過不少新的提案... 很可惜,截止 2021.08 月底爲止,有許多人試圖改變語言層面以達到這些目標,但沒有一個新的提案被接受。
現在也有許多變更併入 Go2 提案,主要是 error-handling 方面的優化。
大家有興趣可以看看我之前寫的:《先睹爲快,Go2 Error 的掙扎之路》,相信能給你帶來不少新知識。
總結
看到這裏,我們不由得想到。爲什麼,爲什麼在 21 世紀前者已經有了這麼多優秀的語言,Go 語言的錯誤處理機制依然這麼的難抉擇?
顯然 Go 語言的開發團隊是有自己的設計哲學和思想的,否則 “less is more” 也不會如此廣泛流傳。設身處地的理解 Go 官方的想法,而不是一味地單向理解,會對我們未來的編程之路更好。
當然,這存在着一系列既要也要的問題,不好處理。歡迎大家關注煎魚,後續我們也可以面向 Go 後續的錯誤處理持續的關注和討論!
參考資料
[1]
proposal: Go 2: use keywords throw, catch, and guard to handle errors: https://github.com/golang/go/issues/40583
[2]
Error Handling — Problem Overview: https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md
[3]
language: Go 2: error handling meta issue: https://github.com/golang/go/issues/40432
[4]
深入理解 Go panic and recover: https://eddycjy.com/posts/go/panic/2019-05-21-panic-and-recover/
[5]
proposal: spec: allow fatal panic handler: https://github.com/golang/go/issues/32333
[6]
No way to catch errors from goroutines automatically: https://github.com/golang/go/issues/20161
你好,我是煎魚。高一折騰過前端,參加過國賽拿了獎,大學搞過 PHP。現在整 Go,在公司負責微服務架構等相關工作推進和研發。
從大學開始靠自己賺生活費和學費,到出版 Go 暢銷書《Go 語言編程之旅》,再到獲得 GOP(Go 領域最有觀點專家)榮譽,點擊藍字查看我的出書之路。
日常分享高質量文章,輸出 Go 面試、工作經驗、架構設計,加微信拉讀者交流羣,記得點贊!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/-aOfIuahQ6O2oFkAhoRTiA