Pythonリファクタリング ― 実際事例:レストラン注文システム
By JoeVu, at: 2024年11月15日13:07
Estimated Reading Time: __READING_TIME__ minutes
                                
                            
                                
                            1. はじめに
複雑化が進むにつれて、入れ子になったif-else文で埋め尽くされた、見づらいコードは現実世界のアプリケーションに潜り込みがちです。時間が経つにつれて、これによりシステムの保守、デバッグ、スケーリングが困難になります。この記事では、レストランの注文システムというシナリオを取り上げ、オブジェクト指向設計によるリファクタリングがどのようにしてロジックを簡素化し、スケーラビリティを向上させるかを示します。
2. 問題点
当社のレストラン注文システムは以下の機能を処理します。
- 顧客の種類(VIP、初回利用など)に基づいた割引。
 - デリバリー、店内飲食、テイクアウトの注文に対する追加料金。
 - 場所によって異なる税金。
 
元の、見づらい実装は次のとおりです。
class Order:
    def __init__(self, order_type, customer_type, base_price, location=None):
        self.order_type = order_type
        self.customer_type = customer_type
        self.base_price = base_price
        self.location = location
    def calculate_total(self):
        total = self.base_price
        
        # 顧客の種類に基づいた割引
        if self.customer_type == "VIP":
            total *= 0.9  # 10%割引
        elif self.customer_type == "first-time":
            total *= 0.85  # 15%割引
        # 注文の種類に基づいた追加料金
        if self.order_type == "delivery":
            if self.location == "urban":
                total += 5
            elif self.location == "rural":
                total += 10
        elif self.order_type == "dine-in":
            total += 2
        # 税金
        if self.location in ["urban", "rural"]:
            total *= 1.05
        elif self.order_type == "takeout":
            total *= 1.02
        
        # その他の条件
        if self.customer_type == "VIP" and self.order_type == "dine-in":
            total -= 5  # VIPは店内飲食で5ドル割引
        return total
order = Order(order_type="delivery", customer_type="VIP", base_price=100, location="rural")
print(f"Total Order Price: ${order.calculate_total():.2f}")
このコードの何が悪いのか:
- 
	
深くネストされたロジック:
- 複数レベルの
if-else文により、コードの読み取りと追跡が困難になります。
 - ロジックがメソッド全体に散らばっているため、コードが何をしているかを一目で理解することが困難です。
 
 - 複数レベルの
 - 
	
スケーラビリティが低い:
- 新しい顧客の種類、割引、または注文の種類を追加するには、すでに複雑なこのメソッドを変更する必要があります。
 - このアプローチは、オープン/クローズドの原則(コードは拡張に対して開かれていなければならないが、変更に対しては閉じられていなければならない)に違反しています。
 
 - 新しい顧客の種類、割引、または注文の種類を追加するには、すでに複雑なこのメソッドを変更する必要があります。
 - 
	
単一責任の原則の違反:
calculate_totalメソッドは、割引、追加料金、税金を1か所で処理するため、「ゴッドメソッド」となり、処理が多すぎます。
- ロジックの1つの部分の変更により、メソッドの関連のない部分が壊れるリスクがあります。
 
 - 
	
テストが困難:
- この関数をテストするには、顧客の種類、注文の種類、場所に関する広範囲の条件を網羅する必要があります。
 - 1つの条件ブロックのバグは、メソッド全体に波及する可能性があります。
 
 - この関数をテストするには、顧客の種類、注文の種類、場所に関する広範囲の条件を網羅する必要があります。
 - 
	
保守性が低い:
- ビジネスルールが変更された場合(例:新しい割引や税率)、コードの関連する部分を注意深く探し出して更新する必要があります。
 - このような変更中にエラーが発生するリスクは高くなります。
 
 - ビジネスルールが変更された場合(例:新しい割引や税率)、コードの関連する部分を注意深く探し出して更新する必要があります。
 - 
	
強い結合:
- すべてのビジネスロジックは
calculate_totalメソッドに強く結合されています。
 - レポートツールや他のサービスなど、他の場所で再利用することはできません。
 
 - すべてのビジネスロジックは
 
3. リファクタリング
元の設計で特定された問題に対処するために、戦略パターンとオブジェクト指向設計を使用してシステムをリファクタリングします。これらの改善は、モジュール性、カプセル化、懸念事項の分離などの重要な原則に従い、ロジックを再利用可能なクラスに分割することに重点を置いています。
 
リファクタリングによる主な改善点
1. 割引のための戦略クラスの導入
- 顧客の種類に基づいた割引ロジック(例:
VIPDiscount、FirstTimeDiscount)を個別のクラスに抽出します。
 - これにより、
Orderクラス内で入れ子になったif-else条件の必要性がなくなります。
 - 改善点:各割引戦略は、独自の動作をカプセル化するため、再利用可能で拡張が容易になります。
 
2. 注文の種類のための戦略クラスの作成
delivery、dine-in、takeoutの追加料金の計算は、独自の戦略クラス(例:DeliveryOrder、DineInOrder)に移動します。
- これらのクラスは、メインの
Orderクラスを乱雑にすることなく、場所に基づくロジック(例:都市部または地方部の配送手数料)を処理します。
 - 改善点:「CateringOrder」などの新しい注文の種類を追加するには、既存のコードを変更せずに新しい戦略クラスを作成するだけです。
 
3. コアOrderクラスの簡素化
Orderクラスは、軽量なオーケストレーターとしての役割を果たすようになりました。- 割引計算を
DiscountStrategyに委任します。
 - 追加料金の計算を
OrderTypeStrategyに委任します。
 - これらの戦略の結果に基づいて税金の計算を処理します。
 
- 割引計算を
 - 改善点:
Orderクラスは、クリーンで、焦点を絞っており、単一責任の原則に準拠しています。
 
4. 継承よりも合成を使用する
- ビジネスロジックを単一クラスにハードコーディングする代わりに、リファクタリングでは合成を使用して、戦略を動的に組み合わせます。
 - 改善点:これにより、システムは柔軟になり、さまざまなシナリオに合わせてカスタマイズしやすくなります。
 
リファクタリング後のコード
class DiscountStrategy:
    """割引戦略の基底クラス。"""
    def apply_discount(self, order):
        return order.base_price
class VIPDiscount(DiscountStrategy):
    def apply_discount(self, order):
        return order.base_price * 0.9  # VIPは10%割引
class FirstTimeDiscount(DiscountStrategy):
    def apply_discount(self, order):
        return order.base_price * 0.85  # 初回利用者は15%割引
class NoDiscount(DiscountStrategy):
    def apply_discount(self, order):
        return order.base_price
class OrderTypeStrategy:
    """注文の種類の戦略の基底クラス。"""
    def apply_surcharge(self, order):
        return 0
class DeliveryOrder(OrderTypeStrategy):
    def apply_surcharge(self, order):
        return 5 if order.location == "urban" else 10  # 配送手数料は場所による
class DineInOrder(OrderTypeStrategy):
    def apply_surcharge(self, order):
        return 2  # 店内飲食のサービス料
class TakeoutOrder(OrderTypeStrategy):
    def apply_surcharge(self, order):
        return 0  # テイクアウトの追加料金なし
class Order:
    def __init__(self, base_price, discount_strategy, order_type_strategy, location=None):
        self.base_price = base_price
        self.discount_strategy = discount_strategy
        self.order_type_strategy = order_type_strategy
        self.location = location
    def calculate_total(self):
        # 割引と追加料金の計算を委任
        discounted_price = self.discount_strategy.apply_discount(self)
        surcharge = self.order_type_strategy.apply_surcharge(self)
        
        # 税金ロジック
        tax_rate = 0.05 if self.location in ["urban", "rural"] else 0.02
        tax = discounted_price * tax_rate
        # 合計計算
        return discounted_price + surcharge + tax
4. なぜそれが優れているのか
リファクタリングされたコードには、いくつかの利点があります。
- 可読性:各責任は、そのクラスで明確に定義されています。
 - スケーラビリティ:新しい割引や注文の種類の追加は簡単です。新しいクラスを作成するだけです。
 - テスト容易性:個々の戦略は、個別にテストできます。
 - 保守性:ロジックの1つの部分の変更が、他の部分に影響を与えることはありません。
 
5. まとめ
レストランの注文システムをリファクタリングすることにより、見づらい入れ子になったif-elseブロックを、モジュール化されたオブジェクト指向設計に変換しました。このアプローチにより、コードはクリーンで、スケーラブルになり、保守が容易になります。
システムが複雑さに溺れている場合は、同様の設計原則を採用して、システムを簡素化し、将来に備えましょう!