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

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

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

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

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

### 具体的な動作
`if errors.As(err, &validErr)`は以下の処理を行います：
1. 引数errに含まれるエラーチェーンの中からValidationError型を探索
2. ValidationError型が見つかった場合：
   - validErrにそのエラーの値が格納される
   - trueを返す
3. 見つからなかった場合：
   - falseを返す

```go
// カスタムエラー型の定義
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エンコードから除外できます。これは、エラー情報の一部を内部的にのみ使用し、外部に公開したくない場合に特に有用です。

### タグの使用例と動作の詳細説明
```go
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）
同じエラーメッセージを複数箇所で再利用する場合に適しています。

```go
// パッケージレベルでエラー変数を定義
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）
実行時の状態や変数の値をエラーメッセージに含める必要がある場合に使用します。

```go
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アプリケーション改訂版』技術評論社
- https://techbookfest.org/product/jXDAEU1dR53kbZkgtDm9zx