durationcheck

Table of Contents

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

durationcheck は time.Duration 同士の誤った乗算を検出するツールです。

解決する問題

time.Duration はナノ秒を表す int64 型のエイリアスです。time.Duration 同士を乗算すると、結果が天文学的な値になります。例えば 5 * time.Second は 5 秒ですが、(5 * time.Second) * time.Second は約 138 万時間になります。この誤りはコンパイルエラーにならないため、実行時まで気づきにくいバグです。

1
2
3
4
5
6
7
8
9
10
// Before: Duration 同士の乗算 → 意図しない巨大な値
func waitFor(d time.Duration) {
timeout := d * time.Second // d が既に Duration なら結果は天文学的な値
time.Sleep(timeout)
}

func main() {
waitFor(5) // 5 * time.Second = 5秒(意図通り)
waitFor(5 * time.Second) // 5000000000 * 1000000000 = 約139万時間(バグ!)
}
1
2
3
4
5
6
7
8
9
// After: 型を明確にして乗算を回避
func waitFor(seconds int) {
timeout := time.Duration(seconds) * time.Second
time.Sleep(timeout)
}

func main() {
waitFor(5) // 5秒(常に意図通り)
}

設定

公式ドキュメント

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

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

linters:
enable:
- durationcheck

オプション

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
// durationcheck が警告するパターン

// 1. 変数が既に Duration 型の場合
func example1(interval time.Duration) {
ticker := time.NewTicker(interval * time.Second) // Multiplication of durations: `interval * time.Second`
defer ticker.Stop()
}

// 2. 関数の戻り値が Duration 型の場合
func example2() {
d := getTimeout()
time.Sleep(d * time.Millisecond) // Multiplication of durations
}

func getTimeout() time.Duration {
return 500 * time.Millisecond
}

// 3. 構造体フィールドが Duration 型の場合
type Config struct {
Timeout time.Duration
}

func example3(cfg Config) {
ctx, cancel := context.WithTimeout(ctx, cfg.Timeout * time.Second) // Multiplication of durations
defer cancel()
}

修正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 引数の型を数値にする、または乗算を除去
func example1(intervalSec int) {
ticker := time.NewTicker(time.Duration(intervalSec) * time.Second)
defer ticker.Stop()
}

// 2. 既に Duration なら変換不要
func example2() {
d := getTimeout()
time.Sleep(d) // 既に 500ms の Duration
}

// 3. フィールドが Duration ならそのまま使う
func example3(cfg Config) {
ctx, cancel := context.WithTimeout(ctx, cfg.Timeout) // そのまま使う
defer cancel()
}

注意点

  • time.Duration は内部的にナノ秒を表す int64 型。time.Second1000000000(10 億ナノ秒)なので、Duration 同士の乗算は天文学的な値になる
  • 引数や設定値が time.Duration 型か数値型かを明確にすることで防げる。環境変数名に単位を含める(例: TIMEOUT_SEC)のも有効
  • 定数同士の乗算(2 * time.Second)は正しい使い方であり、検出されない。型なし定数 2int 型として扱われるため
  • テスト内のスリープ(time.Sleep(100 * time.Millisecond))は正しく、検出されない

参考リンク