LoginSignup
3
2

More than 3 years have passed since last update.

Goでリトライ可能エラーと致命的エラーを投げ分けて区別する試み

Last updated at Posted at 2020-05-14

やりたいこと

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上級者の方のご意見をお待ちしております。

参考文献

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2