Go/error
表示
< Go
errorはGoにおいて事前宣言された識別子で、エラー処理のための標準インターフェース型です。エラーを表現するためのGoの基本的な手段であり、単一のメソッドError() stringを持つインターフェースとして定義されています。
基本的な特徴
[編集]- インターフェース型:
type error interface { Error() string } Error()メソッドを実装した任意の型はerror型として扱える- ゼロ値: nil
- エラー処理の標準的な手段
- 関数の多値戻り値の最後の要素として慣例的に使用される
他のキーワードとの組み合わせ
[編集]戻り値としての使用
[編集]func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("ゼロ除算") } return a / b, nil }
エラー生成関数
[編集]// 基本的なエラー生成 err1 := errors.New("シンプルなエラーメッセージ") // フォーマット付きエラー err2 := fmt.Errorf("値 %d が範囲外です", value) // 既存のエラーをラップ(Go 1.13以降) originalErr := errors.New("元のエラー") wrappedErr := fmt.Errorf("コンテキスト情報: %w", originalErr)
エラー判定
[編集]// nilチェック if err != nil { // エラー処理 } // エラー型の比較(errors.Isを使用 - Go 1.13以降) if errors.Is(err, io.EOF) { // EOFエラーの処理 } // エラー型のアサーション(errors.Asを使用 - Go 1.13以降) var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Println("パスエラー:", pathErr.Path) } // カスタムエラー型の比較(Go 1.13未満) if err, ok := err.(*os.PathError); ok { fmt.Println("パスエラー:", err.Path) }
エラーラッピング(Go 1.13以降)
[編集]// エラーをラップ err := doSomething() if err != nil { return fmt.Errorf("操作に失敗しました: %w", err) } // ラップされたエラーの展開 originalErr := errors.Unwrap(wrappedErr)
カスタムエラー型
[編集]// 構造体ベースのカスタムエラー type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("フィールド %s の検証エラー: %s", e.Field, e.Message) } // カスタムエラーの使用 func validateUser(user User) error { if user.Name == "" { return &ValidationError{Field: "Name", Message: "空にできません"} } return nil }
エラー定数
[編集]var ( ErrNotFound = errors.New("見つかりません") ErrPermission = errors.New("権限がありません") ErrTimeout = errors.New("タイムアウト") ) func findItem(id string) (Item, error) { // ... return Item{}, ErrNotFound }
ジェネリクスとの組み合わせ(Go 1.18以降)
[編集]// エラーを返す可能性のある型を扱うジェネリック関数 func Process[T any](data T) (T, error) { var zero T // 処理... if failed { return zero, errors.New("処理に失敗しました") } return result, nil }
パニックとエラーの関係
[編集]// パニックをエラーに変換 func doSomethingRisky() (result string, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("パニックが発生しました: %v", r) } }() // パニックが発生する可能性のあるコード return "結果", nil } // エラーをパニックに変換(通常は避けるべき) func mustSucceed(err error) { if err != nil { panic(err) } }
ユースケース
[編集]- 基本的なエラー処理
- 関数からのエラー返却と検査
file, err := os.Open("file.txt") if err != nil { log.Fatalf("ファイルを開けませんでした: %v", err) } defer file.Close()
- エラーチェーン(エラーラッピング)
- エラーにコンテキスト情報を追加しながら伝播
func processFile(path string) error { data, err := readFile(path) if err != nil { return fmt.Errorf("ファイル読み込み中のエラー: %w", err) } err = processData(data) if err != nil { return fmt.Errorf("データ処理中のエラー: %w", err) } return nil }
- カスタムエラー型
- 詳細なエラー情報の提供
type QueryError struct { Query string Err error } func (e *QueryError) Error() string { return fmt.Sprintf("クエリ %q の実行中のエラー: %v", e.Query, e.Err) } func (e *QueryError) Unwrap() error { return e.Err }
- エラー型による分岐処理
- エラーの種類に基づく異なる処理
_, err := user.Find(id) if errors.Is(err, ErrUserNotFound) { // ユーザーが見つからない場合の処理 } else if errors.Is(err, ErrDatabaseConnection) { // データベース接続エラーの処理 } else if err != nil { // その他のエラー処理 }
- エラーの無視(特定の状況で)
- 特定のエラーを無視する場合
err := os.Remove("/tmp/tempfile") if err != nil && !os.IsNotExist(err) { log.Printf("ファイル削除中のエラー: %v", err) }
- 複数のエラーの集約
- 複数の操作から発生したエラーの集約
type ErrorList []error func (el ErrorList) Error() string { var sb strings.Builder sb.WriteString("複数のエラーが発生しました:\n") for i, err := range el { sb.WriteString(fmt.Sprintf(" %d: %v\n", i+1, err)) } return sb.String() }
- センチネルエラー(事前定義されたエラー値)
- 特定の状況を表す標準エラー値
// 標準ライブラリのセンチネルエラーの例 // io.EOF, sql.ErrNoRows など // カスタムセンチネルエラー var ( ErrInvalidInput = errors.New("無効な入力") ErrNotAuthorized = errors.New("権限がありません") )
- HTTPエラーハンドリング
- Webアプリケーションでのエラー処理
func handleUser(w http.ResponseWriter, r *http.Request) { user, err := getUser(r.URL.Query().Get("id")) if err != nil { if errors.Is(err, ErrUserNotFound) { http.Error(w, "ユーザーが見つかりません", http.StatusNotFound) } else { http.Error(w, "サーバーエラー", http.StatusInternalServerError) log.Printf("ユーザー取得エラー: %v", err) } return } // 正常処理... }
- リトライロジック
- エラーに基づいたリトライ
func doWithRetry(fn func() error, maxRetries int) error { var lastErr error for i := 0; i < maxRetries; i++ { lastErr = fn() if lastErr == nil { return nil } if !isRetryableError(lastErr) { return fmt.Errorf("致命的エラー (リトライなし): %w", lastErr) } time.Sleep(backoff(i)) } return fmt.Errorf("最大リトライ回数に達しました: %w", lastErr) }
- エラー監視とロギング
- エラーの体系的なロギング
func logError(err error, operation string) { if err == nil { return } log.Printf("操作 %q 中のエラー: %v", operation, err) // スタックトレースなどの詳細情報を追加(pkg/errors などを使用) // log.Printf("%+v", err) }
- データベース操作エラー処理
- データベースエラーの適切な処理
rows, err := db.Query("SELECT * FROM users WHERE id = ?", id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrUserNotFound } return nil, fmt.Errorf("データベースクエリエラー: %w", err) } defer rows.Close()
- グレースフルデグラデーション
- エラー発生時にサービスの一部を無効化
recomms, err := getRecommendations(userID) if err != nil { log.Printf("レコメンデーション取得エラー: %v", err) // レコメンデーションなしで続行 recomms = []Recommendation{} }
注意点と高度なエラーハンドリング
[編集]- エラーメッセージの一貫性: エラーメッセージは一貫した形式で、大文字で始まらず、句読点で終わらないのが慣例です
- エラーのラッピングとアンラッピング: Go 1.13以降では
%w動詞によるエラーラッピングとerrors.Unwrapが標準でサポートされています// ラッピング err = fmt.Errorf("コンテキスト: %w", originalErr) // アンラッピング originalErr = errors.Unwrap(err)
- エラーチェック関数の作成: エラーの種類を判定するヘルパー関数を作成すると便利です
func IsNotFoundError(err error) bool { return errors.Is(err, ErrNotFound) }
- エラートレーシング: スタックトレース付きエラーを提供するサードパーティライブラリ
// github.com/pkg/errors を使用した例 import "github.com/pkg/errors" func loadConfig() error { _, err := os.Open("config.json") if err != nil { return errors.Wrap(err, "設定ファイルを開けません") } // ... }
- エラーハンドリングの集中化: エラー処理ロジックの重複を避けるための集中化
func handleAPIError(w http.ResponseWriter, err error) { switch { case errors.Is(err, ErrNotFound): responseWithJSON(w, http.StatusNotFound, ErrorResponse{Message: "リソースが見つかりません"}) case errors.Is(err, ErrUnauthorized): responseWithJSON(w, http.StatusUnauthorized, ErrorResponse{Message: "認証が必要です"}) default: responseWithJSON(w, http.StatusInternalServerError, ErrorResponse{Message: "内部サーバーエラー"}) log.Printf("予期しないエラー: %v", err) } }
- パッケージレベルでのエラー設計: パッケージのエラーを適切に設計すると、利用者にとっての使いやすさが向上します
// database/sql パッケージの例 if err == sql.ErrNoRows { // 行が見つからない場合の処理 } // カスタムパッケージの例 package payment var ( ErrInsufficientFunds = errors.New("口座残高不足") ErrCardExpired = errors.New("カードの有効期限切れ") )
- エラーラッピングの深さ: 過度に深いエラーチェーンは避け、適切な抽象化レベルでエラーを整理することが重要です
- エラー型の適切な設計: 特に公開APIでは、どのようなエラーを返すかを文書化し、一貫した実装を提供することが重要です
- ゼロ値の安全性: エラー型のゼロ値(
nil)との比較は、必ずif err != nilのパターンで行いましょう
Goのエラー処理設計は「明示的なエラーハンドリング」の哲学に基づいており、try-catchのような例外処理メカニズムとは異なるアプローチを取っています。エラー値を明示的に返し、呼び出し側で検査するこの方法により、エラー処理が明確になり、コードの流れが追いやすくなるというメリットがあります。