nilnil は nil エラーと無効な値(nil ポインタ等)を同時に返しているパターンを検出するツールです。
解決する問題
Go の慣例では「エラーが nil なら戻り値は有効」と期待されます。return nil, nil を返すと呼び出し側は nil チェックを忘れてパニックを起こすリスクがあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func FindUser(ctx context.Context, id string) (*User, error) { row := db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1", id)
var user User if err := row.Scan(&user.ID, &user.Name); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, nil } return nil, fmt.Errorf("scan user: %w", err) } return &user, nil }
user, err := FindUser(ctx, "123") if err != nil { return err } fmt.Println(user.Name)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var ErrUserNotFound = errors.New("user not found")
func FindUser(ctx context.Context, id string) (*User, error) { row := db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1", id)
var user User if err := row.Scan(&user.ID, &user.Name); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrUserNotFound } return nil, fmt.Errorf("scan user: %w", err) } return &user, nil }
|
設定
公式ドキュメント
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| version: "2"
linters: enable: - nilnil settings: nilnil: checked-types: - ptr - func - iface - map - chan
|
オプション
| オプション |
型 |
デフォルト |
説明 |
checked-types |
[]string |
[ptr, func, iface, map, chan] |
チェック対象の型 |
only-two |
bool |
true |
戻り値が 2 つの関数のみチェックする。false にすると 3 つ以上でもチェック |
detect-opposite |
bool |
false |
逆パターン(非 nil エラーと有効な値の同時返却)も検出する |
checked-types の種類
| 型 |
説明 |
nil 時のリスク |
ptr |
ポインタ |
nil pointer dereference |
func |
関数値 |
nil pointer dereference |
iface |
インターフェース |
nil pointer dereference |
map |
マップ |
assignment to entry in nil map |
chan |
チャネル |
deadlock |
uintptr |
uintptr |
invalid memory access |
unsafeptr |
unsafe.Pointer |
invalid memory access |
サンプル
検出例
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
|
func GetConfig(name string) (*Config, error) { if name == "" { return nil, nil } return loadConfig(name) }
func NewHandler(kind string) (http.Handler, error) { if kind == "unknown" { return nil, nil } return createHandler(kind) }
func LoadSettings(path string) (map[string]string, error) { if path == "" { return nil, nil } return parseSettings(path) }
|
修正例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var ErrConfigNotFound = errors.New("config not found")
func GetConfig(name string) (*Config, error) { if name == "" { return nil, ErrConfigNotFound } return loadConfig(name) }
var ErrUnknownHandler = errors.New("unknown handler kind")
func NewHandler(kind string) (http.Handler, error) { if kind == "unknown" { return nil, ErrUnknownHandler } return createHandler(kind) }
func LoadSettings(path string) (map[string]string, error) { if path == "" { return nil, errors.New("settings path required") } return parseSettings(path) }
|
注意点
detect-opposite: true にすると、エラーが非 nil なのに有効な値も返すパターンも検出する。io.ReadAll のように部分的な結果とエラーを同時に返す正当なケースもあるため、誤検知に注意
only-two: false にすると 3 つ以上の戻り値を持つ関数もチェック対象になる
- Go の慣例に従い、エンティティが見つからない場合はセンチネルエラー(
var ErrNotFound = errors.New(...))を返す設計にする
参考リンク