2023/05/16

Go の error をアサーションするメソッドの難しさ

きのうの記事「Goのアサーションライブラリ actually のすべて」 で最後にこう書きました。

errorのアサーションメソッドは今のところ NoError だけで、肝心の error そのものをアサーションするメソッドは実装していません。イケてる感じのを思いついたらやろうと思っているのですが、なかなか思いつかないので保留してます。

きょうはこの error をアサーションする件について少し自分の考えを並べておきます。

Go のエラーハンドリング

  • Go の エラーハンドリングはエラー(いわゆる例外)があったら error を返すのが基本
    • いわゆる Exception や try-catch はない
  • error を受け取る側は都度 error をチェックする
    • error だったらその場にどう振る舞うかが記述される
  • error は原則 無視 されない(まあ、原則ね)

というわけで、Go におけるエラーハンドリングは、基本的に error が起きるかもしれない場所ごとにどう振る舞うかが都度書かれているということになります。たいていは、エラーを wrap して上流に再度流して、アプリケーション全体としてのエラーに対する振る舞いを最上流で行うようにします。

エラーの実装

エラーハンドリングは上記のようにとてもシンプルです。シンプルではない状況もあると思いますが、それは実装というか、エラーハンドリングの基本実装が間違っているという話なので、基本に立ち返った実装に直しましょうということかと思います。

しかし、エラーそのものの実体はあまりシンプルではありません。

  • fmt.Errorf によるエラー
    • エラーをwrapするときは fmt.Errorf("%w", err)
    • ただの文字列やね
  • 標準errorsパッケージ、または 3rd製 errors モジュールによるエラー
    • errors.Wrap でラップする
    • pkg/errors は開発終了?
    • github.com/cockroachdb/errors
  • Error インターフェースを実装した error型 MyError
    • errors の拡張

というわけで、少なくとも上記のような3パターンが error の実体として存在するので、errorアサーションは一筋縄でいく気がしないのです。なんらかのエラーであることは nil でないことを見るだけなので簡単ですが、実際はエラーの具象を見れなければほとんど意味がありません。そしてその具象をみるためのアプローチが単純にはいかなさそうなのです。

エラー文字列を峻別するのはアンチパターンであるとされているので、テストの文脈でエラーをstringify して含まれる文字列をみるのはいまさらやる必要は無い気がします。確実なテストとも言えませんし。また、error.Is/As のラッパーであるなら、それなりのショートカットであるべきですが、わざわざアサーションライブラリで書くほどのことはなく、少々短く書ける程度ならerrorパッケージそのもので書かれている方が明確なコードと言えるでしょう。

あと、Go本体でも errorまわりの改善がまだまだ盛んなイメージがあり、やっぱりこのエラー周辺はまだ生々しい記述の方が適しているのではないかという気持ちもあります。

というわけで、以上が errorアサーションメソッドを書いてない理由です。

もっといろんなユースケースを眺めてみないといまのところアイデアが不成立ですね。

サイト内検索