エラーハンドリングベストプラクティス
エラーハンドリングベストプラクティス
Section titled “エラーハンドリングベストプラクティス”Goのエラーハンドリングは、他の言語とは異なるアプローチを取ります。適切なエラーハンドリングにより、堅牢で保守性の高いコードを書くことができます。
なぜエラーハンドリングが重要なのか
Section titled “なぜエラーハンドリングが重要なのか”エラーを無視する問題
Section titled “エラーを無視する問題”問題のあるコード:
func processFile(filename string) { file, _ := os.Open(filename) // エラーを無視 defer file.Close()
data := make([]byte, 100) file.Read(data) // エラーを無視 // 問題: エラーが発生しても気づかない // 問題: デバッグが困難}適切なエラーハンドリング:
func processFile(filename string) error { file, err := os.Open(filename) if err != nil { return fmt.Errorf("failed to open file: %w", err) } defer file.Close()
data := make([]byte, 100) if _, err := file.Read(data); err != nil { return fmt.Errorf("failed to read file: %w", err) } return nil}メリット:
- エラーの可視化: エラーが発生した箇所が明確
- デバッグの容易さ: エラーの原因を追跡可能
- 堅牢性: エラーに対する適切な処理
エラーのラッピング
Section titled “エラーのラッピング”エラーラッピングの基本
Section titled “エラーラッピングの基本”import ( "errors" "fmt")
// エラーをラップしてコンテキストを追加func processUser(id int) error { user, err := getUser(id) if err != nil { return fmt.Errorf("failed to process user %d: %w", id, err) }
if err := validateUser(user); err != nil { return fmt.Errorf("user validation failed: %w", err) }
return nil}カスタムエラー型
Section titled “カスタムエラー型”type UserError struct { UserID int Message string Err error}
func (e *UserError) Error() string { return fmt.Sprintf("user %d: %s: %v", e.UserID, e.Message, e.Err)}
func (e *UserError) Unwrap() error { return e.Err}
// 使用例func getUser(id int) (*User, error) { user, err := db.Query(id) if err != nil { return nil, &UserError{ UserID: id, Message: "failed to query user", Err: err, } } return user, nil}エラーのチェック
Section titled “エラーのチェック”errors.Isとerrors.As
Section titled “errors.Isとerrors.As”import ( "errors" "os")
// errors.Is: エラーの種類をチェックfunc checkFile(filename string) error { _, err := os.Stat(filename) if err != nil { if errors.Is(err, os.ErrNotExist) { return fmt.Errorf("file does not exist: %s", filename) } return fmt.Errorf("failed to stat file: %w", err) } return nil}
// errors.As: エラーの型をチェックfunc handleUserError(err error) { var userErr *UserError if errors.As(err, &userErr) { log.Printf("User error: %d - %s", userErr.UserID, userErr.Message) }}エラーのロギング
Section titled “エラーのロギング”import ( "log" "os")
func processRequest(req *Request) error { if err := validateRequest(req); err != nil { log.Printf("Validation error: %v", err) return err }
if err := processData(req); err != nil { log.Printf("Processing error: %v", err) return fmt.Errorf("failed to process request: %w", err) }
return nil}
// 構造化ロギングfunc processRequestStructured(req *Request) error { logger := log.New(os.Stdout, "", log.LstdFlags)
if err := validateRequest(req); err != nil { logger.Printf("level=error msg=\"validation failed\" error=%v", err) return err }
return nil}実践的な例: エラーハンドリングパターン
Section titled “実践的な例: エラーハンドリングパターン”// エラーを返す関数func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil}
// エラーをチェックして処理func calculate(a, b float64) error { result, err := divide(a, b) if err != nil { return fmt.Errorf("calculation failed: %w", err) }
log.Printf("Result: %f", result) return nil}
// エラーの種類に応じた処理func handleCalculation(a, b float64) { err := calculate(a, b) if err != nil { if errors.Is(err, errors.New("division by zero")) { log.Println("Cannot divide by zero") } else { log.Printf("Unexpected error: %v", err) } }}Goのエラーハンドリングのポイント:
- エラーのチェック: すべてのエラーを適切にチェック
- エラーのラッピング: コンテキストを追加してエラーをラップ
- カスタムエラー型: 独自のエラー型を定義
- errors.Is/As: エラーの種類や型をチェック
- ロギング: 適切なログレベルでエラーを記録
適切なエラーハンドリングは、堅牢で保守性の高いコードを書くための重要な要素です。