Tony Hoare famously referred to his invention of the null reference as his "Billion-Dollar Mistake".
Decades later, NullPointerException (Java), TypeError: Cannot read properties of undefined (JavaScript), and panic: runtime error: invalid memory address or nil pointer dereference (Go) remain the most frequent causes of production crashes.
Defensive coding does not mean wrapping every line of code in paranoia-driven try-catch blocks.
Defensive coding means designing types and boundaries so that invalid states are unrepresentable by compile-time construction.
Here is how to write bulletproof, self-documenting code that never panics in production.
1. Make Illegal States Unrepresentable
Consider an e-commerce order status model:
# FRAGILE DESIGN:
class Order:
def __init__(self, status: str, tracking_number: str = None, refund_reason: str = None):
self.status = status
self.tracking_number = tracking_number
self.refund_reason = refund_reason
What stops someone from instantiating an order with status="REFUNDED" but with a tracking_number and no refund_reason? Nothing!
The Defensive Architecture: Algebraic Data Types
In modern languages, model mutually exclusive states as distinct types:
from dataclasses import dataclass
from typing import Union
@dataclass(frozen=True)
class PendingOrder:
order_id: str
@dataclass(frozen=True)
class ShippedOrder:
order_id: str
tracking_number: str # Guaranteed present by type definition!
@dataclass(frozen=True)
class RefundedOrder:
order_id: str
refund_reason: str # Guaranteed present by type definition!
Order = Union[PendingOrder, ShippedOrder, RefundedOrder]
Now, code handling ShippedOrder is guaranteed by the type system to have a valid tracking_number. The need for runtime null checks vanishes completely.
2. Replace Exceptions with the Result[T, E] Pattern
from typing import Generic, TypeVar, Union
T = TypeVar("T")
E = TypeVar("E")
class Ok(Generic[T]):
def __init__(self, value: T):
self.value = value
def is_ok(self) -> bool: return True
class Err(Generic[E]):
def __init__(self, error: E):
self.error = error
def is_ok(self) -> bool: return False
Result = Union[Ok[T], Err[E]]
def divide(a: float, b: float) -> Result[float, str]:
if b == 0:
return Err("Division by zero")
return Ok(a / b)
res = divide(10, 0)
if res.is_ok():
print(f"Result: {res.value}")
else:
print(f"Handled error safely: {res.error}")
Summary Checklist for Defensive Code
- [x] Fail Fast at the Boundary: Validate inputs at the API edge.
- [x] Prefer Immutability: Use
frozen=Truedataclasses or records. Immutable data cannot be corrupted by concurrent threads. - [x] Never Return
nullCollections: Return an empty list or set ([]), nevernull.

Top comments (0)