やりたいこと
Java系の言語だとtry-catchブロックで例外の種類ごとに例外処理を記述できます。例外が100種類あるんだけど、うち50種類はリトライ可能で、リトライ処理を一括で書きたい場合に以下のように書けるわけです。
try {
throw new FooException(); // FooExceptionはRetryableExceptionを継承している
} catch (RetryableException e) {
// リトライ処理
} catch (Exception e) {
// 致命的エラーなので諦めて死ぬ
}
Go初心者の私はGoで同じことをやりたくなったんですが、ピンとくる方法がみつからなかったので試しに実装してみました。
エラーのwrapとerrors.Is()
を使う方法(Go 1.13以降が必須)
私の実装を紹介します。
package main
import (
"errors"
"fmt"
)
var RetryableError = errors.New("Retrying...")
func main() {
err := Foo()
if errors.Is(err, RetryableError) {
// リトライ処理
} else {
// 致命的エラーなので諦めて死ぬ
}
}
func Foo() error {
return fmt.Errorf("Foo is temporarily unavailable. %w", RetryableError)
}
リトライ可能エラーとそれ以外のエラーを区別したい場合に、リトライ可能エラーは必ずRetryableError
をwrapするようにします。エラーを受け取った側はerrors.Is(err, RetryableError)
でリトライ可能かどうか区別できます。
RetryableError
のエラーメッセージは単体で見ると意味不明だと思いますが、wrapしたエラーを理解しやすくする意図です。fmt.Errorf()
の%w
ではwrap対象のエラーメッセージが展開されますので、Foo()
が投げるエラーのエラーメッセージは次のようになります。
Foo is temporarily unavailable. Retrying...
これはログに出すのに便利なので個人的には気に入っています。また、リトライ回数上限に到達したような場合など、RetryableError
を外すこともできます。
err := Foo()
if errors.Is(err, RetryableError) {
retry--
if retry >= 0 {
log.Printf("Ignorable error: %+v\n", err)
// リトライ処理
}
err = fmt.Errorf("%s Gave up.", err.Error()) // RetryableErrorを外す
}
ご意見募集
正直なところ、これが良い方法なのかはわかっていません。Go上級者の方のご意見をお待ちしております。