golangci-lintでgoのソースコードにlinterを適応する[Go,GithubActions]

# golangci-lintとは?

Goのlintツールです。

lintツールはlinterとしても呼ばれ、プログラムのソースコードの静的解析を行い、潜在的なバグを引き起こしそうなコードの発見やコードの命名等が規則に沿っているかどうかを指摘や修正等を行えます。

https://github.com/golangci/golangci-lint

# golangci-lintを使用するメリット
linterは基本的にコードの品質を高めるために使われます。

:::note
### コードの品質とは
コードの効率性、読みやすさ、使いやすさを示す指標のことをコードの品質と言います。

コードの品質を高めることで、他の人がコードを読む時間を節約し、なおかつ冗長なコードを減らすことができ、時間とリソースの節約をすることができます。
:::

golangci-lintは、コードの命名規則だけでなく、バグを引き起こしそうなコード、エラーハンドリングしていないコードなど、設定すれば多くのコードの書き方にかける規則の指摘をしてくれます。

加えて、オプションを使えば修正も行なってくれるので、コードレビューをある程度自動化することができます。

# インストール
homebrewでは次のコマンドでインストールします。

```zsh:zsh
brew install golangci-lint
```

GOPATHでは下のコマンドでインストールします。

```zsh:zsh
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0
```

# lintを走らせる

下のコマンドで走らせることができます。

```zsh:zsh
golangci-lint run
```

もし走らせて、構文エラーがあった場合、下のようにerrを返していないところなどを教えてくれます。
```zsh:zsh
❯ golangci-lint run
usecases/interactor/todo.go:74:16: Error return value of `uc.txm.RunInTx` is not checked (errcheck)
        uc.txm.RunInTx(ctx, func(ctx context.Context) error {
                      ^
infrastructure/mysql/conn.go:69:15: ineffectual assignment to err (ineffassign)
        sqlDB, mock, err := sqlmock.New()
                     ^
infrastructure/mysql/repository/todo_test.go:56:13: ineffectual assignment to err (ineffassign)
        deadline2, err := time.Parse("2006-01-02", "2024-10-12")
                   ^
usecases/interactor/todo.go:75:11: ineffectual assignment to err (ineffassign)
                exsist, err := uc.repo.FindById(ctx, strconv.Itoa(int(task.ID)))
                        ^

```

# 設定ファイルを書く

設定ファイルを書くことで細かくlintの設定を行うことができます。

設定するために`.golangci.yml`を作成して、下のように記述します。

```./config/.golangci.yml
run:
  deadline: 5m
  allow-parallel-runners: true
linters:
  disable-all: true
  enable:
    - gofmt
    - goimports
    - govet
    - typecheck
    - errcheck
    - errname
    - errorlint
    - nilerr
    - makezero
    - ineffassign
    - gosec
    - noctx
    - staticcheck
    - unused
    - copyloopvar
    - decorder
    - misspell
    - whitespace

issues:
  exclude-dirs:
    - config
```

## configで設定した内容

### run
linterの実行に関する設定を行なっています。
`deadline`でタイムアウトを5分と設定
`allow-parallel-runners`で並行処理を許可しています。

```yml
run:
  deadline: 5m
  allow-parallel-runners: true
```

### linters
lintする対象を設定しています。
`disable-all`をtrueとすることで実行内容をオプションとしています。
`enable`の下にリストとして、何を対象としてlintを行うかを設定しています。

|名前|効果
|---|---
|gofmt|go標準のフォーマット
|goimports|インポートしているモジュールの整理
|govet|go標準の静的解析
|typecheck|型定義のチェック
|errcheck|エラーチェック
|errname|エラー変数の命名チェック
|nilerr|エラー確認でnilを返していないかを確認
|makezero|長さが0でないスライスに対するappendを確認
|ineffassign|不要な代入をされているところを警告します
|gosec|さまざまなセキュリティの確認をします
|noctx|contextなしでhttpリクエストを返してるところを確認します
|staticcheck|静的解析ツールで確認します
|unused|使用してないフィールドを検知
|copyloopvar|ループ変数に関する警告
|decorder|構造体や関数などの宣言確認
|misspell|スペルミスを確認
|whitespace|余分な改行を確認


```yml
linters:
  disable-all: true
  enable:
    - gofmt
    - goimports
    - govet
    - typecheck
    - errcheck
    - errname
    - nilerr
    - makezero
    - ineffassign
    - gosec
    - noctx
    - staticcheck
    - unused
    - copyloopvar
    - decorder
    - misspell
    - whitespace
```

以下でオプションを確認できます。

https://golangci-lint.run/usage/linters/

# actionsでgolint-ciを使用する

GithubActionsを使って、lintを適応させてみようと思います。

https://github.com/golangci/golangci-lint-action

また、lintの結果をレビューコメントとして出してくれるreviewdogを使おうと思います。

https://github.com/reviewdog/action-golangci-lint

## ymlを書いてみる
実際に下のようにymlを記述しました。

```.github/workflows/golint.yml
name: golangci-lint

on:
  push:
    branches:
    - develop
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
    branches:
      - develop

jobs:
  lint:
    name: lint
    permissions:
      checks: write
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v4.2.2
      - name: setup for go
        uses: actions/setup-go@v5.1.0
        with:
          go-version-file: "./server/go.mod"
      - name: lint
        uses: reviewdog/action-golangci-lint@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          reporter: github-pr-review
          workdir: "./server"
          go_version_file: "./server/go.mod"
          level: error
          fail_on_error: true
          golangci_lint_flags: "--config=./config/.golangci.yml"
          filter_mode: nofilter

```

`develop`ブランチを作成、もしくはPullRequestをdevelopに対して`作成`、`draftを作成`、`PRに新しいコミット`を増やした時にActionsを行うようにします。

```yml
on:
  push:
    branches:
    - develop
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
    branches:
      - develop
```

jobs配下に権限(permission)や実行OS(runs-on)を設定します。
permissionでは、GithubTokenに対する権限を付与しています。今回、lintのciを回すために`contents: read`、reviewdogによるPRにコメントを残すために`checks: write`と`pull-requests: write`を設定しています。
```yml
jobs:
  lint:
    name: lint
    permissions:
      checks: write
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
```

stepsに各々行う処理を記述しています。

```yml
steps:
      - name: checkout
        uses: actions/checkout@v4.2.2
      - name: setup for go
        uses: actions/setup-go@v5.1.0
        with:
          go-version-file: "./server/go.mod"
      - name: lint
        uses: reviewdog/action-golangci-lint@v2
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          reporter: github-pr-review
          workdir: "./server"
          go_version_file: "./server/go.mod"
          level: error
          fail_on_error: true
          golangci_lint_flags: "--config=./config/.golangci.yml"
          filter_mode: nofilter
```

`checkout`でactions内にリポジトリをクローンします。
```yml
 - name: checkout
   uses: actions/checkout@v4.2.2
```

actionsにgoの環境をセットアップします。
```yml
- name: setup for go
  uses: actions/setup-go@v5.1.0
  with:
    go-version-file: "./server/go.mod"
```

lintを実行します。
reviewdogにgolangci-lint向けのactionsが用意されているのでそれを使用します。
```yml
- name: lint
  uses: reviewdog/action-golangci-lint@v2
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    reporter: github-pr-review
    workdir: "./server"
    go_version_file: "./server/go.mod"
    level: error
    fail_on_error: true
    golangci_lint_flags: "--config=./config/.golangci.yml"
    filter_mode: nofilter
```

`level`をerrorにすることで、lintで発見した内容をerrorとすることができ、`fail_on_error: true`でactionsを停止させることができます。
また、`filter_mode: nofilter`で変更したコード以外もチェックしてくれます。

今回使用したプロパティはこんな感じです。
|プロパティ|役割
|---|---
|github_token|GithubのAPIを叩くためのトークンを指定
|reporter|golangci-lintの実行結果をどう示すか
|workdir|作業ディレクトリの指定
|go_version_file|go.modのある場所を指定
|level|実行結果をどの程度のレベルで表示するかを指定
|fail_on_error|errorが出ても処理を続けるように
|golangci_lint_flags|golangci-lintを動かす際のオプション等の引数を付け足す
|filter_mode|lintを回すコードのスコープ(added,file,nofileter)

## 実行してみる
実際にactionsを走らせると`Files Changed`等で下のように表示してくれます。
![スクリーンショット 2024-11-24 7.32.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3766230/a7b3a329-eaee-477d-d43d-b07036cf179e.png)

golangci-lintを使うことでコードの品質を上げ、共同開発の効率を上げてくれます。


ぜひ活用してみてください!