[Go] 構造体フィールドの初期化漏れをCIで防ぐ!~exhaustruct~
はじめに
Go言語では、構造体を使ってデータを定義することがよくあります。
しかし、構造体フィールドの初期化漏れは、意図しないバグを引き起こすことがあります。
本記事では、構造体フィールドの初期化漏れを防ぐツール「exhaustruct」を紹介し、その使い方を解説します。
構造体フィールドの初期化漏れの問題
Go言語で構造体を使用する際、全てのフィールドを適切に初期化しないと、予期しない動作を引き起こす可能性があります。
例えば、以下のような構造体があるとします。
type Item struct {
itemType int
name string
}
この構造体を初期化する際、もしフィールドの一部を初期化し忘れると、未初期化のフィールドはゼロ値を持つことになります。これは意図せずバグを生む原因となります。
func main() {
item1 := Item{itemType: 1, name: "a"} // {itemType:1 name:a}
item2 := Item{name: "b"} // {itemType:0 name:b} ゼロ値を持つ
}
この対応としてよく行われるのは、コンストラクタ関数を用意して初期化を一点に集めることです。例えば、以下のようにします。
func NewItem(itemType int, name string) Item {
return Item{
itemType: itemType,
name: name,
}
}
func main() {
item1 := NewItem(1, "a") // {itemType:1 name:a}
item2 := NewItem(2, "b") // {itemType:2 name:b} 引数で入力を強制
}
しかし、プロジェクトが大規模になると、新しいフィールドが追加されたときにコンストラクタ関数の更新を見落とすことがあります。このため、初期化漏れの問題が依然として発生するリスクがあります。
exhaustructの概要
exhaustructは、構造体の全フィールドが確実に初期化されているかをチェックするツールです。これにより、初期化漏れを防ぐことができます。
さらに、exhaustruct
はgolangci-lintにも組み込まれているため、既にgolangci-lint
を使用しているプロジェクトではすぐに利用可能です。
golangci-lintでの設定方法
既にgolangci-lint
を利用している場合、以下の設定を追加することでexhaustruct
を有効にできます。
linters:
enable:
- exhaustruct
これにより、プロジェクト内の全ての構造体が初期化されているかをチェックし、未初期化のフィールドがある場合は警告が表示されます。
実際に使ってみる
先ほどの例でexhaustruct
の使い方を説明します。
package main
func main() {
item1 := Item{itemType: 1, name: "a"}
item2 := Item{name: "b"}
item3 := Item{}
}
こちらのコードに対してgolangci-lint
を実行すると、出力結果として、未初期化のフィールドがある場合は警告が表示されます。
$ golangci-lint run
main.go:12:11: main.Item is missing field itemType (exhaustruct)
item2 := Item{name: "b"}
^
main.go:13:11: main.Item is missing fields itemType, name (exhaustruct)
item3 := Item{}
^
カスタマイズ
exhaustruct
は特定の構造体やフィールドを無視する設定ができます。
特定構造体のチェックを除外
exhaustruct
は設定ファイルを使って、特定の構造体のチェックを無視できます。
linters-settings:
exhaustruct:
# 構造体のパッケージおよび名前に一致する正規表現のリスト。
# 正規表現はパッケージ/名前/構造体名に一致する必要があります。
# このリストが空の場合、すべての構造体がテストされます。
# デフォルト: []
include:
- '.+\.Test'
- 'example\.com/package\.ExampleStruct[\d]{1,2}'
# チェックから除外する構造体パッケージおよび名前に一致する正規表現のリスト。
# 正規表現はパッケージ/名前/構造体名に一致する必要があります。
# デフォルト: []
exclude:
- '.+/cobra\.Command$'
私が所属するプロジェクトでは、GraphQLの自動生成モデルにおいて初期化漏れが発生しやすいという課題がありました。
また、意図的にゼロ値を使用しているコードも存在するため、まずはGraphQLの自動生成モデルに限定してexhaustruct
を適用することにしました。
linters-settings:
exhaustruct:
include:
- "path/to/graphql/models/..."
特定フィールドのチェックを除外
特定のフィールドを無視する場合、構造体のタグに exhaustruct:"optional"
を設定することができます。例えば、以下のようにします。
type Item struct {
itemType int `exhaustruct:"optional"` // このフィールドはチェック対象外
name string // このフィールドはチェック対象
}
このタグを付けることで、exhaustruct
はそのフィールドの初期化漏れを無視します。
まとめ
構造体フィールドの初期化漏れは、Go言語のプロジェクトにおいて避けたい問題の一つです。
exhaustruct
を使うことで、これらの問題を効果的に防ぐことができます。
さらに、golangci-lint
と統合することで、既存のLintingプロセスに簡単に追加できるため、導入が非常にスムーズです。
みなさんもぜひ、exhaustruct
を導入して、バグ予防に活用してみてください。
「物流の次を発明する」をミッションに物流のシェアリングプラットフォームを運営する、ハコベル株式会社 開発チームのテックブログです! 【エンジニア積極採用中】t.hacobell.com/blog/career
Discussion