Skip to content

stevenacoffman/software-design

v0.1.0MIT

Software design and refactoring: deep modules, information hiding, and incremental change.

3ts-premature-optimization

Use when judging whether a proposed optimization, rewrite, refactor, migration, or reorg is premature, or why a finished one didn't improve outcomes. Applies the 3T test: Thing (real bottleneck?), Time (right moment?), Trade-offs (all costs counted?).

ddd-fitness-scorecard

Apply when a team needs a defensible, shared decision on whether full DDD is worth adopting for a project: at inception, in architecture review, or when someone challenges whether DDD overhead is justified.

define-errors-out-of-existence

Use when designing an API or function that throws for inputs already in the desired state (delete missing item, duplicate insert), when callers wrap calls in defensive exists-checks, or exceptions cross layers that can't handle them. Redefine semantics so the error vanishes.

design-it-twice

Invoke before committing to a significant interface: new module, service boundary, public API, or library contract. Also when someone says the design is obvious or a review only tweaks names. Sketch two or more radically different designs and compare before implementing.

fowler-branch-by-abstraction

Use when replacing a library, framework, or large component used across many call sites in a live codebase without a feature freeze. Introduce an abstraction layer and migrate incrementally. Not git branching, not database schema migration, not small one-commit refactors.

fowler-database-parallel-change

Invoke when altering a live production database schema (add, rename, or drop columns or tables) without downtime. Applies the expand-contract (parallel change) lifecycle: expand, dual-write, backfill, migrate readers, stabilize, contract, instead of in-place renames.

fowler-divergent-shotgun

Use when a module is painful to change: one class changes for many unrelated reasons (Divergent Change) or one logical change needs edits across many files (Shotgun Surgery). Not for whole-codebase smell sweeps or Long Function.

fowler-opportunistic-refactoring

Use when deciding when and why to refactor during normal work: preparatory (code isn't shaped for a new feature), comprehension (code is hard to follow), litter-pickup (problems found in passing), or long-term structural fixes over weeks.

fowler-performance-sequencing

Use when deciding how to handle performance: optimizing while writing code, a slow system, or write it fast vs write it right. Endorses profile-then-tune over constant attention, since most time is spent in a small fraction of code and hot-spot guesses are usually wrong.

fowler-two-hats

Use when refactoring and feature or bug-fix work are mixed in one session and you can't tell whether a failing test comes from structure or behavior changes. Enforces the two-hats discipline of switching modes explicitly. Not for whether to refactor or long migrations.

fowler-yagni-refactoring

Apply when a team wants speculative flexibility: future-proofing, plugin architectures, or abstract interfaces for extensibility with no current use case. Pairs YAGNI with refactoring: build only what's needed now and refactor safely when new needs arrive.

information-hiding-temporal-decomposition

Use when classes or services are split by processing step rather than knowledge: Reader/Parser/Processor/Writer for one format, open-process-close triads, or microservices per saga step sharing a schema. Detects temporal decomposition that leaks information across modules.

pass-through-method-wrong-layer-count

Use when a method, service layer, adapter, or decorator just forwards calls with the same arguments and adds nothing, or a variable is threaded through many signatures only the last one uses. Signals the wrong number of layers; merge or redistribute responsibility.

pull-complexity-downward

Use when an API pushes decisions onto callers: options structs where most fields stay zero, timeout, buffer, batch, or retry parameters set arbitrarily, or every caller passing the same value. Move the decision into the module. Not when callers hold context the module lacks.

somewhat-general-purpose-interface

Use in API design or review when methods are named for UI actions (backspace, onClickSave), the interface grows one method per feature request, or a method has a single caller. Prefer somewhat general-purpose interfaces that capture the underlying capability.

strategic-before-tactical-ddd

Invoke when a team is adopting DDD, planning service decomposition, or doubting its DDD payoff, especially when tactical patterns (entities, aggregates, repositories) exist but service boundaries feel wrong. Do strategic design and bounded contexts first.

strong-consistency-across-bounded-contexts

Apply when designing inter-service communication or choosing sync vs async coordination, especially when reaching for a distributed transaction or synchronous RPC to update another bounded context atomically. Prefer eventual consistency across contexts.

write-comments-first-as-design-tool

Use when starting a new class, interface, or public method: write the interface comment before the implementation as a design check. Also when a module can't be understood from its interface comments, after a refactor changes semantics, or when a long comment hints at complexity.