10-1
目的駆動で名前を設計する
「金額」「値」のような大雑把な名前は何でも受け入れてしまうため、無関係なロジックを引き寄せて低凝集になる。どんなビジネス目的に使う値なのかを分析し、目的に特化した狭い名前を選べば、置けるロジックが名前によって限定され、構造が崩れにくくなる。
汎用の「金額」クラスにすべてを背負わせる
✕ Bad
class Amount:
"""金額。請求でも税でも手数料でもこれを使う"""
def __init__(self, value: int) -> None:
self.value = value
def build_invoice(fee: Amount, rate: float) -> dict:
tax = Amount(int(fee.value * rate)) # これは源泉徴収税額のつもり
transfer_fee = Amount(660) # これは振込手数料のつもり
paid = Amount(fee.value - tax.value - transfer_fee.value)
return {"fee": fee, "tax": tax, "paid": paid}✓ Good
@dataclass(frozen=True)
class AdvisoryFee:
"""税理士顧問料(税抜)"""
amount: int
@dataclass(frozen=True)
class WithholdingTax:
"""源泉徴収税額"""
amount: int
def __post_init__(self) -> None:
if self.amount < 0:
raise ValueError("税額は0以上")
@classmethod
def on_advisory_fee(cls, fee: AdvisoryFee, rate: WithholdingRate) -> "WithholdingTax":
return cls(int(fee.amount * rate.value))名前を狭くすると「このロジックはどこに書くべきか」「仕様変更時にどこを見ればよいか」が名前から決まる。Python では型ヒント+mypy が AdvisoryFee と WithholdingTax の取り違えを機械的に検出してくれるのも実利。