背景
私たちの会社(エイチーム引越し侍)では、長らくバックエンドでPHPを使ってきた歴史がありますが、
最近はRubyを使うことも多くなってきました。
でも、Rubocopさんに怒られる日々。
これじゃあいけない。
ということで、社内でRubyのStyle Guideを勉強しようという話になりました。
数名を集め、議論をしながらスタイルガイドを作成していきました。
ゴール
https://github.com/rubocop-hq/ruby-style-guide
https://github.com/airbnb/ruby
https://github.com/cookpad/styleguide
この辺りを教材として、引越し侍 Ruby Style Guideを作成するところまで
注意事項
本記事はまだ未完成となっております。(ので、これが完全に良い書き方だ!というつもりはございません。)
ノウハウがたまるごとにアップデートを行います。
ご意見等ある方はコメント欄に記載していただけると、勉強になるので、是非お願いいたします。
引越し侍 Ruby Style Guide
インデント系
【MUST】 メソッドのインデントには2つのスペースで統一
コードが右に長くなりすぎず見えやすい
# Bad
def sample_method
do_something
end
# Good
def sample_method
do_something
end
【議論】
異論なし
見やすい & Ruby規約なのでという理由で1秒で決定
【MUST】 case ~ when文には、スペースを入れない
case ~ whenはif ~ elseと同じ扱いなので、入れないのが良い
# Bad
case
when song.name == 'Misty'
puts 'Not again!'
when song.duration > 120
puts 'Too long!'
when Time.now.hour > 21
puts "It's too late"
else
song.play
end
# Good
case
when song.name == 'Misty'
puts 'Not again!'
when song.duration > 120
puts 'Too long!'
when Time.now.hour > 21
puts "It's too late"
else
song.play
end
【議論】
冒頭に話した通り我々はPHP脳の人間なので、switch ~ case文同様、1つめがいいという議論が生まれました。
ただ、if ~ elseと同じ扱いという事には納得。
rubyの文化に慣れようという結論になりました。
【MUST】 複数行にメソッドが連なる場合はドットの位置を合わせる
まだメソッドが続いているということがわかり、かつドットが揃っていて見やすいため
# Bad
User.active
.some_scope(foo)
# Bad
User.active
.some_scope(foo)
# Good
User.active
.some_scope(foo)
※ ドットが上か下かという議論は改行の項目を参照
【議論】
2つめの方が.の位置を合わせるために、スペースを合わせなくていいのでいいという意見もありました。
**1人がスペースを合わせるだけで見やすくなり、のちにそのコードを見る10人の人の時間を削減できたならそれがいい!**という結論で終わりました。
【MUST】 複数行に連なるBooleanの改行は2スペース開ける
Booleanの判定がまだ続いていることを判別しやすいため
# Bad
def eligible?(user)
Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) &&
is_in_program?(user) &&
program_not_expired
end
# Good
def eligible?(user)
Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) &&
is_in_program?(user) &&
program_not_expired
end
【議論】
異論なし
Rubyでは&&も下に下ろせないため、これが最適だろうという結論
インラインスペース系
【MUST】 インラインコメントは半角スペースを開ける
その方がとても見やすいため
# Bad
result = func(a, b)# we might want to change b to c
# Good
result = func(a, b) # we might want to change b to c
【議論】
満場一致で合意
ただ、右に長くなってしまうため、基本的にコメントは上に書いて欲しいという議論が生まれました
【MUST】 オペレーター、コンマ、コロン、セミコロン、{}の後には半角スペースをいれる
その方が見やすいため
# Good
sum = 1 + 2
a, b = 1, 2
1 > 2 ? true : false
[1, 2, 3].each { |e| puts e }
【議論】
異論なし
ただ、eachをdo ~ endで書かないのかという論争に発展
こちらについては、下で別途議論しました
【MUST】 コンマの前にはスペースをいれない
英語では,の前にスペースを入れないためそちらの方が見やすいため
# Bad
result = func(a , b)
# Good
result = func(a, b)
【議論】
異論なし
どの言語も共通して、コンマの前にスペースは入れないが、
なぜここだけ入れないのかという議論がありました。
そこで考えたものがこちらです
英語では
,の前にスペースを入れないためそちらの方が見やすいため
【MUST】 文字列中の変数展開にスペースを入れない
展開する場所の前後にスペースがある場合、展開の中にもスペースがなく、可読性が高いため
# Bad
var = "This #{ foobar } is interpolated."
# Good
var = "This #{foobar} is interpolated."
【議論】
こちらの方が見やすいため
【MUST】 rangeの...の前後にスペースを開けない
こちらの方が可読性が高いため
# Bad
(0 ... coll).each do |item|
# Good
(0...coll).each do |item|
【議論】
異論なし
終端を含む...と終端を含まない..共にGoodの方が見やすいよねということで決着
改行系
【MUST】 区切り文字は;ではなく、改行する
無駄に右側にコードが長くならずに綺麗に見えるため
# Bad
puts 'foo';
# Bad
puts 'foo'; puts 'bar';
# Good
puts 'foo'
puts 'bar'
【議論】
異論なし
そもそもRubyで;使えるんだ、、、意見も
【MUST】 メソッドのパラメーターが多い場合、一行ごとに改行する
その方が見やすいため
# Bad
def create_translation(phrase_id, phrase_key, target_locale,
value, user_id, do_xss_check, allow_verification)
...
end
# Bad for samurai
def create_translation(
phrase_id,
phrase_key,
target_locale,
value,
user_id,
do_xss_check,
allow_verification
)
...
end
# Good for samurai
def create_translation(phrase_id,
phrase_key,
target_locale,
value,
user_id,
do_xss_check,
allow_verification)
...
end
【議論】
ここはすごくもめました
決めの問題かとは思いますが、2つめのものだと、defの下に処理を記述して言った時に、
パラメータのインデントと処理のインデントが同じになるため、見にくいということで3つめのもので決着しました。
【MUST】 複数行にメソッドが連なる場合は.を下におろす
我々は、PHPでも->を下におろしており、慣れしたしんでいて、見やすいため
※ Rubyコミュニティではどちらで見やすいため、OKという見解
# Bad for samurai
User.active.
first_scope(foo).
second_scope(bar)
# Good for samurai
User.active
.first_scope(foo)
.second_scope(bar)
※ インデントに関してはインデントを参照
【議論】
AirbnbさんやCookpadさんは1つめのものを推奨しています。
ドットを下ろし、かつインデントを2つに統一しようとすると
User.active
.some_scope(foo)
となり、ドットの位置が微妙になるため、彼らの定義には納得です
ただ、ドットをあげたものがどうしても我々には見にくいため、ドットの位置も合わせてやることで決着
Collection
【MUST】 配列やハッシュから要素を取り出したい場合は、findを使わずにdetectを使う
ActiveRecordのfindメソッドと混同せずに、それがcollectionだということがわかるため
【議論】
本当に混同するかどうかを議論しましたが、実際に混同してしまったという人がいたため、採用しました。
【MUST】 collectよりもmapを使用してください
jsなどでも使えるmapの方が、親しみやすいため
※ どちらも同じ動きをする
【議論】
満場一致でmapの方が親しみやすいとのことで異論なし
【MUST】 injectではなく、reduceを使ってください
同じ動きをしますが、"配列を折り畳む"という意味が伝わりやすいreduceを使ってください
また、PHPやSwiftなど他の言語でも実装されているreduceの方が親しみやすいです
numbers = [4, 3, 9, 8, 5, 6, 1, 7, 2]
puts numbers.reduce {|sum, n| sum + n }
> 45
# ブロックの第一引数である初期値は以下のように定義できます
puts numbers.reduce(100) {|diff, n| diff - n }
> 55
【議論】
異論なし
そんなに使うことがないため、白熱もせず、、
【MUST】 countやlengthよりもsizeを使うようにする
sizeの方速いため
【議論】
え、、全然countとか使ってるわ、、
本当に遅いの?という議論
TODO: 本当に遅いか調べる(知っている方はコメントお待ちしております。)
【MUST】 中身のないArray、Hashは[]、{}で初期化
こっちの方がわかりやすいし、シンプルだから
# Bad
arr = Array.new
hash = Hash.new
# Good
arr = []
hash = {}
【議論】
異論なし
%リテラル
【MUST】 %()による文字列宣言を使う場合は、1行かつ挿入変数があるかつダブルクウォートを使っているもののみに限り使う
それ以外は適切な宣言方法があるため
# Bad
%(<div class="text">Some text</div>)
# Good
# '<div class="text">Some text</div>'
# Bad
%(This is #{quality} style)
# ダブルクォート使った方がスマート "This is #{quality} style"
# Bad
%(<div>\n<span class="big">#{exclamation}</span>\n</div>)
# heredocの方がスマート<<EOS
# <div>
# <span class="big">
# #{exclamation}
# </div>
# EOS
# Good
%(<tr><td class="name">#{name}</td>)
【議論】
%()は便利だし、綺麗にかけるからGood!という結論に
異論なし
【MUST】 文字列の配列は['sample', 'today']ではなく%wの形で初期化
%wの方が新しく追加された構文で、タイプ数が少なくて済むため
ただし、スペースを含む場合は['1st day', 'today']という形で初期化する
# Bad for samurai
STATES = ['draft', 'open', 'closed']
# Good for samurai
STATES = %w(draft open closed)
# Good
STATES = ['draft', 'open', 'closed', 'already deleted']
【議論】
いつもrubocop怒ってくるけど、%wって本当に必要?
という議論が生まれました
%wとか%iはrubyでしか使わない構文で直感的ではないのでは?という意見がでました
調べて見たところ、
- 速くかける
-
%wという構文を知ってさえいれば、無駄なものを書かなくてもString型の配列であることがわかる
というメリットがありました
ただ、確かに多く書くときは便利だし、これが文字列の配列であることは慣れの問題だけなので、時代の流れにそって採用という結論に
【MUST】 リテラルの配列は[:sample, :today]ではなく%iの形で初期化
%iの方が新しく追加された構文で、タイプ数が少なくて済むため
# Bad for samurai
STATES = %i(draft open closed)
# Good for samurai
STATES = [:draft, :open, :closed]
【議論】
上と同様の議論をしました
【MUST】 スペースを含むシンボルの初期化は%sではなく、:"sample symbol"の形で初期化
ほぼ使うことのない%sがあると、次見た人がわざわざ調べないといけないが、:"sample symbol"の形で書かれて入れば、それが何か一目でわかるため
※ rubyコミュニティでも%sは使わない方針に変更
# Bad
%s(Hello World)
# Good
:"Hello World"
【議論】
異論なし
ほとんど使うことないと思うけど、確かにハイフン-は使ったことあるので、採用
メソッド系
【MUST】 必要ない時は、class << selfを使わず、self.を使う
self.を使っていた場合はそのメソッドを見ればすぐにselfメソッドであることがわかる。
ブロックで定義すると、(ブロックの中が肥大化しているなどの理由で)すぐにselfメソッドであるかどうかがわからない
class TestClass
# Bad
class << self
def first_method
...
end
def second_method
...
end
end
# Good
class << self
attr_accessor :per_page
alias_method :nwo, :find_by_name_with_owner
end
def self.first_method
...
end
def self.second_method_etc
...
end
end
【議論】
異論なし
【MUST】 引数なしのメソッドは()を省略、引数があるときは必ず()を書く
パラメータの有無を明示的に分けることができるから
# Bad
def some_method()
# body omitted
end
# Good
def some_method
# body omitted
end
# Bad
def some_method_with_parameters param1, param2
# body omitted
end
# Good
def some_method_with_parameters(param1, param2)
# body omitted
end
【議論】
異論なし
【MUST】 引数のあるメソッド呼び出しは、メソッドの後にスペースを入れずにf(params1, params2)の形で書く
メソッドの定義と同じ形式でかけるようにするため
# Bad
some_method (params1, params2)
# Good
some_method(params1, params2)
【議論】
異論なし
その他
【MUST】 一行の長さは100文字以内
一行が右によりすぎずエディタに表示できる範囲内だから
【議論】
なぜ100文字なのかという議論がありましたが、エディタに表示できる限界がこれぐらいだろうということと、
これ以上長くしていい事なんかないと言うことで決着
最後に
最初にも記載させていただいた通り、本記事はまだ未完成となっており、内容をアップデートする可能性がございます。
この書き方が万人に良いというものではなく、自分たちに合った書き方を模索している状態になります。
ご意見等ある方はコメントに記載していただけると幸いです。(勉強させていただきます!!)