世界を動かす技術を、日本語で。

エラー処理のための構文サポートについて

2025年6月4日原文(go.dev)

概要

Go言語の エラーハンドリング の冗長さは長年議論されてきた課題。 Goチームとコミュニティは 様々な提案 (check/handle、try、?演算子など)を試みたが、 十分な合意 には至らなかった。 現状のエラーハンドリング方法は冗長だが、 言語仕様の変更は見送られる方針。 今後は 標準ライブラリやツールの改善 で利便性向上を目指す。 IDEやツールの進化も 冗長さの緩和 に貢献可能。

Go言語のエラーハンドリングの歴史と議論

  • Go言語のエラーハンドリング は「if err != nil」パターンが多用される冗長さが課題。
  • API呼び出しや単純なエラー返却が多いプログラムでは、 本質的な処理よりエラー処理が目立つ 傾向。
  • 例:printSum関数の10行中6行がエラー処理となり、ノイズと感じられる。
  • 長年のユーザー調査でも 「エラーハンドリングの冗長さ」 が最上位の不満事項。

Goチームによる主な提案

  • 2018年:「check/handle」機構(Go 2構想)

    • Marcel van Lohuizenのドラフト設計に基づく。
    • 他言語との比較や代替案も詳細に分析。
    • 設計が複雑すぎるとして却下。
  • 2019年:「try」提案

    • check/handle案を簡素化し、「try」組み込み関数を導入。
    • コントロールフローの隠蔽や可読性低下が懸念され、コミュニティの反発も大きく却下。
  • 2024年:「?」演算子案

    • Rustの「?」演算子を参考にした提案。
    • ユーザー調査では直感的な理解が得られたが、細かな修正提案や意見対立が多発し、合意に至らずクローズ。
  • その他コミュニティからも多数の提案

    • Ian Lance Taylorによる「エラーハンドリング改善提案まとめ」やGo Wikiで議論を整理。
    • Sean K. H. Liaoのブログなどで膨大な提案が可視化。

合意形成の難しさと現状維持の決定

  • いずれの提案も 十分なコンセンサスを得られず却下
  • Goの設計思想「同じことを複数の方法で書かせない」にも合致せず。
  • 新構文導入は 既存ユーザーとの分断や非互換性リスク も伴う。
  • Generics導入時との違い:Genericsは使わなくても済むが、エラーハンドリング構文は全員が使う必要が生じる。

現状のエラーハンドリングと今後の方向性

  • 現行の冗長なエラーハンドリングも「 実際にエラーを適切に扱う場合」はノイズが目立たない。
  • エラー情報の付加(例:fmt.Errorfで詳細メッセージ追加) により、実用的なコードが書ける。
  • 標準ライブラリの拡充 (例:cmp.Orで複数エラーの同時処理)による利便性向上が期待。
  • IDEやLLM支援によるコード補完、エラーハンドリング部分の 表示切替機能 など、ツールによる冗長さ緩和も可能。
    • デバッグ時もif文が独立していることでブレークポイント設定が容易。

結論

  • エラーハンドリング構文の抜本的な変更は当面行わない 方針。
  • 標準ライブラリやツールの改善、IDEの進化などで 実用性と可読性の向上 を目指す。
  • Goの設計原則 とコミュニティの多様な意見を尊重した判断。

Hackerたちの意見

Goにとってこれは正しい選択だと思う。最初はGoのエラーハンドリングが嫌いだったけど、今は本当に好きになった。最初にこの言語を知ったときは、全然理解できなかったんだ。変わったきっかけは、この記事にもある「https://go.dev/blog/errors-are-values」を読んで、しっかりと理解したこと。それを元にそこそこ人気のあるパッケージも作ったよ - https://github.com/stytchauth/sqx。あとは、本当にひどい無効な状態のときにちょっとpanic(err)を使うことに慣れたことかな。親のコード全体に無意味なエラー処理を強いる理由はないし、適切な場所でのpanicがあれば、コードベースから何百ものエラーチェックを省けるからね。考えてみて、ctxにデフォルトのロガーはある?

これはただ悲しい。あなたのサポートのどちらも、Goのエラーハンドリングがひどいこととは関係ないし、改善することで悪化することもない。むしろ、改善されるはずだよ。

PHPですら、エラーハンドリングにレベルがあって、呼び出し元でエラーを抑制するための@(atオペレーター)があるんだよ。bashだって-eがあるし :)

チェックボックスのリストを作って、一つ一つのポイントを議論して、チェックを入れていく。致命的な意味のエラーや整合性の穴が見つからない限り、チェックを外すことはない。完成したら実装されて、.await/await.await!()のスペルについて意見を持っていた人たちは、どこかに消えていく。何が問題なの?Rustはこんな感じで動いてる。時には問題が10年以上も遅れることもあるけど、最終的には全てのチェックが入って、最新のナイトリービルドで安定する。もしGoが、言語に対してみんながすぐに抱く唯一の問題を解決できないなら、完璧な提案がいくつもあるのに、提案の選択ができずに人々が無駄に議論を続けるのを待っているだけなら、そのプロセスは茶番だよ。

Goは言語に対してみんながすぐに抱く唯一の問題を解決できない... 何それ?調査によると、13%がエラーハンドリングについて言及してるよ。そして、実際にそのままの方がいいって人もいる。https://go.dev/blog/survey2024-h1-results

これがRustが読みにくい言語で、一貫性のない構文を持つという評判を得る理由なんだよね:委員会によるデザイン。

"完全で完璧な提案がいくつかあるにもかかわらず" そんなものは存在しない。

完全で完璧 これは完全に主観的で、Goコミュニティを逆説的に描いてるね。頑固でありながら変化を求めている。残念な現実は、Goのエラーハンドリングが言語設計の理念とGoを書く開発者を満足させる中で、最もマシな選択肢だってこと。俺はVのスタイルのエラーハンドリングを実装するのが好きだけど、実際にそれを実装するのが全てうまくいくわけじゃないってのも理解してる。

この議論を詳しく追っていないから、関連する話が抜けてたらごめんね。でも、Rustスタイルをそのまま採用すればいいのにと思う。今、Goにジェネリクスがあるから、すぐに追加することにしてる。リンクされた記事にこんな一文があった: > でもRustにはhandleの同等物がない:?演算子の便利さは、適切なハンドリングが省略される可能性を伴う。便利さがエラーを無視することに等しいとは思えない。これがGoのアプローチに対する私の問題の半分で、結果に関する何も強制されず、エラーのチェックも最小限しか強制されないから。例えば、これが「宣言されていて使われていない: err」になる: x, err := strconv.Atoi("123") fmt.Println("result:", x) でも、これが普通に動く(yのデフォルト値が0だから、何が問題か分からない): x, err := strconv.Atoi("123") if err != nil { panic(err) } y, err := strconv.Atoi("1234") fmt.Println("result:", x, y) これもコンパイルされて普通に動くけど、また何が問題か分からない: x, err := strconv.Atoi("123") if err != nil { } fmt.Println("result:", x) 戻り値をresultにすることで、決断を下さなきゃいけなくなる。誰が!を使ったり、便利に?を使ってエラーケースを処理しないことを禁止するの?panicも禁止するつもり?

GoにはResultがないのは、合計型がないからで、奇妙なことに、すべての型に指定されたゼロ値が必要だと主張しているから追加できないんだ。

Rustスタイルが採用されない理由がわからない。主に、GoにおけるRustスタイルの相当物がはっきりしないからだよね。例えば、Rustの「From」はどんな感じになるんだろう?

Goにジェネリクスが使えるようになった今、すぐに追加するものだね。もしよかったら、具体的なコード例を見せてくれるとめっちゃ興味ある!

Hacker Newsで議論の続きを見る