販売チャネルごとの条件分岐を解消 – クラスのリファクタリング
- 最終更新日: 2025年4月10日(木)
- 公開日: 2025年4月10日(木)
はじめに
こんにちは。デザインシステム室の森口です。
弊社で展開している健康食宅配サービス「ミールタイム」で提供している商品には、単体商品と、複数商品をまとめたセット商品が存在します。
また、販売チャネルもWeb・電話・定期便など複数に分かれており、商品が「販売可能かどうか」を判定する処理(salable?
)には、それぞれ異なる条件が必要です。
当初は、こうした条件分岐をすべて一つのクラス(SalesItem
)内で対応しており、以下のような課題がありました。
課題
販売チャネルごとのルール(例:Webでは販売不可、電話では可能など)をSalesItem
内で分岐していたため、責務が肥大化。
条件分岐がネストし、可読性・保守性が低下。
改修方針
単体商品とセット商品の共通属性・処理を抽出し、共通インターフェースとしてSalesItem
にまとめる。
販売チャネルごとの処理はサブクラス(例:SalesItem::WebChannel
)へ分離。
条件分岐のロジックは必要最小限にとどめ、salable?
の読みやすさを担保。
エラー種別ごとに専用の例外を定義し、意図しないエラーとの区別を明確化。
実装概要
以下は、現在のSalesItem
の構成の一部です(簡略化)。
SalesItem.create(code: "XXXX", sales_channel: :web).salable?
def self.create(code: nil, sales_channel: :web)
klass = channel_class(sales_channel)
klass.new(code: code)
end
def self.channel_class(sales_channel)
case sales_channel
when :web then SalesItem::WebChannel
...
else raise ArgumentError, "Invalid sales channel: #{sales_channel}"
end
end
商品種別とチャネルを意識することなく、共通のインターフェースで販売可能判定が可能になっています。
内部的には、単体商品
か セット商品
かを判定し、それぞれに対応する属性を設定。さらに、チャネルに応じて SalesItem::WebChannel
や SalesItem::TelChannel
などのサブクラスで判定ロジックを委譲します。
def salable?(check_params)
case self.type
when セット商品
check_selection(check_params)
when 単体商品
single_item_check(...)
else
raise InvalidTypeError
end
true
end
チャネル固有の判定(例:おせち商品はWebチャネルでは販売不可)などは、規定クラスのメソッドを各サブクラスでオーバーライドして対応しています。
以下ではchannel_specific_checksメソッドをオーバーライドしていますが、基底クラスに定義されている元々のchannel_specific_checksメソッドは何も処理を行いません。
class SalesItem::WebChannel < SalesItem
def channel_specific_checks(check_params)
check_osechi_sales_channel(check_params)
check_web_specific_restrictions
end
end
テストの活用
今回の改修において、既存のRspec
によるテストコードが大きく役立ちました。
SalesItem
クラスを作成した当初から導入していたことで、salable?
の挙動に対する期待値が明確になっており、内部のロジックを刷新してもテストが通るかどうかで正しさを担保できました。
結果として、リファクタリングにおける心理的ハードルを大きく下げることができました。
終わりに
業務ロジックの複雑さは避けられませんが、「どこで判断するか」「誰が責務を持つか」を丁寧に分けることで、見通しの良いコードが実現できます。
今回は販売チャネルごとの条件分岐を減らすために、SalesItem
を中心として販売チャネルごとにサブクラスを持たせる設計を採用しました。要件追加にも柔軟に対応できる構成となったことで、開発・保守の負担を大幅に削減できていると思います。
現在デザイン・システム室では、新しいメンバーを募集しています。
皆様からのご応募をお待ちしております。