Goで静的解析をしよう
この章では、Goの静的解析ツール golangci-lint をハンズオン形式で学習します。これにより、Goコードの品質を1コマンドで機械的にチェックする基本ができるようになります。
1. 本章の概要
1.1 本章の目的
コードのフォーマットや疑わしい記述を人手のレビューで拾うと、レビューコストが増え、指摘の粒度もばらつきます。Goには標準・コミュニティ製の静的解析ツールが揃っているので、これらを組み合わせて機械的にチェックすることで、レビュー時は設計や仕様の議論に集中できます。
本章では、これらを1コマンドに束ねるgolangci-lintを扱います。現場で個別のツールを直接呼び出すことは少なく、golangci-lint runで複数の解析をまとめて実行するのが定番構成です。golangci-lintが内部で束ねているのは、Go公式のgofmt・go vetや、コミュニティ製のstaticcheckなど、複数の静的解析ツールです。これらを1つの設定ファイルで有効/無効化しながら実行できるため、個別のツールをそれぞれインストールして順番に呼び出す必要がありません。
1.2 ハンズオンの流れ
整形が乱れた練習用コードを配布し、golangci-lintで複数の問題を一括検出します。指摘に沿ってコードを修正したうえで、設定ファイル.golangci.ymlで有効にする静的解析ツールを調整する流れを体験します。仕上げに、Visual Studio Codeで保存時に静的解析が実行される設定まで整えます。
1.3 事前準備
前提となる講座
この章では、以下の知識を前提としています。自信がない場合は先に関連講座を実施してみましょう。
| 講座名 | 必要な知識 |
|---|---|
| テストの考え方 | 品質保証における静的解析の位置付けやテスト全体像 |
必要なツール
この章では、以下のツールを使用します。まだインストールしていない場合は、リンク先の手順に沿って準備をお願いします。
| ツール名 | 関連箇所 | 理由 |
|---|---|---|
| Visual Studio Code | Visual Studio Codeのインストール | 静的解析を実行するGoコードのエディタとして使用する |
| Go | Goのインストール | 静的解析対象のコードと解析ツールの実行環境として使用する |
2. サンプルアプリケーションのダウンロード
本章で使うサンプルコードは、静的解析で指摘したい問題(不足引数・未使用変数・スペース漏れ・一行 if)を意図的に埋め込んだGoファイルです。以下のボタンからサンプルアプリケーションのZIPファイルをダウンロードしてください。
ダウンロードしたZIPファイルを任意の場所に解凍すると、go-lint-hands-on という名前のフォルダができます。このフォルダの中身は以下のようになっています。
go-lint-hands-on/
├── go.mod
└── main.go
Visual Studio Codeの「ファイル」→「フォルダーを開く」から、解凍した go-lint-hands-on フォルダを開きます。以降の操作は、Visual Studio Codeのターミナルから行います。
まず、Goがインストールされていることを確認します。
go version
以下のようにGoのバージョンが表示されれば、インストールは確認できています。
go version go1.x.x darwin/arm64
バージョンが表示されない場合は、Goのインストール を先に実施してください。
サンプルコードの中身(main.go)は以下のとおりです。整形が乱れており、静的解析で指摘したい問題を含んだ練習用コードです。
package main
import "fmt"
func main() {
fmt.Printf("hello %s\n")
unused := 42
x:=10
if x==10 { fmt.Println("ten") }
}
このコードには複数の問題が含まれています。次のセクションでgolangci-lintを導入し、一括で検出する流れを確認します。
3. golangci-lint による静的解析
Goでは、golangci-lint1つで複数の静的解析を1コマンドに束ねられます。ここではgolangci-lintの位置付け、インストール、実行、設定ファイル.golangci.ymlによる静的解析ツールのカスタマイズまでを扱います。
3.1 golangci-lint の位置付け
golangci-lint自体は独立した静的解析ツールではなく、複数の静的解析ツールを束ねて並列実行するランナーです。実際に解析を行っているのは、内部で呼び出される個別のツールです。代表的なものは以下です。
| 静的解析ツール | 提供元 | 役割 |
|---|---|---|
gofmt |
Go公式 | インデントや空白、importの並び順を統一する |
govet |
Go標準 | fmt.Printfのフォーマット指定と引数のミスマッチなど、コンパイルは通るが疑わしい記述を検出する |
staticcheck |
コミュニティ | 未使用変数・非効率なイディオム・潜在的なバグなど、300近い厳格なチェックを提供する |
errcheck |
コミュニティ | errorを無視している箇所を検出する |
ineffassign |
コミュニティ | 使われない代入を検出する |
unused |
コミュニティ | 未使用の関数・変数を検出する |
これらを個別にインストールして順番に呼び出すこともできますが、golangci-lintを使えば設定ファイル1つでオン/オフを切り替えつつ1コマンドで実行できます。
3.2 インストール
Macの場合は、Homebrewからインストールします。
brew install golangci-lint
以下のような実行結果が表示されます(バージョンや依存の解決状況は環境により変わります)。
==> Downloading https://ghcr.io/v2/homebrew/core/golangci-lint/manifests/1.61.0
==> Fetching golangci-lint
...
🍺 /opt/homebrew/Cellar/golangci-lint/1.61.0: 8 files, 60.5MB
Windowsの場合は、公式ドキュメントの Install に記載されている手順に沿ってインストールします。バイナリのダウンロードやパッケージマネージャからのインストールなど、複数の方法が案内されています。
インストールできたことをバージョン表示で確認します。
golangci-lint version
以下のような実行結果が表示されます(バージョンは環境により変わります)。
golangci-lint has version 1.61.0 built ...
3.3 実行と警告の確認
golangci-lint runコマンドで、現在のプロジェクトに対して解析を実行します。
golangci-lint run
以下のような実行結果が表示されます。
main.go:6:2: fmt.Printf format %s reads arg #1, but call has 0 args (govet)
main.go:7:5: unused: unused declared and not used (U1000) (staticcheck)
デフォルトの設定でも複数の静的解析ツールが同時に実行され、以下のような問題が一括で指摘されました。
fmt.Printf format %s reads arg #1, but call has 0 args(govet):Printf("hello %s\n")が%sを要求しているのに引数を渡していないunused: unused declared and not used(staticcheck):unused変数が宣言されているが使われていない
指摘に沿ってmain.goを修正します。Printfに引数を渡し、unused := 42の行を削除します。
package main
import "fmt"
func main() {
fmt.Printf("hello %s\n", "world")
x:=10
if x==10 { fmt.Println("ten") }
}
コードを解説します。
fmt.Printf("hello %s\n", "world")
%sに対応する引数として"world"を渡すようにしました。これでgovetの指摘は解消されます。
x:=10
if x==10 { fmt.Println("ten") }
unused := 42の行を削除しました。x:=10のスペース漏れや一行ifの書き方は、フォーマットとしては望ましくない状態ですが、次のセクションで扱うため、この時点ではそのまま残しておきます。
修正後に再度golangci-lint runを実行し、指摘が消えることを確認します。
golangci-lint run
以下のような実行結果が表示されます。
0 issues.
0 issues. が表示され、デフォルトの静的解析ツールでの指摘は解消されています。ただし、x:=10や一行ifのようなフォーマットの乱れは残ったままです。これらを検出するには、次のセクションでgofmtを明示的に有効化する必要があります。
3.4 設定ファイル(.golangci.yml)でのカスタマイズ
プロジェクトごとに使いたい静的解析ツールを.golangci.ymlで指定できます。プロジェクトルートに.golangci.ymlを作成し、以下の内容を書きます。これは、代表的な6つの静的解析ツールを明示的に有効化する設定例です。
linters:
enable:
- gofmt
- govet
- staticcheck
- errcheck
- ineffassign
- unused
コードを解説します。
linters:
enable:
- gofmt
- govet
- staticcheck
- errcheck
- ineffassign
- unused
linters.enableに、有効化したい静的解析ツール名をリスト形式で列挙します。それぞれの役割は「golangci-lint の位置付け」で紹介したとおりです。gofmtを明示的に有効化すると、整形の乱れもgolangci-lint runの指摘として列挙されるようになります。設定ファイルを置くことで、チーム内で誰が実行しても同じ静的解析ツールセットで解析が実行されるようになります。
利用可能な静的解析ツールの一覧や、その他の設定(除外パターン・重大度・特定ルールの無効化など)は公式ドキュメント Configuration に記載があります。
3.5 動作確認
.golangci.ymlを配置した状態で、再度golangci-lint runを実行します。
golangci-lint run
以下のような実行結果が表示されます。
main.go:6:5: File is not `gofmt`-ed (gofmt)
先ほどは0 issues.だった状態から、今度はgofmtによる指摘が追加されました。.golangci.ymlでgofmtを明示的に有効化したことで、デフォルトでは検出されなかったフォーマットの乱れ(x:=10のスペース漏れや一行if)が指摘されるようになったことが確認できます。
指摘に沿ってフォーマットを整えます。gofmtは-wオプション付きで実行することで、ファイルを自動的に整形して上書き保存できます。
gofmt -w main.go
実行結果は何も表示されません(成功時は無出力)。整形後のmain.goは以下のようになります。
package main
import "fmt"
func main() {
fmt.Printf("hello %s\n", "world")
x := 10
if x == 10 {
fmt.Println("ten")
}
}
x:=10がx := 10に、一行ifが複数行に整形されました。整形後に再度golangci-lint runを実行し、指摘が消えることを確認します。
golangci-lint run
以下のような実行結果が表示されます。
0 issues.
.golangci.ymlで追加したgofmtの指摘も含めてすべて解消されました。設定ファイルの内容を変えるだけで、静的解析でチェックする範囲を柔軟に広げられることが確認できます。
4. Visual Studio Code との連携
Visual Studio Code の Go 拡張機能と golangci-lint を連携させることで、コードの保存時に自動でフォーマット・静的解析が実行されるようになります。書きながらリアルタイムに指摘を受けられるため、指摘を後回しにせず、コミット前には必ずクリーンな状態を保てるようになります。
4.1 Go 拡張機能の導入確認
本セクションでは、Goのインストール 章で紹介した Go 拡張機能が Visual Studio Code に導入済みであることを前提とします。導入されていない場合は、先に該当章を実施してください。
導入済みかどうかは、Visual Studio Code の左サイドバーから「拡張機能」アイコン(四角が4つ並んだアイコン)をクリックし、検索ボックスに Go と入力して確認できます。Google が公開している Go(golang.go)に「Uninstall」ボタンが表示されていれば、導入済みです。
この拡張機能により、保存時のフォーマット(gofmt)は既定で有効になります。以降では、これに加えて golangci-lint を保存時に実行するための設定を行います。
4.2 settings.json への設定追加
golangci-lint を保存時に実行するには、ユーザ設定 settings.json に設定を追加します。
Visual Studio Code の「表示」→「コマンドパレット」を選択(Macでは Cmd + Shift + P、Windowsでは Ctrl + Shift + P)し、Preferences: Open User Settings (JSON) と入力して選択します。ユーザ設定の settings.json が開きます。
開いた settings.json の一番外側の { } の中に、以下の3つのキーを追加して保存します。
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"go.lintOnSave": "workspace"
}
コードを解説します。
"go.lintTool": "golangci-lint",
Go 拡張機能が保存時に呼び出す静的解析ツールをgolangci-lintに指定します。
"go.lintFlags": ["--fast"],
golangci-lint に追加で渡すオプションを指定します。--fast は、実行が高速な一部の静的解析ツールだけを実行するモードで、保存のたびに実行する用途に向いています。
"go.lintOnSave": "workspace"
保存時に解析を実行する範囲を指定します。workspace はワークスペース全体、package は保存したファイルが属するパッケージのみが対象です。
4.3 動作確認
先ほど整形済みのmain.goを開き、意図的にPrintfの引数を1つ削除して保存します。
package main
import "fmt"
func main() {
fmt.Printf("hello %s\n")
x := 10
if x == 10 {
fmt.Println("ten")
}
}
保存すると、Visual Studio Code の下部に「問題」タブ(英語表記の場合は PROBLEMS)が表示され、以下のような指摘が並びます。
main.go:6:2 fmt.Printf format %s reads arg #1, but call has 0 args (govet)
Printfの引数不足がgovetによって検出され、コードを書きながらリアルタイムに指摘を受け取れることが確認できました。指摘を確認できたら、Printfの引数を"world"に戻して保存し直します。「問題」タブから指摘が消えれば、Visual Studio Code との連携は完了です。
5. まとめ
この章では、Goの静的解析ツール golangci-lint を体験しました。
golangci-lintは、gofmt・govet・staticcheckなどの複数の静的解析ツールを束ねて1コマンドで実行できるランナーである- デフォルト設定でも複数の静的解析ツールが同時に実行され、フォーマット・疑わしい記述・未使用変数などを一括で検出できる
.golangci.ymlで有効化する静的解析ツールを列挙することで、チーム内で同じルールセットを共有できる- Visual Studio Codeは、保存時に自動で
gofmtを実行し、golangci-lintを連携させることで指摘をリアルタイムに受けられる
次の章では、Goの自動テストを学びつつ、実際にコードでテスト実装を体験します。