1. 導入: なぜ errors.Is / errors.As が重要なのか
Goの開発現場において、エラーハンドリングは最も重要なタスクの一つです。かつてはエラーを文字列比較で判定したり、複雑な型アサーションを繰り返したりしていましたが、これには「ラップされたエラー(元のエラー情報を含むエラー)」を判定できないという課題がありました。Go 1.13で導入された errors.Is と errors.As を使用することで、多層的なエラー構造を透過的に探索し、呼び出し元で適切にリカバリやログ出力を行うことが可能になります。
2. 基礎知識: エラーのラップと構造
Goのエラーは単なるインターフェースです。fmt.Errorf で %w 動詞を使用すると、元のエラーを内部に保持した「ラップされたエラー」を作成できます。これにより、エラーの因果関係(スタックトレース的な情報)を維持したまま、上位レイヤーへ伝播させることができます。
errors.Is: 特定のエラー値(Sentinel Error)がエラーチェーンの中に含まれているか判定します。
errors.As: エラーチェーンの中から、特定のエラー型を見つけてキャスト(代入)します。
3. 実装/解決策: 適切なエラーハンドリングのフロー
エラーを発生させる側では fmt.Errorf の %w を使い、受け取る側で errors.Is か errors.As を使い分けます。
- 定義済みのエラー(io.EOFなど)を確認したい場合 → errors.Is
- 構造体で定義された独自エラーの詳細情報を取り出したい場合 → errors.As
4. サンプルプログラム
以下のコードは、独自エラーを定義し、それをラップして伝播させ、呼び出し側で適切に判定する例です。
package main
import (
"errors"
"fmt"
)
// 1. 独自エラー型の定義
type AppError struct {
Code int
Message string
}
func (e AppError) Error() string {
return fmt.Sprintf("code=%d: %s", e.Code, e.Message)
}
func doSomething() error {
// 2. 独自エラーを発生させ、ラップして返す
return fmt.Errorf("process failed: %w", &AppError{Code: 404, Message: "not found"})
}
func main() {
err := doSomething()
// 3. errors.As で独自型を特定し、詳細を取り出す
var appErr AppError
if errors.As(err, &appErr) {
fmt.Printf("カスタムエラーを捕捉: コード %d, 内容: %s\n", appErr.Code, appErr.Message)
}
// 4. errors.Is で特定のエラー定数と比較する例
// もし fmt.Errorf("... %w", err) を使用していれば、
// チェーンの深い階層にあるエラーも正しく判定できる
}
5. 応用・注意点: 現場で陥りやすい罠
%w を忘れない: fmt.Errorf で %w を使わず %v を使うと、エラーチェーンが断絶され、errors.Is / As が機能しなくなります。これは非常によくあるバグです。
カスタムエラーの Unwrap メソッド: 独自のエラー構造体で、他のエラーをラップして返したい場合は、Unwrap() error メソッドを実装してください。これにより、Goの標準ライブラリがその構造体の中身を自動的に探索してくれるようになります。
パフォーマンスへの配慮: エラーチェーンの走査は再帰的またはループで行われます。頻繁に発生するエラーに対して巨大なチェーンを構築するとパフォーマンスに影響を与える可能性がありますが、一般的なビジネスロジック内であれば、可読性とメンテナンス性を優先して積極的に活用することをお勧めします。

コメント