- Go言語では、エラー処理の冗長さが長年にわたりユーザー不満の上位に挙げられている
- さまざまな**構文改善提案(例: check/handle、try、
?演算子 など)**が議論・実験されたが、コミュニティの十分な合意がないまま、すべて退けられた
- 言語変更が及ぼすコード、ツール、ドキュメントなどへの広範な影響と、Go特有の単純さを維持する原則が主要な検討事項である
- 現行方式の明確さ、デバッグのしやすさ、および一部ユーザーの選好により、あえて構文変更を導入するだけの根拠は弱い
- 近い将来にエラー処理構文を変更する計画はなく、関連提案はすべて追加調査なしで終了予定である
Goのエラー処理の冗長さという問題提起
- Goに対する古くからの不満の一つは、エラー処理構文が過度に冗長であること
- 代表例として、
if err != nilのようなパターンがコード中で繰り返し現れる
- 複数のAPI呼び出しが必要なプログラムほどこの構文が目立ち、実際のロジックよりエラー処理コードの方が多くなる現象が生じる
- 年次ユーザー調査でも、この不満は継続的に上位で言及されている
コミュニティとの協議と初期提案
- Goチームはコミュニティのフィードバックを重視し、エラー処理改善案の研究を続けてきた
- 2018年のGo 2プロジェクト議論では、Russ Coxがエラー処理問題の核心を公式に整理した
- Marcel van Lohuizenが提案した**
checkとhandleのメカニズム**案が登場
- 類似言語との比較分析や、さまざまな代替案の検討も含まれていた
- この方式は実際にコードを簡潔にできたものの、複雑さの増加により採用されなかった
try提案とその後
- 2019年には、はるかに簡素化された**
try組み込み関数**の提案が行われた
check機能のみをコード化し、handleは省略
- この提案は制御フローを隠してしまう問題で批判され、コミュニティの反発の中で廃案となった
- この経験により、十分なフィードバックなしに完成形の提案を出すことの危険性が認識された
- 大規模な変更提案では、初期設計段階でより幅広く意見を集めることの重要性が確認された
追加の試みと多様な提案
- 数多くの派生案や代替的なエラー処理方式の提案が、コミュニティで継続的に登場してきた
- Ian Lance Taylorのumbrella issueで現状が整理され、Go Wikiやブログなどでも事例が継続的に収集されている
- 2024年には、Rustから借用した
?演算子の適用提案が出た
- 小規模なユーザビリティテストでは直感的だというフィードバックがあったが、やはりさまざまな意見の中で合意には至らなかった
議論の膠着と結論
- 公式・非公式の提案が3件以上、コミュニティ提案は数百件に達するものの、十分な共感や合意の不足により、すべて退けられた
- Go内部のアーキテクトグループでさえ、方向性について意見が一致していない
- 状況の変化や特別な合意形成があるまでは、エラー処理構文の変更自体の試みを中止することが決定された
現行方式の維持を擁護する主な論拠
- 言語設計の初期段階で構文糖衣を入れていれば論争はなかったかもしれないが、現在は15年間使われてきた方式に慣れたエコシステムがある
- 新しい構文を導入すると、必然的に既存ユーザーと新規ユーザーの間でコードスタイルの隔たりや一貫性の崩れが懸念される
- Goの設計哲学(同じことを複数の方法で行わない)と、簡潔さ・一貫性を重視する原則にも合致する
- 短い変数宣言(
:=)で再宣言を許容していることも、エラー処理によって生じた副次的な変更である
- 明確なエラー処理構文(
ifによるもの)は、コードの可読性、デバッグ、ブレークポイント設定において直感的な強みがある
- 言語変更は、実際の変更範囲(コード、ドキュメント、ツールなど)とコストの面でも大きな負担となる
代替的な改善と今後の方向性
- 標準ライブラリの機能強化(例: cmp.Or の導入)により、一部の繰り返しコードを減らすことは可能である
- IDEや開発ツールのコード折りたたみ、自動補完、LLM活用などにより、冗長さを実務上ある程度克服できる
- 主要なGoユーザー層(例: Google Cloud Nextイベント参加者)では、言語変更の必要性に否定的な見解が優勢である
- Goの利用経験が増えるほど、冗長さの問題は実際には体感しにくくなる
構文改善の必要性を支持する論拠
- ユーザーフィードバックに基づき、依然としてエラー処理構文の改善要求は存在する
- 文字数を減らすだけでなく、明確性を高めるエラー処理構文が、コード品質や安全性の改善に寄与する可能性もある
- 単純なエラーチェックではなく、実際に役割を果たすエラー処理について、より精密な研究が必要である
最終結論と今後の方針
- 現時点まで目立った合意や実質的な変化がない状況を認め、近い将来においてはエラー処理のための構文的な言語変更に関する議論・提案をすべて中止すると宣言した
- これまでの議論と研究の過程は、Goエコシステムとプロセス改善に間接的に寄与した
- 将来的に、より明確な問題定義と合意が生まれれば、議論が再開される可能性はある
- 当面は、新たな試みよりもGo自体の堅牢さと単純さを維持することに注力する方針である
1件のコメント
Hacker Newsの意見
Goチームなら別の代案を出せたはずだと簡単に言いたいのであれば、ぜひ Go2ErrorHandlingFeedback wiki や GitHub issue検索 のリンクを確認してほしい。提案されたほぼすべてのアイデアはすでに真剣に議論されており、Goチームの透明なアプローチに感謝しているユーザーとして、毎日Goを使うのは楽しい
草案の設計文書では C++、Rust、Swift について言及しているが、自分が探している Haskell/Scala/OCaml のような関数型言語の do-notation/for-comprehensions/monadic-let は見つけにくい。Goチームはまるで言語設計の達人のように見えるが、実際には Java のようにパラメトリック多相性のない静的型付けの限界にぶつかり、エラー処理問題の答えを出せていないように見える。これは言語の根本設計に由来する問題だと思う
賢く熟練した人たちが書いた文書であるにもかかわらず、Haskell の Maybe/Either モナドや bind 演算子(do-notation)のような解決策がどこにも言及されていないのは非常に不思議だ。実際には難解でも衒学的でもなく、エラーを安全に伝播する非常にエレガントで実績のある方法なのに、Goコミュニティがこれを取り入れなかった理由が分からない。このページが存在すること自体には感謝するが、これほど有名な解決策を見落としているのは理解しがたい
ほぼすべての言語がさまざまなより良いアプローチを提供しているのに、なぜ Go でだけこの問題がこれほど大きく目立つのか気になる。単に合意が取れていないだけなのか、それとも Go言語特有の何かがあって他言語の解決策が合わないのか気になる
Go批判でよく見られる現象として、比較的非専門家の人たちが Go開発者は自分たちより言語のことを知らないはずだと前提している傾向がある。実際には Go開発者のほうが多くの場合、はるかに経験があり、はるかに多くを知っている。非専門家は特徴が多い言語のほうが無条件に優れていると考えがちだが、実際には全体のバランスをうまく取ることが重要だという点を見落としている
新しい言語機能の追加に慎重な Go の保守性のおかげで、ユーザーは恩恵を受けていると思う。Swift の場合は機能変更が多すぎて学習も難しく、最新のMacでも時には単純なプロジェクトひとつすらビルドできないことがある。キーワードが増え続けて変わっていくせいで Swift は使い続けにくく、その点 Go は一貫性が強みだ
以前、Go関数で内部関数がエラーを返すことを期待する例外的な状況があり、内部関数がエラーを出さなければ逆にその関数をエラー扱いしなければならなかったことがある。珍しい構造で
if err == nilによって分岐する必要があったのだが、癖でif err != nilと書いてしまい、普段使っているパターンに慣れすぎていたせいでミスを見つけるのに長くかかった。よく使うif err != nilと、めったに使わないif err == nilの文法的な区別を言語レベルで支援してくれれば、ミスを減らせたかもしれないと思うif err == nilを書くたびに// invertedというコメントを付けてパターンを強調している。言語が自動で扱ってくれるのが理想だが、現状ではこうすることで区別をより明確にできるif err == nil { return ... }パターンが、コードの中でむしろより不自然に見えるかもしれない。現在の Go のエラー処理方式は明確で読みやすいため、多くの人が好んでいるという意見だif fruit != "Apple"のようなパターンでも同じ混乱は起こりうるため、本質的にはエラー処理だけの問題ではなく、全般的な状態分岐の問題として見るべきだという主張だ。エラーも結局は他の状態値のように扱われるif err != nilを特殊記号のようにレンダリングして背景に自然に溶け込ませ(目立たなくし)、異なるif err == nilだけを目立たせることで、エディタレベルでミスを防げる可能性があるif err … {のようにパターンを省略表示する方法で可読性を改善できるかもしれないという提案だGo の明示的なエラー処理方式が気に入っている。関数は常に成功する(minimal error)か、失敗する可能性があるかという構造でシンプルに理解している。失敗する可能性のある関数は、次の段階へ進む前に必ず処理しなければならない。多くの言語では例外処理によって、エラーが発生すると catch されるまでスタックをたどって投げられるため、どこでエラーが発生したかは分かっても、実質的なヒントが乏しいという不満がある。Go では次のような選択肢を明確に持てる: 1) エラーを無視する 2) エラー発生時に即座に return する 3) エラーをラップして有用な情報を追加する 4) 特定のエラーを解釈して分岐処理する(例: 404 に変換する)。Go2 では
Result<Value, Failure>型や、より具体的で列挙可能なエラー型を追加してみたい。Go 1 との互換性のためにも、Go 2 で導入するほうが適切だと思うGo のエラー処理方式は最初はあまり好きではなかったが、errors-are-values ブログ記事 を読み、
panic(err)を適切な場面で使い始めてからは、むしろ非常に満足するようになった。親コードが直接処理すべきでない異常状態には panic を使うことで、コード中の雑多なエラー分岐を大幅に減らせた。このようなエラー管理のやり方は、実務で大いに役立っている@演算子による call site でのエラー抑制があり、bash にも-eのようなエラー管理手法があるという言及実際にエラーを処理すれば冗長さはすぐに隠れるという主張に対し、manual stack trace の生成が本当に「処理」なのか疑問が湧く。Go の定義に従えば、例外(exception)も処理になるのではないか? という軽妙な反論がある
この記事が Go のエラー処理の問題を単に「文法が冗長だ」として扱っているのが気に入らない。本当の問題としては、1) エラーが静かに抜け落ちたり誤って無視されたりしやすい 2) 関数結果を値のように簡単に渡したり保存したりできない 3)
errors.Isのようなネストしたエラーが型システムとうまく噛み合っていない 4) エラーの switching が難しい 5) sentinel value の利用が標準ライブラリに多い 6) ジェネリクスとの相性が悪くパッケージが必要になる、など多様な問題があると思うElixir(および Erlang)では、関数は一般に
{:ok, result}または{:error, description}のタプルを返す。Elixir の with 構文のおかげで、エラー処理をブロックの下部にまとめられ、可読性が大幅に良くなる。Go にも with 文に似たものを導入すれば、エラーが nil のときだけ連続実行し、最下部にエラーハンドラーブロックを置く形で、より読みやすく改善できるRust スタイルをそのまま取り入れない理由が分からないという立場だ。特にジェネリクスが入った今なら、かなり早く似た実装も可能だ。Rust の
?演算子が便利とはいえ、エラー無視を助長するという批判には共感できない。実際の Go ではエラー返却値を無視してもコンパイルエラーにならないケースがいくらでもある。Rust スタイルのように Result 型の返却を強制してこそミスを防げる。利便性を名目に問題視するなら、panic も禁止すべきではないかという強い主張だ?演算子」のような利便性機能について、「ラップされたエラーをもう使わなくなる」という主張に対しては、むしろそのような機能でも wrapping を促進する設計は可能だという反論だRust のようにチェックボックスを埋める感覚で機能採用を議論するのではなく、言語は全体的な一貫性の中で設計すべきだと思う。機能リストをすべて埋めたからといって、そのまま導入することが言語の本質に合うとは限らない