golangci-lint を使って個人開発プロジェクトを進めるので設定を紹介

個人開発で Go を使って開発を進めている。
Linter として golangci-lint を使っている。
設定内容の理解を深めるために、施している設定の解説をしてみる。

1. golang-lint とは

golangci-lint は Go 言語用の高速な静的解析ツール。
単体の Linter と異なり、複数の Linter を統合して解析することができる。
Go 言語にはそれぞれ特色が異なる Linter が複数存在している。
それらを1つずつ導入・実行するのは手間がかかるため、設定ファイル一つで一元管理できるツールとして多くのプロジェクトや OSS で活躍中。

2. golangci-lint の設定の全体像

golangci-lint は現在 v2.11.4 を使っている。
これは今後 Renovate などを使って随時更新してく予定。

v2.11.4 において自分が今回施した golangci-lint の設定は以下の通り。

.golangci.yml
version: "2"

run:
  timeout: 5m
  modules-download-mode: readonly

linters:
  enable:
    - errcheck
    - govet
    - staticcheck
    - unused
    - gosec
    - depguard
    - errorlint
    - revive
    - gocritic
    - cyclop
    - nolintlint
  settings:
    cyclop:
      max-complexity: 10
    nolintlint:
      require-explanation: true
    depguard:
      rules:
        domain-no-outward-deps:
          files:
            - "**/internal/domain/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/application
              desc: "domain層はapplication層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "domain層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "domain層はinfrastructure層に依存禁止"
        usecase-no-outward-deps:
          files:
            - "**/internal/application/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "application層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "application層はinfrastructure層に依存禁止"
        presentation-no-infra-deps:
          files:
            - "**/internal/presentation/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/domain
              desc: "presentation層はdomain層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "presentation層はinfrastructure層に依存禁止"

formatters:
  enable:
    - gofmt
    - goimports
  settings:
    gofmt:
      simplify: true
    goimports:
      local-prefixes: github.com/Soh1121/tlane

issues:
  max-issues-per-linter: 50
  max-same-issues: 3

3. version

.golangci.yml
version: "2"

設定バージョンを指定する。
指定可能な値は 2 のみである。

4. run

.golangci.yml
  run:
    timeout: 5m
    modules-download-mode: readonly

timeout は静的解析のタイムアウト時間。
デフォルトは 0 でタイムアウト無しとなっている。
おかしな挙動をしてても止まらないのは嫌なので5分を設定。

modules-download-mode: readonly: go.mod / go.sum
を実行中に書き換えさせない設定。CI等でのモジュール整合性担保に効きます

modules-download-mode が設定されている場合、その値が go list -mod={option} に渡される。
readonly を指定することで、go.mod が更新されないようにしている。
他の選択肢として、以下が設定可能。

  • vendorvendor/ ディレクトリに依存関係のコピーが保持されているとして go.mod を無視する
  • modgo.mod を自動更新して実行する

5. linters

.golangci.yml > linters
linters:
  enable:
    - errcheck
    - govet
    - staticcheck
    - unused
    - gosec
    - depguard
    - errorlint
    - revive
    - gocritic
    - cyclop
    - nolintlint
  settings:
    cyclop:
      max-complexity: 10
    nolintlint:
      require-explanation: true
    depguard:
      rules:
        domain-no-outward-deps:
          files:
            - "**/internal/domain/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/application
              desc: "domain層はapplication層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "domain層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "domain層はinfrastructure層に依存禁止"
        usecase-no-outward-deps:
          files:
            - "**/internal/application/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "application層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "application層はinfrastructure層に依存禁止"
        presentation-no-infra-deps:
          files:
            - "**/internal/presentation/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/domain
              desc: "presentation層はdomain層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "presentation層はinfrastructure層に依存禁止"

5.1. linters.enable

有効化されている Linter 一覧。

Linter役割
errcheckエラーの戻り値を握りつぶしていないか確認する。
govetGo 公式の静的解析。
フォーマット文字列の型不一致など。
staticcheckバグの温床になりがちなコードパターンを検出。
unused未使用の変数・関数・import を検出。
gosecセキュリティ的に危険なコードを検出。
depguard特定パッケージへの依存を禁止する。
オニオンアーキテクチャのために導入。
errorlinterrors.Is/As を使うべきところで == 比較していないか確認。
revivegolint の後継。
命名規則やコードスタイルをチェック。
gocritic診断ルールが豊富なメタリンター。
cyclop循環的複雑度のチェック。
nolintlint// nolint コメントの書き方をチェック。

5.2. linters.settings

.golangci.yml > linters.settings
  settings:
    cyclop:
      max-complexity: 10
    nolintlint:
      require-explanation: true
    depguard:
      rules:
        domain-no-outward-deps:
          files:
            - "**/internal/domain/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/application
              desc: "domain層はapplication層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "domain層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "domain層はinfrastructure層に依存禁止"
        usecase-no-outward-deps:
          files:
            - "**/internal/application/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "application層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "application層はinfrastructure層に依存禁止"
        presentation-no-infra-deps:
          files:
            - "**/internal/presentation/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/domain
              desc: "presentation層はdomain層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "presentation層はinfrastructure層に依存禁止"

5.2.1. linters.settings.cyclop

.golangci.yml > linters.settings.cyclop
    cyclop:
      max-complexity: 10

関数の循環的複雑度が10を超えるとエラーとなるようにする設定。
デフォルトで10だが明示しておく。

5.2.2. linters.settings.nolintlint

.golangci.yml > linters.settings.nolintlint
    nolintlint:
      require-explanation: true

// nolint を使用するときに説明を必須とする設定。
デフォルトで false なので、 true で有効化しておく。
「なぜ」をコメントで残しておくのは大事。

5.2.3. linters.settings.depguard

.golangci.yml > linters.settings.depguard
    depguard:
      rules:
        domain-no-outward-deps:
          files:
            - "**/internal/domain/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/application
              desc: "domain層はapplication層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "domain層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "domain層はinfrastructure層に依存禁止"
        usecase-no-outward-deps:
          files:
            - "**/internal/application/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/presentation
              desc: "application層はpresentation層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "application層はinfrastructure層に依存禁止"
        presentation-no-infra-deps:
          files:
            - "**/internal/presentation/**"
          deny:
            - pkg: github.com/Soh1121/tlane/internal/domain
              desc: "presentation層はdomain層に依存禁止"
            - pkg: github.com/Soh1121/tlane/internal/infrastructure
              desc: "presentation層はinfrastructure層に依存禁止"

オニオンアーキテクチャを静的解析で守るための設定。
ドメイン層は他の層に依存禁止だし、アプリケーション層はプレゼンテーション層・インフラ層に依存禁止、プレゼンテーション層はインフラ層・ドメイン層に依存禁止という設定。

気を抜くとうっかり依存しかねないし、AI に実装を頼むと違反しかねないので、こういう静的解析でガードレールが敷かれていると安心できる。

6. formatters

.golangci.yml > formatters
formatters:
  enable:
    - gofmt
    - goimports
  settings:
    gofmt:
      simplify: true
    goimports:
      local-prefixes: github.com/Soh1121/tlane

v2 から formatters は linters とは別のセクションに分離されたらしい。

6.1. formatters.enable

.golangci.yml > formatters.enable
  enable:
    - gofmt
    - goimports

有効化しているフォーマッターは gofmtgoimports の2つ。
gofmt は Go 公式のフォーマッターで、インデント・スペース・改行位置など、コードの「見た目」を Go 標準に揃えてくれる。
goimports は import 文の自動整形をしてくれるツール。
具体的には以下のようなことを行う。

  1. 使われていない import 文を削除
  2. 使っているのに import し忘れているパッケージを自動追加
  3. import をグループ分けしてアルファベット順に並び替え

6.2. formatters.settings

.golangci.yml > formatters.settings
  settings:
    gofmt:
      simplify: true
    goimports:
      local-prefixes: github.com/Soh1121/tlane

settings は有効化しているフォマッターの設定を行う部分。

gofmt.simplify: true は冗長な書き方を、より簡潔な等価なコードに変換してくれるようにする。

goimports.local-prefixes: github.com/Soh1121/tlane は import 文をグループ分けする際に基準となる設定。
これを指定すると、import 文が標準ライブラリ・外部パッケージ・自プロジェクトの3つのグループに分けられて整形される。

7. issues

.golangci.yml > issues
issues:
  max-issues-per-linter: 50
  max-same-issues: 3

issues は Linter の出力結果を制御するパート。

max-issues-per-linter では Linter ごとに最大いくつ issue を出力するかを設定する。
デフォルトで50となっているが、明示することでわかりやすくしておく。

max-same-issues は同種の issue を何件まで表示するかの設定。
ここでもデフォルト通り3件に設定しておく。

8. まとめ

この記事では個人開発を行っている Go プロダクトの golangci-lint の設定を紹介した。
まだ機能するところまでは進められていないが、この設定をベースに開発を進めていきたい。
AI エージェントを活用しながら進める予定なので、Linter などのガードレールを活用しながら時間をかけずに進められるよう工夫していきたい。