👏

もう迷わない!Go言語の「エラーを返す」ベストプラクティス5選

に公開

呼び出し元にエラーを返す方法

golangでは、関数内でエラーが発生した時に、エラー処理を関数内で完結させず、関数の呼び出し元にエラーを返す(return)するスタイルが一般的です。
returnの方法も様々なので、まとめてみました。

Go言語でerror型を返す主な方法の比較テーブル

方法 用途・特徴 サンプルコード例 備考
errors.New 固定メッセージのエラーを返す return errors.New("error message") シンプルで高速、標準的
fmt.Errorf 変数や値を埋め込んだエラーメッセージを返す return fmt.Errorf("error: %s", value) フォーマット文字列が使える
fmt.Errorf + %w(ラップ) 元のエラーを包んで、追加情報を持たせて返す return fmt.Errorf("wrap: %w", err) Go1.13以降、errors.Is/Asで判定可能
グローバルエラー変数 よく使うエラーを定数的に定義して返す var ErrNotFound = errors.New("not found") 比較や判定に便利
カスタムエラー型 独自の情報(コード等)を持つエラーを返す type MyErr struct{...}; func (e MyErr) Error() string {...} errorインターフェースを実装

1. errors.New

  • 固定のエラーメッセージを返したい場合に使います。
  • 標準パッケージerrorsをimportして利用します。
go
goimport "errors"

func foo() error {
    return errors.New("something went wrong")
}

2. fmt.Errorf

  • エラーメッセージに変数や値を埋め込みたい場合に使います。
  • フォーマット文字列が使える点が特徴です。
go
goimport "fmt"

func foo(name string) error {
    return fmt.Errorf("invalid name: %s", name)
}

3. エラーラップ(%w)で元のエラーを包む

  • Go 1.13以降はfmt.Errorf("...: %w", err)で元のエラーをラップできます。
  • 上位のコンテキストを追加しつつ、元のエラー情報も保持できます。
go
gofunc foo() error {
    err := someFunc()
    if err != nil {
        return fmt.Errorf("foo failed: %w", err)
    }
    return nil
}

4. グローバルなエラー変数を定義して返す

  • 汎用的なエラーや特定の状況を表すエラーを、パッケージレベルで変数として定義して返す方法です。
go
govar ErrNotFound = errors.New("not found")

func search(id int) (string, error) {
    *// ...*
    return "", ErrNotFound
}

5. カスタムエラー型を定義する

  • より詳細な情報を持たせたい場合、自作のエラー型(struct)を作り、errorインターフェースを実装します。
go
gotype MyError struct {
    Code int
    Msg  string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("code=%d, msg=%s", e.Code, e.Msg)
}

func foo() error {
    return &MyError{Code: 404, Msg: "not found"}
}

errを受け取った呼び出し元はどうするのか

呼び出し元がmain関数アプリケーションのエントリポイントの場合

エラー発生時にlog.Fatalやos.Exitでプログラムを終了させることが一般的。
ただし、log.Fatalは内部でos.Exitを呼ぶため、deferによる後処理がスキップされる点に注意が必要です

上記以外のパッケージや関数

エラーをreturnで呼び出し元に伝播する
ライブラリや業務ロジックの関数でlog.Fatalやos.Exitを直接呼ぶのは避け、エラーをreturnで返し、最終的な判断はmain側で行います。。

Discussion