こんにちは、hachi8833です。
「Rubyスタイルガイドを読む」シリーズの文法編もいよいよ第5回目にさしかかりました。今回はブロックとProc関連です。
文法(5) ブロック、procなど
2-32【統一】ブロックの各要素でメソッドを1つだけ呼び出す場合は、proc呼び出しのショートハンド「&:
」を使う
Use the proc invocation shorthand when the invoked method is the only operation of a block.
Rubyを使い始めた頃はこの&:
という書き方にちょっと戸惑いましたが、メソッドが1つの場合にブロックやブロック変数を書かなくて済むので慣れると便利ですね。
# 不可
names.map { |name| name.upcase }
# 良好
names.map(&:upcase)
なお&:
を呼び出すには、そのメソッド(上の例ではupcase
)にSymbol#to_proc
が実装されている必要があります。
当然ながら、&:
記法ではブロック変数を使えません。
参考
2-33【統一】ブロック記法: 1行に収まる場合は原則{...}
を使い、制御フローやメソッド定義では常にdo...end
を使う
Prefer
{...}
overdo...end
for single-line blocks. Avoid using{...}
for multi-line blocks (multi-line chaining is always ugly). Always usedo...end
for "control flow" and "method definitions" (e.g. in Rakefiles and certain DSLs). Avoiddo...end
when chaining.
do...end
の後ろにドット.
でメソッドチェーンするのは見苦しいので避けるようにとのことです。
names = %w(Bozhidar Steve Sarah)
# 不可
names.each do |name|
puts name
end
# 良好
names.each { |name| puts name } # 1行なら { } で書く
# 不可
names.select do |name|
name.start_with?('S')
end.map { |name| name.upcase } # do...endに続けてメソッドチェーンしないこと
# 良好
names.select { |name| name.start_with?('S') }.map(&:upcase)
Some will argue that multi-line chaining would look OK with the use of {...},
but they should ask themselves—is this code really readable and can the
blocks' contents be extracted into nifty methods?
「複数行を{...}
ブロックで囲んでメソッドチェーンするなら見苦しくはならないのでは?」という反論については、「そもそもブロックにコードを何行も書かなければならないなら別途メソッド化すべき」という立場を取っています。
試しに以下のように書き換えてみると、「1行に収まる場合は原則{...}を使う」というスタイルとの共存が面倒になるのがわかります。
names.select { |name|
name.start_with?('S')
...
...
}.map(&:upcase)
ルールを制定する側は例外事項をなるべく避けたいはずなので、「1行に収まる場合は原則{...}を使う」を優先したのだろうと推測しました。
これに限りませんが、目安程度に考えるほうがよいスタイルもいくつかあります。指示に盲目的に従うよりも、時に応じ場合に即して使い分けたいものです。
2-34【統一】引数を別のブロックに渡すだけのブロックは、明示的なブロック引数で書き直す
Consider using explicit block argument to avoid writing block literal that just passes its arguments to another block. Beware of the performance impact, though, as the block gets converted to a Proc.
ブロックを&block
などのブロック引数で受けることで、余分なブロックもyield
も使わずに済み、読みやすくなりますね。
require 'tempfile'
# 不可
def with_tmp_dir
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir) { |dir| yield dir } # 引数を渡すためだけにブロックとyieldを余分に使っている
end # その代わりProcオブジェクトへの変換は発生しない
end
# 良好
def with_tmp_dir(&block) # ブロック引数「&block」で受けることでブロックを1つ減らせる
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir, &block) # その代わりブロックを&blockに保存するときにProcへの変換が発生する
end
end
with_tmp_dir do |dir|
puts "dir is accessible as a parameter and pwd is set: #{dir}"
end
その代わり、ブロックをブロック引数&block
に保存するときにProcオブジェクトに変換されるので、パフォーマンスとのトレードオフであることを理解して使いましょう。
上で不可とされている書き方では、ブロックをコード中で直書きしているので、Procオブジェクトへの変換は発生しません。
2-35【統一】不必要なreturn
は避ける
Avoid
return
where not required for flow of control.
これはRubyのスタイルとして有名ですね。Rubyでは最後に実行した文の値が戻り値になるので、多くの場合return
は不要です。
# 不可
def some_method(some_arr)
return some_arr.size
end
# 良好
def some_method(some_arr)
some_arr.size
end
2-36【統一】不必要なself
は避ける
Avoid
self
where not required. (It is only required when calling a self write accessor.)
これは同じクラスにある他のメソッドを呼ぶ場合の話ですね。書き込みアクセサの呼び出しの場合のみself
を付けるようにします。
# 不可
def ready?
if self.last_reviewed_at > self.last_updated_at
self.worker.update(self.content, self.options)
self.status = :in_progress
end
self.status == :verified
end
# 良好
def ready?
if last_reviewed_at > last_updated_at
worker.update(content, options)
self.status = :in_progress # このself.は略さない(書き込みアクセサの呼び出しであることを示す)
end
status == :verified
end
実際、上のコードはself
なしでも動作します。書き込みアクセサの呼び出しでself
を付けるのは、そうしないとstatus
という変数を新規作成して代入することになってしまうためです。
2-37【統一】ローカル変数でメソッドをシャドウイングすることは避ける
As a corollary, avoid shadowing methods with local variables unless they are both equivalent.
以下の例では、options
というアクセサがあり、さらに#initialize
メソッドの仮引数であるローカル変数も同じ名前のoptions
になっています。
これは確かに紛らわしいので、避けたいです。
class Foo
attr_accessor :options
# ok
def initialize(options)
self.options = options
# ここではoptions と self.optionsが同等になっている
end
# 不可
def do_something(options = {}) # このoptionsの名前を変えるべき
unless options[:when] == :later
output(self.options[:message]) # そうすればこのselfは不要になる
end
end
# 良好
def do_something(params = {})
unless params[:when] == :later
output(options[:message])
end
end
end
- 参考: シャドーイングとイミュータブルプログラミング -- C#とF#が対象です
今回はここまでとします。次回は等号=
などの演算子関連のスタイルです。ご期待ください。
関連記事
- 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スタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)