Writing

−scattered checks 3 min read defensive

Validate Once, Then Trust the Types

Defensive code scattered everywhere is fear. A typed boundary is a decision.

The smell is easy to recognise: isinstance checks and None guards in every second function, because nobody knows what has already been validated. When validation lives everywhere, it is guaranteed nowhere — and every new function pays the fear tax again.

The fix is structural, not more checks. Pick the boundary — the place where outside data actually enters: job parameters, a config file, an API payload. Parse it there, once, into a typed model. Pydantic is built for exactly this job.

The boundary python
from pydantic import BaseModel, Field

class JobConfig(BaseModel):
    catalog: str
    target_schema: str
    retries: int = Field(ge=0, le=5)
    dry_run: bool = False

cfg = JobConfig.model_validate(raw_args)   # invalid input dies HERE, loudly

Past that line, the rules change. Inside the system, function signatures do the talking — a JobConfig cannot exist in an invalid state, so there is nothing left to re-check:

After the boundary python
def build_target(cfg: JobConfig) -> str:
    # no isinstance, no None checks, no re-validation:
    # if this function runs, the config already parsed
    return f"{cfg.catalog}.{cfg.target_schema}"

Parse, don’t validate: make invalid states unrepresentable, then stop checking for them.

This is the same principle as the SQL-escaping I deleted in the trust-boundary piece, seen from the other side. Defenses belong at real boundaries. Inside the boundary, defensive re-checks are noise — and noise is worse than nothing, because it buries the checks that matter.

So: annotate everything, parse at the edge, and let downstream code trust its types. The honest limit — a boundary only works if everything passes through it. Data that leaves and comes back (a JSON round-trip, a table someone else writes) has crossed outside again, and earns one new parse at the door.

Ask the assistant about this