go-error-handling について
Go 開発者向けのエラー処理スキルです。エラーの作成、ラップ、伝播、処理の指針と、よくあるアンチパターンを検出するスクリプトを提供します。
主な機能
- 01エラーのラップと型の選択
- 02エラー伝播と処理の流れ
- 03エラー破棄を避ける指針
- 04スクリプトによる問題検出
活用シーン
- 01呼び出し元が判定できるエラーの設計
- 02エラーを記録するか返すかの判断
- 03Go コードのエラー処理パターンの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-error-handlingインストール後、Claude Code で/go-error-handlingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/ERROR-FLOW.mdERROR-TYPES.mdWRAPPING.mdscripts/check-errors-ast.gocheck-errors.sh詳細
Go エラーハンドリング
互換性:
errors.Is、errors.As、および%wによるラップには Go 1.13 以降が必要です。構造化ログの例では Go 1.21 以降のlog/slogを使用する場合があります。
リソースのルーティング
scripts/check-errors.sh- 文字列ベースのエラー照合、エラーの素通し、ログ記録後の返却パターンを確認するときに実行します。scripts/check-errors-ast.go-check-errors.shから呼び出される実装ヘルパーです。エラーフローの分析動作を変更する場合は、こちらを修正してください。references/ERROR-FLOW.md- エラーをどこで処理、ラップ、記録、返却するかを決める際に参照してください。references/ERROR-TYPES.md- センチネルエラー、型付きエラー、不透明なエラーのどれを選ぶか決める際に参照してください。references/WRAPPING.md-%wと%vのどちらを使うか、またはパッケージ境界を越える際に参照してください。
Go では、エラーは値です — コードによって生成され、コードによって利用されます。
エラー戦略の選択
- システム境界(RPC、IPC、ストレージ)ですか? → 内部情報の漏えいを避けるため
%vでラップします - 呼び出し元が特定の条件を照合する必要がありますか? → センチネルエラーまたは型付きエラーを使い、
%wでラップします - 呼び出し元がデバッグ用のコンテキストだけを必要としていますか? →
fmt.Errorf("...: %w", err) - ラップが不要な末端の関数ですか? → エラーをそのまま返します
デフォルト:%w でラップし、フォーマット文字列の末尾に置きます。
基本ルール
具象エラー型を返さない
エクスポートされた関数から具象エラー型を返してはいけません — 具象型の nil
ポインターが非 nil インターフェースになることがあります:
// Bad: Concrete type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }
// Good: Always return the error interface
func Good() error { /*...*/ }
エラー文字列
エラー文字列は大文字で始めず、句読点で終わらせないでください。 例外:エクスポートされた名前、固有名詞、略語。
// Bad
err := fmt.Errorf("Something bad happened.")
// Good
err := fmt.Errorf("something bad happened")
表示するメッセージ(ログ、テスト失敗、API レスポンス)では、大文字で始めてもかまいません。
エラー時の戻り値
関数がエラーを返す場合、明示的に文書化されていない限り、呼び出し元はエラー以外のすべての戻り値を未規定として扱う必要があります。
ヒント:context.Context を受け取る関数は、呼び出し元がコンテキストのキャンセルを判定できるよう、通常は error を返す必要があります。
エラーの処理
エラーに遭遇したら、意図を持って選択し、_ で破棄しないでください。
- すぐに処理する — エラーに対処して続行します
- 呼び出し元に返す — 必要に応じてコンテキストを付けてラップします
- 例外的な場合 —
log.Fatalまたはpanicを使います
意図的に無視する場合は、その理由を説明するコメントを追加してください。
n, _ := b.Write(p) // never returns a non-nil error
関連する並行処理には、
errgroup を使います:
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return task1(ctx) })
g.Go(func() error { return task2(ctx) })
if err := g.Wait(); err != nil { return err }
インバンドエラーを避ける
エラーを示すために -1、nil、空文字列を返さないでください。複数の戻り値を使います:
// Bad: In-band error value
func Lookup(key string) int // returns -1 for missing
// Good: Explicit error or ok value
func Lookup(key string) (string, bool)
これにより、呼び出し元が Parse(Lookup(key)) と書くことを防げます。Lookup(key) は戻り値が 2 つあるため、コンパイル時エラーになります。
エラーフロー
通常のコードに進む前にエラーを処理してください。早期リターンを使えば、正常系にインデントが入りません:
// Good: Error first, normal code unindented
if err != nil {
return err
}
// normal code
エラーは一度だけ処理してください — ログを記録するか返すかのどちらか一方にし、両方行ってはいけません:
Error encountered?
├─ Caller can act on it? → Return (with context via %w)
├─ Top of call chain? → Log and handle
└─ Neither? → Log at appropriate level, continue
エラー型
参考:推奨されるベストプラクティスです。
| 呼び出し元による照合が必要? | メッセージの型 | 使用方法 |
|---|---|---|
| いいえ | 静的 | errors.New("message") |
| いいえ | 動的 | fmt.Errorf("msg: %v", val) |
| はい | 静的 | var ErrFoo = errors.New("...") |
| はい | 動的 | カスタム error 型 |
デフォルト:fmt.Errorf("...: %w", err) でラップします。errors.Is() を使う場合はセンチネルエラーに、errors.As() を使う場合はカスタム型に切り替えます。
エラーのラップ
参考:推奨されるベストプラクティスです。
%vを使う:システム境界、ログ記録、内部情報を隠す場合%wを使う:errors.Is/errors.Asのためにエラーチェーンを保持する場合
重要なルール:%w は末尾に置きます。呼び出し元が持っていないコンテキストを追加します。注釈に意味がなければ、err をそのまま返します。
検証:エラーハンドリングを実装した後、
bash scripts/check-errors.shを実行して一般的なアンチパターンを検出してください。次にgo vet ./...を実行して、その他の問題を検出します。
関連するスキル
- エラーの命名:センチネルエラー(
ErrFoo)やカスタムエラー型の命名については、go-naming を参照してください - エラーのテスト:
errors.Is/errors.Asを使ったエラーの意味論のテストや、エラーチェック用ヘルパーの作成については、go-testing を参照してください - パニックの処理:パニックとエラーの返却のどちらを選ぶか、または recover ガードの作成については、go-defensive を参照してください
- ガード節:早期リターンによるエラー処理フローの構成や、ネストの削減については、go-control-flow を参照してください
- ロギングの判断:ログレベルの選択、構造化ロギングの設定、ログメッセージに含めるコンテキストの判断については、go-logging を参照してください