Go の errors.New と fmt.Errorf の使い分け
Go でエラーを生成する方法として、errors.New と fmt.Errorf の2つがよく使われる。どちらもエラーを作成するが、用途が異なる。
errors.New の基本
errors.New は最もシンプルなエラー生成関数だ。固定のエラーメッセージを持つ error を返す。
import "errors"
var ErrNotFound = errors.New("item not found")
func findItem(id int) (string, error) {
if id < 0 {
return "", ErrNotFound
}
return "item", nil
}errors.New で作成したエラーはパッケージレベルの変数として定義することが多い。これを「センチネルエラー」と呼び、errors.Is で比較できる。
fmt.Errorf の基本
fmt.Errorf は書式指定付きでエラーメッセージを生成できる。動的な情報をエラーに含めたいときに使う。
import "fmt"
func openFile(path string) error {
if path == "" {
return fmt.Errorf("invalid path: %s", path)
}
return nil
}Printf と同じ書式指定子が使えるため、変数の値をエラーメッセージに埋め込める。
使い分けの基準
errors.New
固定メッセージのエラー。センチネルエラーとして定義し、後で比較したい場合に適している。
fmt.Errorf
動的な情報を含むエラー。ファイルパスや ID など、コンテキストをメッセージに埋め込みたい場合に使う。
エラーのラップ(Go 1.13 以降)
fmt.Errorf では %w 動詞を使ってエラーをラップできる。
func readConfig(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config %s: %w", path, err)
}
// ...
return nil
}%w でラップされたエラーは、errors.Unwrap や errors.Is で元のエラーを取り出せる。これにより、エラーにコンテキストを追加しながら、元のエラー情報も保持できる。
実践的な例
以下はデータベースからユーザーを取得する関数の例だ。
var ErrUserNotFound = errors.New("user not found")
func GetUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("invalid user id: %d", id)
}
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("database error for user %d: %w", id, err)
}
if user == nil {
return nil, ErrUserNotFound
}
return user, nil
}固定のエラー(ErrUserNotFound)と動的なエラー(fmt.Errorf)を状況に応じて使い分けている。呼び出し側では errors.Is(err, ErrUserNotFound) でエラーの種類を判定できる。