← Software design practices guides

How to write specs that prevent technical debt

How-To Software design practices Intermediate 1101011HOWTO-1101011

HOWTO-1101011Software design practicesIntermediate

This guide shows you how to write specs that prevent technical debt in a spec-driven development workflow.

Technical debt is not just old code or imperfect code. In day-to-day work, debt often appears as repeated shortcuts: business rules copied into several layers, adapters making domain decisions, broad modules absorbing unrelated tasks, temporary behavior becoming permanent, or public contracts changing by accident.

SpecDD cannot remove all design pressure. It can prevent many recurring debt patterns by making boundaries, ownership, non-goals, dependencies, and completion criteria explicit before the shortcut becomes normal.

Short answer

Identify the debt pattern you want to prevent, then write a local spec rule at the owner of that pattern. Use Must for the intended behavior, Must not for plausible wrong work, Forbids for blocked dependencies or access, Tasks for local cleanup, and Done when for evidence that the debt prevention rule is actually enforced.

When to use this guide

Use this guide when:

The design idea

Debt prevention works best when the spec targets a concrete failure mode. “Keep this code clean” is not a useful rule. “UI components must not save itinerary items directly” is useful because it blocks a specific shortcut.

Specs are especially good at preventing debt that comes from ambiguity. If no one knows where a rule belongs, the rule will be copied. If no one knows which dependencies are allowed, the convenient import will win. If no one knows when a task is done, work will either stop too soon or spread too far.

Steps

1. Identify the debt pattern

Name the recurring problem:

Avoid writing a generic anti-debt spec. Debt prevention belongs in the spec that owns the behavior or boundary.

2. Write the owning rule

If validation is being duplicated, put the rule in the validation owner:

Spec: Itinerary Validation

Purpose:
  Decide whether an itinerary item can be saved.

Must:
  Reject itinerary items without a place name.
  Reject itinerary items outside the trip date range.

Then make other specs depend on or reference this behavior instead of copying it.

3. Add non-goals and forbidden shortcuts

Use Must not for plausible wrong work:

Must not:
  Duplicate itinerary validation rules in UI components.
  Persist itinerary items after validation fails.

Use Forbids for hard dependency boundaries:

Forbids:
  UI importing ../adapters/*
  direct browser storage access from itinerary validation

Debt prevention often comes from a short, precise Must not.

4. Keep tasks local

Good:

Tasks:
  [ ] Move missing-place validation into Itinerary Validation.
  [ ] Remove duplicate missing-place validation from Itinerary Form.

Weak:

Tasks:
  [ ] Clean up validation debt everywhere.

Local tasks are easier to complete and review. If cleanup crosses several owners, split it into local tasks in the owning specs.

5. Record accepted tradeoffs visibly

Sometimes the team accepts a short-term compromise. Put the durable part in the spec:

Tasks:
  [?] Confirm whether legacy blank-place imports should be rejected or normalized.
  [ ] Remove legacy blank-place normalization after import migration completes.

Must not:
  Apply legacy blank-place normalization to new itinerary items.

This keeps the compromise from silently expanding.

6. Define Done when evidence

Use Done when to make the prevention rule checkable:

Done when:
  UI components use itinerary validation instead of local validation copies.
  Missing-place validation is covered in the validation spec's checks.
  No UI component imports trip storage adapters.

If the project has import-boundary checks, lint rules, contract tests, or code review checklists, reference the relevant evidence in Done when.

Common debt patterns and spec responses

Duplicated business rule

Must:
  Itinerary validation owns missing-place behavior.

Must not:
  Duplicate missing-place validation in UI components, API handlers, or jobs.

Convenience dependency

Forbids:
  domain importing ../adapters/*

Temporary behavior without exit criteria

Tasks:
  [ ] Remove legacy date parsing after all saved trips include explicit time zones.

Done when:
  Legacy date parsing is removed.
  Saved-trip fixtures all include explicit time zones.

Public contract leakage

Must not:
  Expose internal storage keys in public API responses.

Common mistakes

How to verify the result

The spec is preventing debt when:

← Software design practices guides