ツールチェーンと静的解析
.NET (C#) の経験がある開発者向けに、gofmt、go vet、golangci-lint、go generate などの開発ツールを解説します。C# では Roslyn Analyzer、.editorconfig、dotnet format、Source Generator が担う役割を、Go ではツールチェーン標準のコマンドと少数の外部ツールで分担します。設定項目が少なく「議論の余地がない」ことを重視する文化なので、最初に押さえるべきコマンドと CI での固め方を整理します。
1. このドキュメントで扱うこと
- gofmt による議論の余地のないフォーマット
- go vet と golangci-lint による静的解析、および v2 の設定
- go generate の使いどころ
- CIへの組み込み
gofmt による議論の余地のないフォーマット
Go のフォーマットは gofmt が唯一の正解を決めます。インデントはタブ、括弧や空白の位置もツールが決めるため、スタイルに関するレビュー指摘やチーム内の設定議論がほぼ発生しません。
gofmt -l .は整形が必要なファイルを一覧表示します。gofmt -d .は差分を表示し、gofmt -w .はファイルを書き換えます。gofmt -sは[]T{T{...}}から[]T{{...}}への簡略化など、安全な単純化も行います。goimports(golang.org/x/tools/cmd/goimports) はgofmtの機能に加えて、import の追加・削除とグループ分けを行います。多くのエディターは保存時にgopls経由で同等の処理を実行できます。gofumptはgofmtより厳格なフォーマッターです。gofmtと互換な範囲でさらに規則を足します。導入は任意で、golangci-lintのフォーマッターとして有効化できます。
go vet と golangci-lint による静的解析
go vet は Go ツールチェーン標準の解析で、Printf の書式文字列の不一致、context の cancel 呼び忘れ (lostcancel)、ロックを含む値のコピー (copylocks) など、バグである可能性が高いものを検出します。go test は実行時に go vet の一部のチェックを自動で走らせます。
golangci-lint は多数のリンターをまとめて実行する、事実上の標準ランナーです。並列実行とキャッシュで高速に動作し、.golangci.yml で設定を共有します。staticcheck もこの中に統合されています。バージョン 2 (v2) では設定ファイルの書式が v1 から変わっており、version: "2" を必ず書きます。
| 区分 | v2 のキー | 内容 |
|---|---|---|
| リンター | linters | default (standard など) と enable / disable、個別設定は linters.settings |
| 除外 | linters.exclusions | プリセット (comments など)、パス・テキストによる除外ルール |
| フォーマッター | formatters | gofmt、goimports、gofumpt などを enable で有効化 |
default: standard で有効になるのは errcheck、govet、ineffassign、staticcheck、unused です。
go generate の使いどころ
go generate は、ソースコード中の //go:generate コマンド という特別なコメントを見つけて実行するだけのコマンドです。go build や go test からは自動実行されません。生成物はリポジトリにコミットし、生成を忘れていないかを CI で確認します。
stringerによる列挙型のString()メソッド生成mockgenやmoqによるモック生成sqlcやoapi-codegenによる SQL、OpenAPI からのコード生成- Protocol Buffers のコード生成
生成されたファイルには // Code generated ... DO NOT EDIT. という形式の 1 行目付近のコメントを入れる規約があり、リンターやエディターはこれを見て生成コードを特別扱いします。
CIへの組み込み
ローカルと CI で同じコマンドを使い、次の順に確認するのが基本です。
- フォーマット (
gofmt -l、またはgolangci-lint fmt --diff) go vet ./...とgolangci-lint run- 生成コードと
go.mod/go.sumが最新であること (go generate ./...やgo mod tidyの後に差分がない) - テスト (
go test -race ./...。詳細は テスト を参照) - 脆弱性チェック (
govulncheck ./...)
ビルドとデプロイ側の CI は Goのビルドとデプロイ で扱います。
2. .NET との対応表
| .NET (C#) | Go |
|---|---|
| dotnet format / EditorConfig | gofmt / goimports |
| Roslyn Analyzers | go vet / staticcheck / golangci-lint |
| Source Generators | go generate + コード生成ツール |
上の表を補う対応関係です。
| .NET (C#) | Go |
|---|---|
dotnet format --verify-no-changes | gofmt -l . の出力が空であることを確認 / golangci-lint fmt --diff |
.editorconfig の dotnet_diagnostic.*.severity | .golangci.yml の linters / exclusions |
TreatWarningsAsErrors | golangci-lint run が問題を検出すると終了コード 1 を返す |
#pragma warning disable CA1822 | //nolint:linter名 // 理由 |
| NuGet の Analyzer パッケージ | golangci-lint に同梱されたリンター (追加パッケージ不要) |
dotnet-tools.json (ローカルツール) | go.mod の tool ディレクティブ (Go 1.24 以降) と go tool |
| Source Generator (ビルド中に自動実行) | go generate (明示的に実行し、生成物をコミット) |
| T4 テンプレート | text/template と //go:generate を組み合わせた独自ジェネレーター |
NuGet audit / dotnet list package --vulnerable | govulncheck ./... |
3. C# 開発者がつまずきやすいポイント
フォーマットの設定項目がない
.editorconfig でインデント幅や波括弧の位置を調整する感覚は不要です。gofmt には実質的に設定がなく、全員が同じ出力になります。エディターの保存時フォーマットを有効にするだけで済みます。
gofmt -l は差分があっても終了コード 0 を返す
CI で「未フォーマットなら失敗」にしたいときは、gofmt -l . の出力が空かどうかを自分で判定する必要があります。dotnet format --verify-no-changes のような専用フラグはありません。golangci-lint fmt --diff や、golangci-lint run にフォーマッターを含めた構成にすると扱いやすくなります。
ビルド時にアナライザーは動かない
C# では Roslyn Analyzer がコンパイル中に走り、警告やエラーとしてビルドに混ざります。Go のコンパイラは、未使用の変数や未使用の import をエラーにしますが、それ以外の解析はしません。go vet や golangci-lint は別コマンドで、自分で実行する必要があります。
golangci-lint v1 と v2 の設定は互換ではない
ネット上の記事には v1 形式 (linters-settings、issues.exclude-rules など) が多く残っています。v2 では version: "2" が必須で、linters.settings、linters.exclusions、formatters に再編されました。golangci-lint migrate で旧設定を変換でき、golangci-lint config verify で設定ファイルを検証できます。
リンターは「全部有効」にしない
大量のリンターを有効にすると、好みの分かれる指摘が増えて開発体験が落ちます。default: standard から始め、バグ検出に効果の高いものを選んで足します。既存コードに導入する場合は、新しく書いた部分だけを対象にする new-from-rev 設定 (issues.new-from-rev) を使うと段階的に導入できます。
//nolint は理由を添える
//nolint:errcheck のように対象のリンターを指定し、理由をコメントで添えます。リンター名を省略した //nolint はすべてのリンターを黙らせるため避けます。nolintlint を有効にすると、使われていない //nolint や理由のない //nolint を検出できます。
go generate は自動では動かない
Source Generator のように、ビルドすれば生成されるわけではありません。生成元を変更したら go generate ./... を実行し、生成物を一緒にコミットします。忘れると、古い生成物のままビルドが通ってしまいます。CI で生成後の差分を検出してください。
ツールのバージョンをチームで揃える
go install foo@latest を各自が実行すると、ツールのバージョンが人によって異なります。Go 1.24 以降は、go get -tool で go.mod に tool ディレクティブを記録し、go tool 名前 で go.mod に固定したバージョンを実行できます。golangci-lint 自体は、公式ドキュメントが推奨するバイナリのインストールや公式 GitHub Action を使うのが無難です。
4. コード例
例 1: 日常的に使うコマンド
# フォーマット (差分表示、書き換え、簡略化つき)
gofmt -l .
gofmt -d .
gofmt -s -w .
# import の整理 (インストールは一度だけ)
go install golang.org/x/tools/cmd/goimports@latest
goimports -w .
# 標準の静的解析
go vet ./...
# golangci-lint v2
golangci-lint run
golangci-lint fmt # フォーマッターの実行 (書き換え)
golangci-lint fmt --diff # 差分の表示のみ
golangci-lint config verify
# 依存関係の脆弱性チェック
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
C# では dotnet format や IDE のコードクリーンアップに相当する操作です。
例 2: golangci-lint v2 の設定
.golangci.yml の例です。standard を土台に、バグ検出に有効なものを足します。
version: "2"
run:
timeout: 5m
linters:
default: standard # errcheck, govet, ineffassign, staticcheck, unused
enable:
- bodyclose # http.Response.Body の閉じ忘れ
- errorlint # errors.Is / errors.As を使うべき箇所
- gocritic
- misspell
- nolintlint # 不適切な //nolint の検出
- revive
- unparam
settings:
errcheck:
check-type-assertions: true
govet:
enable:
- shadow # 変数のシャドーイング
nolintlint:
require-specific: true # //nolint:linter名 の形式を必須にする
require-explanation: true
exclusions:
presets:
- comments
- std-error-handling
rules:
- path: _test\.go
linters:
- errcheck
- unparam
formatters:
enable:
- gofumpt
- goimports
settings:
goimports:
local-prefixes:
- example.com/app
default には standard、all、none、fast を指定できます。個別の設定項目は、使用する golangci-lint のバージョンの公式ドキュメントで確認してください。
例 3: //nolint の書き方
// 良い例: リンター名を限定し、理由を添える
defer f.Close() //nolint:errcheck // 読み取り専用のため Close のエラーは無視してよい
// 悪い例: すべてのリンターを無効にしてしまう
defer f.Close() //nolint
C# の #pragma warning disable CA2000 や [SuppressMessage] に対応します。
例 4: go generate と tool ディレクティブ
列挙型の String() を stringer で生成する例です。
package color
//go:generate go tool stringer -type=Color
type Color int
const (
Red Color = iota
Green
Blue
)
stringer を go.mod に固定します (Go 1.24 以降)。
go get -tool golang.org/x/tools/cmd/stringer
go.mod には次のような行が追加されます。
tool golang.org/x/tools/cmd/stringer
go generate ./... # color_string.go が生成される
go tool stringer -help
go install tool # go.mod の tool をすべて GOBIN にインストール
go generate の指示は、そのファイルがあるディレクトリで実行されます。複数のディレクティブは上から順に実行されます。Go 1.23 以前では、ツールを tools.go に空 import (_ "golang.org/x/tools/cmd/stringer"、//go:build tools つき) して固定する方法が使われていました。
例 5: GitHub Actions での lint と検証
name: lint
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Check formatting
run: test -z "$(gofmt -l .)"
- name: Vet
run: go vet ./...
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.5
- name: Verify generated code is up to date
run: |
go generate ./...
git diff --exit-code
- name: Verify go.mod is tidy
run: go mod tidy -diff
- name: Test
run: go test -race -cover ./...
- name: Vulnerability check
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
golangci-lint-action の version は、使用する v2 系のバージョンに合わせて固定します。go mod tidy -diff は Go 1.23 以降で使え、変更が必要なら差分を表示して失敗します。
例 6: C# での同等設定
C# 側では、Directory.Build.props と .editorconfig で次のように品質ゲートを構成するのが一般的です。
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>
</Project>
# .editorconfig
[*.cs]
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA1062.severity = warning
Go では、この役割を .golangci.yml の 1 ファイルと CI のコマンド列が担います。ビルド自体には解析が入らないため、CI で明示的に実行する点が違いです。
5. まとめ
- フォーマットは
gofmt(またはgoimports、gofumpt) に任せ、設定を議論しません。CI ではgofmt -lの出力が空かどうかを判定します。 go vetは標準の静的解析で、golangci-lintがstaticcheckを含む多数のリンターをまとめて実行します。v2 ではversion: "2"、linters、formattersの構成で設定します。- リンターは
default: standardから始め、価値の高いものだけを足します。//nolintはリンター名と理由を必ず書きます。 go generateは明示的に実行するコマンドで、生成物はコミットします。CI で再生成して差分がないことを確認します。- Go 1.24 以降は
go.modのtoolディレクティブとgo toolで、開発ツールのバージョンを固定できます。 - CI では、フォーマット、
go vet、golangci-lint、生成コードとgo.modの整合、go test -race、govulncheckを順に確認します。