読者です 読者をやめる 読者になる 読者になる

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 を参照して下さい。

gas のルールを見ていこうの会

あけましておめでとうございます。 id:Pocke です。

GAS(Go AST Scanner) という、Go言語用のセキュリティ検査ツールがあります。
この記事では、そのツールに含まれるルールを洗い出してみました。

記事のルールは2017/01/05現在のものになります。
https://github.com/GoASTScanner/gas/tree/d4f9b88cbf6b9f190a2425120c9cecc466ec363b

コードナンバー

GASの各ルールにはG101の様なコードナンバーが付与されています。
この番号にはそれぞれ以下のような意味があります。

  • G1xx : misc
  • G2xx : injection
  • G3xx : filesystem
  • G4xx : crypto
  • G5xx : blacklist

では、このルールをひとつづつ見ていきましょう。

G101: Look for hardcoded credentials

クレデンシャル情報をハードコードしてあるものを検出します。

package samples
import "fmt"
func main() {
  username := "admin"
  password := "admin"
  fmt.Println("Doing something with: ", username, password)
}

passwd, pass, password, pwd, secret, token と言ういずれかの名前の変数への代入がある場合に警告を出します。

なお、このパターンは設定で変更できる模様です(未検証)。 See. https://github.com/GoASTScanner/gas/blob/d4f9b88cbf6b9f190a2425120c9cecc466ec363b/rules/hardcoded_credentials.go#L78-L80

G102: Bind to all interfaces

全てのネットワークインターフェイスに対してバインドしているコードを検出します。

package main

import (
    "log"
    "net"
)

func main() {
    l, err := net.Listen("tcp", ":2000")
    if err != nil {
        log.Fatal(err)
    }
    defer l.Close()
}

net.Listen("tcp", ":2000")net.Listen("tcp", "0.0.0.0:2000")と等価であり、この様に記述すると全てのネットワークインターフェイスからのアクセスを許容する様になります。

これにより同じネットワークに繋がった他のPCからのアクセスを許すことになってしまうため、特に公衆無線LANなどに繋いでいる場合は開発中のアプリケーションに外部からアクセスされてしまうため危険です。
net.Listen("tcp", "127.0.0.1")の様に明示的にローカルホストからのアクセスのみを許容する様に変更することで、これを防ぐことが可能です。

Rubyist ではれば Rack server の変更が記憶に新しいかも知れません。
https://github.com/rack/rack/commit/28b014484a8ac0bbb388e7eaeeef159598ec64fc

G103: Audit the use of unsafe block

unsafe package の関数の呼び出しを検出します。

G104: Audit errors not checked

エラーチェックをしていないコードを検出します。

os.Mkdir("/foo", 0700)  // os.Mkdir は error を返すが、この例ではそれをチェックしていないため警告が出る
fmt.Println("foo") // Println のようなエラーチェックを行わないのが一般的な関数に対しては警告は出ない

G201: SQL query construction using format string

SQLのクエリをfmt.Sprintfで組み立てているコードを検出します。

q := fmt.Sprintf("SELECT * FROM foo where name = '%s'", name)

なお、組み立てた文字列を実際にSQLとして使用しているかは見ていないため、上記のqfmt.Println(q)の様に使用している場合でもgasは警告を出すfalse positiveがあります。

G202: SQL query construction using string concatenation

SQLのクエリを、文字列を連結することで組み立てているコードを検出します。

q := "SELECT * FROM foo where name = '%s'" + name

先程のルールと似ており、また同様のfalse positiveがあります。

G203: Use of unescaped data in HTML templates

テンプレートでHTMLを組み立てる際、外部から渡ってきた値がエスケープされているかを検査します。

t := template.Must(template.New("ex").Parse(tmpl))
v := map[string]interface{}{
  "Title":    "Test <b>World</b>", // リテラルなので問題はない
  "Body":     template.HTML(a),    // 外部から渡ってきている値がエスケープされずにテンプレートに反映されてしまうため問題
}
t.Execute(os.Stdout, v)

G204: Audit use of command execution

外部プロセスの実行を検査します。

exec.Command("ls")

また、この検査にはconfidenceレベルが3段階あり、

  • コマンド及び引数に、リテラルでない値が使用されている
  • コマンドの実行パスが絶対パスでない
  • その他

の順で指摘の信頼度が高くなっています。

G301: Poor file permissions used when creating a directory

ディレクトリを作る際のパーミッションが余分でないかを検査します。

os.Mkdir("/tmp/mydir", 0777) // 不必要に権限が付いているため問題を指摘する
os.Mkdir("/tmp/mydir", 0700) // 権限が適切に付いているため問題とはしない

デフォルトでは0700より大きい権限が付いていた際に問題を指摘します。

G302: Poor file permisions used with chmod

G301のChmod, OpenFile版です。G301との相違点として、指摘を出すしきい値のデフォルトが0600であることがあります。

G303: Creating tempfile using a predictable path

テンポラリファイルを作る際に、os.Createを使用しているものを検出します。

file1, err := os.Create("/tmp/demo1")

テンポラリファイルを作成する場合は、os.Createではなくioutil.Tempfileを使用するべきです。

G401: Detect the usage of DES, RC4, or MD5

弱い暗号化アルゴリズムを使用しているコードを検出します。

// md5 は脆弱なため、SHA2などの使用を検討しましょう
md5.Sum(bytes)

// DESは脆弱なため、AESなどの使用を検討しましょう。
des.NewCipher([]byte("sekritz"))

G402: Look for bad TLS connection settings

セキュアではないTLSの設定をしているコードを検出します。

tr := &http.Transport{
  // Verify をスキップしてしまっていることを指摘する
  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}

この他にもいくつかの検出項目があります。

G403: Ensure minimum RSA key length of 2048 bits

RSA鍵の鍵長が2048bit未満である場合を検出します。

pvk, err := rsa.GenerateKey(rand.Reader, 1024)

G404: Insecure random number source (rand)

math/randの使用を検出します。

import (
  crand "crypto/rand"
  mrand "math/rand"
)

crand.Rand(nil) // crypto/rand を使っているため問題はない
mrand.Rand(nil) // math/rand を使っているため警告が出る

Golang公式のドキュメントに

For random numbers suitable for security-sensitive work, see the crypto/rand package. https://golang.org/pkg/math/rand/

と記載があるため、乱数にセキュアさを求める場合(WebアプリケーションのセッションIDなど)であれば、crypto/randを使用するよう修正すべきでしょう。

G501: Import blacklist: crypto/md5
G502: Import blacklist: crypto/des
G503: Import blacklist: crypto/rc4
G504: Import blacklist: net/http/cgi

それぞれのパッケージが脆弱であるなどの理由で、これらのパッケージのインポートをしていないか検出をします。

import (
  "crypto/md5" // MD5の代わりにSHA2などを使用すべきでしょう
)

まとめ

以上がgasが持っているルールになります。
gasを使ってGoアプリケーションのセキュリティを担保してはいかがでしょうか。

また、今回紹介したルールで採用できない物がある場合、gas -exclude=G301,G104 main.go のように-excludeオプションで指定したルールのみを無効に出来るので、ぜひご活用下さい。

RuboCop 0.46.0 のCHANGELOGを読む

f:id:Pocke:20161019163732p:plain

こんにちは、 id:Pocke です。 12月に入り寒さも本格化してきましたが、いかがお過ごしでしょうか?

さて、先日RuboCopのバージョン(0.46.0)がリリースされました。

0.46.0 には、多数の機能追加やバグ修正が含まれています。

このリリースには、SideCIメンバーによるPull Requestも4個含まれています。 今日はそのCHANGELOGから、気になる新機能を見ていきましょう。

新規Cop追加

Copとは、RuboCopにおいてひとつのルールを指す言葉です。例えば、「インデントが正しいかチェックする」「非推奨メソッドを使っていないかチェックする」などが1つのCopの単位になります。

この章では、0.46.0で新たに追加されたCopをひとつずつ紹介します。

Bundler/DuplicatedGem

解説

Bundler/* cop は、このバージョンで新設された区分です。 Gemfileなどbundler関連のファイルに対する検査を行うCopです。

このCopは、Gemfile内の重複したgemを検出します。

# Gemfile
gem 'rubocop'
# .....
gem 'rubocop'

上記のコードでは、rubocopが重複して指定されていますが、このCopではそれを検出することができます。

# Gemfile
group :development do
  gem 'rubocop'
end

group :test do
  gem 'rubocop'
end

また、上記のように複数のグループで同じGemが存在している場合にも警告を出します。上記のようなコードは次のように書き換えることが可能です。

group :development, :test do
  gem 'rubocop'
end

Gemfileが肥大化してくる巨大なプロジェクトでは、このCopが役に立つかも知れません。

Style/EmptyMethod

解説

このCopは、内容が空のメソッドを書く場合のスタイルを検査します。

内容が空のメソッドを記述する際、以下のような2通りの書き方が考えられます。

def empty_method
end

def empty_method; end

このCopは、デフォルトでは下の1行で書くスタイルの書き方を推奨し、上の様な複数行になる書き方にwarningを出します。

もしコーディングスタイルが上の複数行で書くような書き方の場合、.rubocop.ymlに以下の記述を追加することで、そちらの書き方を推奨することが出来ます。

Style/EmptyMethod:
  EnforcedStyle: expanded

また、このCopはAuto-Correctに対応しているため、異なったスタイルのコードを自動的に修正することが可能です。

Rails/EnumUniqueness

解説

Rails 4.1 から、enumの機能が導入されました。

class PullRequest < ApplicationRecord
  enum state: [:open, :closed, :merged]
  # or
  enum state: {open: 10, closed: 20, merged: 30}
end

enumはModelで上記のように定義します。
enumの定義には2種類あり、[:open, :closed, :merged]の用に配列で指定する方法と、{open: 10, closed: 20, merged: 30}のようにHashで指定する方法があります。

この内Hashで指定する方法はArrayでの指定と違い、新たな項目をenumに追加しても値がずれることがなくて便利なのですが、気をつけないと次のように値が重複してしまう可能性があります。

class PullRequest < ApplicationRecord
  # open と merged の値が両方共10になっている!
  enum state: {open: 10, closed: 20, merged: 10}
end

このCopは、このような重複したenumの値を検出します。

Bundler/OrderedGems

解説

このCopは、Gemfile内でgemがアルファベット順に並んでいるかを検査します。

# Gemfile
gem 'devise'
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-github'
gem 'omniauth-twitter'

gem 'koala'
gem 'octokit'
gem 'twitter'

上記のようなGemfileはgemがアルファベット順に並んでいると認識されます。 また、例えばomniauth-twitteromniauth-facebookより前にある状態では、warningが発生します。

上記の例からもわかる通り空行を挟んだ場合は段落が分けて考えられるため、カテゴリ毎にGemを分けた上でアルファベット順に並べることが可能です。

このCopは現状Auto-Correctをサポートしていないため、アルファベット順に並べ直すためには手動で行う必要があります。 この件に関してはPull Requestを作成したため、次期リリースでは自動で並び替えが出来るようになると思われます。 https://github.com/bbatsov/rubocop/pull/3757

まとめ

この記事は以上になりますが、RuboCop 0.46.0ではこの他にも多くの機能追加、バグ修正が行われています。 より詳しい変更を知りたい方は、リリースノートをご覧ください。

Release RuboCop 0.46 · bbatsov/rubocop

また、SideCIでは今すぐRuboCop 0.46.0を利用することが可能です。是非お試し下さい!

Flake8 のプラグイン機構についてのお話

こんにちは、 id:yykamei です。 今回の内容は、 SideCI でも利用している Flake8 についてです。 ちょうど 1 年ほど前に 弊社の記事 で取り上げた内容と 重複する部分もありますが、主に Flake8 のプラグインについて述べたいと 思います。

ここでは、 記事執筆時点で最新の Flake8 (バージョン 3.2.1) をベースに、 主に以下の内容を取り上げます。

  • Flake8 についておさらい
  • Flake8 のプラグインとは
  • Flake8 プラグインの作り方

Flake8 についておさらい

Flake8 は以下のツールのラッパーとして実装されています。

ここで 1 年前の記事には出てこなかった、 pycodestyle というツールが登場 します。これは pep8 がリネームされたものになります。 Flake8 を使っていれば、 pep8 が pycodestyle にリネームされたことを意識する必要はありませんが、 頭の片隅に入れておきましょう。

Flake8 の指摘は、特にプラグインなどをインストールしていなければ次のように 分けられます。

  • Exxx, Wxxx: pycodestyle の出力
    • PEP8 に準拠しないコードの指摘
  • Fxxx: PyFlakes の出力
    • 実行時エラーとなるコードの指摘
  • C901: McCabe
    • 循環的複雑度が高いコードの指摘

Flake8 のプラグインとは

標準の Flake8 は上述の通り 3 つのツールをラップしていますが、 McCabe のみ、 Flake8 の純粋なプラグインとして Flake8 から 利用されています (McCabe 自体は単体でも動作しますが、 McCabe 自身が Flake8 からも利用できるような実装になっています) 。

そもそも、 Flake8 のプラグインとは何を指すのでしょうか。

Flake8 では、 setuptools の Entry Points の仕組みを 利用して、サードパーティーのプラグインをロードしています。 setuptools は Python のパッケージングの仕組みを提供するライブラリーで、 Python 界隈で はデファクトとなっています。

重要なポイントは、 Flake8 のプラグインはあくまでも setuptools の 仕組みの上に乗ったものであり、決して独自のプラグイン機構を 備えているわけではないということです。

これによって、 Flake8 プラグイン製作者は標準の仕組みでパッケージングする だけで、容易にプラグインを制作することができます。 また、 Flake8 利用者には、プラグインを自分の環境にインストールするだけで プラグインを利用できるという利点があります。

なお、 Flake8 は、実行時に以下の名前空間の Entry Points を走査しています。

  • flake8.extension
    • コードチェックをする Entry Points のための名前空間
  • flake8.listen
    • Auto Correct をする Entry Points のための名前空間
  • flake8.report
    • 指摘の出力をカスタマイズする Entry Points のための名前空間

主な Flake8 プラグイン

  • hacking
  • flake8-docstrings
  • flake8-import-order

Flake8 プラグインの作り方

このように Flake8 では、プラグイン制作が容易に行える仕組みを備えていますので、 私もプラグインを作ってみたいと思います。

Flake8 プラグインの作成方法は Flake8 のドキュメント にあります。他にも、参考にした記事がございますので本記事下部にあるリンク をご参照ください。

環境準備

今回は、 Python 3.5.2 を利用します。まず、作業用の独立した環境を作り、 Flake8 をインストールします。

$ python -m venv venv
$ source venv/bin/activate
$ pip install flake8

Entry Points の定義

さっそくコードを作成します。まずは、以下の内容で、 setup.py というファ イルを作成します。重要なのは、 setup() に渡す entry_points の内容です。 これが上述の Entry Points になります。

entry_points には dict を渡します。また、ここでは、 Lint をしたいので、 'flake8.extension' をキーに渡します。 'flake8.extension' に対応する値は list で、 エラーコード = module:callable のような str を記述します。今回は X801 という エラーコードを定義したいと思います (なお、ここのエラーコードは現時点での実装では エラーコードではなくてもよいですが、 わかりやすさのためにエラーコードにしています) 。

# -*- coding: utf-8 -*-

from setuptools import find_packages, setup

setup(
    name='flake8-english-please',
    packages=find_packages(),
    entry_points={
        'flake8.extension': [
            'X801 = flake8_english_please.checker:english_checker',
        ],
    },
)

Lint の実装

続いて、 Lint を実際に行うコードを実装します。今回は english_checker という、コメント中に英語 (というより ASCII) 以外の文字を検出したときに エラーを吐き出す Lint を実装します (あまり実用的ではありませんが) 。

まずは、 Python パッケージのディレクトリー構造をつくります。

$ mkdir flake8_english_please
$ touch flake8_english_please/__init__.py

続いて、 flake8_english_please/checker.py というファイルを以下の内容で 作成します。

今回の Lint は物理行単位 physical_line でのチェックを行います。

コメント中に ASCII 以外の文字を検出したときに指摘する Lint
import tokenize

X801 = 'X801 This is a country where we speak English'


def english_checker(physical_line, tokens):
    for token_type, text, start, _, _ in tokens:
        if token_type != tokenize.COMMENT:
            continue
        for index, x in enumerate(text):
            if not ord(x) < 128:
                return (start[1] + index, X801)


english_checker.name = 'english_checker'
english_checker.version = '0.1.0'

補足ですが、 Lint となる実装は第一引数に以下のいずれかを指定する必要があります。

  • tree
    • Python の AST を受け取り、ファイル単位で Lint を行いたいときに利用
    • run というメソッドを定義した class で実装してもよい
      • その場合、コンストラクター (__init__) で tree を受け取るようにする
  • physical_line
    • 物理行ごとに Lint を行いたいときに利用 (今回の実装)
    • 関数しか定義できない
  • logical_line
    • 論理行ごとに Lint を行いたいときに利用
    • ジェネレーターしか定義できない

さらに、第二引数以降に Flake8 に対して欲しい情報を引数に設定して要求することができます。 詳細は こちら をご覧ください。

また、余談ですがこの例では指摘のメッセージを TrumpScript から拝借しています。

実装したプラグインを利用してみる

では、さっそくインストールしてみます。

$ python setup.py install

これでプラグインを利用する準備が整いました。

試しに、以下のようなファイル (example.py) を作成し、 flake8 を実行してみましょう。

# この関数は何かをします
#
# Hi! はい


def some():
    print('Some')
$ flake8 example.py

以下のような結果が出て、コマンド実行後の status code も 1 になっていれば 問題なく実装できています。

example.py:1:3: X801 This is a country where we speak English
example.py:3:7: X801 This is a country where we speak English

まとめ

今回は非常に簡単で実用性のないプラグインを実装しましたが、 Flake8 のプラグインが 容易に実装可能なことがおわかりいただけたかと思います。良いアイディアがあればぜひ 実装してみましょう。

参考にした記事

MItamaeを開発環境構築に使ってみた話

こんにちは。最近何も考えたくないときはアニメ「ゆゆ式」を観ている笠井です。 前回のブログを書いたえりいさんも笠井姓ですが、特に親戚というわけではありません。10人前後の会社で笠井姓が2人いるってどんな確率なんだろうかなどと考えています。

今回は、構成管理ツールの「MItamae」を使って開発環境を作ってみました。なお、環境は macOS Sierra です。

MItamae とは

github.com

MItamae は @k0kubun さんが開発した、mruby-cli を利用した pure mruby 実装の構成管理ツールです。 元々は itamae-mruby という名前でした。 読み方は「見給え」ではなく「えむ・いたまえ」です。

元々 Itamae という Ruby DSL で書ける構成管理ツールがありますが、MItamae と Itamae の大きな違いは、 Ruby や gem に依存しないことです。これまで Itamae を使ってプロビジョニングしようとした場合、レシピを実行するには Ruby であったり itamae gem が必要でした。そのため、Ruby の入っていない環境でプロビジョニングしようとした場合、Ruby をインストールするか外部からプロビジョニングする対象にSSHして Itamae を実行する必要がありました。そのためだけに Ruby を入れるのはめんどくさいです。

一方、MItamae はシングルバイナリをダウンロードするだけで実行できます。また、コンパイル済みなので起動が高速です。 curl なり wget なりでもってきたら、そのまま実行できると思います。

詳しくは k0kubun さんのブログがわかりやすいのではと思います。

MItamae レシピ

ディレクトリ構成

はじめに私の作った dotfiles も一応置いておきます。だいたいの構成等は k0kubun さんの dotfiles にかなり倣っています。

github.com

ざっくりとした構成は以下のようになっていて、Itamae ベストプラクティスに準拠しています。

% tree -aL 2
.
├── .gitignore
├── .gitmodules
├── README.md
├── bin
│   └── setup_mitamae
├── bootstrap.rb
├── config
│   ├── .bash_profile
│   ├── .gitconfig
│   ├── .tmux.conf
│   ├── .vim
│   ├── .vimperatorrc
│   ├── .vimrc
│   ├── .zplug
│   ├── .zshrc
│   ├── bin
│   ├── peco
│   └── zshrc
├── cookbooks
│   ├── .keep
│   ├── git
│   ├── ruby
│   ├── symboliclinks
│   ├── vim
│   └── zsh
├── install.sh
├── plugins
│   └── itamae-plugin-recipe-rbenv
└── roles
    ├── .keep
    ├── darwin
    └── linux

全体の流れは、install.sh を全体の起点にしていて、これが setup_mitamae を呼び、 MItamae のバイナリをダウンロードしたあと、 bootstrap.rb を実行して、OS に合わせて role 及び cookbook を実行します。

レシピづくりは itamae のベストプラクティスに乗っかるので、itamae コマンドを使うために、 itamae をインストールして作っていくのが早いと思います。実行時は Ruby はなくてもいいけど、レシピ作りには必要です。

% gem install itamae
% mv dotfiles old-dotfiles

などとして、既存のdotfilesをリネームして逃しておきます。あとから逃した old-dotfiles 内の各ファイルを dotfiles に再度移植します。

いろいろな cookbook

この cookbook において、一番重要であろうレシピはおそらくシンボリックリンクを張れるようにするレシピだと思います。これを最初に実行する cookbook にしています。

define :ln do
  dotfile = File.join(ENV['HOME'], params[:name])
  link dotfile do
    to File.expand_path("../../../config/#{params[:name]}", __FILE__)
    not_if "test -e #{dotfile}"
  end
end

このようなファイルを書いておくと、後続の Vim や Zsh の cookbook において、

ln '.vimrc'
ln '.zshrc'

などと書いておけば、レシピを流した時に勝手にシンボリックを張ってくれます。なお、私は NeoVim も使うことがあるのですが、NeoVim の設定ファイルである init.vim と .vimrc をシンボリックを張って共通化する部分は、上記の ln は使えないので、以下のようにして逃げています。

directory "#{ENV['HOME']}/.config/nvim" do
  action :create
  not_if "test -d #{ENV['HOME']}/.config/nvim"
end

# http://qiita.com/okamos/items/2259d5c770d51b88d75b
link "#{ENV['HOME']}/.config/nvim" do
  to "#{ENV['HOME']}/.vim"
  force true
end

link "#{ENV['HOME']}/.config/nvim/init.vim" do
  to "#{ENV['HOME']}/.vimrc"
  force true
end

MItamae は Itamae で使える resource のほとんどがそのまま流用できます。べんり。

また、私は普段 Ruby を使って仕事をすることが多いので、Ruby をインストールするための cookbook も作ります。Ruby のインストールでは自力で cookbook を書かず、既存の Itamae plugin を利用したいと思います。

github.com

通常 Itamae plugin は Gemfile に記述して導入するのですが、そもそも mruby である MItamae はそのような機構をもっていません。したがって、dotfiles ディレクトリの中に plugins ディレクトリを作成し、Git submodule を使うことで Itamae plugin を導入します。

% mkdir plugins && cd plugins
% git submodule git@github.com:k0kubun/itamae-plugin-recipe-rbenv.git

MItamae の plugin の扱いについてのドキュメントを置いておきます。

itamae-plugin-recipe-rbenv に関しては MItamae の場合における使い方が既にREADMEにあるので、それを利用してレシピを作りました。

node.reverse_merge!(
  rbenv: {
    user: 'akito',
    global: '2.3.1',
    versions: %w[
      2.3.2
      2.3.1
      2.3.0
    ],
  }
)

include_recipe "rbenv::user"

これで一旦レシピは完成なので、あとは新しい環境で起点用に作った install.sh を実行したらあとは MItamae が勝手にいろいろやってくれます。

既にシンボリックリンクとか張ったあとに実行したやつですが、実行すると以下のような形になります。

 % bin/mitamae-x86_64-darwin-0.6.0-invoke local bootstrap.rb
 INFO : Starting MItamae...
 INFO : Recipe: /Users/akito/dotfiles/bootstrap.rb
 INFO :   Recipe: /Users/akito/dotfiles/roles/darwin/default.rb
 INFO :     Recipe: /Users/akito/dotfiles/cookbooks/symboliclinks/default.rb
 INFO :     Recipe: /Users/akito/dotfiles/cookbooks/vim/default.rb
 INFO :       link[/Users/akito/.vim] to will change from '/Users/akito/dotfiles/config/.vim' to '/Users/akito/.config/nvim'
 INFO :     Recipe: /Users/akito/dotfiles/cookbooks/git/default.rb
 INFO :     Recipe: /Users/akito/dotfiles/cookbooks/zsh/default.rb
 INFO :     Recipe: /Users/akito/dotfiles/cookbooks/ruby/default.rb
 INFO :       Recipe: plugins/itamae-plugin-recipe-rbenv/mrblib/itamae/plugin/recipe/rbenv/user.rb
 INFO :         Recipe: plugins/itamae-plugin-recipe-rbenv/mrblib/itamae/plugin/recipe/rbenv/install.rb
 INFO :           Recipe: plugins/itamae-plugin-recipe-rbenv/mrblib/itamae/plugin/recipe/rbenv/dependency.rb

さいごに

MItamae は、 Roppongi.rb で k0kubun さんが MItamaeの発表をしていて、そこで知りました。ほとんど Itamae そのままに書けるので、個人的にそこまで戸惑うことなく導入できました。

しかし、私の途中のやり方がまずかったのか、途中、MItamae のシンボリックリンクを張るところでそれが壊れてしまっている(なので上の例では bin/mitamae-x86_64-darwin-0.6.0-invoke みたいな長い名前でバイナリ実行してる)ので、締切が近い卒論を片付けたら直そうと思います。。

NeoVim もシンボリックリンクを張るだけじゃなくてインストールしてビルドするあたりまで書きたいし、ほかにもいくつかやりたいことがあるので、そのへんも追い追いやりたいと思っています。

Itamae は Wiki が充実しているので、書き方に困ったら、そのWikiItamae チートシートとかみるとべんりです。

負債カンバンのデザイン変遷

初めまして、笠井枝理依です!

下の名前を読めない方が時々いらっしゃるので念のため、「えりい」と読みます。サザンオールスターズのファンである父が、『いとしのエリー』という曲から名前をとりました。(皆さんに名前を覚えていただく作戦です!)

Actcatには初のデザイナー&初の女性メンバーとして9月頭に入社し、バンクーバーからリモート勤務しています。私は今でこそデザイナーとして働いていますが、ずっとそうだったわけではなく、IT業界で様々な職種を経験してきました。

  • ハウツーサイトを運営する企業でインハウスライター
  • 決済サービスを運営する企業で法人営業
  • 人材紹介プラットフォームを運営する企業でマークアップエンジニア
  • IT企業への投資をメインに展開する、バンクーバーの企業でUIデベロッパー
  • Actcatでデザイナー

Actcatではこれまでの経歴を活かしてライティング、マーケティング施策から実装まで幅広く取り組ませていただいてます!

さてさて自己紹介もさせていただいたところで、この記事の本題に入りましょう。今回はActcatのデザイナーとして初めて取り組んだ、SideCIの新機能「負債カンバン」のデザイン変遷をご紹介します。デザイナーがデザインする際にどんなことを考えているのか、一例として参考になるところがあれば幸いです。

デザインの変遷をご紹介する前に

負債カンバンとは

「負債カンバン」とは、SideCIがユーザーのGitリポジトリを解析して発見した問題点(負債)をカードにし、優先度に応じてエリア毎にカードを並べる機能です。各カードをクリックすると、その問題点の詳細を確認することができるようになっています。現在はベータ版でRubyのみ対応していますが、今後対応言語を広げていく予定です。

ワイヤーフレーム

こちらがデザインのベースになったワイヤーフレームの一部。負債カンバンのモックアップ作成は体験入社の課題も兼ねており、ワイヤフレームはActcatで既に用意されていました。

f:id:elle_kasai:20161104103251p:plain

第1弾: 情報をとにかく削ぎ落とす

受け取ったワイヤーフレームを元に作成した、一番最初のモックアップがこちら。

f:id:elle_kasai:20161104103303p:plain

デザインした当時考えていたこと

ワイヤーを渡されて最初に思ったのは、視覚情報が多すぎるということでした。各カードに表示するテキスト量が多く、ユーザーがぱっと見た時に面倒だと感じさせてしまいそうです。カードの詳細情報も当初はツールチップで表示するようになっていたので、カーソルを合わせただけでぶわっと詳細が出てくる仕様。それだとカーソルを動かす度にカードの詳細が表示され、ガチャガチャとうるさくなりそうでした。

そこでまずは、情報をとにかく削ぎ落とし、極力シンプルになるようデザインしてみました。

ポイント

  • 色情報を抑える
    • 背景やディレクトリをグレースケールにして、色情報を抑える
    • 一方で各カードには緊急度に応じてポイント色を入れ、エリアの優先度をわかりやすくする
  • 指摘内容のカテゴリに合わせたアイコンのみを使用して、極力カード内の文字を少なくする
    • アイコンにカーソルを合わせて初めて、ツールチップでカテゴリを表示する
  • カードの詳細はツールチップではなく、クリックでのモーダル表示にする
    • カード内に表示しなかった情報もモーダル内に収める

このデザインで生じた課題

表示する情報をだいぶ抑えたことでページがスッキリしたのは良いのですが、一方で下記のような課題も出てきました。

  • 結局ツールチップが無くなっていない
    • ツールチップで表示される情報量は少なくなったものの、依然カーソルを動かすことで画面がガチャガチャする
    • むしろ同じカード内なのに、カーソルを合わせる位置によって表現が違うのはユーザーに不親切
  • カード、エリアの優先度がわかりづらい
    • ポイント色で同じタイプのカードがどれかはわかるが、そのカードの優先度や評価がわかりづらい
    • 左から右に向かって優先度が低くなっていることが、テキストを読まないとわからない

第2弾: 優先度の表現を意識しすぎてカラフルに

課題が残ったままのデザインではありましたが、伸び代を見出していただけたようです。晴れてActcatの一員になった私は、早速負債カンバンのデザインをブラッシュアップすることになりました。しかし前回のデザインで生じた課題を解決しようと試行錯誤した結果、とんでもなくカラフルになってしまったのです。

f:id:elle_kasai:20161104103308j:plain

デザインした当時考えていたこと

まず認識不足だったのが、同じエリアにコード品質が高いものと低いもの、両方がある可能性です。つまりコード品質が例えA評価だとしても、指摘内容によっては「早急に対応してください」というエリアに分類されたりもするということ。前回のモックアップではその状態がイメージしにくかったので、各エリアに様々なコード品質のカードを並べることにしました。

次はカード内の情報を再検討。いくつか追加する際に、表現に強弱をつけて情報のメリハリを意識しました。こうしてアイコンは無くなり、ツールチップを完全に廃止。またエリアの優先度についても、直感的にわかるように背景色を敷くことにしました。

ポイント

  • カードの指摘内容とコード品質を表すアルファベットを再追加
    • カード自体の優先度がわかりやすくなるよう、アルファベットをカードのポイント色に揃える
    • 一方で指摘内容は詳細をモーダル内で表示しているので、ここでは薄めのグレーに留める
  • 左から順に、「赤→オレンジ→灰色→白」の背景色を敷く
    • 特に左側の2つに注目してもらいたいので、右側の2つはグレースケールにする
    • カードのポイント色が強いので、視覚的にきつくならないように背景色は薄めにする

このデザインで生じた課題

エリアの優先度も重要、カード自体の優先度も重要…と色を加えるうちに、結局どれが大事な情報なのかが伝わらない、うるさいデザインになってしまいました。これは情報の優先順位を考え直す必要がありそうです。

  • 色を使うルールがそもそも崩れている
    • 背景色は赤/オレンジ/灰色/白
    • カードは赤/オレンジ/緑
  • どれが一番重要なカードなのかわかりづらい
    • エリアとカード、優先度の軸が2つあり、両方の軸で似たようなカラーの表現をいれている
    • 赤のエリアにオレンジや緑のカードが入ってしまうことでユーザーを混乱させる

第3弾: 情報設計の見直しと、色使いの調整

前回のカラフルなデザインで情報の優先順位づけに困った私は、他メンバーや外部のデザイン顧問の方の協力を得て、優先順位を整理することにしました。その結果、カードの優先順位よりもエリアの優先順位をユーザーに理解してもらう方が、より重要だという結論に。そうしてできたモックアップがこちらです。

f:id:elle_kasai:20161104103312j:plain

デザインした当時考えていたこと

エリアの優先順位の方が重要と結論が出たおかげで、色使いのメリハリも考えやすくなりました。まずはカードから思い切ってポイント色を抜き、エリアの優先度をわかりやすくすることに集中したのです。そうした時に気になってくるのは、「果たして背景色だけで、直感的に優先度を理解してもらえるかどうか」ということ。直感的にわかるようにする一案として、エリアタイトルの横にアイコンを置くことにしました。

ポイント

  • エリア > カード
    • カードはグレースケールにして、赤やオレンジといったカラーはエリアの背景色だけに使う
    • カードはファイル毎に作られているので、カード内情報ではファイル名を目立たせ、あとの情報をグレーにする
    • カードは全て同じカラーリングになったので、カードの左端に入れていたカラーボーダーも不要
    • カード自体の優先度はアルファベットの並び順によって認識してもらう
  • タイトル横にアイコンを入れる
    • エリアによってアイコンを変えることで、違いを表現
    • 左から右にいくに従って優先度が低くなるので、アイコンに一貫性をもたせる

このデザインで生じた課題

色使いがスッキリしたことで、カード内情報のグルーピングや、アイコンの表現により目を向けられるようになりました。

  • カード内情報のグルーピングがわかりづらい
    • 現状はファイル名の下に線が一本入っているだけで、グルーピングとしては物足りない
    • アルファベットだけカードに表示されても、パッと見ただけでは何を表しているのかわからない
  • アイコンの視認性が高くない
    • アイコンの肌色が背景に馴染んでおり、表情も細かいので違いがわかりづらい

第4弾: 情報のグルーピングとアイコンの変更

そしてこれが現状の負債カンバンです。第1弾に比べるとだいぶわかりやすくなったのではないかと思います。情報のグルーピングとアイコンのわかりやすさを改善するため、色んなアイディアを出し合っては形にしてみて、ここまでたどり着きました。

f:id:elle_kasai:20161104103314j:plain

デザインした当時考えていたこと

せっかくカードから色を抜くことができたのだから、カードに必要以上のものを載せたくありませんでした。またアイコンについても、顔のような複雑なものは使わないようにしつつ、抽象的過ぎてユーザーに伝わらないのも避けたいと考えていました。

ポイント

  • カード内情報のグルーピングは必要最低限を意識
    • 線を無くす代わりに薄いグレーの背景を入れることで、よりグルーピングをはっきりさせる
    • アルファベットだけだと何を表しているのかわかりづらいので、「コード品質」というラベルを小さく挿入する
  • タイトル横のアイコンをビックリマークにすることで視認性を高める
    • 案1として考えたお天気アイコンはわかりやすいが、複雑なデザインになる
    • 案2として考えたの目盛りアイコンは単純なデザインだが、抽象度が高く、違う意味で捉えられる可能性がある
    • アイコンとエリアの背景色を同系色にして調和させ、中のビックリマークの数を増やすことで重要度を表現する

おわりに

いかがでしたでしょうか?自身が過去に作った拙いデザインを公開するのは恥ずかしいものですが、各段階を見れた方が読み手にとっても参考になると思ったので、思い切って載せることにしました。

こうして何段階かの変遷を経た負債カンバンですが、ベータ版ということで今も尚改善中です。ユーザーの皆さまにとって使いやすいものを提供したいと、デザインサイドでも日々奮闘しておりますので、ぜひお気軽にご意見・ご要望をお寄せください!

そしてその改善を手伝ってくれるメンバーも随時募集しております!興味のある方はぜひこちらもチェックしてみてくださいね。

RuboCop 0.45.0 のCHANGELOGを読む

f:id:Pocke:20161019163732p:plain

こんにちは、id:Pocke です。 ハロウィンはどう過ごされたでしょうか? 私は渋谷にあるオフィスには行かずリモートで勤務していました。

さて、先日RuboCopのバージョン0.45.0(The Halloween Release!)がリリースされました。

0.45.0 には、多数の機能追加やバグ修正が含まれています。

このリリースには、SideCIメンバーによるPull Requestも10個含まれています。 今日はそのCHANGELOGから、気になる新機能を見ていきましょう。

新規Cop追加

Copとは、RuboCopにおいてひとつのルールを指す言葉です。例えば、「インデントが正しいかチェックする」「非推奨メソッドを使っていないかチェックする」などが1つのCopの単位になります。

この章では、0.45.0で新たに追加されたCopをひとつずつ紹介します。

Lint/EmptyWhen

解説

このCopは、空のwhen節を検出します。

case variable
when 'x'
  do_something
when 'y'
  # Nothing
end

上記のコードではvariable'y'の場合のwhen節が書かれていますが、この中身は空であるため意味を成しません。 そのため、上記のwhen 'y'節はそのまま削除することが出来ます。

ただし、以下の様なコードの場合はwhen節を削除すると結果が変わってしまう為、安全にwhen節を削除出来ないので注意が必要です。

# else 節が存在する場合
case variable
when 'x'
  do_something
when 'y'
  # Nothing
else
  do_something2
end

# 空のwhen節を、他のwhen節がカバーする場合
case number 
when 42
  # 42 ならば何もしない
when 1..100
  do_something
when 101..200
  do_something2
end

# when 節の条件が他に影響を及ぼす場合
case variable
when p('x')
  # Nothing
when 'y'
  do_something
end

Style/MultilineIfModifier

解説

Rubyには通常のif式とは別に、修飾子スタイルのif式が存在します。 この二つのif式は、両者共に同様の結果を返します。

# 通常の if 式
if cond
  do_something
end

# 修飾子スタイルの if 式
do_something if cond

修飾子スタイルのifは記述が簡潔になるため便利ですが、以下のように複数行に渡る式に対して使用すると分かりづらいと言う欠点があります。

array.each do |item|
  item.do_something
end if cond

このCopは、上記のような複数行にかかるif修飾子を検出します。

また、このCopはAuto-Correctに対応しているので、RuboCopに-aオプションを付与して実行することで、自動的に修正することが可能です。

Style/SpaceInLambdaLiteral

解説

Rubyには無名関数を定義する方法がいくつかありますが、そのうちの一つにlambdaのSyntaxがあります。

a = -> (x) { puts x }

参考: Rubyで使われる記号の意味(正規表現の複雑な記号は除く) (Ruby 2.3.0)

このCopは、lambdaを定義する際の->(の間にスペースを入れるかどうかを検査します。

a = -> (x) { puts x }
a = ->(x) { puts x }

デフォルトの設定では、間にスペースを入れない後者のコードを正しいものとし、スペースが入った前者のコードにはwarningを出します。

.rubocop.ymlに以下のように記述することで、逆の設定を行うことが可能です。

Style/SpaceInLambdaLiteral:
  EnforcedStyle: require_space

Lint/EmptyExpression

解説

Rubyは以下のような空のカッコも正しい式とみなします。 また、その値はnilとなります。

a = ()
p a # => nil

このようなコードは何らかの誤りである可能性が高いでしょう。
そのため、このCopは上記のようなコードに対してwarningを出します。

その他新機能

今回のリリースでは新規Cop以外にもいくつかの新機能が追加されています。 そのうちのいくつかを見ていきましょう。

ドキュメントにCopのリストが追加

正確には新機能ではないのですが、今回のリリースに含まれているので紹介させていただきます。

上記のリンクに全てのCopのドキュメントが書かれています。

各Copについて、解説、使用例、デフォルト値などが書かれているので、Copを設定する際の良い手助けになるでしょう。

--force-default-configオプションの追加

--force-default-configオプションを使用すると、.rubocop.ymlなどの存在を無視してRuboCopを動作させることが可能です。
今までは同様のことをrubocop --config /dev/nullのようなワークアラウンドで実現することが出来ましたが、このオプションによって公式にこの機能が提供されました。

RuboCop自体のデバッグの際などに便利かも知れませんね。

まとめ

この記事は以上になりますが、RuboCop 0.45.0ではこの他にも多くの機能追加、バグ修正が行われています。 より詳しい変更を知りたい方は、リリースノートをご覧ください。

Release RuboCop 0.45 (The Halloween Release) · bbatsov/rubocop

また、SideCIでは今すぐRuboCop 0.45.0を利用することが可能です。是非お試し下さい!