Go の error インターフェースの基本

Go のエラーハンドリングは、他の言語でよく見る try-catch 方式とは異なる。Go では error インターフェースを返り値として扱い、呼び出し元で明示的にチェックする設計になっている。

error インターフェースの定義

error は Go の組み込みインターフェースで、非常にシンプルな構造を持つ。

type error interface {
    Error() string
}

Error() メソッドを実装していれば、どんな型でも error として扱える。この単純さが Go のエラーハンドリングの柔軟性を支えている。

関数からエラーを返す

Go の慣習として、エラーが発生しうる関数は最後の返り値として error を返す。

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

エラーがない場合は nil を返す。呼び出し側ではこの error が nil かどうかをチェックして処理を分岐させる。

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

なぜ例外ではなくエラー値なのか

Go が例外機構を採用しなかった理由はいくつかある。

例外機構

制御フローが見えにくくなる。どこで例外が発生し、どこでキャッチされるか追跡が難しい。

エラー値

エラー処理が明示的で、コードを読むだけで制御フローがわかる。

エラーを値として扱うことで、エラー処理を強制し、見落としを防ぐ効果もある。コンパイラは未使用の変数に警告を出すため、エラーを無視しにくい設計になっている。

error の nil チェック

error 型の変数が nil かどうかのチェックは、Go プログラムで最も頻出するパターンだ。

file, err := os.Open("config.json")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

この if err != nil というイディオムは Go コードのいたるところで見かける。冗長に感じるかもしれないが、エラー処理が明示的であることの代償として受け入れられている。