1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go】3つのエラーハンドリング基本テクニック(errors.As/json:"-"/errorチェーン)

Posted at

はじめまして!フリーランスエンジニアのこたろうです!

エラーチェーンについて、学びで得た知見を共有します。

errors.As関数の活用

errors.As関数は、エラーチェーンの中から特定の型のエラーを探し出し、安全にその型の情報にアクセスするために使用します。

errors.As関数の主な特徴

  1. エラーチェーン内のどの階層にあるエラーでも検出可能
  2. 型安全性の保証
  3. ラップされたエラーも検索対象に含む

具体的な動作

if errors.As(err, &validErr)は以下の処理を行います:

  1. 引数errに含まれるエラーチェーンの中からValidationError型を探索
  2. ValidationError型が見つかった場合:
    • validErrにそのエラーの値が格納される
    • trueを返す
  3. 見つからなかった場合:
    • falseを返す
// カスタムエラー型の定義
type ValidationError struct {
    Field string    // エラーが発生したフィールド名
    Issue string    // エラーの詳細な内容
}

// Error インターフェースの実装
func (e *ValidationError) Error() string {
    return fmt.Sprintf("バリデーションエラー: %sフィールドが%s", e.Field, e.Issue)
}

// エラーをラップする関数の例
func validateUser(user User) error {
    if user.Age < 0 {
        // ValidationErrorを作成し、さらにfmt.Errorfで追加情報とともにラップ
        return fmt.Errorf("ユーザー検証エラー: %w", 
            &ValidationError{
                Field: "age",
                Issue: "負の値です",
            })
    }
    return nil
}

// エラーを処理する関数
func handleUserError(err error) {
    var validErr *ValidationError
    // エラーチェーン内からValidationError型を探し、見つかった場合はvalidErrに格納
    if errors.As(err, &validErr) {
        // ValidationError型の場合の処理
        // validErr.FieldやvalidErr.Issueなど、ValidationError特有の情報にアクセス可能
        fmt.Printf("フィールド '%s' の問題: %s\n", 
            validErr.Field, validErr.Issue)
        return
    }
    // ValidationError型以外のエラー処理
    fmt.Printf("その他のエラー: %v\n", err)
}

// 実際の使用例
func main() {
    user := User{Age: -5}
    if err := validateUser(user); err != nil {
        handleUserError(err) // 出力: "フィールド 'age' の問題: 負の値です"
    }
}

errors.Asを使用するメリット

  1. 型安全性:コンパイル時に型の整合性をチェック
  2. エラーチェーンのサポート:ラップされた深い階層のエラーも検出可能
  3. 簡潔な記述:型アサーションと値の取得を1行で実現
  4. バグの防止:型の不一致による実行時エラーを防ぐ

JSONエンコードの制御

json:"-"タグを使用することで、特定のフィールドをJSONエンコードから除外できます。これは、エラー情報の一部を内部的にのみ使用し、外部に公開したくない場合に特に有用です。

タグの使用例と動作の詳細説明

type APIError struct {
    Message    string `json:"message"`      // JSONに含める(キー名:message)
    Code       int    `json:"code"`         // JSONに含める(キー名:code)
    InternalErr error  `json:"-"`           // JSONから除外
}

// APIErrorを作成するコンストラクタ関数
func NewAPIError(msg string, code int, err error) *APIError {
    return &APIError{
        Message:    msg,        // クライアントに表示するメッセージ
        Code:       code,       // HTTPステータスコードなど
        InternalErr: err,       // 内部的なエラー情報(JSONには含まれない)
    }
}

// 使用例と出力されるJSONの形式
err := &APIError{
    Message:    "ユーザー認証に失敗しました",
    Code:       401,
    InternalErr: errors.New("token expired"),  // この情報はJSONには含まれない
}

// JSONエンコード後の出力例
// {
//     "message": "ユーザー認証に失敗しました",
//     "code": 401
// }

json:"-"タグを使用するメリット

  1. セキュリティの向上:機密情報の外部露出を防止
  2. レスポンスサイズの最適化:不要な情報を除外
  3. APIの整理:クライアントに必要な情報のみを提供
  4. デバッグのしやすさ:内部エラー情報を保持しながら、クライアントには適切な情報のみ表示

エラーメッセージの作成方法の使い分け

状況に応じて適切なエラー作成方法を選択することで、より明確で管理しやすいエラーハンドリングが可能になります。

固定メッセージの場合(errors.New)

同じエラーメッセージを複数箇所で再利用する場合に適しています。

// パッケージレベルでエラー変数を定義
var (
    ErrInvalidInput = errors.New("入力値が不正です")
    ErrNotFound     = errors.New("リソースが見つかりません")
    ErrUnauthorized = errors.New("認証が必要です")
)

// エラー変数を使用する関数の例
func validateUser(user User) error {
    if user.Name == "" {
        return ErrInvalidInput    // 定義済みエラーを返す
    }
    return nil
}

errors.Newを使用するメリット

  1. エラーメッセージの一貫性:同じエラーには同じメッセージを使用
  2. 保守性の向上:メッセージの変更が一箇所で可能
  3. エラー比較の容易さ:errors.Isによる比較が可能
  4. メモリ効率:同じエラーインスタンスを再利用

動的メッセージの場合(fmt.Errorf)

実行時の状態や変数の値をエラーメッセージに含める必要がある場合に使用します。

func findUser(id string) (*User, error) {
    user, err := db.Find(id)
    if err != nil {
        // %wを使用して元のエラーをラップし、コンテキスト情報を追加
        return nil, fmt.Errorf("ユーザーID %s の検索に失敗: %w", id, err)
    }
    return user, nil
}

// エラーチェーンの例
func processUser(id string) error {
    user, err := findUser(id)
    if err != nil {
        // さらにエラーをラップしてコンテキストを追加
        return fmt.Errorf("ユーザー処理エラー: %w", err)
    }
    return nil
}

// 使用例:
// エラーチェーン:"ユーザー処理エラー: ユーザーID 123 の検索に失敗: レコードが見つかりません"

fmt.Errorfを使用するメリット

  1. 詳細な情報提供:動的な値をエラーメッセージに含められる
  2. エラーのコンテキスト追加:元のエラーを保持しながら情報を追加
  3. デバッグのしやすさ:エラーの発生箇所や状況の特定が容易
  4. エラーチェーンの構築:%wを使用することで、エラーの階層構造を作成可能

参考文献:
-『Goで作るはじめてのWebアプリケーション改訂版』技術評論社

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?