之前在寫 Go 程式碼時 IDE 經常會提示。 外加我有一個朋友他團隊內 CodeReview 也會遇到一些方法的返回值,處理不處理的問題。 一開始大家還會討論一下,久而久之基本也就麻木了。
假期時翻資料學習時,看到了 Go 社群這個相關的 issues#20803 [1] 。之前已經有大佬提過類似的疑惑,Go 團隊也進行了回復。
官方算是給了一個初步的定論,今天分享給大家。和煎魚一起學習!
快速背景
現在我們寫 Go 程式時,如果函式或方法同時返回了返回值和錯誤參數,使用者(程式設計師)必須要做出一些處理。
最經常的返回 error 參數的場景。如下程式碼:
v, err := computeTheThing()
或是明確的忽略他。如下程式碼:
v, _ := computeTheThing()
相信大家都這麽幹過。(沒錯,經常翻程式碼看到...)
但是在很多有唯一返回值(例如:
io.Closer.Close
、
proto.Unmarshal
)的場景下,寫習慣後,有的就順手忽略了。
最常見的程式碼:
_ = json.Unmarshal(jsonBlob, &eddycjy)
又或是:
defer fd.Close()
看了直呼好家夥...業務程式碼寫的久的同學應該知道不可信任原則。我是經常遇到有人這麽寫,結果沒有記錯誤資訊,查半天沒查到哪裏出問題的。
即使不是錯誤參數的處理。在其他 API 中也有類似的問題。
如下程式碼:
t := time.Now()
t.Add(10 * time.Second) // Should be t = t.Add(…)
又或是:
strconv.AppendQuote(dst, "eddycjy") // Should be dst = strconv.AppendQuote(…)
總而言之,提案作者 @Bryan C. Mills 認為 「忘記」 進行錯誤檢查或丟棄賦值的後果可能相當嚴重。
可能會出現如下問題:儲存到生產資料庫中的數據被破壞、使用者數據無法送出到儲存中、因未驗證使用者輸入而導致程式崩潰等問題。
期望的解決方案
提案作者 @Bryan C. Mills 給出思路:建議 Go 預設拒絕未使用的返回值。
配合的解決方式是增加
ignore
內建關鍵字或其他方式來忽略任意數量的返回值。
如下 ignore 程式碼:
gofunc() { ignore(fmt.Fprintln(os.Stderr, findTheBug())) }()
或是以下變形:
gofunc() { ignore fmt.Fprintln(os.Stderr, findTheBug()) }()
又或是使用別的方式:
gofunc() { _ fmt.Fprintln(os.Stderr, findTheBug()) }()
簡而言之,就是較為顯式的指定來忽略。
其他語言是如何處理的
Swift 會對未使用的返回值發出警告,但如果設定了 @discardableResult 內容,則允許抑制該警告。
在 Swift 3 中進行修改之前,預設情況下可以忽略返回值(但可以使用 @warn_unused_result 添加警告)。
Rust 有 #[must_use] [2] 內容。
C++17 有
[[nodiscard]]
[3]
內容,它將長期存在的
__attribute__((warn_unused_result))
GNU 擴充套件標準化。
ghc Haskell 編譯器為未使用的結果提供了警告標誌( -fwarn-unused-do-bind 和 -fwarn-wrong-do-bind [4] )。
OCaml 預設會對未使用的返回值發出警告。它在標準庫中提供了一個 ignore [5] 函式。
提案作者認為 OCaml(這是一個函式式、指令式、模組化、物件導向的通用的程式語言)提供 ignore 函式的方式與 Go 最為貼合。
因此他提出的提案和方向也與此語言保持基本一致。
核心團隊回復
Go 核心團隊成員之一的老大哥 @Ian Lance Taylor 和 Go 創始人 @ Rob Pike,直接發了張好人卡給提案作者。(尷尬了...)
轉譯過來的關鍵意思是:「 我很同情大家的擔憂,但你這個解決方案不夠好 」。
反對的原因是:
降低程式碼可讀性 :認為用 ignore 函式來包裝運算式會掩蓋程式碼的主要內容,從而增加程式碼的閱讀難度。
這個例子不夠好
:在一門重視簡潔性的語言中,用
_, _ =
開頭的典型例子 hello, world 在我看來是很不幸的。
結論是:「我同意這個問題,但不同意建議的解決方案」。(煎魚註:高情商發言人?)
總結
通篇看下來,其實 Go 核心團隊是較為認可這個問題的存在。但是既要也要還要的模式下,一時半會也找不到更好的解決思路。
同時許多社群內提出的解決思路都會一定程度的破壞 Go 現有的相容性保障,讓這件事變得前後都 「復雜」 了起來。
因此在後續中,Go 官方更推薦使用 vet 等 linter 工具檢測來規避這個問題。
推薦閱讀
參考資料
[1]
issues#20803:
https://github.com/golang/go/issues/20803
#[must_use]:
https://doc.rust-lang.org/std/result/#results-must-be-used
[[nodiscard]]:
https://en.cppreference.com/w/cpp/language/attributes
-fwarn-unused-do-bind 和 -fwarn-wrong-do-bind:
https://downloads.haskell.org/~ghc/7.8.4/docs/html/users_guide/options-sanity.html
ignore:
https://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html