実用Go言語 第5章 エラーハンドリング より
エラーハンドリングの基本
エラーハンドリングとしては多くの場合以下のような方針が考えられる。
- 呼び出し元に関数の引数などの情報を付与してエラーを返す
- ログを出力して処理を継続する
- リトライを実施する
- リソースをクローズする
Goではpanicを多用せずエラーハンドリングを行いエラーを返すべき。
呼び出し元に情報を付与して返す
エラーが発生した場合そのまま返していては発生個所の特定がむずかしいため、情報を付加して返す。
user, err := getInvitedUserWithEmail(ctx, email)
if err != nil {
return fmt.Errorf("fail to get invited user with email(%s): %w", email, err)
}
ログを出力して処理を継続
ex. データベースに指定されたレコードが存在しないとデフォルトの値を用いて処理を継続するなど
//func featchCapacity(ctx context.Context, key string) (int, error) {
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
log.Printf("using default value")
return 10, nil
}
}
リトライの実施
ネットワーク呼び出しの場合一瞬だけ接続できない場合もあるため、即座にエラーを返すよりも堅牢なアプリケーションになる。
var b []byte
err := retry.Retry(2, 0, func() error { //トライ回数、待機時間
_, ierr *= tcpClient.Read(b)
return ierr
})
if err != nil {
// error handling
}
リソースのクローズ
たとえばファイルをOpenした場合など、deferとして後処理を記述することで後続の処理でエラーが発生してもファイルのクローズ処理が行われる。
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
※log.Fatal()を呼ぶとプログラムが即座に終了するためdeferの呼び出しが行われない。関数の実装でlog.Fatal()が登場するとほとんどの場合は間違い
複数のエラーをまとめる
同じエラーを繰り返しチェックするようなケースの場合、ラッパーメソッドを定義することでコードをシンプルにすることが出来る。
before
_, err = fd.Write(p0[a:b])
if err != nil {
return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
return err
}
after
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p0[c:d])
ew.write(p0[e:f])
if ew.err != nil {
return ew.err
}