Error Handling in Go
Goにおけるエラーハンドリングのベストプラクティス
Error Interface
error型は特殊な型のように見えるがとてもシンプル
以下のインタフェースを満たすだけで良い
code:go
type error interface {
Error() string
}
エラーにデータを詰めたいときはstructにし、メソッドを定義すれば良い
code:go
type DivisionError struct {
IntA int
IntB int
Msg string
}
func (e *DivisionError) Error() string {
return e.Msg
}
Error Defining
errors.Newかfmt.Errorfでエラーメッセージを詰めることでerrorを作れる
予め定義しておくこともできる
code:go
var ErrDivideByZero = errors.New("divide by zero")
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, ErrDivideByZero
}
return a / b, nil
}
Error Checking
定義したエラーかどうかをerrors.Isでチェックすればtry/catchと似たような制御ができる
errors.IsはGo 1.13で追加されており、err == SomeErrorではなくerrors.Isを使うのが望ましい
https://go.dev/blog/go1.13-errors
code:go
if err != nil {
switch {
case errors.Is(err, ErrDivideByZero):
fmt.Println("divide by zero error")
default:
fmt.Printf("unexpected division error: %s\n", err)
}
return
}
Wrapping
Go 1.13からエラーのbubble upを適切に行えるerrors.Wrapとerrors.Unwrapが追加された
fmt.Errorfで%wを使うことでerror値をwrapできる
code:go
func FindUser(username string) (*db.User, error) {
u, err := db.Find(username)
if err != nil {
return nil, fmt.Errorf("FindUser: failed executing db query: %w", err)
}
return u, nil
}
エラーに関するコンテクストを付加することが望ましい
例外となるのはエンドユーザーや外部アプリケーションにエラーが露出してしまう場合
このケースでは境界で適切なエラーハンドリングを行う
ハンドリングパターン
上流に委譲
ほとんどこれ
ログに出力
リトライ
リトライによって成功する可能性があれば使う
続行を諦めpanicまたはexit
ほとんどない
参考
Effective Error Handling in Golang
Goエラーハンドリング戦略