はじめに
出題者のtbpgrです。当記事では以下の内容を取り扱います。
- RuboCopに関して
- RuboCopのメリット
- RuboCopのTips
- 「Ruby警官から警告を受けろ Lv1/Lv2」問題のランキング発表
- 「Ruby警官から警告を受けろ Lv1/Lv2」問題の出題者解答例
- 「Ruby警官から警告を受けろ Lv1/Lv2」問題の挑戦者解答例
※Rubyの利用経験者を前提として話を進めます。
1.RuboCopに関して
RuboCopとは
RuboCopはRubyの静的コード解析ツールです。
Ruby Style Guideのガイドラインに沿って コーディングスタイルをチェック します。
Ruby Style Guide
Rubyプログラマが保守しやすい、ベストプラクティスとも言えるコードを書くための規約です。
Rubyコミュニティの意見や、Rubyの有名書籍など広く受け入れられている意見を取り入れた内容になっています。
詳しくは以下を参照ください。
Ruby Style Guide 原文
Ruby Style Guide 和訳
RuboCopのインストール
Rubyのgemとして提供されています。
gemやbundlerでインストールできます。
gem install rubocop
RuboCopの実行
ターミナルでrubocopコマンドを実行してください。
オプション指定無しで実行すると、カレントディレクトリ配下の全てのrbファイルを対象とします。
$ rubocop
特定のディレクトリや、ファイルを指定しての実行も可能です。
$ rubocop lib/some_directory
$ rubocop lib/sample.rb
各種オプションについては
$ rubocop -h
で参照してください。
RuboCopの規約設定
.rubocop.yml ファイルに規約内容を記載することで
RuboCop がデフォルトで採用している規約内容を変更することができます。
開発チームのコーディングルールに合わせて規約を変更することができます。
2.RuboCopのメリット
全般
以降にあげる全ての項目に言えるのですが、 自動的にチェックできる ということが 大きなメリット です。
RuboCopで確認するようなルール・規約・好ましくない記法などを目でチェックする場合に比べて、 コストをかけず ・ 正確 に問題を抽出してくれます。
ソースコードの見栄えを整える
一定のスタイルで記述されたソースコードはバラバラのスタイルで
書かれたものよりも 早く理解する ことができます。
プロジェクトに関わる人数が多いほどこの恩恵を得ることができます。
ソースコード中のゴミ変数などを検出してくれる
未使用の変数などは、こういったツールを導入しないとあちこちに残りがちです。
自分が作成したプログラムではない場合に、そういった未使用の変数が本当に使用されていないのかどうか
確認するのは手間のかかるものです。
巨大なクラス、メソッドを検出してくれる
基本的にプログラムは 小さいほうが簡単で、大きいほうが難しい です。
大抵の場合、過剰に大きな処理は複数の責務が混在したものになっています。
リファクタリングすれば単一責務の小さなクラス、メソッドに分割できるケースが多いです。
RuboCopはデフォルトで10行より長いメソッドを書くと警告が出ます。
他の言語(特に静的言語)出身の方から見ると厳しすぎるように
見えるかもしれませんが「The Ruby Style Guide」にも
10行を超えるメソッドはやめる、また理想は5行以内
と記述されています。
著名な開発者を多く擁することでも有名な、ThoughtWorks社でも Sandi Metzルール というRubyのコーディングルールが公開されており、1メソッドは5行以内と記されています。
Rubyが言語として持っている記述力で適度にリファクタリングを行っていれば10行以内に収まることが多いです。
普段長いコードを書く方からすると 5行 というのは衝撃を受けるかもしれません。
if/else のコードを書くだけで 5行 になってしまいます。
def hoge(msg)
if msg.nil? # 1
'msg is nil' # 2
else # 3
msg # 4
end # 5
end
Rubyの学習効果
チームにRubyの作法に詳しくないエンジニアがいた場合、
Rubyの作法を覚える良い機会になります。
3.RuboCopのTips
警告をJSONファイルに出力する
rubocopコマンドの出力フォーマットを変更して JSON 形式で出力できます。
rubocop -fj > rubocop_warnings.json
警告をTODOとしてYAMLファイルに出力する
既存プロジェクトに対してRuboCopの利用を開始する際などに
現在発生している警告をYAMLファイルに出力してTODO化することができます。
rubocop --auto-gen-config
上記コマンドにより、現状発生している警告が rubocop-todo.yml ファイルに出力されます。
例えば未使用のメソッド引数が4つあった場合は以下の様な内容になります。
# This configuration was generated by `rubocop --auto-gen-config`
# on 2014-05-25 22:39:40 +0900 using RuboCop version 0.21.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 4
UnusedMethodArgument:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
IndentationConsistency:
Enabled: false
このファイルを .rubocop.yml ファイルで inherit することで警告対象外になります。
.rubocop.yml は以下のように記述します。
inherit_from: rubocop-todo.yml
上記の設定後に、rubocop を実行すると警告が出なくなります。
そして、UnusedMethodArgument が出ないようにプログラムを修正したら
警告無効の設定を削除して再度 rubocop で確認します。
該当する警告が表示されなくなったら
TODOをひとつ消化したことになります。
次に IndentationConsistency の警告が出ないように…というように
TODOを消化していきます。
ちなみに IndentationConsistency の警告に以下のコメントがあります。
# Cop supports --auto-correct.
これは、次に説明する警告の自動修正が可能である、ということです。
一部警告の自動修正を実行する
auto-correct オプションを利用することで、RuboCopが検出する警告のうち自動修正可能なものを修正してくれます。
スペーシングやシングルクォートとダブルクォートの使い分けなどはこのコマンドで自動修正してくれます。
rubocop -a
# もしくは
rubocop --auto-correct
例:修正前のコード
- 変数展開を利用していないがダブルクォートで囲まれている文字列がある
- 12345の数値リテラルが記述されている行のインデントがずれている
- 12345は4桁以上の数値リテラルだが「_」で区切られていない
# Sample
class Sample
def hoge
"hoge"
12345
end
end
例:rubocop -aで修正後のコード
- 文字列hogeを囲んでいたダブルクォートがシングルクォートに修正された
- 12345の数値リテラルが記載されている行のインデントが修正された
- 12345の数値リテラルが 12_345 に修正された
# Sample
class Sample
def hoge
'hoge'
12_345
end
end
4.「Ruby警官から警告を受けろ Lv1/Lv2」問題のランキング発表
獄中王バッジ取得者を対象に、警告種類のランキングを発表いたします。
Lv1
| Rank | 警告数 | 名前 |
|---|---|---|
| 1 | 67 | ciel 様 |
| 2 | 48 | tomwot 様 |
| 3 | 34 | fortissimo1997 様 |
| 4 | 33 | zono244 様 |
| 5 | 32 | k.hamada 様 |
| 6 | 28 | NeoCat 様 |
| 7 | 27 | みけCAT 様 |
| 8 | 26 | StupidDog 様 |
| 9 | 24 | ryosy383 様 |
| 9 | 24 | permil 様 |
Lv2
| Rank | 警告数 | 名前 |
|---|---|---|
| 1 | 62 | ciel 様 |
| 2 | 40 | tomwot 様 |
| 3 | 31 | fortissimo1997 様 |
| 4 | 28 | zono244 様 |
| 5 | 27 | k.hamada 様 |
| 6 | 26 | みけCAT 様 |
| 7 | 23 | NeoCat 様 |
| 8 | 19 | permil 様 |
| 8 | 19 | StupidDog 様 |
| 9 | 17 | ryosy383 様 |
Lv1, Lv2 ともに ciel様 の圧勝でした!!
また、2位の tomwot様 も他の解答者と比べるとかなり飛び抜けていました。
5.「Ruby警官から警告を受けろ Lv1/Lv2」問題の出題者解答例
Lv1
解答例(警告24種)
class Answer
def aA( a)
c=12345
["a","b"]
[].map{|v|v}
"h"#TODO:
;
{:a=>1}
%w{}
a(1,
2)
end
end
発生する警告
1, %w-literals should be delimited by ( and )
2, Align the parameters of a method call if they span more than one line.
3, Annotation keywords should be all upper case, followed by a colon and a space, then a note describing the problem.
4, Do not use semicolons to terminate expressions.
5, Extra blank line detected.
6, Extra empty line detected at body beginning.
7, Extra empty line detected at body end.
8, Inconsistent indentation detected.
9, Missing space after #.
10, Missing top-level class documentation comment.
11, Prefer single-quoted strings when you don't need string interpolation or special symbols.
12, Separate every 3 digits in the integer portion of a number with underscores(_).
13, Space between { and | missing.
14, Space inside parentheses detected.
15, Space inside { missing.
16, Space inside } missing.
17, Space missing after comma.
18, Space missing inside }.
19, Space missing to the left of {.
20, Trailing whitespace detected.
21, Use %w or %W for array of words.
22, Use 2 (not 0) spaces for indentation.
23, Use snake_case for methods.
24, Use the new Ruby 1.9 hash syntax.
Lv2
解答例(警告15種)
class Answer
def aA()
1234
["a","b"]
[].detect{|v|v}
"h"#TODO:
{:a=>1}
;
%w{}
a(1,
2)
1
.a
end
end
発生する警告
1, %w-literals should be delimited by ( and )
2, Align the parameters of a method call if they span more than one line.
3, Annotation keywords should be all upper case, followed by a colon and a space, then a note describing the problem.
4, Do not use semicolons to terminate expressions.
5, Inconsistent indentation detected.
6, Method has too many lines. [11/10]
7, Missing top-level class documentation comment.
8, Omit the parentheses in defs when the method doesn't accept any arguments.
9, Prefer find over detect.
10, Prefer single-quoted strings when you don't need string interpolation or special symbols.
11, Space between { and | missing.
12, Trailing whitespace detected.
13, Use %w or %W for array of words.
14, Use snake_case for methods.
15, Use the new Ruby 1.9 hash syntax.
6.「Ruby警官から警告を受けろ Lv1/Lv2」問題の挑戦者解答例
Lv1, Lv2ともに1位の ciel様 、2位の tomwot様 が他の大きく引き離す警告数だったため両名の解答をご紹介致します。
Lv1
- ciel様[特殊解答] (警告数 67)
出題側の想定漏れにより、「Replace interpolated variable」の警告を多数作成出来る事を利用した解答です。
(他にも未使用変数の警告など、簡単に同種の警告を増やせるケースが複数ありましたが事前に警告抑止の設定をしていました。)
解答
Answer="";->{#あ
A{{A:?A%$",}}if(!!あ="#$:#$0#$!#$@#$;#$,#$/#$.#$_#$>#$<#$$#$?#$~#$=#$*#$A#$1".())
}
発生する警告
1, Assignment in condition - you probably meant to use ==.
2, Avoid comma after the last item of a hash.
3, Avoid the use of Perl-style backrefs.
4, Avoid the use of double negation (!!).
5, Avoid using {...} for multi-line blocks.
6, Do not introduce global variables.
7, Do not use parentheses for method calls with no arguments.
8, Do not use semicolons to terminate expressions.
9, Do not use the character literal - use string literal instead.
10, Favor format over String#%.
11, Favor unless over if for negative conditions.
12, Line is too long. [80/79]
13, Missing space after #.
14, Prefer $ARGV from the English library, or ARGV over $*.
15, Prefer $CHILD_STATUS from the English library over $?.
16, Prefer $DEFAULT_INPUT from the English library over $.
18, Prefer $ERROR_INFO from the English library over $!.
19, Prefer $ERROR_POSITION from the English library over $@.
20, Prefer $FIELD_SEPARATOR or $FS from the English library over $;.
21, Prefer $IGNORECASE from the English library over $=.
22, Prefer $INPUT_LINE_NUMBER or $NR from the English library over $..
23, Prefer $INPUT_RECORD_SEPARATOR or $RS from the English library over $/.
24, Prefer $LAST_MATCH_INFO from the English library over $~.
25, Prefer $LAST_READ_LINE from the English library over $_.
26, Prefer $LOADED_FEATURES over $".
27, Prefer $OUTPUT_FIELD_SEPARATOR or $OFS from the English library over $,.
28, Prefer $PROCESS_ID or $PID from the English library over $$.
29, Prefer $PROGRAM_NAME over $0.
30, Prefer single-quoted strings when you don't need string interpolation or special symbols.
31, Prefer the use of lambda.call(...) over lambda.(...).
32, Put a space before the modifier keyword.
33, Replace interpolated variable $! with expression #{$!}.
34, Replace interpolated variable $$ with expression #{$$}.
35, Replace interpolated variable $* with expression #{$*}.
36, Replace interpolated variable $, with expression #{$,}.
37, Replace interpolated variable $. with expression #{$.}.
38, Replace interpolated variable $/ with expression #{$/}.
39, Replace interpolated variable $0 with expression #{$0}.
40, Replace interpolated variable $1 with expression #{$1}.
41, Replace interpolated variable $; with expression #{$;}.
42, Replace interpolated variable $< with expression #{$ with expression #{$>}.
45, Replace interpolated variable $? with expression #{$?}.
46, Replace interpolated variable $@ with expression #{$@}.
47, Replace interpolated variable $A with expression #{$A}.
48, Replace interpolated variable $_ with expression #{$_}.
49, Replace interpolated variable $~ with expression #{$~}.
50, Space inside { missing.
51, Space inside } missing.
52, Space missing after colon.
53, Space missing after comma.
54, Space missing after semicolon.
55, Space missing inside {.
56, Space missing inside }.
57, Space missing to the left of {.
58, Tab detected.
59, Trailing whitespace detected.
60, Use 2 (not -1) spaces for indentation.
61, Use SCREAMING_SNAKE_CASE for constants.
62, Use only ascii symbols in comments.
63, Use only ascii symbols in identifiers.
64, Use snake_case for variables.
65, Use space after control keywords.
66, Use the lambda method for multi-line lambdas.
67, end at 3, 1 is not aligned with - > {#あ at 1, 10 or Answer="";- > {#あ at 1, 0
ciel様コメント
・今回文字数判定がバイト単位ではないので、日本語の識別子・コメントを使って警告数を稼いでいます。
・VariableInterpolationで増殖バグが見つかりましたが、1個につき3文字使うので、警告数が40個を越えると実用的でなくなります。今回は使わない縛りとしました。
・Answerはclassである必要はないので、$1を代入してごまかします。「Use SCREAMING_SNAKE_CASE for constants」の警告も戴けて一石二鳥。
・Lv1に限り、2行目のsplatをブロック呼び出しに変更することで2警告稼げます。
・#$:のようなinterpolationを使うことで「3文字につき2つ」警告を稼げてしまうことがわかりました。
・Lv1については、%W!!を$""と改行とタブに置換してもう1個稼げました。
・Lv2は{a:X,}をタブと[X,]に置換してもう1個稼げました。
- ciel様[正統派解答] (警告数 56)
「Replace interpolated variable」の警告を利用しない解答も補足コメント欄でいただきました。
特殊解答を利用しなかった場合でも ciel様 の警告数がトップでした。
解答
Answer=$1;->{def A _#あ
END{A{[{:A=>%W!!*"",A:$A,}, ]}if(!!あ=_===_)
not"#$_#{}#{?A}"%$$or$!.()}end}
- tomwot様(警告数 48)
解答
class Answer
def a b=c#1
if
(not@@d=""%e);f.
g( ){||}while(!! h({:i=>[$j, ]*?k,},))end;end
end;
Lv2
- ciel様[特殊解答] (警告数 62)
Lv1同様、出題側の想定漏れにより「Replace interpolated variable」の警告を多数作成出来る事を利用した解答です。
解答
Answer="";->{#あ
[?A%%W!!, ]if(!!あ="#$:#$0#$!#$@#$;#$,#$/#$.#$_#$>#$<#$$#$?#$~#$=#$*#$A#$1#$"".())}
発生する警告
1, %W-literals should be delimited by ( and )
2, Assignment in condition - you probably meant to use ==.
3, Avoid comma after the last item of an array.
4, Avoid the use of Perl-style backrefs.
5, Avoid the use of double negation (!!).
6, Avoid using {...} for multi-line blocks.
7, Do not introduce global variables.
8, Do not use %W unless interpolation is needed. If not, use %w.
9, Do not use parentheses for method calls with no arguments.
10, Do not use semicolons to terminate expressions.
11, Do not use the character literal - use string literal instead.
12, Favor format over String#%.
13, Favor unless over if for negative conditions.
14, Line is too long. [82/79]
15, Prefer $ARGV from the English library, or ARGV over $*.
16, Prefer $CHILD_STATUS from the English library over $?.
17, Prefer $DEFAULT_INPUT from the English library over $.
19, Prefer $ERROR_INFO from the English library over $!.
20, Prefer $ERROR_POSITION from the English library over $@.
21, Prefer $FIELD_SEPARATOR or $FS from the English library over $;.
22, Prefer $IGNORECASE from the English library over $=.
23, Prefer $INPUT_LINE_NUMBER or $NR from the English library over $..
24, Prefer $INPUT_RECORD_SEPARATOR or $RS from the English library over $/.
25, Prefer $LAST_MATCH_INFO from the English library over $~.
26, Prefer $LAST_READ_LINE from the English library over $_.
27, Prefer $LOADED_FEATURES over $".
28, Prefer $OUTPUT_FIELD_SEPARATOR or $OFS from the English library over $,.
29, Prefer $PROCESS_ID or $PID from the English library over $$.
30, Prefer $PROGRAM_NAME over $0.
31, Prefer single-quoted strings when you don't need string interpolation or special symbols.
32, Prefer the use of lambda.call(...) over lambda.(...).
33, Put a space before the modifier keyword.
34, Replace interpolated variable $! with expression #{$!}.
35, Replace interpolated variable $" with expression #{$"}.
36, Replace interpolated variable $$ with expression #{$$}.
37, Replace interpolated variable $* with expression #{$*}.
38, Replace interpolated variable $, with expression #{$,}.
39, Replace interpolated variable $. with expression #{$.}.
40, Replace interpolated variable $/ with expression #{$/}.
41, Replace interpolated variable $0 with expression #{$0}.
42, Replace interpolated variable $1 with expression #{$1}.
43, Replace interpolated variable $; with expression #{$;}.
44, Replace interpolated variable $< with expression #{$ with expression #{$>}.
47, Replace interpolated variable $? with expression #{$?}.
48, Replace interpolated variable $@ with expression #{$@}.
49, Replace interpolated variable $A with expression #{$A}.
50, Replace interpolated variable $_ with expression #{$_}.
51, Replace interpolated variable $~ with expression #{$~}.
52, Space inside square brackets detected.
53, Space missing after semicolon.
54, Tab detected.
55, Trailing whitespace detected.
56, Use SCREAMING_SNAKE_CASE for constants.
57, Use only ascii symbols in comments.
58, Use only ascii symbols in identifiers.
59, Use snake_case for variables.
60, Use space after control keywords.
61, Use the lambda method for multi-line lambdas.
62, end at 2, 81 is not aligned with ->{#あ at 1, 11 or Answer="";->{# あ at 1, 1
- ciel様[正統派解答] (警告数 48)
「Replace interpolated variable」の警告を利用しない解答も補足コメント欄でいただきました。
特殊解答を利用しなかった場合でも ciel様 の警告数がトップでした。
解答
Answer=$1;->{def A _#あ
END{A{[{:A=>%W!!*"",A:$A,}, ]}if(!!あ=_===_)
not"#$_#{}#{?A}"%$$or$!.()}end}
- tomwot様 (警告数 40)
解答
class Answer
def a b=c
if
(not@@d=""%e);f.
g(){||}while(!! h({ :i=>["#$j", ]*?k, },))end;end
end;
出題の狙い
今回の問題は、ゲーム感覚でRubyの標準的な規約やイディオムやを覚えることができるのでは、ということろが狙いでした。
ただ単に規約を暗記するよりも試行錯誤をしながらソースコードとRuby Style Guideのドキュメントを何度も往復することによって、
強く記憶に残って良いのではないか、と考えたのです。
結果としては、挑戦環境構築の面倒さもあってか、あまり挑戦数が伸びず。
ただ、 別の成果 があったので結果オーライといったところです。
別の成果については、後述の Special Thanks1-3 を御覧ください。
まとめ
Rubyは柔軟に記述できる反面、その自由度ゆえに同じ内容を記述する方法が数多くあります。
RuboCopを利用することでチームのコーディングスタイルをRubyの標準的な記法に統一することができます。
Rubyの標準的な記法に詳しくない初心者や独学のRubyistが標準的な記法を覚える助力にもなります。
統一されたスタイルのソースコードは、その作法になれた開発者にとって読みやすいコードとなり、コードリーディングに費やす時間を減らすことができます。
利用しているスタイルが広く使われているものであればあるほど、多くの人にとって読みやすいコードとなります。
Special Thanks1
出題のために和訳版の Ruby Style Guide を確認していたところ、
いくつか手を入れる箇所があったのでプルリクエストを送らせていただきました。
マージありがとうございます
Special Thanks2
この問題の挑戦支援ツールとして Vagrant を利用した環境構築ツールを提供させていただきましたが、VagrantのWindows向け警告メッセージに好ましくない内容があることを みけCAT様 が発見。
ciel様 がプルリクエストを送って、Vagrantの開発者である Mitchell Hashimoto氏 によってマージされました。
https://github.com/mitchellh/vagrant/pull/3880
みけCAT様 、 ciel様、 OSSへの貢献ありがとうございます!
Special Thanks3
今回の問題の鬼門となった(?) Vagrant + virtualbox を利用した挑戦支援環境の構築ですが、他の挑戦者のためにブログのエントリを作成してくださった方が複数名いらっしゃいました。
Tails様 、 電脳妖精様 誠にありがとうございます。
内容が気になる方は、出題者のブログの下記記事内の「関連リンク」の項目にリンク先が列挙されています。
採用すべきエンジニア
周囲のために 想像力を働かせ 、 積極的に行動し 、 実行に移す
上記の方々のような姿勢を持つエンジニアは
きっと企業にとって 心強い戦力 となる人材だと思います。
- アプリケーションが使いにくいなら 改良してプルリクエストを送る
- 自分の経験を ブログの記事で共有する
というような行動ができるエンジニアは 仕事ができないわけがない と思います。
参考資料
CodeIQ運営事務局より
tbpgrさんは他にもいろいろな問題を出題されています。
ぜひその他の問題にもご挑戦ください!!
バッジが付与される問題!
デスマコロシアム問題
tbpgrさん問題