Software Design Roadmap

Nine stages in one suggested order, starting with names, functions, cohesion and coupling — the file-level observations everything later is built on. Every stage names what it needs first and what you should be able to do before moving on. Progress is stored locally in your browser.

Where to start

0 / 196 lessons masteredNot started 196Learning 0Practicing 0Mastered 0
  1. 1

    Names, functions and the first two forces: cohesion and coupling

    Start here
    0/21

    The domain starts at the file level, because every later structure is made of files someone has to read. What makes software hard to change and why the cost of the next change is the thing design optimises, names that come from the business rather than the framework, functions whose signature says what they can and cannot do, and the first two forces — cohesion and coupling — that every later stage measures itself against.

    Before moving on: Look at an existing file and say which of its parts belong together and which are only neighbours, then rename and extract until a colleague can read it once and predict it correctly.

  2. 2

    Responsibilities, module interfaces and state you can see

    0/22

    The design loop proper: from a requirement to its constraints, invariants and responsibilities, then to a module interface narrow enough that callers cannot reach past it. State sits here because a lifecycle written as named states and transitions is the first invariant a module can actually enforce, and error modeling closes the loop by treating failure as part of the contract rather than an afterthought.

    Before moving on: Take a feature request, decide who owns each piece, give that owner an interface that protects its invariants, and model its lifecycle as states and transitions rather than a pile of booleans.

  3. 3

    Abstraction, dependency direction, pattern vocabulary and the domain model

    0/22

    With responsibilities settled, the question becomes which way the arrows point. What an abstraction costs and when duplication is cheaper, dependency direction, inversion and injection taught as three separate ideas, the pattern vocabulary as names for problems you have already met rather than designs to reach for, and the domain model — entities and value objects — as the place the business rules live.

    Before moving on: Build a feature whose business rules do not import the database, the HTTP framework or the payment vendor, and explain why each dependency arrow points the way it does.

  4. 4

    Refactoring under tests, reading smells, and letting review and testing talk back

    0/22

    Now that you can say what a good structure looks like, learn to move working code towards it without changing what it does: the refactoring loop with a safety net, the named smells that tell you where to start and the case where each one is fine, and the two feedback channels — code review and hard-to-write tests — that report on the design before the next change prices it.

    Before moving on: Refactor a module in small green steps, name the smell that prompted each one, and read a test that needs six mocks as a report on where the hidden dependencies are.

  5. 5

    Package structure, architecture boundaries and the modular monolith

    0/22

    Scale the dependency rules from one module to a whole codebase. Package by feature or by layer and what each does to change locality, cycles and fan-in / fan-out read off a real dependency graph, the layered, hexagonal, clean and onion styles compared without naming a winner, and the modular monolith whose module contracts are enforced by the build rather than by a convention nobody remembers.

    Before moving on: Lay out a codebase so a typical feature lands in one directory, break a dependency cycle, and tell a boundary that earns its adapters from one that only adds a file to open.

  6. 6

    Legacy code, safe migration and debt you can actually account for

    0/21

    The refactoring discipline applied where it is hardest: code with no tests and no author left, and systems that must keep running while they change. Characterization tests and seams, the strangler and expand-and-contract, backward compatibility, versioned interfaces and data migration, and technical debt written down as an interest payment a product manager will fund rather than as a preference.

    Before moving on: Change a decade-old service safely — observe, characterize, find a seam, change, verify — and run a migration in steps where old and new coexist and every step can be reverted.

  7. 7

    Evolvability, the complexity budget and decisions you write down

    0/22

    Design judged over time rather than at review. Change amplification and encapsulation radius as the properties that decide whether the tenth change costs what the first did, extension points a second team can use without you, essential versus accidental complexity and the budget you spend on it, YAGNI and speculative generality, and decision records with revisit triggers so the reasoning outlives its author.

    Before moving on: Predict which future changes a design makes cheap and which it makes expensive, say the second half out loud, and leave a decision record that names what would make the choice wrong.

  8. 8

    Large refactors, aggregates, and the repository as a design artefact

    0/22

    Refactors too big for one pull request, sequenced into slices that each ship. Consistency boundaries, aggregates and domain services drawn around the data a rule protects — and the honest case for a transaction script when DDD does not pay. SOLID read critically, principle by principle, in terms of the change each one makes cheap, and the repository itself as a design artefact: ownership, review and bus factor matched to how the code actually changes.

    Before moving on: Plan a multi-week refactor that can stop at any point without leaving the codebase worse, draw an aggregate around a real invariant, and argue for or against a SOLID principle by the change it makes cheap.

  9. 9

    Designing a codebase that many teams change, and modernising one that already exists

    0/22

    The last stage designs for people who are not in the room. Internal interfaces with a stated compatibility promise and a deprecation path, a feature-design template that forces states, failures and migration to be decided before code, idempotency and partial failure chosen up front, debuggability as a property of the design, and a model treated as one more external dependency that is non-deterministic, fallible and costly.

    Before moving on: Write a feature design that decides states, failures and migration before code, publish an internal interface with a versioning and deprecation policy, and say when a system should be left alone.