Go のカスタムエラー型の作成
Go の error インターフェースは Error() string メソッドさえ実装すれば満たせる。この柔軟性を活かして、追加情報を持つカスタムエラー型を作成できる。
基本的なカスタムエラー型
構造体に Error() メソッドを実装すれば、それがエラー型になる。
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}これで ValidationError は error インターフェースを満たす。エラーメッセージだけでなく、どのフィールドで問題が起きたかという情報も保持できる。
カスタムエラーの使用例
func validateUser(u *User) error {
if u.Name == "" {
return &ValidationError{
Field: "name",
Message: "名前は必須です",
}
}
if u.Age < 0 {
return &ValidationError{
Field: "age",
Message: "年齢は0以上である必要があります",
}
}
return nil
}呼び出し側では errors.As を使ってカスタムエラー型を取り出せる。
err := validateUser(user)
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("フィールド '%s' でエラー: %s\n", valErr.Field, valErr.Message)
}Unwrap メソッドの実装
カスタムエラーが他のエラーをラップしている場合、Unwrap メソッドを実装する。
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string {
return fmt.Sprintf("query error [%s]: %v", e.Query, e.Err)
}
func (e *QueryError) Unwrap() error {
return e.Err
}Unwrap を実装すると、errors.Is や errors.As がエラーチェーンをたどれるようになる。
func executeQuery(q string) error {
_, err := db.Exec(q)
if err != nil {
return &QueryError{Query: q, Err: err}
}
return nil
}
// 呼び出し側
err := executeQuery("SELECT * FROM users")
if errors.Is(err, sql.ErrNoRows) {
// QueryError でラップされていても検出できる
}Is メソッドのカスタマイズ
errors.Is の比較ロジックをカスタマイズしたい場合、Is メソッドを実装する。
type HTTPError struct {
StatusCode int
Message string
}
func (e *HTTPError) Error() string {
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Message)
}
func (e *HTTPError) Is(target error) bool {
t, ok := target.(*HTTPError)
if !ok {
return false
}
return e.StatusCode == t.StatusCode
}この実装では、ステータスコードが同じなら同一エラーとみなす。
err1 := &HTTPError{StatusCode: 404, Message: "page not found"}
err2 := &HTTPError{StatusCode: 404, Message: "user not found"}
errors.Is(err1, err2) // true(ステータスコードが同じ)実践的な設計パターン
複数のエラー情報を持つ構造体を定義し、アプリケーション全体で一貫したエラーハンドリングを行う例を示す。
type AppError struct {
Code string
Message string
Err error
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
func (e *AppError) Unwrap() error {
return e.Err
}
// エラーコード定数
const (
ErrCodeNotFound = "NOT_FOUND"
ErrCodeUnauthorized = "UNAUTHORIZED"
ErrCodeInternal = "INTERNAL"
)エラーコードを使うことで、API レスポンスへの変換やログ出力が統一的に行える。カスタムエラー型は、エラーに意味のある構造を持たせたいときに有効な手段だ。