I lost a week of work to a silent bug.

A blanket try/except mechanism swallowed a KeyError occurring in a hot path, and it returned None.

Fortunately, a downstream function helpfully defaulted to an empty list. No crash, just wrong behavior that looked fine in the logs.

That was when I abandoned the habit of catching everything.

The Hidden Cost of Blanket Exceptions

Debugging opacity: A broad except Exception hides the real failure and the line number where it occurred. Bugs can hide in plain sight.

Control-flow by exception: Business logic was all jumbled in with error handlers, and future maintainers have to pay that cognitive tax.

Over-trust in resilience: Maybe catching exceptions everywhere feels more resilient and safer, but that produces states of undefined behavior and silent data corruption.

What Replaced the "Wrap-It-All" Habit

1. Catch exceptions specifically near the source Use tight try blocks with narrow exception types; fail fast when unknown things happen.

try:
    price = float(row["price"])
except KeyError as e:
    raise ValueError("Missing 'price' field") from e
except ValueError as e:
    raise ValueError(f"Invalid price: {row.get('price')!r}") from e

2. Separate error from business with Result types Return either an explicit Ok(value) or Err(error), so callers have to account for and handle outcomes, rather than guessing.

from dataclasses import dataclass
from typing import Generic, TypeVar, Union
T = TypeVar("T")
E = TypeVar("E")
@dataclass(frozen=True)
class Ok(Generic[T]): value: T
@dataclass(frozen=True)
class Err(Generic[E]): error: E
Result = Union[Ok[T], Err[E]]
def parse_price(s: str) -> Result[float, str]:
    try:
        return Ok(float(s))
    except ValueError:
        return Err(f"invalid price: {s!r}")
def handle(row: dict) -> float:
    r = parse_price(row.get("price", ""))
    if isinstance(r, Err): raise ValueError(r.error)
    return r.value

3. Use else and finally to make flows obvious else runs only when no exception occurs; finally handles cleanups.

try:
    conn = connect()
    data = conn.read()
except TimeoutError:
    backoff()
else:
    process(data)
finally:
    conn.close()

Good error handling is about catching exactly what you expect and letting everything else be as loud as possible.

If you replace maybe swallow with prove behavior, then bugs will stop getting away with being polite.

Have a great day, and I hope I'll see you in the next one :)