當前位置: 妍妍網 > 碼農

Go新提案:返回值應該明確使用或忽略?

2024-04-29碼農

之前在寫 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

    [2]

    #[must_use]: https://doc.rust-lang.org/std/result/#results-must-be-used

    [3]

    [[nodiscard]]: https://en.cppreference.com/w/cpp/language/attributes

    [4]

    -fwarn-unused-do-bind 和 -fwarn-wrong-do-bind: https://downloads.haskell.org/~ghc/7.8.4/docs/html/users_guide/options-sanity.html

    [5]

    ignore: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html