Software Design Reference Core Designer · Seeing Structural Problems

Lesson 0001 · Core Designer · Module 1

Seeing Coupling & Cohesion

Before you can fix structure, you have to see it. This lesson trains your eye to measure the two forces every structure is made of.

Mission tie-in: code that "works" but isn't maintainable is usually failing on exactly these two forces. Every later lesson — patterns, ports, domain protection — is a tool for steering them.

Knowledge: the two forces

Coupling measures how much one module depends on another. Low coupling means a module can change, be tested, or be replaced without its neighbors noticing. Cohesion measures how strongly the elements inside a module belong together — whether it has one purpose.

Look at this function. It works. It also does three different jobs:

def process_order(order_id: str, db: Database) -> None:
    order = db.fetch_order(order_id)
    total = 0.0
    for line in order["lines"]:
        total += line["price"] * line["qty"]
    order["total"] = round(total, 2)
    db.save_order(order)
    EmailClient().send_receipt(order_id, order["total"])
    logger.info("Order %s processed", order_id)

Ask two questions. Cohesion: how many jobs is this one function doing? It computes a business total, persists it, sends email, and logs — four. Coupling: what does the order flow depend on? It is now fused to the email client and the logging framework; change either and order processing breaks.

Notice what you didn't need: a diagram, a framework, or a tool. You saw the problem by asking how many reasons does this have to change, and who is it joined to. That is the skill of this module: not applying rules, but seeing.

A function named with "and" ("process and notify", "validate and send") is a confession of low cohesion written into the name.

Cohesion and coupling trade against each other across a boundary: to lower coupling between two modules you often raise the cohesion of each — you pull each job into its own module and leave each module with one job.

Field notes · what it looks like in real code
You see thisWhat it costsThe move
def process_and_save(...)The "and" in a name is two change sources admitted out loud.Split at the “and”; each half gets one job.
import smtplib in a pricing modulePricing redeploys when the mail vendor changes.Inject a Notifier port; keep the vendor at the edge.
4 mocks to test 1 assertionMock count tracks neighbour count — the unit has too many.Pass collaborators in; delete the ones the job never uses.
one file in 8 of your last 10 PRsIt is the junction where unrelated changes meet.List its jobs; give each one its own module.

Skill: point at the problem

Read each snippet below and name the force that is failing. Answer before checking — the act of deciding is the practice.

The function above computes totals, sends email, and logs. The primary problem is:

Which change best raises cohesion without breaking the order flow?

Two modules are tightly coupled when:

Practice on your own code

Take the messiest function in a file you own. Write down (in your head or a note): its jobs, and every neighbor it touches. If the list has more than one job or more than two neighbors, you have found this lesson's win.

Reveal: the refactored version of the example
class Order:
    def __init__(self, order_id: str, lines: list[Line]) -> None:
        self.order_id = order_id
        self.lines = lines

    def total(self, pricing: Pricing) -> Money:
        return pricing.total(self.lines)   # domain knows money, not email


# one job: order flow
def process_order(order_id: str, orders: OrderRepository, pricing: Pricing) -> Money:
    order = orders.get(order_id)
    return order.total(pricing)            # persistence, email, logging live elsewhere

Order flow now depends on a repository and a pricing strategy — not on email or logging. Those became separate modules with one job each.

Your win

You can now look at any function and name which force is failing: too many jobs inside (cohesion) or too many neighbors outside (coupling). That single judgment is the basis of every other lesson in this course.

Read and watch deeper

Anything unclear? Ask your agent-teacher — re-explain any part of this lesson, or bring your own code and have it pointed at together.