Tech Racho エンジニアの「?」を「!」に。
  • 開発

Rubyスタイルガイドを読む: 文法(2)アンダースコア、多重代入、三項演算子、if/unless

こんにちは、hachi8833です。「Rubyスタイルガイドを読む」シリーズ、文法(1)の続きです。

文法(2)アンダースコア、多重代入、三項演算子、if/unlessなど

2-06【統一】多重代入の左辺で単独のアンダースコア_変数を末尾に置くことはなるべく避ける

Avoid the use of unnecessary trailing underscore variables during parallel assignment. Named underscore variables are to be preferred over underscore variables because of the context that they provide.
Trailing underscore variables are necessary when there is a splat variable defined on the left side of the assignment, and the splat variable is not an underscore.

アンダースコア_変数を多重代入の末尾に置くことは、左辺でsplat演算子*で始まる変数が定義されていて、かつそのsplat付き変数がアンダースコア単体でない場合に認められます。

# 不可
foo = 'one,two,three,four,five'
# 不要な代入(有用な情報が変数名で示されていない)
first, second, _ = foo.split(',')
first, _, _ = foo.split(',')
first, *_ = foo.split(',')

# 良好
foo = 'one,two,three,four,five'
# この単独アンダースコアは必要: splat付き変数が左辺にあり、欲しい値が「末尾の値以外のすべて」であることを示すため
*beginning, _ = foo.split(',') # beginning => ["one", "two", "three", "four"]
*beginning, something, _ = foo.split(',')  # beginning => ["one", "two", "three"], something => "four"

a, = foo.split(',')
a, b, = foo.split(',')
# 使わないアンダースコア変数への代入が発生するが、変数名が有用な情報を示しているので認められる
first, _second = foo.split(',')
first, _second, = foo.split(',')
first, *_ending = foo.split(',')

Rubyでは、宣言だけ行って使わない変数があると、-wオプション付きで実行したときに警告が表示されます。

unused var

ただし以下については、-wオプションがある場合でも警告は表示されなくなります。

  • 単独のアンダースコア: _ (これも一応変数ではあります)
  • 名前付きアンダースコア変数: _secondのようにアンダースコア_で始まる変数(Ruby 2.0以降で有効)

これらは、代入された値をその後使う予定がない場合にプレースホルダとして使えます。値の捨て場所ですね。

そして本スタイルガイドでは、コンテキストがRubyの多重代入では、左辺の末尾に単独アンダースコア変数_を置くことを推奨していません。コンテキストを明確にするために、名前付きアンダースコア変数(_secondのようにアンダースコア_で始まる識別子)の利用が推奨されます。

確かに、値を捨てるにしてもどんな値を捨てたのかを名前付きアンダースコア変数で示す方が親切だと思えます。

なお、アンダースコア_そのものは識別子としては小文字と同等なので、形式上はアンダースコア単体またはアンダースコア変数を通常の変数として使い回せそうに思えますが、スタイルとしてよくないのでやめておく方がよいように思えます。

アンダースコア変数を宣言すると変数オブジェクトが生成されてしまいますが、値を使わないという意図を示すためにスタイルとして推奨されていると理解しました。

morimorihogeさんによるとRubocopもアンダースコアに対応しているとのことです。

# 無理矢理な例: indexだけ使ってeachする
hoges.each_with_index do |_, index| # <= この_を普通の変数にするとRubocopで警告される
  puts index
end

参考

splat付き変数については以下のとおりです。

左辺の最後の式の直前に * がついていると、対応する 左辺のない余った要素が配列として代入されます。余った要素が 無い時には空の配列が代入されます。
多重代入より

2-07【統一】forは原則使わない

Do not use for, unless you know exactly why. Most of the time iterators should be used instead. for is implemented in terms of each (so you're adding a level of indirection), but with a twist—for doesn't introduce a new scope (unlike each) and variables defined in its block will be visible outside it.

明確な理由がある場合は除くとありますが、通常はeachなどのイテレーション用メソッドが推奨されます。

そもそもforは実際にはeachとして実装されています。しかもforでは(eachと異なり)新しいスコープが導入されないので、forブロック内部の変数がスコープで仕切られずに外側のブロックからアクセスできてしまいます。

arr = [1, 2, 3]

# 不可
for elem in arr do
  puts elem
end

# elemはforループの外からアクセスできてしまう
elem # => 3

# 良好
arr.each { |elem| puts elem }

# elemはeachブロックの外からアクセスできない
elem # => NameError: undefined local variable or method `elem'

2-08【統一】thenは、ifunlessが複数行の場合は使わない

Do not use then for multi-line if/unless.

このような場合にthenを書かないのはRubyで広く使われているスタイルです。

# 不可
if some_condition then
  # (略)
end

# 良好
if some_condition
  # (略)
end

2-09【統一】ifunlessと条件は常に同じ行に書く

Always put the condition on the same line as the if/unless in a multi-line conditional.

条件が次の行にあると見づらいので、これはひと目で納得です。Rubyではifと条件の間が改行されていても動作することを初めて知りました。

# 不可
if
  some_condition
  do_something
  do_something_else
end

# 良好
if some_condition
  do_something
  do_something_else
end

2-10【統一】三項演算子(?:)の利用を推奨する

Favor the ternary operator(?:) over if/then/else/end constructs.
It's more common and obviously more concise.

他に何も書いていませんが、1行で収まるif文を指していると理解しました。

Rubyのifunlessは値を返すので、三項演算子と同様result = if some_condition then something else something_else endと代入文の形で書くことができますが、このスタイルガイドでは1行で書く場合には推奨していません。

三項演算子については常に議論の種になっています

個人的にも、三項演算子はそれが代入であるという意図が伝わりやすいように思えます。

# 不可
result = if some_condition then something else something_else end

# 良好
result = some_condition ? something : something_else

2-11【統一】三項演算子はネストしないこと

Use one expression per branch in a ternary operator. This also means that ternary operators must not be nested. Prefer if/else constructs in these cases.

三項演算子をネストすると途端に読みづらくなるので、ネストを禁止するスタイルは納得です。コード例では、複数行のifの中での三項演算子は認められていますね。

# 不可
some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else

# 良好
if some_condition
  nested_condition ? nested_something : nested_something_else
else
  something_else
end

2-12【統一】if x; ...は三項演算子にすること

Do not use if x; .... Use the ternary operator instead.

この項は前述の「三項演算子(?:)の利用を推奨する」と内容が重なっているように見えます。

おそらく、if xのような簡素な条件であればなおさら三項演算子を使うべき、という意味なのかもしれません。

# 不可
result = if some_condition; something else something_else end

# 良好
result = some_condition ? something : something_else

2-13【統一】ifunlessが値を返す機能を積極的に使う

Leverage the fact that if and case are expressions which return a result.

確かに、以下の例のようにifの結果をresultに代入することで、resultを1回書くだけで済みます。

# 不可
if condition
  result = x
else
  result = y
end

# 良好
result =
  if condition
    x
  else
    y
  end

morimorihogeより

これはRubyの設計思想に関わる部分で地味に大事だったりします。
Rubyはすべてのものが式なので、評価して値を返すようにできてるからこういう書き方ができます。

2-14【統一】case文で、1行で終わるwhen節ではthenを使う(when x; ...は使わない)

Use when x then ... for one-line cases. The alternative syntax when x:... has been removed as of Ruby 1.9.
Do not use when x; .... See the previous rule.

;よりthenの方がやや英文らしく見えるからかもしれません。

「for one-line cases」は、y = case x; when 100 then "hunny"; when 110 then "anten"; else "other" endみたいな長大なワンライナーのことなのかとも思いましたが、こんなひどい書き方のためだけにわざわざスタイルの制限を付けるとは考えにくいので、主にwhen 100 then "hunny"のような「条件 then 式」の行を指している(ついでにcaseからendまでの長いワンライナーも含む)と理解しました。

以下、原文にサンプルがなかったので私が追加してみました。

## 不可
y = case x
    when 100; "hunny"
    when 110; "anten"
    else "other"
    end

# 良好
y = case x
    when 100 then "hunny"
    when 110 then "anten"
    else "other"
    end

追伸: when x:...はRuby 1.9で廃止された

昔のこととなると意外と調べにくいのですが、確かRuby 1.9ではシンボルのハッシュ記法:a => "b"a: "b"と書けるようになったので、記法の衝突を避けるためだったのかもしれないと推測しています。

1.9 hash notation

1.9以降のa: "b"という記法は、以前の:a => "b"よりもタイプ量も少なく意味も明確なので、私は大好きです。

それにwhen 1..100:などのように範囲にコロンを適用しようとすると動作も意味もあいまいになりそうなので、1.9でこの書き方が廃止されたのは納得です。

今回はここまでとします。

関連記事



CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。