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

SideCI TechBlog

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

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を利用することが可能です。是非お試し下さい!

RuboCop 0.44.0 / 0.44.1 のCHANGELOGを読む

f:id:Pocke:20161019163732p:plain

こんにちは、RuboCop大好き!Pockeです。

先日、RuboCopのバージョン0.44.0、0.44.1がリリースされました。

0.44.0 には、多数の機能追加やバグ修正が含まれています。また、0.44.1は1件のバグ修正を含んだhotfixリリースです。

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

新規Cop追加

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

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

Rails/HttpPositionalArguments

解説

Rails 5からActionController::TestCase::Behaviorの挙動がかわり、以下のようなコードは非推奨になりました。

describe UsersController do
  it '#show' do
    get :show, id: 1
  end
end

get :show, id: 1の部分が非推奨です。 これをget :show, params: {id: 1}の様に書き換える必要があります。

この変更によって、以下のようにparamssessionの両方を区別して扱うことが可能になります。

describe UsersController do
  it '#show' do
    get :show, params: { id: 1 }, session: { user_id: 1 }
  end
end

このCopは、上記のような非推奨になった書き方を検出します。 また、Auto-Correct機能によって自動的に修正することも可能です。

参考: https://til.hashrocket.com/posts/e7a2880383-rails-5-deprecation-warning-for-controller-tests

なお、Rails 5系を使っていない場合、このCopは誤検知を起こしてしまうようです(Rails/HttpPositionalArguments points offenses in rails 4.2 apps · Issue #3629 · bbatsov/rubocop)。 そのような場合には以下のコードを.rubocop.ymlに追記することでこのCopを無効に出来ます。

Rails/HttpPositionalArguments:
  Enabled: false

Metrics/BlockLength

解説

このCopは、一つのブロックに記述される行数をチェックします。 デフォルトでは、1つのブロックに26行以上のコードがある場合、警告を出します。

do_something do
  method1
  method2
  method3

  # ...

  method26
end

上記の様に26行以上のブロックがあるrubyファイルに対してRuboCopを走らせると、以下の警告が発生します。

$ rubocop
Inspecting 1 file
C

Offenses:

test.rb:1:1: C: Block has too many lines. [30/25]
do_something do ...
^^^^^^^^^^^^^^^

1 file inspected, 1 offense detected

この値は、.rubocop.ymlの中で以下のように変更可能です。

Metrics/BlockLength:
  Max: 25 # この値を修正することで変更可能

Rails/DynamicFindBy

解説

このCopは、ActiveRecordが動的に生成するfind_by_*メソッドの使用を検出します。

# Bad
User.find_by_email(email)

# Good
User.find_by(email: email)


# Bad
User.find_by_email_and_name(email, name)

# Good
User.find_by(email: email, name: name)

また、このCopはAuto-Correctをサポートしているため、上記のようなコードを自動で修正することが可能です。

Rails/DelegateAllowBlank

解説

ActiveSupport には委譲をサポートするModule#delegateメソッドがあります。

http://api.rubyonrails.org/classes/Module.html#method-i-delegate

このメソッドはdelegate先のメソッド、変数がnilの場合、通常はNoMethodErrorを発生します。 これを抑制し単にnilを返すようにするオプションとして、allow_nilが用意されています。

class Foo
  delegate :name1, to: :@hoge
  delegate :name2, to: :@fuga, allow_nil: true
end

foo = Foo.new
foo.name1 # raises NoMethodError
foo.name2 # => nil

一方、Railsではvalidationメソッドなどでallow_blankという似たようなオプションが提供されています。

このCopでは、上記の名前の似たオプションを間違えていないかを検査します。

class Foo
  # Good
  delegate :name, to: :@hoge, allow_nil: true

  # Bad
  delegate :age, to: :@fuga, allow_blank: true
end

Style/MultilineMemoization

解説

このCopは、Rubyでメモ化を行う際のスタイルを統一します。

Rubyで複数文の戻り値に対してメモ化を行う際は、以下の二つの書き方が想定されます。 なお、この2つのプログラムは全く同じ挙動を示します。

foo ||= begin
  bar
  baz
end

foo ||= (
  bar
  baz
)

このCopは、この内の前者の書き方(begin-end)に統一します。
現状ではカッコを使用する書き方に統一するルールは用意されていませんが、需要があれば実装するようなので、そのようなコーディングスタイルを使用している方は要望してみてはいかがでしょうか?

まとめ

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

Release RuboCop 0.44 (The Birthday Release) · bbatsov/rubocop

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

GitリポジトリをAmazon EFSにcloneしたら遅かった話

この記事で説明したGitの話には、私の誤解が含まれています。コメントまで見てください。(EFSにリポジトリを置くと遅いのは本当。)

先日教えてもらったのですが、Amazon EFSというめちゃくちゃ便利に見えるサービスがあります。

  • 複数のEC2インスタンスで共有できるストレージ
  • 事前に容量を決める必要がない(使ったら使った分だけ増えていく)

要するにNFSで、EBSと違って複数のEC2インスタンスから共有できるのが特に便利に見えます。具体的に言うと、SideCIでgit cloneしてきたリポジトリを保存して共有するのに最適に見えます。(見えました。)

SideCIでは、Gitリポジトリの操作を抽象化したサーバの開発を現在進めていて、

  • git cloneして欲しいリビジョンをgit archive
  • git diffして変更された行を特定

などの操作をWeb API経由で実行できるようになりたいと考えています。(リファクタリングの話なのでサービスの強化には、すぐには繋がらないのですが……)このとき問題になるのが、GitHubからcloneしてきたリポジトリをどこに保存するかということです。

Webサーバそれぞれのローカルストレージに保存しても良いのですが、

  • 複数台のWebサーバでリポジトリを共有できるとcloneが減ってより高速に実行できる
  • できるだけローカルのリポジトリには消えて欲しくないのでデプロイが面倒になる

といった問題があります。EFSにリポジトリを保存するようにすれば、これらの問題が全部解決できるのでは!?NFSということでネットワーク越しにアクセスするのですから、当然PCI Expressなどで接続されたストレージにアクセスするよりは遅くなるはずですが、そもそもEBSにしても物理的にはネットワーク越しにアクセスしているはずですから、そんなに酷いことにはならないでしょう。

などと考えながら試してみたところ、6倍以上もEFSが遅かったので、問題は一切解決しなかったというご報告です。

実行結果

us-eastリージョンのt1.microインスタンスで試しました。AMIはamzn-ami-2016.03.i-amazon-ecs-optimized-4ce33fd9-63ff-4f35-8d3a-939b641f1931-ami-3d55272a.3 (ami-03562b14)というので、Dockerの中でData Volumeとして読み書きしています。

まずは普通のEBS上のディレクトリで。

$ time git clone https://github.com/rails/rails.git
Cloning into 'rails'...
remote: Counting objects: 556404, done.        
remote: Compressing objects: 100% (74/74), done.        
remote: Total 556404 (delta 30), reused 9 (delta 9), pack-reused 556321        
Receiving objects: 100% (556404/556404), 134.66 MiB | 14.66 MiB/s, done.
Resolving deltas: 100% (411204/411204), done.

real    0m23.724s
user    0m18.688s
sys 0m2.396s

次にEFSでマウントしたディレクトリで。

$ time git clone https://github.com/rails/rails.git
Cloning into 'rails'...
remote: Counting objects: 556404, done.        
remote: Compressing objects: 100% (74/74), done.        
remote: Total 556404 (delta 30), reused 9 (delta 9), pack-reused 556321        
Receiving objects: 100% (556404/556404), 134.66 MiB | 11.08 MiB/s, done.
Resolving deltas: 100% (411204/411204), done.
Checking out files: 100% (3180/3180), done.

real    2m34.889s
user    0m18.956s
sys 0m3.852s

EBSでは24秒でgit cloneが終わりましたが、EFSでは2分35秒かかりました。timeの出力を見ると、realは大きく増えていますがusersysはほとんど変更がないので、IO待ちで遅くなっていることがわかります。

しかしEFSはこんなに遅くて大丈夫なのでしょうか。もう少しディスクの読み書きの速度に注目して、テストしてみましょう。簡単にddでテストしてみます。

EBSの場合。

$ dd if=/dev/zero of=/ebs/test ibs=1M obs=1M count=1024                 
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 17.5108 s, 61.3 MB/s

EFSの場合。

$ dd if=/dev/zero of=/efs/test ibs=1M obs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 18.4784 s, 58.1 MB/s

あれっ、速いぞ……EBSより少し遅いけど、でも速い。なんでGitだけこんなに遅いんでしょう?

なんでGitだけこんなに遅いのか

Gitのリポジトリ操作を実装するlibgit2というライブラリがありますが、ここにgit_odb_backendという型があったりします。バックエンドというのはGitリポジトリのオブジェクトを保管する場所のことで、git_odb_backendのメンバに適当な関数ポインタを設定することで、Gitリポジトリをファイルシステムだけではなくていろんなところに保存できるように作られています。MySQLとかMemcachedとか。

git_odb_backendの定義を見てみましょう。

/**
 * An instance for a custom backend
 */
struct git_odb_backend {
    unsigned int version;
    git_odb *odb;

    /* read and read_prefix each return to libgit2 a buffer which
    * will be freed later. The buffer should be allocated using
    * the function git_odb_backend_malloc to ensure that it can
    * be safely freed later. */
    int (* read)(
        void **, size_t *, git_otype *, git_odb_backend *, const git_oid *);

    /** 中略 **/

    /**
    * Write an object into the backend. The id of the object has
    * already been calculated and is passed in.
    */
    int (* write)(
        git_odb_backend *, const git_oid *, const void *, size_t, git_otype);

    /** 以下略 **/
};

色々ありますが大胆に削って、readwriteだけで。これらの関数の型を見るとオブジェクトのIDを表しているgit_oidの数を渡す引数がないので、「えっ、こいつら一個ずつ読み書きしてるんじゃ……」ということに気づきます。それは遅いだろ……

Webアプリケーションをバリバリ開発されている皆さんがよくご存知のN+1クエリという問題があります。N個のレコードを取ってくるときにN回SELECTすると遅いけど、一回のSELECTでN個取ってくると速い、というやつです。Gitのオブジェクトにも同じことが言えます。つまり、一個ずつディスクから読むと、まとめて読むより遅い。普通に接続されたディスクならそれでも十分に速く動作しますが、NFSでネットワーク越しにいちいちファイルを読み書きするとあからさまに遅かった、ということなのでしょう。

というと多分少し語弊があって、Gitでは高速に動作するよう工夫があるようです。実装があるファイルを眺めると、長々とコメントが書いてあります。(流し読みして、頑張ってるんだなーと思いました。)

(多分カスタムバックエンドのサンプル的な扱いになっている)MySQLバックエンドなどはわかりやすい感じで、毎回一個ずつSELECTしてくるいかにも遅そうな実装になっています。

残った問題

GitHub.comとかはどうなっているんだろう?

ところで、世界で一番Gitリポジトリを持っている組織の一つであろう、GitHubさんはどうやって実装しているんでしょう。「GFSを使っていた」などという声もありますが、現在は頑張って実装しているようです。リポジトリをローカルのストレージに保存するサーバがたくさんあるようです。

Introducing DGitを読むと、

Git is optimized to be fast when accessing fast disks, so the DGit file servers store repositories on fast, local SSDs. (Gitはディスクに高速にアクセスできるときに速く動作するように最適化されているので、DGitもファイルを高速なローカルSSDに保存している。)

などと書いてありますね。

EBSなんでこんなに速いの?

EFSが遅い理由は(間違っているかもしれませんが)納得しましたが、EBSがこんなに速い理由がむしろ気になります。EBSは一個のEC2インスタンスからしかアクセスされないので、積極的にキャッシュなどできるのかなーなどと考えています。

結局SideCIはどうするの?

ひとまずは、速いインスタンスを少数用意して、それぞれEBSにリポジトリを保管することにしました。GitHub.comと違ってSideCIにあるリポジトリはただのキャッシュなので、気軽に消すことができます。消してしまうとgit cloneし直さないといけないので、少し遅くなりますが、EC2とGitHub.comの通信が意外と早かったので許容範囲内ではないかという結論になりました。

それで耐えられないくらい遅くなるようなら、仕方がない。我々のDGitを作ります。