質問の背景:
Goは、エラーを例外ではなく値として扱うエラーハンドリングのアプローチで多くの言語と異なっています。このデザインは透明性とシンプルさのために選ばれました:何かがうまくいかない可能性がある場合、関数は明示的にエラーを2つ目の戻り値として返します。
問題:
多くの初心者はGoに慣れたtry-catchパターンを適用しようとして、多くのエラーチェックに苦しんだり、コンテキストを伝えるためのエラーラッピングを利用しなかったりします。これは情報の喪失やデバッグの難しさにつながります。
解決策:
Goでは、エラーを返す可能性のある関数は次のように宣言されます:
func doSomething() (ResultType, error) { // ... if somethingWrong { return nil, errors.New("something went wrong") } return result, nil }
エラーチェックは呼び出し側の責任です:
res, err := doSomething() if err != nil { log.Fatalf("process failed: %w", err) }
Go 1.13以降は、fmt.Errorf("%w", err)を使用してエラーを「ラッピング」し、エラーチェーンを構築できるようになりました。これにより診断が改善され、エラーのコンテキスト説明に関するベストプラクティスが促進されます。
主な特徴:
errors.New("...")を返すだけで済むのに、なぜ独自のエラー型を作成する必要があるのか?
正しい回答:独自のエラー型を使用することで、エラーメッセージを通知するだけでなく、追加の情報(例:リターンコード、コンテキスト)を保持し、タイプアサーションを通じてより細かなエラーハンドリングを実現できます。
例:
type NotFoundError struct { Resource string } func (e NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) } // チェック if _, ok := err.(NotFoundError); ok { // 検索エラー処理 }
panicは重大なエラーのための良い代替手段か?
いいえ、Goにおけるpanicは本当に行き詰まった状況(プログラムの自己エラーに関する状況)でのみ使用されますが、一般的な失敗(例:ファイルを開けなかったなど)には使用しません。ルーチンエラーの通知にpanicを使用することは害であり、可読性のない管理しづらいコードを生み出します。
ネストされた関数でエラー処理をスキップした場合、何が起こるか?
エラーは「失われ」、コードは引き続き実行され、不正な状態が広がる可能性があります。エラーの各戻り値を正しく処理することが常に重要です。
_ = ...を使用して返されたエラーを無視する各関数は単にerrors.New(...)を返し、エラーはラッピングされず、エラータイプは異なるが、処理は常に同じ—ロギングされて投げられる。結果として、ログファイルは情報のないメッセージで満たされ、元の原因まで追跡することができません。
利点:
欠点:
fmt.Errorf("%w", err)を使用したエラーのラッピング、役に立つフィールドを持つ独自のエラー、errors.Is()/errors.As()によるチェックを行います。このおかげで、詳細情報を含めてロギングし、ビジネスエラーと環境の失敗を分けて信頼できるリトライロジックを書くことができます。
利点:
欠点: