Rubyスタイルガイドを読む: 文法(5)ブロック、proc

こんにちは、hachi8833です。

「Rubyスタイルガイドを読む」シリーズの文法編もいよいよ第5回目にさしかかりました。今回はブロックとProc関連です。

文法(5) ブロック、procなど

ブロックの各要素でメソッドを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が実装されている必要があります。

当然ながら、&:記法ではブロック変数を使えません。

参考

ブロック記法: 1行に収まる場合は原則{...}を使い、制御フローやメソッド定義では常にdo...endを使う

Prefer {...} over do...end for single-line blocks. Avoid using {...} for multi-line blocks (multi-line chaining is always ugly). Always use do...end for “control flow” and “method definitions” (e.g. in Rakefiles and certain DSLs). Avoid do...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行に収まる場合は原則{…}を使う」を優先したのだろうと推測しました。

これに限りませんが、目安程度に考えるほうがよいスタイルもいくつかあります。指示に盲目的に従うよりも、時に応じ場合に即して使い分けたいものです。

引数を別のブロックに渡すだけのブロックは、明示的なブロック引数で書き直す

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オブジェクトへの変換は発生しません。

不必要な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

不必要な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を付けるのはスタイル上の指示ということですね。

ローカル変数をメソッドでシャドウイングすることは避ける

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

今回はここまでとします。次回は等号=に関連するスタイルです。ご期待ください。

関連記事

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833 コボラー、ITコンサル、ローカライズ業界を経てなぜかWeb開発者志願。 これまでにRuby on Rails チュートリアルの大半、Railsガイドのほぼすべてを翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

Rubyスタイルガイドを読む

BigBinary記事より

ActiveSupport探訪シリーズ