wrapcheck

Table of Contents

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

wrapcheck は外部パッケージから返されたエラーがラップされているかをチェックするツールです。

解決する問題

外部パッケージのエラーをそのまま返すと、エラーの発生箇所を特定できなくなります。fmt.Errorf("...: %w", err) でラップすることでコンテキストが付与され、デバッグ時にエラーの伝播経路を追跡できます。

1
2
3
4
5
6
7
8
// Before: 外部パッケージのエラーをそのまま返す
func GetUser(ctx context.Context, id string) (*User, error) {
user, err := db.FindByID(ctx, id) // db は外部パッケージ
if err != nil {
return nil, err // どこで発生したか分からない
}
return user, nil
}
1
2
3
4
5
6
7
8
// After: エラーをラップしてコンテキストを付与
func GetUser(ctx context.Context, id string) (*User, error) {
user, err := db.FindByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("find user %s: %w", id, err)
}
return user, nil
}

設定

公式ドキュメント

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# .golangci.yml 設定例
version: "2"

linters:
enable:
- wrapcheck
settings:
wrapcheck:
ignore-sigs:
- .Errorf(
- errors.New(
- errors.Unwrap(
- errors.Join(
- .Wrap(
- .Wrapf(
- .WithMessage(
- .WithMessagef(
- .WithStack(
ignore-package-globs:
- encoding/*
- github.com/pkg/errors

オプション

オプション デフォルト 説明
ignore-sigs []string [.Errorf(, errors.New(, errors.Unwrap(, errors.Join(, .Wrap(, .Wrapf(, .WithMessage(, .WithMessagef(, .WithStack(] この文字列を含む関数シグネチャから返されたエラーを無視する
ignore-sig-regexps []string [] 正規表現でマッチする関数シグネチャを無視する
ignore-package-globs []string [] エラーを返すパッケージの glob パターンで無視する
ignore-interface-regexps []string [] 指定したインターフェースのメソッドから返されたエラーを無視する
report-internal-errors bool false 同一パッケージ内のエラーもチェック対象にする

サンプル

検出例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "github.com/redis/go-redis/v9"

// wrapcheck が警告: error returned from external package is unwrapped
func GetCache(ctx context.Context, key string) (string, error) {
val, err := rdb.Get(ctx, key).Result() // 外部パッケージのエラー
if err != nil {
return "", err // ラップされていない
}
return val, nil
}

// wrapcheck が警告: error returned from interface method is unwrapped
func ReadConfig(r io.Reader) ([]byte, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err // ラップされていない
}
return data, nil
}

修正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func GetCache(ctx context.Context, key string) (string, error) {
val, err := rdb.Get(ctx, key).Result()
if err != nil {
return "", fmt.Errorf("get cache %s: %w", key, err)
}
return val, nil
}

func ReadConfig(r io.Reader) ([]byte, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
return data, nil
}

注意点

  • デフォルトでは同一パッケージ内のエラーはチェック対象外。report-internal-errors: true で有効化できる
  • fmt.Errorferrors.Newerrors.Join などのエラー生成関数はデフォルトで除外されている
  • encoding/json などのエラーをそのまま返したいケースでは ignore-package-globs で除外する
  • errorlint と併用すると、エラーのラップと比較の両方を包括的にチェックできる

参考リンク