Filter concepts by levelShowing all levels.

Python · Static analysis and engineering practices

Static analysis and engineering practices

mypy, pyright, and a real security finding, plus the human-facing half of code quality — naming, cohesive functions, and safe refactoring

  • mypy / pyrightstatic type checking — catches bugs before the code runs
  • banditcatches DANGEROUS patterns a style linter has no concept of
  • Small cohesive functionsone job, one reason to change, trivially testable
  • Refactor safelystructure changes, behavior never does — tests prove it

1.Code review, clear naming, type annotations, and documentation

Code review is a second person reading code before it merges, catching what the author could not see. Clear naming, type annotations, and documentation make that review — and every later reading — fast, not a slow reverse-engineering act.

Key features

  • Code review catches bugs, design issues, and readability problems a solo author misses
  • A good name states what it holds or does — retry_count, not n or temp
  • A type annotation is documentation a type checker verifies — it cannot silently go stale
  • A docstring explains WHY and the contract (inputs, exceptions) — not what is obvious
  • Documentation duplicating what code already says adds cost without adding information

Naming — vague vs. clear

Naming — vague vs. clear
VagueClear
n, temp, dataretry_count, parsed_config, active_users
def process(x):def calculate_shipping_cost(order: Order) -> Decimal:
# increment counter# retry only on 5xx -- 4xx means the request itself is invalid
The same function, unreadable vs. self-explaining

calc(o)

  • Vague name — what does it calculate?
  • No type hints — o could be anything
  • No docstring — the $50 rule is hidden in the body

calculate_shipping_cost(order: Order) -> Decimal

  • Name states exactly what it does
  • -> Decimal is checked by a type checker, not just claimed
  • Docstring states the one non-obvious rule, nothing else
  • calc(o)
    • Vague name — what does it calculate?
    • No type hints — o could be anything
    • No docstring — the $50 rule is hidden in the body
  • calculate_shipping_cost(order: Order) -> Decimal
    • Name states exactly what it does
    • -> Decimal is checked by a type checker, not just claimed
    • Docstring states the one non-obvious rule, nothing else

Command

python
def calculate_shipping_cost(order: Order) -> Decimal:
    """Return shipping cost, free above the $50 threshold.

    Raises ValueError if order.items is empty.
    """
    ...

Code review catches what a solo author cannot see; naming, types, and docstrings that explain WHY make code fast to read.

2.Small cohesive functions, project structure, and premature abstraction

A small, cohesive function does one job and is named for it. A consistent project structure means every developer can guess where something lives. Premature abstraction generalizes before a second real use case exists.

Key features

  • A cohesive function has one reason to change — mixing validation, logic, I/O adds more
  • A function doing one thing is trivially testable, without mocking half the codebase
  • Consistent structure lets a developer navigate a new file without hunting for things
  • Premature abstraction: building a generic solution for a case that does not exist yet
  • Write the concrete version twice before extracting an abstraction — that reveals its shape

One function, several responsibilities — split

One function, several responsibilities — split
BeforeAfter
process_order() validates, charges, emails, logsvalidate_order(), charge_order(), send_confirmation(), log_order()
Each change risks breaking an unrelated concernEach function has one reason to change
Testing requires mocking payment AND email AND loggingEach function tests in isolation, one mock at a time
One function, three jobs — vs. three cohesive ones

process_order() does everything

  • Validates, charges, emails, logs — one function
  • Any change risks breaking an unrelated concern
  • Testing needs mocking payment AND email AND logging

validate_order(), charge_order(), ...

  • Each function has exactly one reason to change
  • Each is testable in isolation, one mock at a time
  • process_order() just names the steps, in order
  • process_order() does everything
    • Validates, charges, emails, logs — one function
    • Any change risks breaking an unrelated concern
    • Testing needs mocking payment AND email AND logging
  • validate_order(), charge_order(), ...
    • Each function has exactly one reason to change
    • Each is testable in isolation, one mock at a time
    • process_order() just names the steps, in order

Command

python
def validate_order(order): ...
def charge_order(order, gateway): ...
def send_confirmation(order): ...

def process_order(order, gateway):
    validate_order(order)
    charge_order(order, gateway)
    send_confirmation(order)

A cohesive function has one reason to change; write a concrete case twice before extracting a shared abstraction.

3.Refactoring safely and technical debt management

Refactoring changes HOW code is structured without changing WHAT it does — a passing test suite before and after proves that. Technical debt is a shortcut that trades short-term speed for a larger cost paid later.

Key features

  • Refactoring changes internal structure only — external behavior must stay identical
  • A test suite passing before AND after a refactor is the actual evidence behavior held
  • Refactoring with no test coverage is refactoring on faith — real risk of silent breakage
  • Technical debt is a shortcut taken deliberately (tracked) or accidentally (a rushed fix)
  • Deliberate debt with a repayment plan can be right — untracked debt is the real danger

Deliberate vs. accidental technical debt

Deliberate vs. accidental technical debt
KindExampleRisk if untracked
Deliberate"ship the simple version now, generalize after launch" — a real TODOLow, if actually revisited
Accidentala rushed fix under deadline pressure, no comment explaining itHigh — nobody remembers why it exists
A safe refactor, one confirmable step at a time

Confirm coverage

tests exist and pass against current behavior, before any change

Change structure

extract, rename, or simplify — one small step, no new behavior mixed in

Run the SAME tests

green means behavior held; a single giant rewrite defers this too long

Repeat, or stop

each step is independently confirmable — nothing is "done" until tests prove it

  1. Confirm coverage — tests exist and pass against current behavior, before any change
  2. Change structure — extract, rename, or simplify — one small step, no new behavior mixed in
  3. Run the SAME tests — green means behavior held; a single giant rewrite defers this too long
  4. Repeat, or stop — each step is independently confirmable — nothing is "done" until tests prove it

Command

python
# 1. Write/confirm tests covering current behavior
# 2. Refactor internal structure
# 3. Run the SAME tests -- unchanged behavior means they still pass
pytest test_module.py

A refactor changes structure, never behavior — a passing test suite before and after is the proof.

Remember

  • mypy and pyright both catch type errors statically, without running code — neither replaces real tests.
  • A security linter (bandit) checks for dangerous PATTERNS a style linter has no concept of checking.
  • A cohesive function has one reason to change and tests in isolation, with no mocking gymnastics.
  • A refactor changes structure, never behavior — keep behavioral changes in separate, independently reviewable commits.

Pro tip

  • mypy only fully checks functions that already have type annotations — an untyped function is mostly unchecked.
  • A bare # nosec suppresses every future finding on that line; # nosec B602 suppresses only that specific one.
  • Write the concrete version at least twice before extracting a shared abstraction — the second case reveals its real shape.
  • A docstring earns its keep by stating something NOT already obvious from the function signature.

Next up

  • Performance

Sources

  • mypy — mypy.readthedocs.io
  • bandit — bandit.readthedocs.io
  • Every tool run for real: mypy 2.3.0, pyright 1.1.411, bandit 1.9.4 · checked 2026-08-21

Spotted a mistake on this page? Tell us.