1 ポイント 投稿者 GN⁺ 2025-06-04 | 1件のコメント | WhatsAppで共有
  • Goの if err != nil の繰り返しは、長年にわたりユーザー調査で大きな不満として残っていたが、Goチームは エラー処理構文の変更 を当面進めないことにした
  • 2018年の check/handle、2019年の try、2024年の ? 提案はいずれも十分な合意を得られず、特に try隠れた制御フロー のため強い反発を受けた
  • Goの提案プロセスでは一般的な合意がなければ提案は通常却下され、GoogleのGoチームのシニアメンバーの間でも現在の 最善の方向性 について全会一致に至っていない
  • 現状維持を支持する側は、Goにはすでに機能しているエラー処理方式があり、新しい構文はコードスタイル・デバッグ・ドキュメント・ツール・既存コードに大きなコストをもたらすと見ている
  • Goチームは、エラー処理構文を主対象とする 既存の提案と今後の新規提案 を追加調査なしで閉じ、より明確な問題認識が生まれるまでは別の改善機会に集中する

if err != nil が生んだ長年の不満

  • Goで最も長く続いている不満のひとつは、エラー処理コードの 冗長さ である
  • 代表的なパターンは次の形である
x, err := call()
if err != nil {
    // handle err
}
  • API呼び出しが多く、エラーを単に返すだけのプログラムでは、if err != nil がコードの残り部分を圧倒してしまうことがある
  • 例の関数 printSum では、関数本体10行のうち実際の処理に見える行は呼び出し・出力・返却を含めて4行で、残り6行はノイズのように見える
  • Goの年次ユーザー調査では、エラー処理は長年にわたり最上位の不満だった。一時期はジェネリクス不在がこれを上回っていたが、Goがジェネリクスをサポートした後は再びエラー処理が最上位の不満に戻った

3回の主要な構文提案

  • Goチームの最初の明示的な試みは、2018年のGo 2の取り組みの一環として Russ Cox が問題を 公式に整理 したことから始まった
  • Marcel van Lohuizen の ドラフト設計checkhandle の仕組みに基づいており、他言語のアプローチや代替案の分析も含んでいた
func printSum(a, b string) error {
    handle err { return err }
    x := check strconv.Atoi(a)
    y := check strconv.Atoi(b)
    fmt.Println("result:", x + y)
    return nil
}
  • check/handle アプローチは複雑すぎると判断され、2019年にはより単純な try 提案 が出された
    • check に似たキーワードは try 組み込み関数になった
    • handle 部分は省かれた
    • 既存のエラー処理コードを try 方式に変換するツール tryhard が作られた
    • 関連する GitHub issue では900件近いコメントで激しい議論が行われた
func printSum(a, b string) error {
    // use a defer statement to augment errors before returning
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x + y)
    return nil
}
  • try はエラーが起きると囲んでいる関数から返る方式で 制御フロー に影響し、深くネストした式の中でも return が発生し得るため、多くのユーザーには受け入れにくかった
  • 当時は新しいキーワードを導入するほうがよかった可能性があり、現在では go.mod ファイルとファイル単位のディレクティブで言語バージョンを細かく制御できる
  • Jimmy Frasche の 最近の提案 は、元の check/handle 設計に戻りつつ一部の欠点に対処する方向を取っている

try 以後の手続き面の振り返りと ? 提案

  • try 提案の後、Russ Cox は “Thinking about the Go Proposal Process” 連載を通じて提案プロセスを振り返った
  • “Go Proposal Process: Large Changes” では、try は実装スケジュール付きの提案ではなく、2番目のドラフト設計であるべきだったと評価している
  • その後数年間、Goチームはエラー処理構文の変更を進めず、コミュニティからは類似したもの、興味深いもの、理解しにくいもの、実現不可能なものまでさまざまな提案が続いた
  • Ian Lance Taylor は、エラー処理改善提案の現状を整理する umbrella issue を作成し、関連するフィードバックや議論を集める Go Wiki も作られた
  • Sean K. H. Liao の “go error handling proposals” は、長年にわたる多数のエラー処理提案を追跡している
  • 不満が続く中、Ian Lance Taylor は2024年に ? を使ってエラー処理のボイラープレートを減らす提案 を発表した
    • Rustの ? 演算子 から借りた記法だった
    • 小規模な非公式ユーザー調査では、参加者の大半が ? を使ったGoコードの意味を正しく推測できた
    • 一般的なGoコードを新構文に変換するツールとコンパイラのプロトタイプも作られた
func printSum(a, b string) error {
    x := strconv.Atoi(a) ?
    y := strconv.Atoi(b) ?
    fmt.Println("result:", x + y)
    return nil
}
  • この提案もすぐに多くのコメントと、好みベースの細かな修正提案であふれ、Ianは提案を閉じたうえで内容を discussion に移した
  • わずかに修正された版は やや前向きな反応 を得たが、幅広い支持は得られなかった

なぜ今は止まろうとしているのか

  • Goチームは、予見可能な将来においてはエラー処理の 構文上の問題 を解決しようとする試みを止めるべきだと判断した
  • 提案プロセス がこの判断を支えている
    • 提案プロセスの目標は、適時に結果について一般的な合意へ到達することにある
    • issue tracker 上の議論で一般的な合意が見つからなければ、提案は通常却下される
    • 合意も次の一手も見つからない場合、Go architects が議論をレビューし、内部合意を試みる
  • どのエラー処理提案も合意に近い支持を得られず、すべて却下された
  • GoogleのGoチームのシニアメンバーでさえ、現在の 最善の進め方 について全会一致ではなく、強い合意なしに合理的に進めることはできない

現状維持と変化の論拠

  • 現状維持側には、Goの成熟度とエコシステムコストという現実的な理由がある
    • Goが初期にエラー処理専用の構文糖衣を導入していたなら、今ほど議論は少なかったかもしれないが、Goはすでに15年が経ち、すでに機能しているエラー処理方式を持っている
    • 今、完璧な解決策を見つけたとしても、不満を抱く側が変更支持者から現状維持支持者へ入れ替わるだけかもしれない
    • ジェネリクスはユーザーが必ずしも自分で書く必要はないが、エラー処理の新構文は使わなければコードが非イディオマティックに見える可能性があり、事実上ほとんどの人が使うことになる
    • 同じことを行う方法を複数提供しないというGoの設計原則とも、新構文の追加は衝突しうる
  • := による短い変数宣言の 再宣言 機能は、エラー処理によって生じた問題を解決するために導入された
    • 再宣言がなければ、連続するエラーチェックごとに異なる err 名や別個の変数宣言が必要だった
    • 当時エラー処理によりよい構文サポートがあれば、再宣言ルールやそれに伴う複雑さも不要だったかもしれない
  • エラーを適切に補強して処理すれば、単純な繰り返しの比重は減る
    • ユーザー調査では、エラーにスタックトレースがないという繰り返しの意見がある
    • 補助関数で補強したエラーを作って返す方式が可能である
    • fmt.Errorf("invalid integer: %q", a) のように入力値の情報を付ければ、ボイラープレートの相対的な比重は小さくなる
  • 標準ライブラリの機能でもエラー処理のボイラープレートを減らせる
    • Rob Pike の “Errors are values” と同じ方向性である
    • 一部のケースでは cmp.Or で複数のエラーを一度に扱える
  • 書くこと、読むこと、デバッグすることはそれぞれ異なる活動である
    • 繰り返しのエラーチェックを書くのは退屈だが、IDEやLLMによる補助コード補完で基本的なエラーチェックは簡単に生成できる
    • 読む際には冗長さがより目立ち、IDEがエラー処理コードを隠すトグルを提供できるかもしれない
    • デバッグ時には、すでに独立した if 文があれば println の追加やブレークポイント設定がしやすい
    • checktry? の後ろにエラー処理が隠れると、通常は if 文へ戻して考える必要があり、この過程がデバッグを複雑にしたり微妙なバグを生んだりする可能性がある
  • 言語変更には、設計・実装だけでなく既存コードの変更、ドキュメント更新、ツール調整のコストも伴う
    • Goチームは比較的小規模で、対処すべき他の優先事項も多い
    • 優先順位やチーム規模は変わりうる
  • Google Cloud Next 2025 でGoチームが会った一部のGoユーザーは、より良いエラー処理のために言語を変えるべきではないと強く述べた
    • 彼らは他言語から移ってきた直後は、Goに専用のエラー処理構文がないことが最も目につくが、よりイディオマティックなGoコードを書くようになると重要性は下がると述べた
    • このサンプルは代表性を持つほど大きくはないが、GitHubで見られる人々とは異なる集団かもしれない
  • 変化を支持する論拠も依然として有効である
    • より良いエラー処理サポートの欠如は、ユーザー調査で依然として最上位の不満として残っている
    • 文字数を減らすことだけに集中するアプローチは、誤った方向かもしれない
    • 基本的なエラー処理をキーワードで目立たせつつ err != nil のボイラープレートを除去できれば、コードレビューでエラー処理の有無をより確認しやすくなる
    • 問題の本質が単なる構文の冗長さなのか、それともAPIや開発者・エンドユーザーにとって意味のあるエラーを構成する「良いエラー処理」の冗長さなのかは、まだ十分に分かっていない

Goチームの決定

  • これまでエラー処理を扱おうとしたどの試みも、十分な推進力を得られなかった
  • Goチームは、共有された問題理解が不足しており、そもそも問題が存在するかどうかについてさえ全員が同意していないと判断している
  • 予見可能な将来において、エラー処理のための 構文的な言語変更 は進めない
  • エラー処理構文を主対象とする既存の提案と今後提出される提案は、追加調査なしで閉じる予定である
  • コミュニティによる探索と議論は、エラー処理構文の変更にはつながらなかったが、Go言語とプロセスのさまざまな改善にはつながった

1件のコメント

 
GN⁺ 2025-06-04
Hacker Newsの意見
  • Goチームに「こうすればよかったのに」という類いの提案を軽く投げたいなら、まず記事でリンクされているWikiページ https://go.dev/wiki/Go2ErrorHandlingFeedback と、GitHubのIssue検索 https://github.com/golang/go/issues?q=+is%3Aissue+label%3Aerror-handling を見てほしい
    いま提案しようとしているものは、ほぼ間違いなく初出ではなく、その多くはすでに深く検討されている可能性が高い
    Goチームのこうした率直な姿勢は好ましく思っているし、今でも仕事で毎日Goを楽しく使っている

    • フィードバックの基になった設計ドラフトにはC++、Rust、Swiftが出てくるが、リンク先の膨大なフィードバック文書では、Haskell/Scala/OCamlで使われる do記法、for-comprehension、monadic-letのようなアプローチは見つけられなかった
      コメント数の多いGitHub Issueを数ページ見ても似たものは見当たらず、Goチームが言語設計の魔法使いだから、ここにいる人たちが軽く投げる解決策は当然検討済みだろう、と見るのは無理がある
      GoチームはJavaと同じ過ち、つまり静的型付けでありながらパラメトリック多相性がなかったという問題を犯したし、このエラー処理問題の根もそこにあるのに、手を上げて直そうとしていないように見える
    • 本当に賢く経験豊富な人たちがそのページを書き、何年も議論してきたのに、Haskell式の解法である Maybe/Eitherモナド とbind演算子を使うdo記法がどこにも見当たらないのは不思議だ
      大げさで威圧的に聞こえるかもしれないが、エラーを処理可能な場所まで伝播させつつ、忘れられないようにする優雅で関数的に純粋な方法だ
      Haskellのコードを書く人たちにはあまりに深く根付いたやり方なので、Goコミュニティにこれを知っていて好む人がいなかったというのは理解しがたい
      ページ自体とリンクには感謝しているが、自分たちの言語をそこまで気にかける人たちが、これほど確立された解法を飛ばしたのは混乱させられる
    • すでにどこかに答えはあるのだろうが、なぜ Goだけで特に難しい問題 なのか気になる
      ほぼすべての言語がそれぞれより良い方法を持っているのに、単に決められない、あるいは全員を満足させられないからなのか、それともGoという言語自体に他言語の解法が合わない具体的な理由があるのか知りたい
    • Go批判でよく見られるパターンは、比較的アマチュアな人たちが、Goを作っている人々はプログラミング言語について自分たちより知らないと仮定することだ
      実際には、ほぼすべての場合で彼らのほうがはるかに多くを知っている
      アマチュアは、機能を最も多く詰め込んだ言語が最高だと素朴に考えがちで、特に自分好みの機能が入っていればなおさらそうだと思う
      ナイフ作りを始めたばかりの人が和包丁を見て物足りないと感じ、指用の溝、秘密の収納スペース、ライター、Bluetoothスピーカー付きの3Dプリント製ハンドルを付ければもっと良くなると考えるようなものだ
    • 今では編集するのに承認が必要なのに Wiki と呼ぶのは変だ
  • チェックボックスの一覧を作り、各項目を議論して埋め、致命的な意味論上の誤りや健全性の穴が見つからない限り、再び外さなければよい
    全部埋まったら実装し、.awaitで書くのか/awaitで書くのか.await!()で書くのかと騒いでいた人たちはまた姿を消す
    Rustはこのように進んでいて、Issueによっては10年以上遅れることもあるが、最終的には項目が埋まり、最新のnightlyで安定化される
    Goが、誰もがすぐにぶつかる単一の問題について、完成度の高い提案が複数あるにもかかわらず、そのうち一つを選べず、bikesheddingが止まるのを待って解決できないのだとしたら、その手続きは喜劇だ

    • 「完成した完璧な提案が複数ある」などというものは存在しない
    • それこそが、Rustが 委員会設計 によって読みにくく不格好で、構文に一貫性のない言語だという評判を得るやり方だ
    • プログラミング言語は設計されたシステムであり、全体として筋が通っていなければならない
      チェックボックスの要件を満たせばただ追加される機能の寄せ集めではない
    • 25年後にリマインダーを設定したい
      RustはC++のようにごちゃごちゃになっているだろうか? Goはリリース当時のように 時代に左右されない言語 のままでいるだろうか?
    • 「Goが誰もがすぐに経験する単一の問題を解決できない」というのはおかしい
      調査では エラー処理 に言及した割合は13%で、今の方式のままを好む人たちもいる
      https://go.dev/blog/survey2024-h1-results
  • 以前、内部関数がエラーを返すことを期待する特殊な Go 関数を書いたことがある
    そのため、内部関数がエラーを返さなければ外側の関数がエラーを返して別の処理をしなければならず、内部関数がエラーを返した場合は nil を返す必要があった
    要約すると、if err != nil { ... } ではなく if err == nil { // return an error } を使うべきだったのに、習慣で前者を書いてしまい、デバッグにかなり時間がかかった
    if err != nil にあまりにも鈍感になっていて、脳がその構文が入ってはいけない可能性をそもそも考慮しなかったからだ
    だから、よくある表現には構文糖が必要だと思う。極めて一般的な if err != nil と、まれな if err == nil の違いがもっと目立っていれば、実際に役に立ったはずだ

    • if err == nil を書くたびに // inverted とコメントして目立つようにしている
      言語レベルで扱われるとよいが、少なくともより見えやすくする方法として共有する
    • もちろん if fruit != "Apple" { ... } でも同じ状況は作れる
      これを改善する一般的な解法があるのか気になるし、これをエラー処理だけの問題と見るのは少し的外れな気がする
      エラーに特別または固有な点はなく、ほかと同じ状態にすぎない
    • これはむしろ構文変更に反対する根拠になる
      よくある if err == nil { return ... } パターンが生まれると、今度はそれがコード中にあふれることになるからだ
      現在の解法は問題なく、主に Go を始めたばかりの人や初級者が嫌っているように見える
      周囲では、明示的で明確で読みやすい「冗長な」エラー処理を好んでいる
    • 悪魔の代弁をすると、IDE とフォントが Go の構文モードでのみ if err != nil を単一の小さな合字記号のように強調したり、背景として薄くレンダリングしたりできる
      そうすれば if err == nil のように、まさにその文字列と異なる形はかえって目立つようになる
    • 良い指摘だ。エディタで if err … { のような折りたたみ表記で解決できそうにも見える
  • Go の明示的なエラー処理は気に入っている
    関数は常に成功するか、成功または失敗し得る。常に成功する関数は単純で、失敗し得る関数が失敗した場合、外側のコードは失敗状態のまま進めないので処理しなければならない
    ここで言語ごとに分かれる。多くの言語は例外を投げ、誰かが明示的に捕捉するまで上へ伝播させ、ある種のスタックトレースを提供する
    Go では、コードを書くときに常に選ぶべき選択肢がある点がよい。エラーを無視して進める(foo, _ := doSomething())、意味のある情報なしに早期リターンする(return nil, err)、有用な文脈を付けて早期リターンする、受け取ったエラーを解釈して分岐する、などだ
    例えばデータベースで更新対象の行が見つからなければ、サービス層が not found エラーを返して API では 404 になったり、冪等な削除関数が not found を成功として解釈したりできる
    Go 2 や別の言語なら、nil になり得るタプルの代わりに Rust/Swift 式のResult 型と、常に error を直接使うのではなく、よりよく型付けされ列挙可能なエラー型があるとよい
    ただし Go 1 の慣用的なタプル返却の上に Result を追加すると、同じことをする方法が複数生まれて混乱と分断を招くため、Go 2 や新しい言語のほうが向いている

    • エラー処理の方針は呼び出し側に委ねられるべきだ、というのが自分の経験だ
      スタックの低い層はたいてい何をすべきか分からないので、エラーを処理すべきではない
      エラーを処理するという方針は、結局エラーをラップしてスタックの上へ返し直す方針になりがちで、かなりの雑務になる
    • 「関数が失敗したら、その失敗を処理しなければならない」という点こそ、Go が失敗しているところだ
      Go はエラーを完全に無視できるようにしており、その結果クラッシュにつながることがある
      堅牢なソフトウェアを作るための要件を正確に指摘していながら、Go のエラー処理方式を好むというのは少し理解しにくい
    • borgo[1] の構文がそのまま Go 2 言語になってくれたらいい。夢を見ることはできる
      [1]: https://borgo-lang.github.io/ | https://github.com/borgo-lang/borgo
    • このペースだと、Go2 は決してリリースされないアイデア実験場のように見える
    • 何と比べて良いと言っているのか聞きたい
      すべての関数型言語、Rust のような多くの現代的な言語、さらには checked exception を持つ Java もこれを提供している
      ジェネリクスのある言語なら Go 式の「エラー処理」はたいてい再現でき、おそらくより良いコードになるはずだ
      答えが JavaScript や Python なら、それはよくある比較パターンではある
  • Go にはこれが正しい判断だ。最初に Go に触れたときはエラー処理が嫌いだったが、今では本当に好きになった
    きっかけは二つある。https://go.dev/blog/errors-are-values の記事を読んで「エラーは値」という見方をきちんと受け入れ、その基盤の上である程度人気のあるパッケージ https://github.com/stytchauth/sqx も作った
    また、本当にあり得ない不正な状態には panic(err) を少しずつ使うことに慣れてきた
    親のコードが処理する感覚もないような見当違いの状態を無理にすべて処理させる理由はなく、うまく配置した panic 一つか二つで、コードベース内の何百ものエラーチェックを取り除ける
    例えば ctx の中に基本ロガーがあるか、といった問題を考えられる

    • 残念な話だ。挙げられた根拠は二つとも Go のエラー処理がどれほどひどいかとは関係がなく、エラー処理を改善したからといって悪くなるものでもない
      むしろ改善される可能性が高い
    • PHP でさえ、エラーレベルと呼び出し地点でエラーを抑制する**@ 演算子**のおかげで、より良いエラー処理を持っている
      bash にも -e がある
    • 自分も Go 方式が好きだ。何が起きているのかをより確実に分かるなら、コード行数が増えることは受け入れる
      昔 C# を初めて使ったときは、try/catch/finally の流れ、using、ネスト、catch でエラーが起きたらどうなるか、finally で起きたらどうなるかを理解するのが賢いことだと思っていた
      今は、そういうことを考えないほうがよい
    • Rust 式の合併型エラーも値だ
  • この記事が、Goのエラー処理の主な問題は構文があまりに冗長なことだ、と言っているように見える点が気に入らない。それはあまり気にしていない
    もっと重要なのは、エラーが黙って捨てられたり、うっかり無視されたりし得ること、関数呼び出しの結果が値ではないため簡単に保存したり渡したりできないこと、errors.Isが必要で、「ネスト」されたエラー全体が型システムとうまく合わない奇妙なランタイム上の仕組みだという点
    エラーに対するswitchも難しく、標準ライブラリはsentinel値を使い、ジェネリクスとの相互作用もよくないため、errgroupのようなパッケージが必要になる
    ほかに見落としているものはあるだろうか?

    • Goでプロとして働く時間の90%は、各エラー返却分岐をステートメントカバレッジで覆うためにテストケースを無理やり作ることに費やされている
      例外のある言語なら、誰もそんなことはしなかったはず
    • この記事のどこにも「主な問題は構文があまりに冗長だ」という主張はないと思う
      予測可能な将来においては、エラー処理構文を変えようとする試みをこれ以上しないことにしたので、そのおかげでエラーであれ他の話題であれ、別の問題に目を向ける余裕が生まれる
    • Goが何らかの形でジェネリクスをサポートするまでにも、ものすごく長い時間がかかったことを覚えておくべき
      Goの進化は氷河のように遅く、多くの人にとってそれはバグではなく機能
    • 100%同意。どちらもGooglerなのに、Goチームにまた失望させられることになってとても残念
    • 1番には同意するが、errcheckのような開発ツールである程度は緩和できる: https://github.com/kisielk/errcheck?tab=readme-ov-file
  • 「エラーにスタックトレースがないというアンケートの意見は、補助関数が補強されたエラーを作って返すようにすれば解決できる」というような説明はおかしい
    if err != nil { return fmt.Errorf("invalid integer: %q", a) }のようにスタックトレースを手動で提供することを「エラー処理」と呼ぶのは面白い
    Goチームの定義では、例外はエラーを自動的に処理してくれることになる。もちろんC++を除く言語での話だが

    • 画面いっぱいのスタックトレースを見て、明確で有用だと言う人たちがいるのは面白い
      そうかもしれないが、本当にそのすべてが必要なのか?ログのコストはどうなのか?
      フレームワークやランタイムのノイズを切り落とした、1行のラップされたエラーのほうがずっと良いと思う
      うまくラップすれば検索もしやすく、たいていはスタックトレースより効果的に追跡できる
      Goを10年以上フルタイムで使ってきたが、ランタイム関数やコールスタックの冗長なノイズが必要だったことは一度もない
  • Elixir開発者の視点では、これは正気とは思えない
    Erlang/Elixirでは、関数が通常{:ok, result}または{:error, description_or_struct}のタプルを返すことで解決される
    これにElixirのwith文を使うと、エラー処理を下のほうにまとめられるので、はるかに読みやすくなる
    Goもwith節に相当するものを追加して、エラーがnilである間は関数群を進め、下のほうにエラー処理節を置けばよい

    • 利用可能な証拠だけを見ると、Goがwith文を採用する可能性はまったくなさそう
      Goはジェネリクス、エラー処理、パッケージ管理のような基本的で明らかに価値のある構造を、コミュニティの合意不足を理由に非常に長く先送りする点が興味深い
      ジェネリクスはオープンソース公開後13年かかり、16年経った今もエラー処理はなく、パッケージ管理には約9年かかった
      熟考にも価値はあるが、リリースにも価値はある。GitHubコメントを900件書く人たちも結局Goを使い続けるだろうし、言語に何かが入るほうが、先送りし続けるより良かった可能性が高い
    • Goの複数戻り値自体が、私の観点では奇妙
      戻り型が複数ある関数は、変数に代入すること以外にできることがない
    • HaskellユーザーやRustファンはsum typeを自分たちのもののように見なし、人々は彼らのコメントや記事を読んで信じたあと、恐ろしいHindley-Milnerのウサギ穴に落ちたくないのでsum typeを敬遠するようになる
      しかしErlangとElixirでは、何の重荷もなく完全に慣用的なやり方
      実際にはML系よりずっと強力でもある。そちらのsum typeは開いているから
  • この議論を詳しく追っていたわけではないが、なぜ Rust 式のやり方をそのまま採用しないのか分からない
    Go にジェネリクスが入った後、自分がすぐ追加するようになった方法もそれだ
    リンク先の記事では「Rust には handle に相当するものがなく、? 演算子の便利さは適切な処理を省略させる可能性がある」という説明だけが見える
    しかし、便利であることがエラーを無視するという意味なのかは理解できない
    Go 方式の問題の半分は、結果について何も強制せず、エラーチェックも最低限しか強制しない点にある
    x, err := strconv.Atoi("123"); fmt.Println("result:", x)declared and not used: err になるが、2 回目の変換の後で err を確認しなくても、y のゼロ値 0 のせいで問題に気づかないまま正常に実行されてしまうことがある
    if err != nil { } のように空にしてもコンパイルされ実行されるので、何かがおかしいとは分からない
    戻り値を Result にすれば、判断を下さなければならないことが強制される。誰かが ! を乱用したり、? で楽に上へ投げてエラーケースを処理しないとしても、それなら panic も禁止するのか?

    • Go には 合併型 がないので Result を持てない
      そして、すべての型に定められたゼロ値がなければならないという奇妙な執着のために、合併型も追加できない
    • ? が使いやすいと、誰ももうエラーをラップしなくなる、という意味だと理解している
      かなり疑わしい論理だ
      そもそも ? がエラーのラップを促すように設計すればよい
    • Rust 式を Go に入れるとき、正確に何が等価な形なのかが明確でないためだ
      例えば Rust の From に相当するものは、Go ではどのような姿であるべきなのか?
    • ? は視認性が低く、制御フローの分岐を 1 つの文や式の中に隠してしまう
      Go が三項演算子をなくし、各分岐が別々の行にある if 文を選んだ理由の一つもそれだ
      ブレークポイントも置きにくく、エラーを補強したり処理したりするより、そのまま上へ渡すことを好ませる
    • := は単一文での宣言および代入だと思っていたが、例の 5 行目で err を再宣言し、新しい err が既存の err を隠すのではないか?
      そうなら、新しい err 変数が使われていないので declared and not used: err で失敗するはずだと思う
      それとも、変数がすでにある場合は := が単に通常の代入のように動作するのか?
  • 「エラーにスタックトレースがない点は、補助関数が補強されたエラーを作って返すようにすれば解決できる」という話は、現実を楽観しすぎている
    スタックトレースがある言語はこれをただで提供してくれるが、Go では毎回実装しなければならない
    自分は常に詳細情報を付ける規律ある開発者かもしれないが、チームメンバー全員が同じ規律を持っているわけではない
    スタックトレースの一番よいところは、エラーまでの 呼び出し経路 を示してくれることだ
    複数の場所から呼ばれるメソッドでエラーが起きた場合、スタックトレースがあればどの経路を通ったのかすぐに分かる
    何年もの間 sysadmin/SRE のような仕事をしながら多くの問題を解決してきたが、スタックトレースがあるときの簡単な問題は原因が明らかなので 1〜2 分で終わった
    Go では、誰かがエラーを補強しなかったり同じエラーメッセージを再利用したりすると、簡単な問題でも推理作業になって時間がかかる