【Go】デファクトなlinter staticcheckの設定方法

# はじめに
今回はGoのlinterとしてデファクトスタンダードになりつつある`staticcheck`の設定方法を解説していきたいと思います！
デフォルトで無効なチェック項目を有効にすべきかも検討していきます。

自分は普段VSCodeでGoを開発しており、linterに関しては特にこだわるわけでもなく、標準パッケージだった`golint`を使っていました。
しかし、少し前にVSCodeのGo Extenstionのデフォルトが`staticcheck`になったことに伴い、自分もこちらに移行しました。
その時検討したことをまとめようと思います。

# staticcheckがデファクトなの？
Goには様々なlinterが存在しています。
メジャーどころでいうと、

- [golint](https://github.com/golang/lint)　(凍結済み)
- [golangci-lint](https://golangci-lint.run/)
- [staticcheck](https://staticcheck.io/)

あたりでしょうか。

しかし、標準ライブラリである`golint`は2018年あたりからメンテが行われておらず、リポジトリも凍結されました。
VSCodeのGo Extensionの標準linterも以前は`golint`だったのですが、これを受け、2021/03/11のアップデートで標準linterが`staticcheck`に変更されました。

https://github.com/golang/vscode-go/issues/189

VSCodeのGo ExtensionはGo開発チームが開発しているので、これはデファクトスタンダードと言っても良いのではないでしょうか！（適当）

# 初期設定
## インストール
VSCodeのGo Extensionをインストールすれば、特にインストールは不要です。
CLIでインストールする場合は、以下のコマンドを実行します。

```
$ go install honnef.co/go/tools/cmd/staticcheck@latest
```

## 有効化
`setting.json`に以下を追記します。

```json
"gopls": { "ui.diagnostic.staticcheck": true }
```

# 設定ファイル
デフォルト設定で良いままであれば、初期設定するだけでOKです。
好みに合わせてチェック内容を変更したい場合は、設定ファイルを記述する必要があります。

## ファイル名、配置場所
開発ディレクトリのroot直下に`staticcheck.conf`をおき、そこに設定を記述します。
VSCodeでは`setting.json`でも設定できます。

【2024/08/13追記】
VSCode上で動作させるには、`setting.json`を編集する必要があるようです。
`staticcheck ./...`は`staticcheck.conf`を参照します。

【参考】公式 - configuration-files

https://staticcheck.io/docs/configuration#configuration-files

【参考】VSCodeでの書き方

https://github.com/golang/vscode-go/discussions/1493


## staticcheck.confの書き方
以下のように記述します。

```:staticcheck.conf
checks = ["inherit", "ST1003", "ST1016", "ST1020", "ST1021", "ST1022"]
```

`checks`という配列の中に有効にしたいチェックの番号を記述します。
`inherit`でデフォルトで有効なチェックを全て指定することができます。

基本的には、上記のように「`inherit` + デフォルトで無効だけど有効にしたいチェック」のような書き方になると思います。

`checks`の他にもいくつかの設定項目がありますが、個人的にはあまり使わなそうな印象です。

【参考】公式 - configuration

https://staticcheck.io/docs/configuration

## setting.jsonの書き方

以下のように記述します。

```json:setting.json
{
    "gopls": {
        "ui.diagnostic.staticcheck": true,
        "analyses": {
            "ST1003": true,
            "ST1016": true,
            "ST1020": true,
            "ST1021": true,
            "ST1022": true
        }
    }
}
```

# 各チェック項目
以下の公式ページに一覧があります。

https://staticcheck.io/docs/checks

# デフォルトで無効なチェック項目の精査
## 基本方針
デフォルトで有効なチェックは、理由がない限り有効のままで良いと思います。
開発を進めていくうちに、どうしても無効にしないといけない理由が出た時に無効にするくらいでしょうか。

なので、ここではデフォルトで無効なチェック項目を有効にした方が良いのか、1項目ずつ見ていきたいと思います。
といっても、7つしかないのでそんな労力ではないです。

有効・無効の検討は個人の偏見によるところが多いので、実際に案件に導入する際はチームで議論すべきだと思います。
正直、以前使っていた`golint`に引きずられている部分もあります。

## デフォルトで無効なチェック項目一覧
- [ST1000 – Incorrect or missing package comment](https://staticcheck.io/docs/checks#ST1000)
- [ST1003 – Poorly chosen identifier](https://staticcheck.io/docs/checks#ST1003)
- [ST1016 – Use consistent method receiver names](https://staticcheck.io/docs/checks#ST1016)
- [ST1020 – The documentation of an exported function should start with the function's name](https://staticcheck.io/docs/checks#ST1020)
- [ST1021 – The documentation of an exported type should start with type's name](https://staticcheck.io/docs/checks#ST1021)
- [ST1022 – The documentation of an exported variable or constant should start with variable's name](https://staticcheck.io/docs/checks#ST1022)
- [ST1023 – Redundant type in variable declaration](https://staticcheck.io/docs/checks#ST1023)

## 有効にするか検討
### 有効にする
- [ST1003 – Poorly chosen identifier](https://staticcheck.io/docs/checks#ST1003)
  - package名にキャメルケースやスネークケースを使ったり、命名で`userAPI`を`userApi`にすると怒られる
        - 他にも細かな命名規則がある。詳細はリンク先
  - `golint`ではチェックされていた項目
  - package名が読みづらくなるというつらみもある
        - kubernetesにも`credentialprovider`のようなパッケージ名が存在する
            - https://github.com/kubernetes/kubernetes/tree/master/pkg/credentialprovider
  - 人によっては鬱陶しく感じる規則ですが、 自分は`golint`の名残で有効にしました
  - 個人的にはこの規則のおかげで、やたらと長いパッケージ名を付けたりしないように意識できています
        - なるべくシンプルに、がGoですからね
- [ST1016 – Use consistent method receiver names](https://staticcheck.io/docs/checks#ST1016)
  - 同じ構造体のメソッドレシーバの名前が異なると怒られる
  - 統一した方がわかりやすいので有効にする。あえて別の名前を使う理由もない
  - 怒られる例

```
type UserRepository struct {
}

func (repo *UserRepository) Create{}
func (repository *UserRepository) Update{}
```

- [ST1020 – The documentation of an exported function should start with the function's name](https://staticcheck.io/docs/checks#ST1020)
  - exportedな関数のコメントを関数名で始めないと怒られる
  - `golint`ではチェックされていた項目
  - 正直あってもなくてもという感じではありますが、こちらも自分は`golint`の名残で有効にしました
  - Effective Goいわく、検索性があがるそうです
        - [Effective Go - commentary](https://golang.org/doc/effective_go#commentary)

> If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep. Imagine you couldn't remember the name "Compile" but were looking for the parsing function for regular expressions, so you ran the command,
$ go doc -all regexp | grep -i parse

> If all the doc comments in the package began, "This function...", grep wouldn't help you remember the name. But because the package starts each doc comment with the name, you'd see something like this, which recalls the word you're looking for.
$ go doc -all regexp | grep -i parse
　　Compile parses a regular expression and returns, if successful, a Regexp
　　MustCompile is like Compile but panics if the expression cannot be parsed.
　　parsed. It simplifies safe initialization of global variables holding

- [ST1021 – The documentation of an exported type should start with type's name](https://staticcheck.io/docs/checks#ST1021)
  - exportedな型のコメントを型名で始めないと怒られる
  - 同上
- [ST1022 – The documentation of an exported variable or constant should start with variable's name](https://staticcheck.io/docs/checks#ST1022)
  - exportedな変数・定数のコメントを変数・定数名で始めないと怒られる
  - 同上

### 無効のまま
- [ST1000 – Incorrect or missing package comment](https://staticcheck.io/docs/checks#ST1000)
  - パッケージ名にコメントを入れないと怒られる
  - ライブラリを開発して外部に公開する、とかでなければなくても良いかなと思います
- [ST1023 – Redundant type in variable declaration](https://staticcheck.io/docs/checks#ST1023)
  - 変数宣言の時に、型を明示的にすると怒られる
  - 特にlintで制限するほどでもないかなと思います
  - 怒られる例

```
type example struct{}

var ex example = example{}
var ex2 example = ex
```

# 感想
Effective GoやCodeReviewGuideを読み込む良い機会になりました。
Goに入門した時からなんども読んでいます。Goの思想に染まるには何回読んでもいいですからね！

あとは以下を強制するチェックがほしかったなーと思いました。

- exportedな関数へのコメント
- 1行あたりの最大文字数
  - Goでは特に決まりはない
    - [Effective Go - formatting](https://golang.org/doc/effective_go#formatting)
      - Go has no line length limit. Don't worry about overflowing a punched card. If a line feels too long, wrap it and indent with an extra tab.
  - golangci-lintには制限するチェックがありそう
        - https://golangci-lint.run/usage/configuration/
            - 「line length」で検索
  - ライブラリもあるが、ファイル保存時の動作が重くなるかも？
        - [golines](https://github.com/segmentio/golines)
- 引数が複数ある場合の関数の宣言方法

特に`引数が複数ある場合の関数の宣言方法`は悩みます。。
どんな書き方がいいんでしょう。

```Go
func functionExample(args1 string, args2 int, args3 int) int{
}

func functionExample(
  args1 string, args2 int, args3 int) int{
}

func functionExample(
  args1 string,
  args2 int,
  args3 int,
) int{
}
```

# さいごに
Twitterの方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Goのlinterであるstaticcheckに関する記事書いた！<br><br>⬇️【Go】デファクトなlinter staticcheckの設定方法<a href="https://t.co/xhMZzvXSZM">https://t.co/xhMZzvXSZM</a><br><br>標準パッケージのgolintが凍結され、staticcheckに移行したんだけど、色々とチェック項目が違った<br>VSCodeのGo Extenstionでも標準のlinterになってるから要チェック😇</p>&mdash; やぎぬ😇QOL追求エンジニア (@yagi_eng) <a href="https://twitter.com/yagi_eng/status/1409286516698861569?ref_src=twsrc%5Etfw">June 27, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

また、BOT開発を通じてGoとLINE BOTにまとめて入門する記事をZennに掲載していますので、良かったらそちらもご覧ください！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ZennでGoとLINE BOTの記事を書いてみました<br><br>⬇️BOT開発を通じてGoとLINE BOTにまとめて入門する<a href="https://t.co/QqsEESJMKa">https://t.co/QqsEESJMKa</a><br><br>5ステップに分け、「Hello Worldから始めて、飲食店検索ができるLINE BOTの実装まで」を解説しています<br>GoやLINE BOTに興味のある人は是非読んでみてください😇<br>24,000字超え😇</p>&mdash; やぎぬ😇行動力エンジニア (@yagi_eng) <a href="https://twitter.com/yagi_eng/status/1325027213784326152?ref_src=twsrc%5Etfw">November 7, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>