errorlint

Table of Contents

  1. 解決する問題
  2. 設定
  3. オプション
    1. allowed-errors の使い方
  4. サンプル
    1. 検出例
    2. 修正例
  5. 注意点
  6. 参考リンク

errorlint は Go 1.13 で導入されたエラーラッピングの仕組みに沿わないコードを検出する静的解析ツールです。

解決する問題

Go 1.13 以降、errors.Is / errors.As / fmt.Errorf("%w", err) によるエラーチェーンが推奨されていますが、旧来の == 比較や型アサーションを使い続けるとラップされたエラーを見逃します。

1
2
3
4
5
6
7
8
9
10
11
12
// Bad: ラップされたエラーを検出できない
if err == io.ErrUnexpectedEOF {
// io.ErrUnexpectedEOF をラップしたエラーはここに到達しない
}

// Bad: ラップされた型を検出できない
if e, ok := err.(*os.PathError); ok {
// *os.PathError をラップしたエラーはここに到達しない
}

// Bad: エラーチェーンが切れる
return fmt.Errorf("read config: %v", err)
1
2
3
4
5
6
7
8
9
10
11
12
13
// Good: ラップされたエラーも検出できる
if errors.Is(err, io.ErrUnexpectedEOF) {
// エラーチェーンをたどって検出
}

// Good: ラップされた型も検出できる
var pathErr *os.PathError
if errors.As(err, &pathErr) {
// エラーチェーンをたどって検出
}

// Good: エラーチェーンを維持
return fmt.Errorf("read config: %w", err)

設定

公式ドキュメント

1
2
3
4
5
6
7
8
9
10
11
12
# .golangci.yml
version: "2"

linters:
enable:
- errorlint
settings:
errorlint:
errorf: true
errorf-multi: true
asserts: true
comparison: true

オプション

オプション デフォルト 説明
errorf bool true fmt.Errorf%w ではなく %v を使っている箇所を検出
errorf-multi bool true %w を複数使用することを許可(Go 1.20 以降で有効。errorf: true が前提)
asserts bool true err.(*Type) のような直接の型アサーションを検出
comparison bool true err == ErrFoo のような直接のエラー比較を検出
allowed-errors list [] 直接比較を許可するエラーと関数の組み合わせを指定
allowed-errors-wildcard list [] ワイルドカードで許可パターンを指定

allowed-errors の使い方

io.EOF のように意図的にラップしないことが保証されているエラーは、許可リストに追加して警告を抑制できます。

1
2
3
4
5
6
linters:
settings:
errorlint:
allowed-errors:
- err: "io.EOF"
fun: "example.com/pkg.Read"

サンプル

検出例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. fmt.Errorf で %v を使用(errorf チェック)
func readFile(path string) error {
_, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file: %v", err) // errorlint: non-wrapping format verb
}
return nil
}

// 2. 直接のエラー比較(comparison チェック)
func processItem(ctx context.Context, id string) error {
item, err := fetchItem(ctx, id)
if err == sql.ErrNoRows { // errorlint: comparing with == will fail on wrapped errors
return ErrItemNotFound
}
// ...
}

// 3. 直接の型アサーション(asserts チェック)
func handleError(err error) {
if netErr, ok := err.(*net.OpError); ok { // errorlint: type assertion will fail on wrapped errors
slog.Error("network error", "op", netErr.Op)
}
}

修正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. %w を使用してエラーチェーンを維持
func readFile(path string) error {
_, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file: %w", err)
}
return nil
}

// 2. errors.Is を使用
func processItem(ctx context.Context, id string) error {
item, err := fetchItem(ctx, id)
if errors.Is(err, sql.ErrNoRows) {
return ErrItemNotFound
}
// ...
}

// 3. errors.As を使用
func handleError(err error) {
var netErr *net.OpError
if errors.As(err, &netErr) {
slog.Error("network error", "op", netErr.Op)
}
}

注意点

  • io.EOF など標準ライブラリでラップしないことが保証されているエラーは自動的に除外される
  • --fix オプションで %v%w==errors.Is、型アサーション → errors.As を自動修正できるが、switch 文は手動修正が必要

参考リンク