前書き
「Go のエラーハンドリングは書くのが煩雑すぎる」——これはほぼすべての Go プログラマが同意する意見です。
そして最近、Go 公式はブログ記事を発表し、正式に「今後、新たなエラーハンドリング構文の提案は推進しない」と宣言しました。つまり、これからも Go コードを書くときには、あの馴染み深い if err != nil { return err }
を頻繁に書き続けることになります。
これは単なる構文糖衣の提案が終わったという話ではなく、Go 言語の哲学そのものの再確認でもあります。では、なぜ Go チームはこのような決断を下したのでしょうか? そして、私たちはこの「こだわり」をどう受け止めるべきなのでしょうか?
三度の試み、三度の失敗:Go のエラーハンドリング構文の模索の道
過去 7 年間、Go チームは 3 度にわたり、新たな構文を導入することでエラーハンドリングの「繰り返し記述」問題を解決しようとしました。しかし、いずれも実現には至りませんでした。
第 1 回目:2018 年の check
/ handle
提案
この提案は Go 2 のドラフトに基づくもので、次のような構文変更を導入することを目的としていました:
-
check()
:関数呼び出し時の error をキャッチするための関数; -
handle
ブロック:それらのエラーを一括で処理するための構文ブロック。
func printSum(a, b string) error {
handle err { return err } // すべてのcheck失敗時にここへジャンプ
x := check(strconv.Atoi(a))
y := check(strconv.Atoi(b))
fmt.Println("result:", x + y)
return nil
}
- 🔍 メリット:構造が明確で、エラールートを統一的に管理できる。
- ⚠️ デメリット:新しい構文ブロックを導入するため、学習曲線が急で、意味的な複雑さも高く、激しい議論を招いた。
最終的に、Go チームはこの仕組みを「複雑すぎる」と判断し、ドラフトの段階から進めないことになりました。
第 2 回目:2019 年の try()
提案
第 1 回の経験を活かし、Go チームはより軽量な構文として try()
関数を提案しました。
func printSum(a, b string) error {
x := try(strconv.Atoi(a))
y := try(strconv.Atoi(b))
fmt.Println("result:", x + y)
return nil
}
これは、実際には以下と同等です:
x, err := strconv.Atoi(a)
if err != nil {
return err
}
- 🔍 メリット:シンプルで明確。構文ブロックを導入せず、ただの組み込み関数。
- ⚠️ デメリット:自動的な return により制御フローが不透明となり、Go が一貫して重視してきた「明示的で可読性の高い」設計方針に反する。デバッグが難しく、理解にもコストがかかる。
結果として、コミュニティからの反対意見が強く、この提案は正式に却下されました。
第 3 回目:2024 年の ?
演算子提案
今回は、Go チームはより現実的なアプローチとして、Rust の ?
を参考に、エラーハンドリングのための接尾語構文糖衣を導入する案を出しました:
func printSum(a, b string) error {
x := strconv.Atoi(a) ?
y := strconv.Atoi(b) ?
fmt.Println("result:", x + y)
return nil
}
しかし残念ながら、これもまた他の提案と同様、多くのコメントや、個人の好みに基づく細かな修正要望に埋もれてしまいました。
最終的な決定:構文の変更を今後進めない
三度の努力のいずれも、広範な合意を得ることができませんでした。そして 2025 年 6 月、Go チームは公式ブログで次のように宣言しました:
「今後しばらくの間、Go チームはエラーハンドリングに関する構文の変更を追求することを停止します。また、エラーハンドリングの構文に関する提案があれば、それらも調査せず、すべてクローズすることにします。」
この決定は、技術的に解決策がないわけではなく、合意の欠如、コスト評価、そして言語哲学など複数の要素に基づいたものです。
これは、現実的な結末であり、同時に Go 言語の設計哲学への再確認でもあります。
なぜ Go チームは「変更しない」と決めたのか? 2 つの核心的な理由
Go チームがエラーハンドリングの「煩雑さ」を理解していないわけではありませんし、もっと簡潔な構文が見つからないわけでもありません。実際、過去 7 年間で 3 度も提案を進め、3 度とも自ら中止しました。これは技術的にできないからではなく、価値観の問題です。
私たちは、Go チームが最終的に「変更しない」を選択した理由を 2 つの観点から理解することができます。
1. 「圧倒的な合意」が形成されなかった
Go チームは何度も強調しています。「私たちは単に動作する解決策を求めているのではなく、多くの人々が受け入れ、使いたいと思う解決策を探している」と。
しかし現実はどうか。各提案はすべて「自分が求めていたものではない」といった声を引き起こしました。
-
check/handle
は複雑すぎると感じる人もいれば; -
try()
は自動すぎると感じる人も; -
?
演算子は直感的ではあるが、その意味が不明瞭だと感じる人も。
どの構文糖衣案にも、スタイルの対立、哲学的な違い、そして終わりのないbikeshedding(無駄な議論)が伴いました。Go チームは明確に述べています:
「現在見ている中で最良の提案でさえ、圧倒的な支持を得ることができていません。」
Go の設計哲学は非常に現実主義的です:共通の認識がないのであれば、変更しない。
2. 技術的な利得とコストが釣り合わない
各提案には、Go チームがプロトタイプのツールチェーンをサポートするための努力(コンパイラ、ドキュメント、ツールチェーンなど)が含まれていましたが、最終的に以下のような事実が明らかになりました。
- 構文がシンプルに見えたとしても;
- コードの行数は減らせるかもしれませんが;
- しかし、読みやすさと理解のコストはそれに見合わない。
例えば:
x := strconv.Atoi(a)?
この行のコードは確かに if
文を省略していますが、プログラムの制御フローはもはや明示的ではありません。エラーがどのように返され、誰がそれを処理するのかが、言語の隠れたロジックの一部になってしまっています。Go チームは次のように懸念しています:
「言語の魔法が増えるほど、ユーザーがデバッグし、読み、問題を特定するコストが増加します。」
彼らにとって、Go の強みは決して「最も少ないコードを書くこと」ではなく、「読みやすく、デバッグがしやすく、安定して動作すること」です。
今後の方向性:構文は変わらないが、体験は改善可能
Go チームが「エラーハンドリングの構文を変更しない」と明言したからと言って、エラーハンドリングがこれで「封印された」わけではありません。むしろ、開発体験の改善にはまだ大きな余地が残されています。記事ではいくつかの重要な方向性が示されています。
ライブラリ関数を使って繰り返しコードを減らす
Go 公式は、標準ライブラリの強化を通じて、エラーハンドリングの繰り返しを減らす方法を明確に支持しています。例えば、次のようなコードです:
x, err1 := strconv.Atoi(a)
y, err2 := strconv.Atoi(b)
if err := cmp.Or(err1, err2); err != nil {
return err
}
ここでは cmp.Or
を使って複数のエラーを統一的に処理しています。この方法は Go の構文の一貫性を保ちながら、可読性を向上させています。
IDE とツールチェーンのサポート強化
ブログでは現実的な方向性として、IDE を賢くして「冗長な部分を隠す」方法が提案されています。
現代の IDE(特に LLM を活用したスマート補完)は、if err != nil
のような繰り返しコードの記述を大いに簡素化できます。将来的にはさらに次のような改善が期待できます:
- IDE 内で「エラーハンドリング文を隠す」スイッチを追加;
- 必要なときにのみ
if err != nil
のブロックを展開して、読みやすさを向上; - AI を使って、より文脈に応じたエラーメッセージを自動生成する。
この方法は言語自体の変更を伴わず、Go コードの記述と読みやすさの効率を実際に向上させる可能性があります。
エラーハンドリングの重要性に焦点を当てる
実際のエラーハンドリングコードに戻ると、エラーを単に返すだけではなく、エラーメッセージに追加情報を付加することに重点を置くべきです。ユーザー調査では、エラーに関連するスタックトレースの不足が繰り返し指摘されており、これを解決するためにエラーを拡張するサポート関数を生成して返すことができます。例えば:
func printSum(a, b string) error {
x, err := strconv.Atoi(a)
if err != nil {
return fmt.Errorf("invalid integer: %q", a)
}
y, err := strconv.Atoi(b)
if err != nil {
return fmt.Errorf("invalid integer: %q", b)
}
fmt.Println("result:", x + y)
return nil
}
このように、良好なエラーハンドリングでは、エラーに追加情報を盛り込むことが重要です。これにより、開発者はエラーの原因を迅速に特定でき、デバッグが容易になります。
小結
Go チームがエラーハンドリングの構文変更を今後進めないと決定したことは、言語の進化における重要な節目ですが、これが「エラーハンドリングの改善が終わった」というわけではありません。標準ライブラリの強化やツールチェーンの改良、エラーに関連するコンテキスト情報を重視することによって、依然としてコードの可読性と開発効率を高めることができます。この決定は、Go 言語の明示性とシンプルさを守る姿勢を反映したものであり、今後のツールエコシステムや開発体験の最適化に向けた更なる可能性を開いています。
私たちはLeapcell、Goプロジェクトのホスティングの最適解です。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:
複数言語サポート
- Node.js、Python、Go、Rustで開発できます。
無制限のプロジェクトデプロイ
- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。
比類のないコスト効率
- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。
洗練された開発者体験
- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。
Xでフォローする:@LeapcellHQ