Communityコーディング&開発github.com

teneQ/Go開発スタック

golangci-lint v2、gofumpt、gotestsum、golang-migrate、just を使用した、意見に基づくGo開発環境。Goプロジェクトの開始、lint、フォーマット、テスト、カバレッジやCIの設定、Justfileの作成、マイグレーションの配線、Makefileからの移行時に使用します。

Go開発スタック とは?

このスキルは、Goプロジェクト向けの最先端かつ統一された開発環境を提供します。トップレベルのツール(golangci-lint v2、gofumpt、gotestsum、just、golang-migrate、lefthook)を厳選し、重複なく各関心事に特化させています。新しいプロジェクトの初期化、コード品質チェック、テスト、データベースマイグレーション、CI/CDパイプラインのセットアップを効率化します。golangci-lintはサポートされているGoバージョンとの互換性が重要であり、このスキルでは特定バージョンの依存関係を推奨し、`go get -tool`を用いたGoツールの追跡や、`go install`を用いたツールの直接インストールといったベストプラクティスに従っています。Justfileとlefthook設定により、`go fmt`や`make`の手動作業を置き換え、再現可能なローカル開発とCI環境を実現します。このスキルはClaude Code、Cursor、CodexなどのAIエージェントと連携し、設定ファイルの生成やJustfileの作成を支援することで、開発者が高品質なGoコードを迅速に記述できるようにします。

対応✓Claude Code✓Codex CLI✓Cursor
npx skills add https://github.com/tenequm/skills/tree/HEAD/skills/go-dev

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Go 開発スタック

意見に基づいた、モダンなGo開発セットアップ。関心事ごとに一つのツール、重複はありません。

使用するタイミング

  • ゼロから新しいGoプロジェクトを始める場合
  • lint、フォーマット、テストインフラを追加する場合
  • GoサービスやライブラリのCI/CDを設定する場合
  • Makefileを置き換えるJustfileを作成する場合
  • データベースマイグレーションツールを追加する場合
  • バラバラの gofmt/govet/staticcheck 呼び出しから統一されたセットアップに移行する場合

上流から定期的にマージするフォークには使用しないでください。Makefileを置き換え、gofumptでツリーを再フォーマットすると、マージのたびに競合が発生し、実際の変更が再フォーマットの差分に埋もれてしまいます。上流のgofmtとビルドツールはそのままにし、チェックのみを追加してください。

スタック

ツールバージョン役割置き換えるもの
Go1.27+言語、ツールチェーン、go mod、go fix-
golangci-lintv2.14+メタリンター (100以上のリンター+フォーマッター+fmt コマンド)個別に実行する gofmt, govet, staticcheck, errcheck
gofumptv0.12+厳格なフォーマッター (gofmtのスーパーセット、19のデフォルトルール)gofmt
gotestsumv1.13+読みやすい出力、ウォッチモード、JUnit XMLを備えたテストランナー生の go test
just1.58+タスクランナーMakefile
golang-migratev4.20+DBマイグレーション (CLI + ライブラリ + embed.FS)手動SQLスクリプト
lefthookv2.1+Gitフック (単一バイナリ、並列実行)pre-commit (Python)

バージョンの下限は重要です。 golangci-lint は「コンパイルに使用されたGoバージョン以下のGoバージョンをサポート」するため、Goツールチェーンより古いバージョンを指定すると完全に失敗します。Go 1.27のサポートはgolangci-lint v2.13.0で追加されたため、Go 1.27プロジェクトでは v2.13 が下限となり、このスキルではgofumpt v0.12.0をバンドルする最初のリリースである v2.14.0 を固定しています。さらに最近、2つの下限が移動しました。gofumpt v0.12.0 は「Go 1.27のgofmtに基づいており、Go 1.26以降が必要」であり、lefthookの go install パスはGo 1.26+を要求するようになりました。

クイックスタート: 新規プロジェクト

# 1. モジュールを作成
mkdir myapp && cd myapp
go mod init github.com/yourorg/myapp

# 2. ディレクトリをスキャフォールド
mkdir -p cmd/myapp internal migrations

# 3. golangci-lintをバイナリとしてインストール(モジュールツールとしてではありません。以下の注意を参照)
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.14.0

# 4. 残りは go.mod で追跡(Go 1.24+ ツールディレクティブ)。バージョンを固定 - @latest は避けてください。
#    @latest はCI実行のたびにツールを再コンパイルし、マシン間でバージョンがずれます。
go get -tool mvdan.cc/[email protected]
go get -tool gotest.tools/[email protected]

# golang-migrate はビルドタグが必要なため、直接インストール
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/[email protected]

# 5. 設定ファイルを作成(テンプレートは以下)
# 6. 実行: just check

golangci-lint をツールパターンでインストールしないでください。 上流は明示しています: 「go install/go get、"tools pattern"、tool コマンド/ディレクティブによるインストールは保証されません。バイナリインストールを推奨します。」共有リポジトリでこれが問題になる理由は依存関係の漏洩にあります - 「ツールの依存関係が別のツールやプロジェクトの依存関係を変更する可能性があります。」どうしても go.mod に入れたい場合は、独自の -modfile の背後に隔離してください - golangci-lint リファレンス を参照。

go get -tool は追跡し、go tool は実行します。 ツールディレクティブは go.mod に依存関係を記録しますが、PATHには何も追加しません。ツールチェーン経由で呼び出すか (go tool gofumpt -l .、go tool gotestsum --format testname)、一度 go install tool を実行して $(go env GOPATH)/bin に配置します。以下のJustfileは生のバイナリを呼び出すため、go install tool ルート(またはHomebrewによるシステムインストール - ただしgolangci-lintの場合、「Homebrewは予期しないバージョンのGoを使用してバイナリをビルドする可能性があり」、バージョンを固定できません)を想定しています。go tool はカレントディレクトリのモジュールに対して解決されることに注意してください。「現在のモジュールのgo.modで追加のツールが定義されている場合もあります」 - そのため、モノレポでは、レシピに [working-directory(...)] を設定しないと go: no such tool "..." で失敗します。

最初のコミットの前に知っておくべき2つのGoコマンドの動作:

  • go mod init を1.Nツールチェーンで実行すると、go 1.(N-1).0 と書き込まれ、1.N にはなりません - 「バージョン 1.N.X のツールチェーンを使用して go mod init を実行すると、Goバージョン go 1.(N-1).0 を指定する go.mod ファイルが作成されます。」1.Nの言語機能を使用したい場合は、ディレクティブを意図的に上げてください。
  • go.mod に toolchain go1.27.1 行を設定しますが、これは下限であり、固定ではないことを認識してください。デフォルトの GOTOOLCHAIN=auto の下では、go コマンドは「<tname> がデフォルトのGoツールチェーンよりも新しい場合」にのみ切り替わるため、新しいローカルの go が優先されます。正確なツールチェーンを設定するには GOTOOLCHAIN=go1.27.1 を設定します(CIまたは go env -w); GODEBUG=toolchaintrace=1 go version でどれが選択されたかを確認できます。.0 ではなく、現在のパッチバージョンで行を維持してください。これは、自分自身の go が古いマシン上で govulncheck がstdlibアドバイザリをスキャンする対象となる go を選択するためです。古いパッチはそれ自体でCIを赤くします - 以下のFootgunsを参照。

.golangci.yml

version: "2"

run:
  timeout: 5m
  build-tags:
    - integration   # そうしないと、Justfileのintegrationタグの背後にあるファイルがlintされない

linters:
  default: standard
  enable:
    - bodyclose
    - copyloopvar
    - dupl
    - durationcheck
    - err113
    - errname
    - errorlint
    - exhaustive
    - exptostd
    - fatcontext
    - goconst
    - gocritic
    - gosec
    - intrange
    - misspell
    - modernize
    - musttag
    - nakedret
    - nestif
    - nilerr
    - noctx
    - nolintlint
    - nonamedreturns
    - perfsprint
    - prealloc
    - revive
    - sqlclosecheck
    - testifylint
    - thelper
    - unconvert
    - unparam
    - usestdlibvars
    - usetesting
    - wastedassign
    - whitespace
    - wrapcheck
  settings:
    govet:
      enable:
        - shadow
    gocritic:
      enabled-checks:
        - nestingReduce
    revive:
      enable-all-rules: true
      rules:
        # enable-all-rules は `unhandled-error` をオンにし、main 内の `fmt.Println` にフラグを立てます。
        # enable-all-rules の下では、ルールの `arguments` は無視されます(ルールが2回登録されるため)、
        # そのため許可リストはここでは機能しません - `disabled` のみが有効です。
        - name: unhandled-error
          disabled: true
    errcheck:
      check-type-assertions: true
  exclusions:
    generated: strict
    presets:
      - comments
      - std-error-handling
      - common-false-positives
    rules:
      - path: _test\.go
        linters:
          - errcheck
          - dupl
          - gosec
          - wrapcheck

formatters:
  enable:
    - gofumpt
    - goimports
  settings:
    gofumpt:
      # ルールを個別に選択します。`extra-rules: true` は非推奨であり、さらに
      # `balance_calls` もオンにしますが、gofumpt自身が論争の的として格下げしました。
      extra:
        group-params: true
        clothe-returns: true
        balance-calls: false
  exclusions:
    generated: strict
    paths:
      - vendor/

output:
  formats:
    text:
      path: stdout
      print-linter-name: true
      colors: true
  sort-order:
    - linter
    - file
  show-stats: true

Justfile

set shell := ["bash", "-euo", "pipefail", "-c"]
set dotenv-load

binary := "myapp"

[private]
default:
    @just --list --unsorted

# ── コード品質 ──────────────────────────────────────────

# すべてのGoコードをフォーマット
[group('quality')]
fmt:
    golangci-lint fmt ./...

# 変更せずにフォーマットをチェック (CIセーフ)
[group('quality')]
fmt-check:
    golangci-lint fmt --diff ./...

# リンターを実行
[group('quality')]
lint:
    golangci-lint run ./...

# 自動修正付きでリンターを実行
[group('quality')]
lint-fix:
    golangci-lint run --fix ./...

# 脆弱性チェックを実行
[group('quality')]
vuln:
    govulncheck ./...

# ── テスト ───────────────────────────────────────────────

# 競合検出付きですべてのテストを実行
[group('test')]
test *args="./...":
    gotestsum --format testname -- -race {{ args }}

# カバレッジ付きでテストを実行
[group('test')]
test-cov:
    gotestsum --format testname -- -race -coverprofile=coverage.out -covermode=atomic ./...
    go tool cover -func=coverage.out

# ブラウザでカバレッジレポートを開く
[group('test')]
coverage: test-cov
    go tool cover -html=coverage.out

# 統合テストを実行
[group('test')]
test-integration:
    gotestsum --format testname -- -race -tags=integration ./...

# 開発中にテストをウォッチ
[group('test')]
test-watch:
    gotestsum --watch --watch-clear --format testname

# ベンチマークを実行
[group('test')]
bench:
    go test -bench=. -benchmem ./...

# ── ビルド ─────────────────────────────────────────────────

# バイナリをビルド
[group('build')]
build:
    go build -o {{ binary }} ./cmd/{{ binary }}

# 最適化されたリリースバイナリをビルド
[group('build')]
build-release:
    CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o {{ binary }} ./cmd/{{ binary }}

# ── 依存関係 ──────────────────────────────────────────

# モジュールを整頓し検証
[group('deps')]
tidy:
    go mod tidy
    go mod verify

# go.mod/go.sum が整頓されていない場合に、触らずに失敗 (CIセーフ)
[group('deps')]
tidy-check:
    go mod tidy -diff

# コードジェネレーターを実行
[group('deps')]
generate:
    go generate ./...

# ── データベース ──────────────────────────────────────────────

# 保留中のマイグレーションをすべて適用
[group('db')]
migrate-up:
    migrate -path migrations -database "$DATABASE_URL" up

# 最後のマイグレーションを元に戻す
[group('db')]
migrate-down:
    migrate -path migrations -database "$DATABASE_URL" down 1

# 新しいマイグレーションを作成
[group('db')]
migrate-create name:
    migrate create -ext sql -dir migrations -seq {{ name }}

# ── CI ────────────────────────────────────────────────────

# 完全なCIゲート (フォーマットチェック + lint + テスト)
[group('ci')]
check: fmt-check lint test
    @echo "All checks passed"

# ビルドアーティファクトをクリーン
[group('ci')]
clean:
    go clean
    rm -f {{ binary }} coverage.out

check は意図的に vuln を除外しています。govulncheck はネットワークを呼び出し、脆弱性データベースの更新により、触っていない変更でゲートが赤くなることがあります。代わりに、独自のCIジョブとして実行してください。golangci-lint run は有効なフォーマッターも実行し、その問題を報告するため、fmt-check はそのチェックを重複しています - 読みやすい差分のために残しておいてください。

Lefthook 設定

Lefthook は Go プロジェクトでは pre-commit よりも推奨されます。単一のGoバイナリであり、フックを並列実行し、Pythonを必要としません。

go install github.com/evilmartians/lefthook/[email protected]   # Go 1.26+ が必要
lefthook install
# lefthook.yml
assert_lefthook_installed: true   # ルールを静かにスキップするのではなく、大きな声で失敗する

pre-commit:
  piped: true   # ファストフェイル - 最初に失敗したジョブで停止
  commands:
    fmt:
      glob: "*.go"
      run: golangci-lint fmt {staged_files}
      stage_fixed: true
    lint:
      glob: "*.go"
      # 生のファイルリストを `golangci-lint run` に渡さないでください。2つのディレクトリに
      # またがるリストは完全に拒否され、マルチファイルパッケージの1つのファイルは
      # 幻の `undefined:` 型チェックエラーを報告します。代わりにパッケージをlintしてください。
      run: printf '%s\n' {staged_files} | xargs -n1 dirname | sort -u | xargs golangci-lint run --fix
      stage_fixed: true
    mod-tidy:
      glob: "*.{go,mod,sum}"
      run: go mod tidy

pre-push:
  commands:
    test:
      run: go test -race ./...

piped: true はファストフェイルであり、順序付けではありません。lefthook は「デフォルトでコマンドとスクリプトを順次実行」し、piped は「コマンドとスクリプトのいずれかが失敗した場合に実行を停止する」機能を追加します。これは parallel: true と組み合わせることはできません。

jobs: (lefthook 1.10.0で追加) は、commands:/scripts: 分割とともに新しいプリミティブです。「ジョブはタスクを定義する柔軟な方法を提供し、コマンドとスクリプトの両方をサポートします。ジョブは高度なフロー制御のためにグループ化できます。」commands: は非推奨ではなく、完全に文書化されたままです。グループ化、ネストされたフロー制御、または1つのフック内のインラインコマンドとスクリプトの混合が必要な場合は jobs: に手を伸ばしてください。

さらに配線する価値のある4つの事項:

  • 上記の assert_lefthook_installed: true は、以下にある休眠footgunに対する解毒剤です。「$PATHに lefthook 実行可能ファイルが見つからない場合、失敗する (終了ステータス1)」。
  • CI内の lefthook validate は、不正な lefthook.yml が静かにフックを無効にする前にキャッチします。lefthook dump は、フックが記述されたとおりに動作しない場合に、マージされた有効な設定を出力します。
  • gitignoreされた lefthook-local.yml により、開発者はチームメイトに強制することなく、ジョブを追加またはスキップできます。「これは、lefthookをローカルで使用したいが、チームメイトに強制したくない場合に便利です。」
  • モノレポでは、各ジョブにモジュールディレクトリを指す root: を指定します。これがないと、go mod tidy と go tool はリポジトリルートに対して実行され、失敗します。
  • mod-tidy と lint ジョブの skip: [merge, rebase] により、マージまたはリベースの進行中に行われたコミットからそれらを除外し、競合の解決がtidyの書き換えや --fix の編集をステージングしないようにします。

pre-push のテストスイートは、CIがすでに実行していることを繰り返します。時間がかかるようになったら、ドロップするか、変更されたパッケージに限定してください。そうしないと、開発者は LEFTHOOK=0 でプッシュすることを覚えます。フックから実行されるテストは、gitのフック環境 (GIT_DIR、GIT_INDEX_FILE、GIT_WORK_TREE) も継承するため、t.TempDir() 内で git init または git commit を実行するテストヘルパーは実際のリポジトリに対して動作します。これらの変数を cmd.Env でクリアしてください。

競合のない git merge は決して pre-commit を実行しません。git は代わりに pre-merge-commit を呼び出し、競合のあるマージを git commit で終了した場合にのみ pre-commit を実行します。チェックを pre-merge-commit: キーの下にミラーリングするか、マージがlintされずにランディングします。

フックコマンドは呼び出し元のPATHからツールを解決するため、バイナリがないと exit 127 でコミットが失敗します。それらを go tool <name> (ツールディレクティブ) として実行すると、フックはビルドと同じくらい再現可能になります。

ベータ版ですが、知っておく価値があります。ai: は同じファイル内にLLMエージェントフックを宣言します - 「lefthook install 中に、lefthook はプロバイダー固有の設定ファイルを生成し、エージェントがトリガーされたときに lefthook run <hook> を呼び出すようにします。」

Footguns(よくある落とし穴)

lefthook の休眠

「lefthook install は _ で始まらないフックをインストールします。」誤って _lefthook.yml と名付けたり、lefthook.yml を壊したり、ある開発者がインストールを忘れたりすると、フックは静かに実行されません。これが assert_lefthook_installed: true と CI での lefthook validate が存在する理由です。

golangci-lint run へのファイルリストの受け渡し

golangci-lint run file1.go file2.go を決して行わないでください。2つの異なるパッケージからのファイルは「golangci-lint: 1 つのディレクトリのみ許可」で拒否され、1つのパッケージからの1つのファイルは「不明なx」型チェックエラーを報告します。常にパッケージを渡すか、./... を渡すか、lefthookの printf ... xargs dirname トリックを使用してください。

go.work は go vet の動作を変更する

go.work ファイルが存在する場合、go vet (および golangci-lint) はすべてのワークスペースモジュールに対して実行されます。大きなモノレポでは、不要なモジュールをチェックするため、予期せずタイムアウトになる可能性があります。GOWORK=off go vet ./... または golangci-lint run --modules-download-mode vendor ./my-service/... を使用してスコープを制限してください。

golangci-lint 出力のパイプ処理

--color および --output.text.path を stdout に設定した golangci-lint は、TTYが接続されている場合にのみ色を出力します。golangci-lint run ./... | tee report.txt は生のリセットコード ([0m) をファイルに書き込みます。CIでは --color=always を使用してください。

lefthook 環境の継承

pre-push 内で go test として実行されるテストは、GIT_DIR、GIT_INDEX_FILE、GIT_WORK_TREE を継承します。gitリポジトリ内で t.TempDir() を使用する func TestRepo は、親のgitメタデータを継承します。cmd.Env でそれらをクリアしない限り、テストは奇妙に失敗するか、開発者の作業ディレクトリを台無しにします。

マージフックの欠落

pre-commit フックは git merge 中には実行されません。マージは pre-merge-commit を呼び出します(手動の git commit --no-edit は除く)。lint ジョブを pre-merge-commit キーの下にミラーリングしない限り、lint されていないコードがマージされます。

GOTOOLCHAIN のディストリビューションパッケージとの相互作用

Goが apt, brew, nix 経由でインストールされているマシンでは、GOTOOLCHAIN=auto がディストリビューションの go バージョンよりも優先される場合があります。GOTOOLCHAIN=go1.27.1 を go env -w するか、CIランナーにエイリアスを設定してください。toolchain ディレクティブがどのように強制されるかを理解するには、Go wikiの GOTOOLCHAIN を参照してください。

govulncheck の誤検知

govulncheck は、Goの脆弱性データベースに対してコード内で使用されているシンボルと関数をクロスリファレンスします。未使用の推移的依存関係にある脆弱性は報告しませんが、データベース自体が変更される可能性があります。check から除外し、代わりに別のCIジョブとして実行する理由はここにあります。

パッチバージョンのずれ

go.mod の toolchain ディレクティブを意図的に現在のパッチ(go1.27.1)に設定し、.0 にしないでください。ローカルのGoがデフォルトのままのマシン(たとえばGitHub Actionsの stable)では、toolchain ディレクティブが go バイナリを選択します。govulncheck はそのバージョンに対してstdlibアドバイザリをスキャンします。go1.27.0 は、Go 1.27.0のリリース後に発見された脆弱性に対して既知の脆弱性としてフラグを立てます。toolchain がまだ壊れている可能性のあるパッチに設定されている場合、govulncheckはCI全体を赤くします。

golangci-lint fmt の並列処理

golangci-lint fmt は複数のコアを使用できるため、大きなリポジトリでは gofmt -l . よりも大幅に高速です。ただし、goimports も有効になっている場合、golangci-lint fmt は goimports を直接呼び出しません。代わりにリンターとフォーマッターエンジンを介して実行されるため、スタンドアロンの goimports とは動作が異なる場合があります。整合性が重要な場合は、goimports のみに頼らず、.golangci.yml のフォーマッター設定に注目してください。

移行の失敗を見逃さない

golang-migrate の移行は、SQLエラーが発生してもゼロ以外の終了コードで失敗しない場合があります。特に migrate -path ... up は、データベースへの接続が少なくとも一度成功した場合、ステータスコード0で終了することが知られています。CIで安全策として migrate -verbose -path ... up 2>&1 | grep -i error をパイプするか、アプリケーションの起動シーケンスの一部として go-migrate ライブラリを使用してください。ここでのCLIは迅速な初期開発に適しています。

アンインストール

各ツールはクリーンなパスを持っています:

  • golangci-lint: rm $(which golangci-lint)
  • gofumpt、gotestsum: go.mod からツールディレクティブ行を削除
  • golang-migrate: rm $(which migrate)
  • just: 使用したインストール方法に従う (brew uninstall just、rm $(which just))
  • lefthook: lefthook uninstall してから rm $(which lefthook)
  • フック: lefthook uninstall は .git/hooks 内のlefthookフックをすべて削除します

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

関連スキル