← Software design practices guides

How to design for low coupling and high cohesion

How-To Software design practices Intermediate 1101010HOWTO-1101010

HOWTO-1101010Software design practicesIntermediate

This guide shows you how to design for low coupling and high cohesion with SpecDD in a spec-driven development workflow.

Low coupling means one part of the system does not know more about another part than it should. High cohesion means the behavior inside a unit belongs together. These two qualities reinforce each other: cohesive units have clearer contracts, and clear contracts reduce unnecessary coupling.

SpecDD helps because every local spec asks you to name what belongs together, what the unit depends on, and what it must not own. That makes coupling and cohesion reviewable instead of abstract.

Short answer

Use Purpose, Owns, and Must to group related behavior into cohesive specs. Use Depends on, References, and contract sections to make dependencies explicit. Use Must not and Forbids to prevent unrelated behavior and unwanted dependencies from entering the unit. Review dependency changes as design changes, not just implementation details.

When to use this guide

Use this guide when:

The design idea

Cohesion is about belonging. If a rule belongs with the unit’s purpose, it should be easy to justify in the spec. If it does not belong, the spec should either reject it or point to the owner that does.

Coupling is about knowledge. A unit may need to depend on another unit, but it should depend on the smallest stable contract that supports its work. When a spec lists dependencies, it creates a review moment: is this collaborator part of the contract, or did implementation convenience leak into design?

Steps

1. Define cohesive responsibilities

Good:

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.

Weak:

Spec: Itinerary Utility

Purpose:
  Validate itinerary items, format destination names, save drafts, and render empty states.

The weak purpose groups unrelated reasons to change.

2. Minimize dependency lists

Use Depends on for meaningful collaborators:

Depends on:
  TripDateRange

Do not list every helper import. A dependency belongs in the spec when it affects architecture, review, testing, or future maintenance.

3. Prefer contracts over internals

Low coupling depends on stable contracts:

Exposes:
  validateItineraryItem

Accepts:
  itinerary item input
  trip date range

Returns:
  validation result

Callers should rely on that contract, not private helper names or storage shape.

4. Use non-goals for unrelated behavior

Use Must not to preserve cohesion:

Must not:
  Save itinerary items.
  Render itinerary UI.
  Fetch destination search results.

The unit can remain cohesive because unrelated behavior has a visible place to stop.

5. Forbid unwanted coupling

Use Forbids for dependency boundaries:

Forbids:
  direct browser storage access
  ../ui/*
  ../adapters/*

This is especially useful when a shortcut would couple a cohesive unit to infrastructure or presentation code.

6. Review dependency changes

When a spec adds a dependency, ask:

Dependency additions are design changes. Treat them accordingly.

7. Split when cohesion drops

If a spec keeps adding unrelated Must rules, split it:

Do not let one “utility” spec become a home for behavior that has no better owner.

Example cohesive service

Spec: Itinerary Service

Purpose:
  Coordinate validation and storage when itinerary items are added.

Owns:
  ./itinerary-service.js
  ./itinerary-service.test.js

Must:
  Validate itinerary input before storage is called.
  Save itinerary changes only after validation succeeds.
  Return validation feedback when input is rejected.

Must not:
  Own itinerary validation rules.
  Persist trips directly.
  Render UI feedback.

Depends on:
  ItineraryValidation
  TripStorage

Done when:
  Validation failure does not call storage.
  Successful validation calls storage once.

This service is cohesive because it coordinates one workflow. It is low-coupled because it depends on stable validation and storage contracts rather than their internals.

Common mistakes

How to verify the result

The design has low coupling and high cohesion when:

← Software design practices guides