Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyリファクタリング入門:コードの品質を高める基本テクニック

リファクタリングとは、プログラムの動作(振る舞い)を一切変えずに、コードの品質を向上させる作業のことです。リファクタリングを適切に行うことで、コードは格段に扱いやすくなり、読みやすく保守しやすいものになります。

この記事では、Rubyでよく使われる代表的なリファクタリング手法をいくつかご紹介します。

それでは始めましょう!

メソッドの抽出(Extract Method)

最も一般的なリファクタリングのひとつが「メソッドの抽出」です。既存のメソッドから一部のコードを取り出して新しいメソッドに移すことで、説明的な名前を持つ小さなメソッド群を作れます。

まずは次の例を見てみましょう。

@sold_items = %w( onions garlic potatoes )

def print_report
  puts "*** Sales Report for #{Time.new.strftime("%d/%m/%Y")} ***"
  @sold_items.each { |i| puts i }
  puts "*** End of Sales Report ***"
end

このメソッドの中で最も見栄えが悪いのは、現在日付を生成している部分です。まずここを独立したメソッドとして切り出してみます。

def print_report
  puts "*** Sales Report for #{current_date} ***"
  @sold_items.each { |i| puts i }
  puts "*** End of Sales Report ***"
end

def current_date
  Time.new.strftime("%d/%m/%Y")
end

これだけでもかなり読みやすくなりましたが、さらに一歩進んでみましょう。処理をいくつかのメソッドに分割すると、次のようなコードになります。

def print_report
  print_header
  print_items
  print_footer
end

def print_header
  puts "*** Sales Report for #{current_date} ***"
end

def current_date
  Time.new.strftime("%d/%m/%Y")
end

def print_items
  @sold_items.each { |i| puts i }
end

def print_footer
  puts "*** End of Sales Report ***"
end

確かにコード全体の行数は増えましたが、そのぶん格段に読みやすくなったと思いませんか? 小さなメソッドを恐れないでください。小さく分割されたメソッドこそ、良いコードへの近道です。

条件分岐のリファクタリング

複雑な条件式も、メソッドとして切り出すことで読みやすさを大きく改善できます。

例:

def check_temperature
  if temperature > 30 && (Time.now.hour >= 9 && Time.now.hour <= 17)
    air_conditioner.enable!
  end
end

この if 文の後半部分(営業時間内かどうかの判定)は、一見しただけでは意図が分かりにくいですね。ここをメソッドとして抽出してみましょう。

def check_temperature
  if temperature > 30 && working_hours
    air_conditioner.enable!
  end
end

def working_hours
  Time.now.hour >= 9 && Time.now.hour <= 17
end

ここで行ったのは、条件式に working_hours(営業時間中)という説明的な名前を与えることです。これにより、将来このコードを読む人(未来の自分自身を含めて!)にとって、意図がとても理解しやすくなります。

メソッドオブジェクトへの置き換え(Replace Method with Method Object)

肥大化して制御しきれなくなった巨大なメソッドに遭遇することもあります。大きなメソッドはローカル変数を大量に抱えがちで、単純な分割が難しいのが難点です。そんなときに有効なのが「メソッドオブジェクトへの置き換え」という手法です。

「巨大なメソッドは、クラスが隠れられる場所である。」 — アンクルボブ(Robert C. Martin)

実際の例を見てみましょう。

require 'socket'

class MailSender
  def initialize
    @sent_messages = []
  end

  def send_message(msg, recipient = "rubyguides.com")
    raise ArgumentError, "message too small" if msg.size < 5

    formatted_msg = "[New Message] #{msg}"

    TCPSocket.open(recipient, 80) do |socket|
      socket.write(formatted_msg)
    end

    @sent_messages << [msg, recipient]

    puts "Message sent."
  end
end

sender = MailSender.new
sender.send_message("testing")

このリファクタリングでは、新しいクラスを作成し、ローカル変数をインスタンス変数へと昇格させます。こうすることで、データを受け渡しする手間を気にせず、さらなるリファクタリングを進められるようになります。

リファクタリング後の MailSender クラスがこちらです。

class MailSender
  def initialize
    @sent_messages = []
  end

  def deliver_message(message)
    send(message)
    @sent_messages << message
    puts "Message sent."
  end

  def send(msg)
    TCPSocket.open(msg.recipient, 80) { |socket| socket.write(msg.formatted_msg) }
  end
end

そして、新しく導入したクラスがこちらです。

class Message
  attr_reader :msg, :recipient

  def initialize(msg, recipient = "rubyguides.com")
    raise ArgumentError, "message too small" if msg.size < 5

    @msg       = msg
    @recipient = recipient
  end

  def formatted_msg
    "[New Message] #{msg}"
  end
end

sender = MailSender.new
msg    = Message.new("testing")

sender.deliver_message(msg)

まとめ

今回紹介したリファクタリング手法を活用すれば、「単一責任の原則(Single Responsibility Principle)」に沿った設計を保ちやすくなり、クラスやメソッドの肥大化を防ぐことができます。

まずは手元のコードから、小さなメソッドの抽出から試してみてはいかがでしょうか。日々の積み重ねが、長期的に見て大きな生産性の向上につながります。

  1. Rubyで学ぶニューラルネットワーク超入門――思っているほど難しくない!

    この記事では、ニューラルネットワークの基礎と、Rubyを使った実装方法を学びます。人工知能やディープラーニングに興味はあるものの、何から始めればいいのか分からない方にぴったりの内容です。シンプルな例題を通じて、重要な概念をわかりやすく解説していきます。実際にRubyで多層ニューラルネットワークを書くことはほとんどありませんが、コードがシンプルで読みやすいため、内部で何が起きているのかを理解するには最適なアプローチです。まずは少し立ち止まって、私たちがここに至るまでの道のりを見てみましょう。 映画『エクス・マキナ』のワンシーン 『エクス・マキナ(Ex Machina)』は2014年に公開され

  2. Ruby 2.6の新機能9選|コード例でわかる注目ポイントを徹底解説

    Ruby 2.6には、開発者の生産性を高める新しい機能やパフォーマンス改善が多数盛り込まれています。 本記事では、Ruby 2.6で導入された9つの注目新機能を、実際のコード例とともにわかりやすく紹介します。最新のRuby動向をキャッチアップしたい方は、ぜひ最後までご覧ください。 1. 無限Range(Endless Range) Ruby 2.5以前でもFloat::INFINITYを使えば終端のない範囲を表現できましたが、Ruby 2.6ではさらに直感的な記法が使えるようになりました。 新しい無限Rangeは次のように書きます。 (1..) 通常のRangeが(1..10)のように終端