5-3
Common/Util クラスの肥大化
「共通で使いそうだから」という理由だけで Common や Util と名付けたクラスに置くと、互いに無関係なロジックが際限なく集まってくる。重要なのは再利用性ではなく関心事。それぞれのロジックを、扱うデータと同じクラスへ引き取らせる。
01 経理ユーティリティの解体
✕ Bad
class AccountingUtil:
@staticmethod
def calc_amount_including_tax(amount: Decimal, rate: Decimal) -> Decimal:
return amount * (1 + rate)
@staticmethod
def days_until_due(issued: date, payment_terms: int) -> int:
return (issued + timedelta(days=payment_terms) - date.today()).days
@staticmethod
def mask_my_number(value: str) -> str:
return "*" * 8 + value[-4:]
# 「共通っぽいから」で今後も増え続ける…✓ Good
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True)
class TaxRate:
value: Decimal # 例: Decimal("0.10")
@dataclass(frozen=True)
class AmountIncludingTax:
value: Decimal
@classmethod
def from_excluding_tax(
cls, amount: Decimal, rate: TaxRate
) -> "AmountIncludingTax":
return cls((amount * (1 + rate.value)).quantize(Decimal("1")))02 例外: 横断的関心事は共通化してよい
✓ Good
# ログ出力・例外通知・計測などの「横断的関心事」は
# 特定のデータに属さないため、共通モジュールに置いてよい
def report_error(message: str) -> None:
logger.error(message)
notify_slack(message)
try:
ledger.post(entry)
except ValueError:
report_error("仕訳の記帳に失敗しました")すべての共通化が悪いわけではない。ログ・エラー通知・キャッシュのように業務データと無関係にどこからでも使われるもの(横断的関心事)だけが、共通モジュールの正当な住人。