reassign

Table of Contents

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

reassign はパッケージレベルの変数が再代入されていないかをチェックするツールです。

解決する問題

io.EOFsql.ErrNoRows のようなセンチネルエラーや、パッケージレベルの変数を再代入すると、その変数を参照する全箇所の動作が変わってしまいます。特にセンチネルエラーの再代入は errors.Is による比較が壊れる原因になります。

1
2
3
4
5
6
7
8
9
10
11
// Before: パッケージレベル変数を再代入
func init() {
sql.ErrNoRows = errors.New("custom error") // 全パッケージに影響!
}

func main() {
_, err := db.Query("SELECT 1")
if errors.Is(err, sql.ErrNoRows) {
// もう正しく動作しない
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// After: 独自のエラーを定義して使う
var ErrNotFound = errors.New("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, ErrNotFound // 独自のセンチネルエラーを返す
}
return nil, fmt.Errorf("scan user: %w", err)
}
return &user, nil
}

設定

公式ドキュメント

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

linters:
enable:
- reassign
settings:
reassign:
patterns:
- "EOF"
- "Err.*"

オプション

オプション デフォルト 説明
patterns []string ["EOF", "Err.*"] チェック対象の変数名パターン(正規表現)

patterns の詳細

デフォルトでは EOFErr で始まる変数名が対象です。

1
2
3
4
5
6
7
8
9
# カスタムパターンの例
linters:
settings:
reassign:
patterns:
- "EOF"
- "Err.*"
- "Default.*" # Default で始まる変数も保護
- "Std(out|err)" # os.Stdout、os.Stderr の再代入を検出

サンプル

検出例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// reassign が警告するパターン

import (
"io"
"net/http"
"os"
)

// 1. io.EOF の再代入
func example1() {
io.EOF = errors.New("custom eof") // reassignment of the `EOF` variable
}

// 2. http.ErrServerClosed の再代入
func example2() {
http.ErrServerClosed = nil // reassignment of the `ErrServerClosed` variable
}

// 3. テスト内でのセンチネルエラーの再代入
func TestSomething(t *testing.T) {
originalErr := os.ErrNotExist
os.ErrNotExist = errors.New("mock") // reassignment of the `ErrNotExist` variable
defer func() { os.ErrNotExist = originalErr }()
}

修正例

1
2
3
4
5
6
7
8
9
10
11
12
// 1. io.EOF を再代入する必要はない。errors.Is で比較する
func example1(err error) bool {
return errors.Is(err, io.EOF)
}

// 2. 独自のエラーを定義
var ErrCustomServerClosed = errors.New("custom server closed")

// 3. テストではモックを使うか、インターフェースで抽象化する
type FileChecker interface {
Exists(path string) (bool, error)
}

注意点

  • デフォルトの patterns["EOF", "Err.*"] で、ほとんどのセンチネルエラーをカバーする
  • テスト内での一時的な再代入(save/restore パターン)も検出される。テストではインターフェースによる抽象化やモックを使用すべき
  • patterns に追加することで、DefaultClientStdout などの重要なパッケージレベル変数も保護できる
  • パッケージ内部の変数(小文字始まり)は外部から再代入できないため、通常はエクスポートされた変数が問題になる

参考リンク