はじめまして!フリーランスエンジニアのこたろうです!
エラーチェーンについて、学びで得た知見を共有します。
errors.As関数の活用
errors.As関数は、エラーチェーンの中から特定の型のエラーを探し出し、安全にその型の情報にアクセスするために使用します。
errors.As関数の主な特徴
- エラーチェーン内のどの階層にあるエラーでも検出可能
- 型安全性の保証
- ラップされたエラーも検索対象に含む
具体的な動作
if errors.As(err, &validErr)
は以下の処理を行います:
- 引数errに含まれるエラーチェーンの中からValidationError型を探索
- ValidationError型が見つかった場合:
- validErrにそのエラーの値が格納される
- trueを返す
- 見つからなかった場合:
- 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行で実現
- バグの防止:型の不一致による実行時エラーを防ぐ
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:"-"タグを使用するメリット
- セキュリティの向上:機密情報の外部露出を防止
- レスポンスサイズの最適化:不要な情報を除外
- APIの整理:クライアントに必要な情報のみを提供
- デバッグのしやすさ:内部エラー情報を保持しながら、クライアントには適切な情報のみ表示
エラーメッセージの作成方法の使い分け
状況に応じて適切なエラー作成方法を選択することで、より明確で管理しやすいエラーハンドリングが可能になります。
固定メッセージの場合(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を使用するメリット
- エラーメッセージの一貫性:同じエラーには同じメッセージを使用
- 保守性の向上:メッセージの変更が一箇所で可能
- エラー比較の容易さ:errors.Isによる比較が可能
- メモリ効率:同じエラーインスタンスを再利用
動的メッセージの場合(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を使用するメリット
- 詳細な情報提供:動的な値をエラーメッセージに含められる
- エラーのコンテキスト追加:元のエラーを保持しながら情報を追加
- デバッグのしやすさ:エラーの発生箇所や状況の特定が容易
- エラーチェーンの構築:%wを使用することで、エラーの階層構造を作成可能
参考文献:
-『Goで作るはじめてのWebアプリケーション改訂版』技術評論社