ホーム DoRuby ブロックを使ったリファクタリング

ブロックを使ったリファクタリング

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは、shimadaです。

今日は、Rubyの特徴の一つであるブロックを引数にとるメソッドの書き方を説明します。

題材として、「あちこちに同じ順番で処理が並んでいる時のコードの整理」を取り上げたいと思います。

重要な処理を行う場合に、開始、終了、エラーが起こったときの詳細を記録しなければならない。

そんな時の処理は、だいたいこんな風になるのではないでしょうか。

class SimpleWorker
  def action(arg)
    logger = Application.logger
    begin
      logger.info("SimpleWorker.action", "start")
      do_something
    rescue => e
      logger.error("SimpleWorker.action", e.message + e.backtrace.join("¥n"))
      logger.info("SimpleWorker.action", "abnormal end")
      raise
    else
      logger.info("SimpleWorker.action", "normal end")
    end
  end
end

class ComplexWorker
  def complex_action(arg1, arg2, arg3)
    logger = Application.logger
    begin
      logger.info("ComplexWorker.complex_action", "start")
      do_complex_thing1
      do_complex_thing2
      do_complex_thing3
      do_complex_thing4
      do_complex_thing5
    rescue => e
      logger.error("ComplexWorker.complex_action", e.message + e.backtrace.join("¥n"))
      logger.info("ComplexWorker.complex_action", "abnormal end")
      raise
    else
      logger.info("ComplexWorker.complex_action", "normal end")
    end
  end
end

同じような記述がたくさん並んでいてうんざりですね。

気になる点は2つあります。

  1. “SimpleWorker.action” という文字列が何度も出てきてうざい。
  2. begin .. rescue .. else .. end のパターンが似ているっていうか同じ

これをリファクタリングしてみましょう。合い言葉は「DRY」です。

1.は変数に入れてしまえばなんとかなりそうです。

2.については、「同じ処理の並びを共有する」というところからGoFで有名なテンプレートメソッドパターンとかを思いつきます。

でも、shimadaは継承が割と嫌いです。

「重要な処理」というのはシステムの色々なところにちょこちょこ出てくるので、「必ず同じ親クラスから派生していなければいけない」という制限をかけるのは、システムを設計する上で大きな足かせになりえるのです。

ではどうするか。Rubyの特色はmix-inです。そしてshimadaの好物は高階関数です。

手続きを引数にとる手続き、つまりブロックを受け取るメソッドをモジュールとして書いてみましょう。

module LogHandler
  def logging(task_name)
    logger = Application.logger
    begin
      logger.info(task_name, "start")
      yield(logger)
    rescue => e
      logger.error(task_name, e.message, e.backtrace.join("¥n"))
      logger.info(task_name, "abnormal end")
      raise
    else
      logger.info(task_name, "normal end")
    else
    end
  end
end

早速、各クラスにincludeして使ってみます。

class SimpleWorker
  include LogHandler
  def action(arg)
    logging("SimpleWorker.action") do |logger|
      do_something
    end
  end
end

class ComplexWorker
  include LogHandler
  def complex_action(arg1, arg2, arg3)
    logging("ComplexWorker.complex_action") do |logger|
      do_complex_thing1
      do_complex_thing2
      do_complex_thing3
      do_complex_thing4
      do_complex_thing5
    end
  end
end

すっきりしましたね。

また、ブロックを使ったプログラミングで混乱しがちなのが、「ブロックが受け取る引数」です。

この例ではyield(logger)に渡された値を do |logger| で受け取っています。

処理の流れが普通の関数呼び出しの逆になるので、初めての人は慣れるまでたくさん素振りして下さい。

ところでブロックを引数にとるメソッドの書き方には何種類かありますが、引数名に ‘&’ をつける方法は、受け取ったブロックを他のメソッドにそのまま渡したい時に便利です。

def method1(obj, &block)
  obj.method2(&block)
end

例を書くための例といった感じで、なにをしたいのかさっぱり不明ですが、実際のプロジェクトでまさにこういうコードが実際に必要になったというか先週書いたばかりです。プログラミングって不思議ですね!

ところで、振る舞いのリファクタリング以外でも、ブロック渡しは例えばテストを書くとき等にも役に立ちます。

shimadaがどんな風に使ったかは、また今度の機会に紹介したいと思います。

みなさんもブロックをどんどん使ってRubyを楽しんで下さい。

記事を共有

最近人気な記事