0
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言語のエラーハンドリング:実践的なパターンと最適解

0
Posted at

Go言語のエラーハンドリング:実践的なパターンと最適解

はじめに

Go言語のエラーハンドリングは、他の多くのプログラミング言語とは異なるアプローチを取っています。例外(Exception)を使わず、エラーを明示的に戻り値として返すという設計思想は、最初は冗長に感じるかもしれません。しかし、この方法には多くの利点があり、適切に使いこなすことでより堅牢なコードを書くことができます。

本記事では、Go言語におけるエラーハンドリングの基本から実践的なパターンまでを解説します。

基本的なエラーハンドリング

Go言語では、関数がエラーを返す可能性がある場合、慣習的に最後の戻り値としてerror型を返します。

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("エラー:", err)
        return
    }
    fmt.Println("結果:", result)
}

この例では、ゼロ除算が発生する可能性がある場合にエラーを返しています。呼び出し側では必ずerr != nilをチェックすることが必要です。

カスタムエラーの作成

より詳細なエラー情報を提供したい場合、カスタムエラー型を定義することができます。

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("バリデーションエラー [%s]: %s", e.Field, e.Message)
}

func validateUser(name string, age int) error {
    if name == "" {
        return &ValidationError{
            Field:   "name",
            Message: "名前は必須です",
        }
    }
    if age < 0 || age > 150 {
        return &ValidationError{
            Field:   "age",
            Message: "年齢が不正です",
        }
    }
    return nil
}

カスタムエラー型を使うことで、エラーの種類を型で判別でき、より柔軟なエラーハンドリングが可能になります。

エラーのラップと追跡

Go 1.13以降では、fmt.Errorf%w動詞を使ってエラーをラップできるようになりました。これにより、エラーが発生した箇所のコンテキスト情報を保持しながら、元のエラーも保持できます。

func readConfig(filename string) (*Config, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("設定ファイルの読み込みに失敗: %w", err)
    }
    
    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("設定ファイルのパースに失敗 (%s): %w", filename, err)
    }
    
    return &config, nil
}

ラップされたエラーは、errors.Is()errors.As()を使って元のエラーを確認できます。

config, err := readConfig("config.json")
if err != nil {
    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("設定ファイルが存在しません")
    } else {
        fmt.Println("エラー:", err)
    }
}

センチネルエラーの使用

特定のエラー条件を表現するために、あらかじめ定義されたエラー値(センチネルエラー)を使用するパターンも一般的です。

var (
    ErrUserNotFound    = errors.New("ユーザーが見つかりません")
    ErrInvalidPassword = errors.New("パスワードが正しくありません")
    ErrAccountLocked   = errors.New("アカウントがロックされています")
)

func login(username, password string) error {
    user, err := findUser(username)
    if err != nil {
        return ErrUserNotFound
    }
    
    if user.IsLocked {
        return ErrAccountLocked
    }
    
    if !user.ValidatePassword(password) {
        return ErrInvalidPassword
    }
    
    return nil
}

センチネルエラーを使うことで、エラーの種類を明確に区別でき、呼び出し側で適切な処理を行うことができます。

エラーハンドリングのベストプラクティス

1. エラーを無視しない

// 悪い例
file, _ := os.Open("config.json")

// 良い例
file, err := os.Open("config.json")
if err != nil {
    return fmt.Errorf("ファイルを開けません: %w", err)
}
defer file.Close()

2. エラーメッセージに大文字や句点を使わない

Go言語の慣習として、エラーメッセージは小文字で始め、句点で終わらせません。これは、エラーメッセージが他のメッセージと組み合わせて使用されることを想定しているためです。

// 悪い例
errors.New("ファイルが見つかりません。")

// 良い例
errors.New("ファイルが見つかりません")

3. コンテキスト情報を追加する

エラーが発生した際には、デバッグに役立つコンテキスト情報を追加します。

func processFile(filename string) error {
    data, err := readFile(filename)
    if err != nil {
        return fmt.Errorf("ファイル処理エラー (file: %s): %w", filename, err)
    }
    // 処理...
    return nil
}

4. パニックは本当に回復不可能な場合のみ

panicは例外的な状況(プログラムが継続できない場合)にのみ使用し、通常のエラーハンドリングにはerrorを使用します。

// パニックを使うべき例(初期化時の致命的エラー)
func init() {
    if !systemRequirementsMet() {
        panic("システム要件を満たしていません")
    }
}

// 通常のエラーハンドリングを使うべき例
func createUser(name string) (*User, error) {
    if name == "" {
        return nil, errors.New("名前は必須です")
    }
    return &User{Name: name}, nil
}

まとめ

Go言語のエラーハンドリングは、明示的で予測可能なコードを書くことを促進します。以下のポイントを押さえることで、堅牢で保守性の高いコードを書くことができます:

  • エラーを戻り値として明示的に返す
  • カスタムエラー型で詳細な情報を提供する
  • エラーをラップしてコンテキストを保持する
  • センチネルエラーで特定のエラー条件を表現する
  • エラーを無視せず、適切に処理する
0
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
0
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?