6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

社内で行なった Ruby Style Guide勉強会について

Last updated at Posted at 2020-09-30

背景

私たちの会社(エイチーム引越し侍)では、長らくバックエンドで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 ~ whenif ~ 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を使う

ActiveRecordfindメソッドと混同せずに、それが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】 countlengthよりもsizeを使うようにする

sizeの方速いため

【議論】
え、、全然countとか使ってるわ、、
本当に遅いの?という議論
TODO: 本当に遅いか調べる(知っている方はコメントお待ちしております。)

【MUST】 中身のないArrayHash[]{}で初期化

こっちの方がわかりやすいし、シンプルだから

# 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とか%irubyでしか使わない構文で直感的ではないのでは?という意見がでました
調べて見たところ、

  1. 速くかける
  2. %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文字なのかという議論がありましたが、エディタに表示できる限界がこれぐらいだろうということと、
これ以上長くしていい事なんかないと言うことで決着

最後に

最初にも記載させていただいた通り、本記事はまだ未完成となっており、内容をアップデートする可能性がございます。
この書き方が万人に良いというものではなく、自分たちに合った書き方を模索している状態になります。
ご意見等ある方はコメントに記載していただけると幸いです。(勉強させていただきます!!)

6
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?