こんにちは、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
オプション付きで実行したときに警告が表示されます。
ただし以下については、-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 ofeach
(so you're adding a level of indirection), but with a twist—for
doesn't introduce a new scope (unlikeeach
) 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
は、if
やunless
が複数行の場合は使わない
Do not use
then
for multi-lineif
/unless
.
このような場合にthen
を書かないのはRubyで広く使われているスタイルです。
# 不可
if some_condition then
# (略)
end
# 良好
if some_condition
# (略)
end
2-09【統一】if
やunless
と条件は常に同じ行に書く
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(
?:
) overif/then/else/end
constructs.
It's more common and obviously more concise.
他に何も書いていませんが、1行で収まるif
文を指していると理解しました。
Rubyのif
やunless
は値を返すので、三項演算子と同様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
- 参考: Rubyの三項演算子の話
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【統一】if
やunless
が値を返す機能を積極的に使う
Leverage the fact that
if
andcase
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 syntaxwhen x:...
has been removed as of Ruby 1.9.
Do not usewhen 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以降のa: "b"
という記法は、以前の:a => "b"
よりもタイプ量も少なく意味も明確なので、私は大好きです。
それにwhen 1..100:
などのように範囲にコロンを適用しようとすると動作も意味もあいまいになりそうなので、1.9でこの書き方が廃止されたのは納得です。
今回はここまでとします。
関連記事
- Rubyスタイルガイドを読む: ソースコードレイアウト(1)エンコード、クラス定義、スペース
- Rubyスタイルガイドを読む: ソースコードレイアウト(2)インデント、記号
- Rubyスタイルガイドを読む: 文法(1)メソッド定義、引数、多重代入
- Rubyスタイルガイドを読む: 文法(2)アンダースコア、多重代入、三項演算子、if/unless
- Rubyスタイルガイドを読む: 文法(3)演算子とif/unless
- Rubyスタイルガイドを読む: 文法(4)ループ
- Rubyスタイルガイドを読む: 文法(5)ブロック、proc
- Rubyスタイルガイドを読む: 文法(6)演算子など
- Rubyスタイルガイドを読む: 文法(7)lambda、標準入出力など
- Rubyスタイルガイドを読む: 文法(8)配列や論理値など
- Rubyスタイルガイドを読む: 命名
- Rubyスタイルガイドを読む: コメント、アノテーション、マジックコメント
- Rubyスタイルガイドを読む: クラスとモジュール(1)構造
- Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど
- Rubyスタイルガイドを読む: クラスとモジュール(3)クラスメソッド、スコープ、エイリアスなど
- Rubyスタイルガイドを読む: 例外処理
- Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)
- Rubyスタイルガイドを読む: 数値、文字列、日時(日付・時刻・時間)
- Rubyスタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)