nilerr

Table of Contents

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

nilerr はエラーが発生しているにもかかわらず nil を返しているパターン(エラーの握りつぶし)を検出するツールです。

解決する問題

err != nil のチェック後に nil を返すと、エラーが静かに握りつぶされます。呼び出し側はエラーが発生したことを知る手段がなく、データの不整合や予期しない動作の原因になります。

1
2
3
4
5
6
7
8
// Before: エラーを握りつぶしている
func SaveUser(ctx context.Context, user *User) error {
err := db.Insert(ctx, user)
if err != nil {
return nil // エラーが発生したのに nil を返す!
}
return nil
}
1
2
3
4
5
6
7
8
// After: エラーを適切に伝播
func SaveUser(ctx context.Context, user *User) error {
err := db.Insert(ctx, user)
if err != nil {
return fmt.Errorf("insert user %s: %w", user.ID, err)
}
return nil
}

設定

公式ドキュメント

設定オプションはありません。有効化するだけで動作します。

1
2
3
4
5
6
# .golangci.yml
version: "2"

linters:
enable:
- nilerr

オプション

golangci-lint 経由では設定オプションはありません。

サンプル

検出例

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
26
27
28
29
30
31
32
33
// nilerr が警告するパターン

// 1. エラーチェック後に nil を返す
func GetConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, nil // error returned from os.ReadFile is not nil but is returned as nil
}
return parseConfig(data)
}

// 2. err を別の変数に代入してから nil を返す
func ProcessItem(ctx context.Context, id string) error {
item, err := fetchItem(ctx, id)
if err != nil {
slog.Error("fetch item", "id", id, "error", err)
return nil // ログは出力されるがエラーは握りつぶされる
}
return process(item)
}

// 3. switch 文内でのエラー握りつぶし
func HandleError(err error) error {
if err != nil {
switch {
case errors.Is(err, ErrNotFound):
return nil // エラーを握りつぶしている
default:
return err
}
}
return nil
}

修正例

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
26
27
func GetConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config %s: %w", path, err)
}
return parseConfig(data)
}

func ProcessItem(ctx context.Context, id string) error {
item, err := fetchItem(ctx, id)
if err != nil {
return fmt.Errorf("fetch item %s: %w", id, err)
}
return process(item)
}

func HandleError(err error) error {
if err != nil {
switch {
case errors.Is(err, ErrNotFound):
return fmt.Errorf("not found: %w", err)
default:
return err
}
}
return nil
}

注意点

  • ログ出力してから nil を返すパターンも検出される。ログだけでは呼び出し側にエラーを伝えられないため、エラーを返すべき
  • 意図的にエラーを無視したい場合は //nolint:nilerr // <理由> で明示的に抑制する
  • nilnil とは異なるチェック。nilnil は「nil, nil を同時に返す」パターンを検出し、nilerr は「err != nil なのに nil を返す」パターンを検出する
  • エラーハンドリングの方針として、ログ出力はエラーの最終的な処理場所(通常はアプリケーションの最上位層)で行い、中間層ではエラーをラップして伝播させるのが推奨

参考リンク