😺

Go言語におけるエラーハンドリングベストプラクティス

2023/02/25に公開
3

もう散々書かれている話だけど、Go に触るたびに毎回調べ直す羽目になっているので、忘備録としてまとめておく

結論

  • errors パッケージは使わない
  • その代わりに golang.org/x/xerrors パッケージを使う
  • エラーを自分で作成するときは xerrors.New もしくは xerrors.Errorf を使う
  • 関数から受け取ったエラーを返すときは xerrors.Errorf(": %w", err) を使う
    • コメントを付与したいときは xerrors.Errorf("comment: %w", err)
  • エラーの出力は fmt.Printf("%+v\n", xerrors.Errorf(": %w", err)) こんな感じ

具体例

package main

import (
	"fmt"
	"os"
	"strconv"

	"golang.org/x/xerrors"
)

func main() {
	if err := f(os.Args[1]); err != nil {
		fmt.Printf("%+v\n", xerrors.Errorf(": %w", err))
	}
}

func f(s string) error {
	n, err := strconv.Atoi(s)
	if err != nil {
		return xerrors.Errorf(": %w", err)
	}
	if n < 10 {
		return xerrors.Errorf("n is %d", n)
	}
	return nil
}

説明

  • 標準の errors パッケージを使うとスタックトレースが取れず、どこでエラーが発生したのか全くわからないので、何らかの手段を用意する必要があり、その一番妥当な選択肢が golang.org/x/xerrors
  • API は github.com/pkg/errors の方が好みだけどすでにアーカイブされており、今後メンテナンスされない見込みなので使わない。
    • xerrors も全然コミットされていないけど、 This package is based on the Go 2 proposal と書いてあり、Go 言語のメインストリームの実装と言えそうだし特にバグも無さそうなので、良い意味で安定しているのかも。
  • xerrors.Errorf は実行した関数のスタックトレースをとっているわけではなく、あくまで Errorf を実行した位置をスタックトレースに追加していくだけっぽいのでちょっと使いづらい。出力時にもスタックトレースを付与した方がいい。
  • xerrors.Errorf("%w", err): を書かずにラップすると、受け取ったエラーのコメントを勝手にコメントとして付与してしまい、とてもみづらいことになる。
GitHubで編集を提案

Discussion

tenkohtenkoh

知見の共有ありがとうございます。

  • pkg/errorsはアーカイブされたようなので、そのあたりを考慮しながらご使用されるのが良いと思いました。
  • 1.13以降(うろ覚えです)に導入されたfmt.Errorfを使ったエラーのラップ、標準のerrors.Isやerrors.Asを使ったエラーハンドリングでは不足するシーンがあるのでしょうか?1.20からは複数エラーも扱えるようになって、標準パッケージも痒いところに手が届くようになったなぁという印象です。
malt03malt03

コメントいただきありがとうございます!
恥ずかしながら pkg/errors がアーカイブされている事実に気づいておりませんでした。
大幅に記事を書き直しました。
僕としては、 fmt.Errorf ではスタックトレースが付与されないようでしたのでやはり xerrors を使う方針にしました!
まだ勘違いがあるかもしれないので、またコメントいただけたら嬉しいです。

tenkohtenkoh

メンテナンス具合はxerrorsもトントンのようですが、スタックトレースがMustであればどちらかを選択して使うんですかねぇ…🤔

スタックトレースってどこまで必要なんだろう?と私自身考えるきっかけになりました、ありがとうございました…!