Slide 1

Slide 1 text

ゆるやかにgolangci-lint のルールを強くする id:utgwkk / @utgwkk (うたがわきき) 2024/12/15 Kyoto.go #56 オフライン忘年LT会@マネフォ京都 1

Slide 2

Slide 2 text

自己紹介 ● うたがわきき (@utgwkk) ● 株式会社はてな ○ Webアプリケーションエンジニア ● 好きなパッケージはreflect 2

Slide 3

Slide 3 text

アジェンダ ● golangci-lintについて ● lintルールを強くするためにまずやったこと ● あとからlintルールを強くするコツ 3

Slide 4

Slide 4 text

アジェンダ ● golangci-lintについて ● lintルールを強くするためにまずやったこと ● あとからlintルールを強くするコツ 4

Slide 5

Slide 5 text

golangci-lintについて ● https://golangci-lint.run/ ● Goのlinterをまとめて実行するrunner ● アンケート: golangci-lintを使っている? 5

Slide 6

Slide 6 text

アジェンダ ● golangci-lintについて ● lintルールを強くするためにまずやったこと ● あとからlintルールを強くするコツ 6

Slide 7

Slide 7 text

golangci-lintは使っていたが ● 最小構成でスタート ● 検査しないファイルだけ設定する ● デフォルトで有効のlinterのみ ● CIで走らせる 7

Slide 8

Slide 8 text

設定をよくする機運が高まる ● 実装ミスがlinterで検知されると嬉しい ● どこまで検査してくれるのか知らなかった ● 底力を引き出せていないのでは 8

Slide 9

Slide 9 text

9 よっしゃやっていくぞ

Slide 10

Slide 10 text

10 まずは何をするか ● 全てのlinterを有効にする?

Slide 11

Slide 11 text

11 全てのlinterを有効にする? ● エラーが大量に出てしまう ● 既存のコードベースに合わせた調整が困難 ● 設定項目が無限にある!!

Slide 12

Slide 12 text

まずはgolangci-lintを知る (1) ● golangci-lint自体の設定項目を知る ● どこまで柔軟にできるか・できないか知る ● https://golangci-lint.run/usage/configur ation/ 12

Slide 13

Slide 13 text

まずはgolangci-lintを知る (2) ● 搭載されているlinter一覧がある ● 上から順番に見ていく ● 1つずつ有効にして検査してみる ● https://golangci-lint.run/usage/linters/ 13

Slide 14

Slide 14 text

14 golangci-lintを知った ● どんなlinterが搭載されているのか知る ● 無理なく導入できるlinter・設定が分かる ● 既存のコードベースに合わない設定を知る

Slide 15

Slide 15 text

15 完全に理解した

Slide 16

Slide 16 text

アジェンダ ● golangci-lintについて ● lintルールを強くするためにまずやったこと ● あとからlintルールを強くするコツ 16

Slide 17

Slide 17 text

17 あとからlintルールを強くするコツ ● linterの象限を考えて導入する ● 部分的にlinterを有効化・無効化する

Slide 18

Slide 18 text

linterの象限 ● コーディング規約に合う・合わない ● 簡単に導入できる・できない ● (他にもあると思うけど簡略化した) 18

Slide 19

Slide 19 text

コーディング規約に合う ● コーディング規約に合う・簡単に導入できる ○ linterに引っかかるコードがない (多くない) ○ 機械的に修正できて動作確認が容易である ○ 設定を少し調整したら有効化できる ● 迷わず有効化する 19

Slide 20

Slide 20 text

コーディング規約に合う ● コーディング規約に合う・簡単に導入できない ○ 既存のコードが引っかかりまくる ○ 機械的に修正できるか判断しづらい 20

Slide 21

Slide 21 text

コーディング規約に合う ● 部分的にlinterを有効化できないか検討する ○ 特定のファイルだけ有効・無効にする ○ 特定のディレクトリ以下だけ有効・無効にする ○ 既存のコードに対してlinterを無効化する ● (具体的な実現方法は後述します) 21

Slide 22

Slide 22 text

コーディング規約に合わない ● コーディング規約に合わないlinter ● 基本的には導入しなくていいだろう ○ 簡単には導入できないなら、なおさら ○ こういうlinterがある、というのを知っておくことは 損にはならない 22

Slide 23

Slide 23 text

23 部分的にlinterを有効化・無効化する ● ファイル単位で切り替える ○ 例: テストコードかどうかで切り替える ● //nolint: ディレクティブを活用する ● 固有のディレクティブを活用する ○ 例: exhaustruct

Slide 24

Slide 24 text

24 ファイル単位で切り替える ● exclude-files ○ 指定したファイルは検査しない ● exclude-dirs ○ 指定したディレクトリ以下は検査しない ● exclude-rules ○ 指定したファイルで有効にするlinterを変える

Slide 25

Slide 25 text

25 例: テストコードかどうかで切り替える # テストコードでは特定の linterを無効化する例 exclude-rules: - path: _test\.go linters: - contextcheck - noctx

Slide 26

Slide 26 text

26 //nolint: を活用する ● //nolint: で始まるコメントを書いた行に対す るlintエラーは報告されない ○ eslint-disable-next-lineみたいなイメージ ● lintエラーを抑制する理由を書ける ○ //nolint:unused // ここに理由

Slide 27

Slide 27 text

//nolint: を活用する var x int //nolint:unused // インライン //nolint:unused // 次の行 var y int 27

Slide 28

Slide 28 text

28 ディレクティブを活用する ● linter固有のディレクティブが使える場合があ る ○ 同じファイル内でも挙動を切り替えられる ○ 徐々にコードベースを良くする足がかりとなる

Slide 29

Slide 29 text

29 固有のディレクティブを活用する ● 例: exhaustruct ○ structリテラルの全てのフィールドに値が埋まってい ることを検査するlinter ○ //exhaustruct:enforce と書いた次の行だけlinterの 検査対象となる

Slide 30

Slide 30 text

まとめ ● 一気に全部やろうとしない ○ enable-all, disable-all以外の選択肢もある ○ 「要はバランス」 ● 部分的に有効化できるルールを活用する ○ 敷いたガードレールに行き手を阻まれないように 30

Slide 31

Slide 31 text

31 ここから先 時間余ったら・質問があっ たら取り上げるコーナー

Slide 32

Slide 32 text

トピック集 ● linterから見るGoの進化 ● 独自linterの運用 (未解決) ● 循環的複雑度をlintする? ● チームで有効にしているlinter ● ISUCONでもlinterを活用している 32

Slide 33

Slide 33 text

linterから見るGoの進化 ● exportloopref ○ ループ変数をコピーせずに参照を取るコードを検出する ● copyloopvar ○ ループの先頭でループ変数をコピーするコードを検出する ● linter同士が衝突している? 33

Slide 34

Slide 34 text

linterから見るGoの進化 ● Go 1.22でループ変数がコピーされるように なった ○ Fixing For Loops in Go 1.22 ● exportlooprefがdeprecatedになった 34

Slide 35

Slide 35 text

linterから見るGoの進化 ● 言語の進化によって正解が変わる ● Go 1.23で導入されたiteratorに対する linterもいつかは出てくるでしょう 35

Slide 36

Slide 36 text

linterから見るGoの進化 ● Go 1.22以降のループ変数のコピーについて は以下の資料が詳しい ○ 詳解 "Fixing For Loops in Go 1.22" 自作linterを golangci-lintへコントリビュートした話 36

Slide 37

Slide 37 text

独自linterの運用 (未解決) ● 独自linterは意外と簡単に書ける ○ コツが掴めたらいける ○ 日本語の情報も割とある ■ つくってまなぶ静的解析のすすめ - LayerX エンジニアブロ グ など 37

Slide 38

Slide 38 text

独自linterの運用 (未解決) ● CIにどうやって組み込む? ○ CIがコケないと無視される (無視してしまう) ● golangci-lintにプラグインの仕組みはあるが ○ カスタムビルドしたバイナリを使う必要がある ○ linter側でinitでプラグインを登録する必要がある 38

Slide 39

Slide 39 text

独自linterの運用 (未解決) ● go vet -vettoolの仕組みがgolangci-lintでも 使えると楽になりそうだが…… ○ Goの静的解析ツールを簡単に使うためのエコシステム について考える #golang - tenntenn.dev 39

Slide 40

Slide 40 text

独自linterの運用 (未解決) ● 既存のlinterで解決できないか検討する ○ 探すと意外と見つかることもある ○ 正規表現マッチで事足りるならforbidigoとか ● 探したけど見つからない場合 ○ 新規性チャンス 40

Slide 41

Slide 41 text

独自linterの運用 (未解決) ● 作らずに話すのもな、と思って実装した ○ https://github.com/utgwkk/goqumysqllint ○ goqu (クエリビルダ) に対するlinter 41

Slide 42

Slide 42 text

循環的複雑度をlintする? ● gocyclo linterで検査できるが ● 答えを持ち合わせていない ○ 最大値をどこに設定すべきか ○ 「lintエラー」にすべきなのか 42

Slide 43

Slide 43 text

チームで有効にしているlinter ● contextcheck ● depguard ● exhaustive ● exhaustruct ● fatcontext ● gocheckcompilerdirec tives ● makezero 43 ● mirror ● misspell ● nilerr ● noctx ● nolintlint ● predeclared ● reassign ● unconvert

Slide 44

Slide 44 text

contextcheck ● context.Contextを引き回しているかどうか検査 するlinter ● テストコードでは無効にしている ○ ヘルパー関数がいっぱいあるから ● https://github.com/kkHAIKE/contextcheck 44

Slide 45

Slide 45 text

exhaustive ● enumに対するswitch-caseの分岐が網羅され ているか検査するlinter ● https://github.com/nishanths/exhaustive 45

Slide 46

Slide 46 text

exhaustruct ● structの全てのフィールドに値が埋まってい ることを検査するlinter ● https://github.com/GaijinEntertainment /go-exhaustruct 46

Slide 47

Slide 47 text

fatcontext ● context.Contextが肥大化するコードを検出 するlinter ○ 例: forループ内でcontext.Contextを同じ変数に再代 入する ● https://github.com/Crocmagnon/fatcon text 47

Slide 48

Slide 48 text

gocheckcompilerdirectives ● compiler directiveが正しいことを検査する linter ○ //go: で始まるコメント群のこと ● https://github.com/leighmcculloch/goch eckcompilerdirectives 48

Slide 49

Slide 49 text

makezero ● スライスのlenを指定して初期化したのに appendしているコードを検出するlinter ● https://github.com/ashanbrown/makezero 49

Slide 50

Slide 50 text

misspell ● スペルミスを検出するlinter ● 同名のツールは有名だと思う ○ 実はGoのlinterとしても走らせられる ● https://github.com/client9/misspell 50

Slide 51

Slide 51 text

nilerr ● 適切にエラーハンドリングできていないコード を検出するlinter ○ if err != nil の分岐内でエラーを返していない ○ if err == nil の分岐内でエラーを返している ● https://github.com/gostaticanalysis/nilerr 51

Slide 52

Slide 52 text

noctx ● context.Contextを使わずにHTTPリクエス トを送るコードを検出するlinter ● https://github.com/sonatard/noctx 52

Slide 53

Slide 53 text

nolintlint ● //nolint コメントが適切に使われていることを 検査するlinter ○ 抑制するlinterが指定されている、など ● https://github.com/ashanbrown/nolintlint 53

Slide 54

Slide 54 text

predeclared ● Goの予約済キーワードと重複する変数名を使っ ていないか検査するlinter ○ Goの予約済キーワードの一覧 ● https://github.com/nishanths/predeclared 54

Slide 55

Slide 55 text

reassign ● package単位のグローバル変数を他のpackageか ら再代入していないか検査するlinter ● https://github.com/curioswitch/go-reassign 55

Slide 56

Slide 56 text

unconvert ● 不要な型変換を行うコードを検出するlinter ○ int型をint型に変換する、など ● https://github.com/mdempsky/unconvert 56

Slide 57

Slide 57 text

チームで有効にしているlinter ● いかがでしたか? ● デフォルト有効のlinterも有効です 57

Slide 58

Slide 58 text

ISUCONでもlinterを活用している ● 前提: ISUCONについて ○ >ISUCONとはLINEヤフー株式会社が運営窓口となっ て開催している、お題となるWebサービスを決められ たレギュレーションの中で限界まで高速化を図る チューニングバトルです ○ https://isucon.net/ 58

Slide 59

Slide 59 text

ISUCONでもlinterを活用している ● 前提: ISUCONについて ○ つまり? ○ 8時間でWebサービスを高速にする 59

Slide 60

Slide 60 text

ISUCONでもlinterを活用している ● デフォルト設定でlintされている ● 呼んでない関数や使ってない引数が分かる ○ 実装をスリムにできると有利 ○ 使ってない実装をじゃんじゃん消せると有利 60