📌

Ruby におけるコードの多様性

2021/12/25に公開

Ruby の世界では、ひとつのことを実現するのにさまざまの記法が提供されている。シンプルな課題を解決するコードであっても、人それぞれの色が表に出てくる。だから、プルリクエストが来たときに、どうしても「私ならこうする」ということを考えがちで、その個人的な理想に対するギャップをコメントしてしまうことが多い。けれど、どのコードであっても

  • 要件を満たしている
  • 計算量のオーダーに差がない(またはデータサイズが極めて小さい)
  • 可読性がある

なら、特に議論せずに、そのコードを受け容れるのが良いと思った。人種の違いや宗教の違いを受け容れようとする風潮が社会に生まれてきたように、コードの多様性も受け容れることが好ましいような気がしている。

たとえば、下記の課題があるとする。

  # 入力例
  Member = Struct.new(:name, :score, :active)
  MEMBERS = [
    Member.new("santa", 70, true),
    Member.new("shiro", 100, true),
    Member.new("nana", 100, false),
    Member.new("eleven", 2000, true)
  ]

  # 期待する出力(json)
  # - 属性 active が true のレコードのみを出力
  # - 属性 name をキーとし、score を値とする
  # - score が 100 を超えている場合は不正値としてメッセージを出力
  {
    "santa": 70,
    "shiro": 100,
    "eleven": "wrong score!"
  }

私が良いと思うコードを3つ書いてみた。どれも入出力の要件を満たしていて、計算量のオーダー上は差がなく、可読性がある。私にこのようなレビューリクエストが来たとしたら、コメントなしにそのまま承認すると思う。

  def summary0(members)
    result = {}

    members.each do |member|
      if member.active
        if member.score > 100
          result[member.name] = "wrong score!"
        else
          result[member.name] = member.score
        end
      end
    end
    
     result
  end
  def summary1(members)
    members.each_with_object({}) do |member, result|
      next unless member.active

      score = member.score
      score = "wrong record!" if score > 100

      result.merge!({ member.name => score })
    end
  end
  def summary2(members)
    members
      .filter(&:active)
      .to_h {|member| [member.name, member.score] }
      .transform_values {|score| score > 100 ? "wrong score!" : score }
  end

問題が単純すぎる上に、データ型が固定されているからあまり議論することはないけれど、それでもコードレビューが荒れたりすることはある。each_with_object を使うのがエレガントで、ローカル変数を使って計算結果を保持するのは醜いことだと言う人もいる。手続き的なコードがダサいという人もいる。

たぶんそんなことはどうでもいい。結局、時と場合による。好みによる。そして好みは変わっていく。それなら、コードの多様性を受け入れて、みんなで楽しくプログラミングするほうが生産的なのではないかと思う。

Discussion