Memra

Cohesion and coupling

◈ 7 cards

The two qualitative criteria that assess functional independence: the cohesion ladder inside a component, the coupling categories between components, and why lowering one naively wrecks the other.

The property, and the two measures that assess it

Functional independence is achieved by building modules that each do one job and that reach outside themselves no more than that job actually requires. It is not measured directly. It is assessed using two qualitative criteria, and this is the sentence that anchors both definitions: cohesion looks inside one component, coupling looks between components.

Both are qualitative measures. Neither is a count you compute and neither is a defect you find; they are properties that increase or decrease as a design changes, and they predict the same downstream costs — how hard the system will be to implement, to test and to maintain.

Cohesion — how single-minded one component is

Cohesion asks whether a component holds everything one job needs and nothing else: every attribute and every operation inside it should be there because that single responsibility requires it. The levels run best-first.

  • Functional — the component performs one and only one computation and returns a result. This is the target.
  • Layer — a higher layer uses the services of a lower one, and lower layers never reach upward. Exhibited by packages and components rather than single operations.
  • Communicational — every operation that touches the same data lives in one class, which then focuses solely on that data.

Below those sit weaker rungs — sequential (one operation's output feeds the next), procedural (operations grouped because they run in a fixed order), temporal (grouped because they happen at the same time, such as start-up), and utility (grouped because there was nowhere else to put them). Functional, layer and communicational cohesion make a component easy to implement, test and maintain, and you should reach for them. Pragmatics sometimes force a lower level; that has to be a decision you can defend, not something that happens to your design while you are not looking.

Coupling — how entangled components are with each other

Coupling is a qualitative measure of the degree to which classes or components are connected to one another. As components become more interdependent, coupling increases; as coupling increases, so does the difficulty of implementing, testing and maintaining the software, because a change in one place forces a change or a retest somewhere else. The objective is always to keep coupling as low as the problem allows — not zero, because software must communicate, but low.

The categories, worst first:

  • Content coupling — one component reaches in and modifies data that is internal to another. It violates information hiding outright, and it means the second component can no longer be reasoned about on its own.
  • Control coupling — one operation passes another a flag that directs the callee's internal logic. The two are now bound by a shared private meaning, so an unrelated change inside the callee can silently invalidate what the flag means.
  • External coupling — a component talks directly to infrastructure: the operating system, a database, a telecoms or payments service. This kind is necessary, so the design rule is not to eliminate it but to confine it to a small number of components.
  • Data and stamp coupling — communication by parameters only. Stamp coupling passes a whole record where a field would do; data coupling passes just what is needed. This is where you want to be.

They trade against each other

The pair is not two independent dials. Merging components to reduce the number of connections between them lowers coupling and destroys cohesion. Splitting a component to raise cohesion adds connections and can raise coupling if you split on the wrong axis. The judgement is in choosing the axis: split along responsibility boundaries and both improve; split along convenience boundaries and both get worse.

Worked example — the late-fee call

BorrowBox's locker estate carries eleven sensor classes across three hardware generations — door sensors, weight pads, scan pads, tamper switches. Each of them can detect the condition "this tool came back late". The tempting design gives every sensor a chargeLateFee() operation that calls the Payments component directly. It works, it is the shortest path, and it is wrong.

Count what it costs. Eleven classes are now coupled to billing, and coupled externally through billing to a third-party payments gateway. Change the fee schedule — say the first two hours become free — and you edit and retest eleven classes. Worse, each sensor now holds a fragment of business policy it has no business knowing, so nobody can say where the fee rule lives.

Route it instead. Every sensor publishes a plain observation: ToolReturned(copyId, at). LendingService subscribes, compares the timestamp against the loan's due time, decides whether a fee is owed, and asks Payments. Now exactly one component is coupled to billing, the sensors know only about sensing, and the fee change is a one-file edit with one set of tests.

The same example shows cohesion. Somebody proposes merging renderDoorState(), renderQueueDepth() and renderBatteryBar() on the locker-bank tile into a single renderTile() — three small operations replaced by one, less code, apparently simpler. It destroys functional cohesion: renderTile() no longer does one thing, so a change to the battery display can break the door-state display, no part of it can be reused alone, and the support engineer who touches it in two years has to understand all three concerns to change one.

DoorSensorScanPadWeightPadLendingowns the fee rulePaymentsexternalDashed = the high-coupling design. Solid = the routed alternative.
The dashed red edges are the tempting design: every sensor class calls Payments itself, so eleven classes are coupled to billing. The solid path is the fix — sensors publish an observation, one component owns the fee rule and talks to Payments. Changing the fee schedule edits eleven classes on the left and one on the right.
RungCohesion — inside onecomponentCoupling — betweencomponentsBestfunctional: onecomputation, one resultdata: plain parameters,nothing sharedGoodlayer: a higher layer usesa lower onestamp: a whole record wherea field would doFaircommunicational: operationson the same dataexternal: talks toinfrastructure, so confineitPoortemporal: grouped by whenthey runcontrol: a flag that steersthe calleeWorstutility: grouped becausenowhere else fittedcontent: reaches in andedits another componentWant cohesion high and coupling low — and they trade against each other.
Read the two columns in opposite directions and the pair stops being confusable: you climb the cohesion column and descend the coupling column. The left column never leaves one component; the right column is only ever about the wires between two.
NORMAL ~/memra/learn/comp-410/cohesion-and-coupling utf-8 LF