goconst

Table of Contents

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

goconst は繰り返し使用されている文字列リテラルや数値を検出し、定数への置き換えを促すツールです。

解決する問題

同じ文字列リテラルがコード内の複数箇所に散在していると、値を変更する際にすべての箇所を手動で更新する必要があり、修正漏れが発生しやすくなります。定数に置き換えることで一元管理が可能になります。

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
// Before: 同じ文字列が複数箇所に散在
func CreateUser(db *sql.DB, name string) error {
_, err := db.Exec("INSERT INTO users (name) VALUES ($1)", name)
if err != nil {
return fmt.Errorf("insert into users: %w", err)
}
return nil
}

func DeleteUser(db *sql.DB, id string) error {
_, err := db.Exec("DELETE FROM users WHERE id = $1", id)
if err != nil {
return fmt.Errorf("delete from users: %w", err)
}
return nil
}

func CountUsers(db *sql.DB) (int, error) {
var count int
err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
return 0, fmt.Errorf("count users: %w", err)
}
return count, nil
}
1
2
3
4
5
6
7
8
9
10
11
// After: テーブル名を定数化
const tableUsers = "users"

func CreateUser(db *sql.DB, name string) error {
query := fmt.Sprintf("INSERT INTO %s (name) VALUES ($1)", tableUsers)
_, err := db.Exec(query, name)
if err != nil {
return fmt.Errorf("insert into %s: %w", tableUsers, err)
}
return nil
}

設定

公式ドキュメント

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

linters:
enable:
- goconst
settings:
goconst:
min-len: 3
min-occurrences: 3
match-constant: true
ignore-calls: true

オプション

オプション デフォルト 説明
min-len int 3 検出対象の文字列の最小長
min-occurrences int 3 報告するための最小出現回数
match-constant bool true 値が一致する既存の定数を検索する
numbers bool false 重複する数値リテラルも検出する
min int 3 検出する数値の最小値(numbers 有効時)
max int 3 検出する数値の最大値(numbers 有効時)
ignore-calls bool true 関数の引数として使われていない定数を無視する
ignore-string-values []string [] 除外する文字列の正規表現リスト
find-duplicates bool false 同じ値を持つ定数の重複を検出する
eval-const-expressions bool false Prefix + "suffix" のような定数式を評価する

サンプル

検出例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// goconst が警告: string "application/json" has 3 occurrences, make it a constant

func SetJSONHeader(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
}

func IsJSON(r *http.Request) bool {
return r.Header.Get("Content-Type") == "application/json"
}

func RespondJSON(w http.ResponseWriter, data any) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}

修正例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const contentTypeJSON = "application/json"

func SetJSONHeader(w http.ResponseWriter) {
w.Header().Set("Content-Type", contentTypeJSON)
}

func IsJSON(r *http.Request) bool {
return r.Header.Get("Content-Type") == contentTypeJSON
}

func RespondJSON(w http.ResponseWriter, data any) {
w.Header().Set("Content-Type", contentTypeJSON)
json.NewEncoder(w).Encode(data)
}

注意点

  • テストコードでは同じ文字列が繰り返されることが多い。issues.exclude-rules でテストファイルを除外すると誤検知を減らせる
  • min-occurrences: 2 にすると検出が増えすぎる場合がある。3(デフォルト)が一般的に適切
  • すべてのリテラルを定数化すべきとは限らない。文脈から意味が明確な短い文字列("""," など)は ignore-string-values で除外を検討する

参考リンク