SideCI TechBlog

SideCIを作っているアクトキャットのエンジニアによる技術ブログです。


Go Meta Linter がサポートするツールまとめ

こんにちは、id:Pocke です。
この記事では、Go Meta Linter がサポートするツールについて記そうと思います。

Go Meta Linter とは

Go Meta Linter とは、Go言語で使用できるLinterをまとめて実行することが出来るツールです。
そのため、Go Meta Linterは多くのGoのツールに依存しています。

この記事では、その依存している各ツールがどのような機能を持つのかを述べようと思います。

なお、既に Go の CI で lint と カバレッジ回して非人間的なレビューは自動化しよう in 2016年 - haya14busa という素晴らしい記事があるため、そちらで説明されているツールに関しては適宜この記事へのリンクを貼っています。
上記の記事で言及されていないツールを重点的に説明させていただきます。

Check 三兄弟

レポジトリ: opennota/check: A set of utilities for checking Go sources

AlignCheck

構造体のサイズに無駄がないかを教えてくれます。
http://golang-sizeof.tips/ を見るとわかりやすく解説がされているのでご覧ください。

// Bad
type Foo struct {
    a string
    b bool
    c string
    d bool
}

// Good
type Bar struct {
    a string
  // bool をまとめることでメモリ消費量が減る
    b bool
    d bool
    c string
}

StructCheck

構造体の未使用フィールドを検出します。
以下のように、構造体の埋め込みの場合にfalse positiveが存在します。

package main

import "fmt"

func main() {
    f := new(Foo)
    _ = f
    // fmt.Println(f.a) // この行があれば Foo.a は unused ではない

    b := new(Bar)
    fmt.Println(b.a) // この行があっても Foo.a は unused とみなされる
}

type Bar struct {
    Foo
}

type Foo struct {
    a string
}

なお、このfalse positive は 後述する unused であれば発生しません。

varcheck

未使用グローバル変数、定数を検出します。

// Bad
var foo = "unused"


// Good
var foo = "unused"
fmt.Println(foo)

deadcode

使用していない関数 / 変数などを検知します。

tsenart/deadcode: Standalone repo of deadcode package from http://github.com/remyoudompheng/go-misc

package main

import "fmt"

func main() {
    fmt.Println("hoge")
}

// It's dead code!
func unusedFunc() int {
    return 1
}

// It's dead code!
var unusedVar = "hogehoge"

dupl

mibk/dupl: a tool for code clone detection

重複コードを検出します。 以下のように、変数名などがちょっと変わっていても検出可能で便利です。

package main

import "fmt"

func main() {

}

func F1(cond bool, cond2 string) {
    if cond {
        fmt.Println("hoge")
    } else {
        switch cond2 {
        case "a":
            fmt.Println("A")
        case "B":
            fmt.Println("b")
        }
    }
    for _, bar := range []string{"x", "y", "z"} {
        fmt.Print(bar)
    }
}

func F2(cond bool, cond2 string) {
    if cond {
        fmt.Println("hoge")
    } else {
        switch cond2 {
        case "a":
            fmt.Println("A")
        case "B":
            fmt.Println("b")
        }
    }
    for _, foo := range []string{"x", "y", "z"} {
        fmt.Print(foo)
    }
}

errcheck

kisielk/errcheck: errcheck checks that you checked errors.

error型の戻り値を投げ捨てていないかをチェックします。 詳しくは http://haya14busa.com/ci-for-go-in-end-of-2016/ の errcheck の項目を参照してください。

gas

GoASTScanner/gas: Go AST Scanner

Inspects source code for security problems by scanning the Go AST.

Goのプログラムのセキュリティ上の問題を検出します。

このツールについては別の記事で詳しく解説しているので、そちらも是非ご覧ください。 tech.sideci.com

exec.Commandの使用のみで警告が出るなど、作成するアプリケーションの種類によってはマッチしないルールも多いため、ルールの調整が必要でしょう。

package main

import "os/exec"

func main() {
  // Subprocess launching with partial path. (Confidence: HIGH, Severity: MEDIUM)
    exec.Command("ls")
}

goconst

jgautheron/goconst: Find in Go repeated strings that could be replaced by a constant

同じ文字列を複数ヶ所で使っている場合、それを定数にすることを促すツールです。

package main

func main() {
}

func foo() string {
    return "xxxx"
}
func bar(target string) string {
    if "xxxx" == target {
        return "xxxx"
    }
    return ""
}

デフォルトでは、3箇所で同じ文字列が使用されている場合に怒られる。

gocyclo

alecthomas/gocyclo: Calculate cyclomatic complexities of functions in Go source code.

Goのコードの循環的複雑度を測ることができます。

大量のif文などで多くの分岐がある場合循環的複雑度が増加し、しきい値を超えた場合警告が出されます。

goimports

goimports - GoDoc

goimportsでフォーマットされていない様なimportを検出します。

package main

// "strings" は "fmt" より後にあるべき
import (
    "strings"
    "fmt"
)

func main() {
    fmt.Println(strings.Join([]string{"hoge", "fuga"}, "/"))

}

golint

golang/lint: This is a linter for Go source code.

詳しくは http://haya14busa.com/ci-for-go-in-end-of-2016/ のgolintの項を参照して下さい。

gosimple

simple - GoDoc

Goのコードをよりシンプルにする術を教えてくれます。

詳しくは http://haya14busa.com/ci-for-go-in-end-of-2016/ のgosimpleの項を参照して下さい。

gotype

gotype - GoDoc

Goコンパイラのフロントエンドの様に、型のエラーなどを出力します。

ineffassign

gordonklaus/ineffassign: Detect ineffectual assignments in Go code.

効果のない代入を検出します。

package main

import "fmt"

func main() {
    x := "str" // ここで代入した値は無視されている
    x = "str2"

    fmt.Println(x)
}

interfacer

不必要に広い範囲の引数を受け取る関数を、より狭い範囲を示すインターフェースを受け取るような関数に変更することを提案します。

package main

import (
    "io/ioutil"
    "os"
)

func main() {
}

// f can be io.Reader
func Read(f *os.File) ([]byte, error) {
    return ioutil.ReadAll(f)
}

lll

walle/lll: Line length linter

1行の長さが規定長さ以上になっていないかを検出します。デフォルトでは80文字です。

package main

import "fmt"

func main() {
  // line is 86 characters
    fmt.Println("This is toooooooooooooooooooooooooooooooooooooooooooooooooooooooo long")
}

misspell

client9/misspell: Correct commonly misspelled words in source files

よくあるスペルミスを検出します。Golangのソースコード限定というわけではなく、汎用的に使うことが可能なツールです。

package main

import "fmt"

func main() {
  // "langauge" is a misspelling of "language"
    fmt.Println("langauge")
}

staticcheck

dominikh/go-staticcheck: Staticcheck is go vet on steroids, applying a ton of static analysis checks you might be used to from tools like ReSharper for C#

haya14busa氏いわく、サードパーティのgo vetです。 詳しくは http://haya14busa.com/ci-for-go-in-end-of-2016/ を参照して下さい。

unconvert

mdempsky/unconvert: Remove unnecessary type conversions from Go source

必要のないキャストを検出します。

package main

import "fmt"

func main() {
    var x int64
    var y int64

  // unnecessary conversion
    x += int64(y)
    fmt.Println(x)
}

unused

未使用グローバル変数、定数、などなどを警告します。

詳しくは http://haya14busa.com/ci-for-go-in-end-of-2016/ の go-unused の項を参照して下さい。

go vet

vet - The Go Programming Language

詳しくは http://haya14busa.com/ci-for-go-in-end-of-2016/ の go vet の項を参照して下さい。

go vet shadow

名前が覆い隠されている変数を警告します。

詳しくは go vet の shadow を知る | eureka tech blog を参照して下さい。