やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

日々、自分のベストプラクティスは変化している

ある日のこと、久しぶりに過去に自分が実装した機能のコードを見ていると「何だこのコードは...」と悪い意味で驚愕しました。内容はデータベースから取ってきたデータをひたすら加工し、集計して最終的に画面に表示するというものです。
ひたすら加工と書いた通り、本当にひたすらデータを加工しまくっています。配列からハッシュ(Rubyなので)へ。ハッシュをさらにハッシュや配列に記録したりと、データ加工の処理がベルトコンベアのように淡々と実行されていきます。

class Pipeline
  def exec(params)
    fetch_records(params)
      .then { |records| do_something1(records) }
      .then { |records| do_something2(records) }
      .then { |records| do_something3(records) }
  end
  
  def fetch_records(params)
    ...
  end
  
  def do_something1(records)
    records.filter do |record|
      ...
    end
  end
  
  def do_something2(records)
    records.reduce({}) do |record, accum|
      ...
    end
  end
  
  def do_something3(recods_hash)
    recods_hash.reduce({}) do |record, accum|
      ...
    end
  end
end

なぜそのような実装をしたのか

実装を担当した当時、自分は関数型言語の世界にハマっていました。特に影響を受けたのはelixirというプログラミング言語です。複数の処理(正しくは関数の実行)をパイプライン演算子で簡単に繋げていくことが出来るため、小さな関数を作って組み合わせることで非常にパワフルな処理を生み出すことが出来ます。

Enum.to_list(1..100)
|> Enum.map(fn n -> n * 2 end) # [2,4,6...]
|> Enum.filter(fn n -> n < 100 end) # [2,4...98]
|> Enum.reduce(0, fn n, acc -> acc + n end)
|> IO.puts() # 2450

www.okb-shelf.work

この方法に感銘を受けたので、当時はクラスメソッドに小さな関数を定義し組み合わせることで「Rubyでも同じことをやろう」と考えていました。結果的に、実現することは出来て満足しましたが、今になって改めてコードを見てみると「何だこのコードは...」と感じてしまいました。

らしさを大切に

どのプログラミング言語にも適切な書き方やパラダイムが存在しており得意なこと、苦手なことがあります。これぐらいの規模のコードならelixirのパイプラインを組み合わせるという方法は上手く機能していますが、大規模な場合や仕様が複雑なケースでは可読性が低下します。

# これぐらいならまだ良い
class Pipeline
  class << self
    def exec
      # thenを使うともう少しマシになるが |> のスッキリさには劣る
      lst = (1..100).to_a
      lst = lst.map { |n| n * 2 }
      lst = lst.filter { |n| n < 100 }
      lst.reduce(0) { |n, acc| acc + n }
    end
  end
end

puts Pipeline.exec # 2450

それよりかはRubyで実装するのならRubyの強みを生かして、クラス設計などを適切に行う必要があり、いわゆる「らしさ」を大切にしたいと思いました。
「何だこのコードは...」と感じたのは自分の視野、知識が広がったという成長の証なのかもしれません。自分の中のベストプラクティスというのは流動的であり「日々、変化しているんだ」と実感しました。先月に書いたコードも、今になって見れば書き直したくなることはよくあることです。