コードの信頼性と保守性を高める: データに対して信頼できる唯一の情報源を持つ

コードの信頼性と保守性を高める: データに対して信頼できる唯一の情報源を持つ プログラミング

コードがデータを処理する際、信頼できる唯一の情報源を持つことが重要です。
本記事では、データの種類、複数の情報源を持つことによる問題、およびその解決策について解説します。

データの種類

  1. 一次データ: コードに提供する必要があるデータ
  2. 派生データ: 一次データに基づいてコードが計算できるデータ

例えば、家計簿アプリケーションを考えてみましょう。

一次データは、収入と支出の取引履歴です。
派生データは、収入の合計、支出の合計、および収支バランスです。

収入の合計と支出の合計は、取引履歴から計算できます。
収支バランスは、収入の合計から支出の合計を差し引いて計算できます。

複数の情報源による問題

派生データを別の情報源として保持すると、不整合な状態が発生する可能性があります。
以下のコードは、その問題を示しています。

class HouseholdAccount:
    def __init__(self, transactions, income, expense, balance):
        self.transactions = transactions
        self.income = income
        self.expense = expense
        self.balance = balance

このクラスでは、取引履歴、収入の合計、支出の合計、および収支バランスを個別に保持しています。
しかし、収入の合計、支出の合計、および収支バランスは、取引履歴から計算できるため、冗長な情報です。

以下のようにインスタンス化すると、不整合な状態が発生します。

transactions = [
    Transaction('income', 1000),
    Transaction('expense', 500)
]

account = HouseholdAccount(transactions, 1000, 500, 1000)  # 収支バランスが不整合

取引履歴から計算すると、収入の合計は1000、支出の合計は500、収支バランスは500になるはずです。
しかし、収支バランスが1000になっています。

このような不整合な状態は、バグの原因となります。

解決策: 一次データを信頼できる唯一の情報源とする

派生データは、一次データから計算するようにします。
以下のコードは、その解決策を示しています。

class Transaction:
    def __init__(self, type, amount):
        self.type = type
        self.amount = amount

class HouseholdAccount:
    def __init__(self, transactions):
        self.transactions = transactions
    
    def get_income(self):
        return sum(t.amount for t in self.transactions if t.type == 'income')
    
    def get_expense(self):
        return sum(t.amount for t in self.transactions if t.type == 'expense')
    
    def get_balance(self):
        return self.get_income() - self.get_expense()

このコードでは、Transaction クラスを導入しています。
Transaction クラスは、取引の種類(type)と金額(amount)を表します。

取引の種類は、’income’(収入)または’expense’(支出)のいずれかです。
HouseholdAccount クラスは、Transaction オブジェクトのリスト(transactions)を受け取ります。

このリストは、家計簿の取引履歴を表します。
get_income() メソッドは、取引リストから収入の合計を計算することになります。

具体的には、取引の種類が’income’である取引の金額を合計します。
同様に、get_expense() メソッドは、取引リストから支出の合計を計算することになります。

get_balance() メソッドは、収入の合計から支出の合計を差し引いて、収支バランスを計算します。
取引リストを一次データとして扱い、収入の合計、支出の合計、および収支バランスを派生データとして計算しています。

このことにより、信頼できる唯一の情報源を維持しています。

派生データの計算コストが高い場合

派生データの計算コストが高い場合は、計算結果をキャッシュすることを検討しましょう。
以下のコードは、その例を示しています。

class HouseholdAccount:
    def __init__(self, transactions):
        self.transactions = transactions
        self.cached_income = None
        self.cached_expense = None
    
    def get_income(self):
        if self.cached_income is None:
            self.cached_income = sum(t.amount for t in self.transactions if t.type == 'income')
        return self.cached_income
    
    def get_expense(self):
        if self.cached_expense is None:
            self.cached_expense = sum(t.amount for t in self.transactions if t.type == 'expense')
        return self.cached_expense
    
    def get_balance(self):
        return self.get_income() - self.get_expense()

このクラスでは、収入の合計と支出の合計を計算した結果をキャッシュします。
キャッシュには、cached_income と cached_expense を使用する形です。

初回の get_income() または get_expense() の呼び出し時に、キャッシュに保存します。
その際、取引リストから収入または支出の合計を計算することになります。

次回以降の呼び出し時は、キャッシュから値を返すことで、計算コストを削減します。

ただし、キャッシュを使用する場合は、データの整合性に注意が必要です。
取引リストが変更された場合は、キャッシュをクリアする必要があります。

以下は、HouseholdAccount クラスの使用例です。

transactions = [
    Transaction('income', 1000),
    Transaction('expense', 200),
    Transaction('income', 500),
    Transaction('expense', 100)
]

account = HouseholdAccount(transactions)

print(account.get_income())   # 1500
print(account.get_expense())  # 300
print(account.get_balance())  # 1200

transactions.append(Transaction('income', 200))

print(account.get_income())   # 1500 (キャッシュされた値)
print(account.get_expense())  # 300  (キャッシュされた値)
print(account.get_balance())  # 1200 (キャッシュされた値)

この例では、4つの取引(2つの収入と2つの支出)を持つ家計簿が対象です。
HouseholdAccount クラスを使って、収入、支出、および収支バランスを計算しています。

取引リストに新しい取引を追加しても、キャッシュされた値が返されるため、計算結果が更新されていないことに注意してください。

キャッシュを使用する場合は、データの変更に応じてキャッシュをクリアする仕組みが必要です。

まとめ

データに対して信頼できる唯一の情報源を持つことは、コードの読みやすさと保守性を高めるために重要です。
派生データは、一次データから計算するようにしましょう。

計算コストが高い場合は、キャッシュの検討も考慮することも必要です。
ただし、キャッシュを使用する場合は、データの整合性に注意が必要となります。

これらのテクニックを活用し、より読みやすく、保守性の高いコードを書くことを心がけましょう。

タイトルとURLをコピーしました