Rubyでif文の戻り値を代入するときのコーディングスタイル、あなたはどっち!?


はじめに

Rubyではif文の戻り値を変数に代入することができます。その場合、大きく分けて次のような2つのスタイルがあると思います。

# = と if を同じ行に書く
greeting = if japanese?
             'こんにちは'
           else
             'Hello'
           end
# if を一段改行する
greeting =
  if japanese?
    'こんにちは'
  else
    'Hello'
  end

他にもこういうスタイルもあるかもしれませんが、あまり見かけないので、今回は除外します。

# 2行目以降はインデントさせない
greeting = if japanese?
  'こんにちは'
else
  'Hello'
end

# 2行目以降は1つだけインデント
greeting = if japanese?
    'こんにちは'
  else
    'Hello'
  end

あと、「三項演算子を使わないの?」というツッコミもなしです。ここではif文のインデントの話をしたいだけなので。

# 三項演算子は今回の議論の対象外
greeting = japanese? ? 'こんにちは' : 'Hello'

で、冒頭に示した「= と if を同じ行に書く」スタイルと「if を一段改行する」スタイルは、どちらで書くのが良いのでしょうか?

答えは「自由」です。どちらでも構いません。

もちろん、チーム内で「こっち!」とコーディングルールが決まっている場合はそのルールに従ってください。

ですが、どっちでもいいと言われたら、僕は後者の「if を一段改行する」スタイルを選びます。この記事ではその理由を説明します。

「if を一段改行する」スタイルのいいところ

なぜ、僕は「if を一段改行する」スタイルを選ぶのか?それはこのスタイルが好きだからというよりも、「= と if を同じ行に書く」ときのデメリットが気に入らないからです。

変数をリネームしたときに無駄なdiffが発生しない

「= と if を同じ行に書く」スタイルだと、代入する変数の名前を変更したときに無駄なdiffが発生します。たとえば先ほどのコードで変数のgreetinghelloにリネームした場合、2行目以降も修正する必要があります。

-greeting = if japanese?
-             'こんにちは'
-           else
-             'Hello'
-           end
+hello = if japanese?
+          'こんにちは'
+        else
+          'Hello'
+        end

しかし、「if を一段改行する」スタイルであれば最初の1行目しかdiffが発生しません。

-greeting =
+hello =
   if japanese?
     'こんにちは'
   else
     'Hello'
   end

プルリクエストのレビューをするときは「インデントが変わっただけでコードの中身は変わっていないdiff」というのはレビュー時のノイズになるので、僕は無駄なdiffが発生しにくい「if を一段改行する」スタイルが好きです。

インデント幅が奇数になったりしない

「= と if を同じ行に書く」スタイルだと、変数名によっては2行目以降のインデント幅が奇数になることがあります。エディタの設定でインデント幅を視覚化するようなオプションを付けていると、奇数のインデントはすごく落ち着かない表示になります。

たとえば、変数名がgreetingだと、2行目以降のインデント幅が奇数になるため「黒い隙間」が発生します(隙間がわかりやすいように、画像に緑色の点を入れています)。

これがもしgreetinという変数名だったら、インデント幅が偶数になるため、「黒い隙間」が発生しません。

とはいえ、インデント幅を気にしながらわざわざ変数名を決めたりすることはないので、結局インデント幅が奇数個になることは避けられません。

また、TABキーを押してインデントする場合も、「TABキーでインデントするのはスペース2個」というようなエディタの設定になっていると、インデント幅を奇数個にするために「TABキーを押して、最後にスペースを1つ追加」みたいなキーボード操作が発生します。

一方「if を一段改行する」スタイルであれば、上で挙げたようなデメリットは発生しません。代入する先の変数名がなんであろうと、2行目以降のインデント幅は常に2個です。

# 2行目以降のインデント幅は常に2個(不変)
greeting =
  if japanese?
    'こんにちは'
  else
    'Hello'
  end

もちろん、「= と if を同じ行に書く」スタイルであっても「自動整形ツールに任せれば簡単に修正できるよ!」という意見もあるかもしれませんが、プレーンなテキストエディタでもなるべく読み書きしやすいコードスタイルはメリットになることはあってもデメリットになることはないはずです。

コード上に「謎の空白地帯」が発生しない

冒頭で示したコード例はごく単純なコードですが、実務ではこんな単純なコードばかりではありません。次のようにクラス定義やメソッド定義、ループ処理、長い変数名など、さまざまな要因でif文の2行目以降のインデントが深くなります。たとえばこんなふうに!

class Foo
  def bar
    users.each do |user|
      my_long_long_greeting = if japanese?
                                'こんにちは'
                              else
                                'Hello'
                              end
      puts "#{my_long_long_greeting}, #{user.name}!"
    end
  end
end

ここから先は個人の感覚の問題かもしれませんが、僕は「if文のせいで発生する謎の空白地帯」がすごく気になります。みなさんは気になりませんか?

一方、「if を一段改行する」スタイルであればこの「謎の空白地帯」が発生しません。

class Foo
  def bar
    users.each do |user|
      my_long_long_greeting =
        if japanese?
          'こんにちは'
        else
          'Hello'
        end
      puts "#{my_long_long_greeting}, #{user.name}!"
    end
  end
end

if文の中の処理が多少横長になっても問題になりにくい

上で説明した「謎の空白地帯」はif文の中の処理にも影響を与えます。たとえば、以下のようなコードだとQiita上ではコードブロックを横スクロールしないとコード全体を把握できないはずです(この処理の内容自体は深い意味はありません)。

class Foo
  def bar
    users.each do |user|
      my_long_long_greeting = if japanese?
                                comment = user.japanese_blogs.first.comments.first
                                comment.update!(message: 'こんにちは') unless comment.start_with?('こんにちは')
                                'こんにちは'
                              else
                                comment = user.english_blogs.first.comments.first
                                comment.update!(message: 'Hello') unless comment.start_with?('Hello')
                                'Hello'
                              end
      puts "#{my_long_long_greeting}, #{user.name}!"
    end
  end
end

「謎の空白地帯」がなければ、横スクロールしなくてもコード全体が見えるのではないでしょうか?(PCブラウザの場合)

class Foo
  def bar
    users.each do |user|
      my_long_long_greeting =
        if japanese?
          comment = user.japanese_blogs.first.comments.first
          comment.update!(message: 'こんにちは') unless comment.start_with?('こんにちは')
          'こんにちは'
        else
          comment = user.english_blogs.first.comments.first
          comment.update!(message: 'Hello') unless comment.start_with?('Hello')
          'Hello'
        end
      puts "#{my_long_long_greeting}, #{user.name}!"
    end
  end
end

なるべく横スクロールせずに読めるコードの方が読みやすいのは言うまでもありません。

まとめ

以上のような理由から僕はif文の戻り値を変数に代入するときはいつも「if を一段改行する」スタイルで書いています。

「if を一段改行する」スタイルにデメリットはほとんどないと思っているのですが、あえて挙げるなら「行数が1つ増えてしまうこと」と、「=の右隣に何もないのが気になる人がいるかもしれない」ということぐらいでしょうか。

# これは5行 / = の右隣にif文が来てるので安心?
greeting = if japanese?
             'こんにちは'
           else
             'Hello'
           end

# これは6行 / = の右隣が空っぽなので不安?
greeting =
  if japanese?
    'こんにちは'
  else
    'Hello'
  end

冒頭にも書いたとおり、Ruby自体には「絶対こっち」というルールはなく、どういう書き方をしても自由なのですが、自由なのであれば僕はメリットが多い(と僕は思っている)「if を一段改行する」スタイルを選択するようにしています。

もし強いこだわりがないのであれば、みなさんも「if を一段改行する」スタイルで書いてみるといいのではないでしょうか!(という布教活動w)

おまけ:Twitterでアンケートやってみました

こんな結果になりました〜。

おまけ・その2: = の位置を揃える?揃えない?問題にも同じことが言えそう

この記事で書いた内容と同じようなことが = の位置を揃える?揃えない?問題にも言えそうですね。

# =の位置を揃えるスタイル
name          = 'Alice'
age           = 20
height        = 160
date_of_birth = Date.new(2002, 2, 19)
# =の前後は必ず空白1つにするスタイル
name = 'Alice'
age = 20
height = 160
date_of_birth = Date.new(2002, 2, 19)

僕が知っている範囲では「=の前後は必ず空白1つにするスタイル」が主流なように思いますが、たまに「ツールが自動整形してくれるので」といった理由で「=の位置を揃えるスタイル」で書く人を見かけます。

僕もごくまれに、「=を揃えた方が明らかに読みやすい」というときは「=の位置を揃えるスタイル」で書くことがありますが、基本的には「=の前後は必ず空白1つにするスタイル」で書きます。

変数名が変わったりしたときに、本質的なdiffが発生したりするのはやっぱりイヤなんですよね〜。