Contracts, invariants, and exceptions
Design by contract describes a function by three things: preconditions that callers must satisfy, postconditions that the function guarantees, and class invariants that stay true whenever no method is running. When a precondition is violated, the caller has a bug. When a postcondition or invariant fails, the function itself has a bug.
Python
class InsufficientFunds(Exception):
pass
class Account:
def __init__(self, balance):
if balance < 0:
raise ValueError("opening balance cannot be negative")
self._balance = balance
@property
def balance(self):
return self._balance
def withdraw(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
if amount > self._balance:
raise InsufficientFunds(f"balance {self._balance} is less than {amount}")
self._balance -= amount
assert self._balance >= 0
return self._balance
account = Account(100)
print(account.withdraw(30))
for amount in (0, 500):
try:
account.withdraw(amount)
except InsufficientFunds as error:
print("rejected:", error)
except ValueError as error:
print("invalid:", error)
print(account.balance)