Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Identifying Coupling and Cohesion Problems

Welcome back. Last lesson, you documented why reservation confirmation should remain inside the Laravel application and why its boundary needs protecting. An ADR states the intended design; the codebase may tell a different story. Confirmation logic might live in a workshop service, a React component might repeat its booking-limit rule, and a reporting module might depend on reservation table details.

This lesson is about detecting those gaps. You’ll use cohesion to assess whether responsibilities belong together and coupling to assess what one part of the application must know about another. The goal is not to eliminate every dependency. It is to find the dependencies and groupings that make ordinary changes unexpectedly expensive or risky. Allow about 40 minutes for the resources and worked inspection.


First, decide what boundary you are inspecting

Cohesion and coupling answer different questions:

  • Cohesion: Do the elements inside this boundary serve a coherent purpose?
  • Coupling: What assumptions or dependencies cross between this boundary and others?

The boundary could be a function, class, feature, or domain module. “The reservation code is cohesive” is difficult to evaluate until you say which reservation code you mean. A controller action may have one clear purpose while the controller class has several unrelated reasons to change. A class may be tidy while its module reaches into another module’s data.

Watch the selected passages of SOLID Principles? Nope, just Coupling and Cohesion by CodeOpinion. The video moves from classes to business capabilities, then to code organized around features.

SOLID Principles? Nope, just Coupling and Cohesion

Watch these passages for a working definition of each concept and for the difference between grouping code by a business behavior and grouping it solely by a data structure or technical layer.

Start with the definitions, which apply the two ideas at class and system scale. Continue through business capabilities, focusing on why a single data-centered service can attract many unrelated consumers. Finish with code organization, where the speaker contrasts feature groupings with large collections of controllers, models, and views.

Treat the video’s preference for feature-based organization as a diagnostic lens, not a ban on Laravel’s familiar directories. A controller directory is not evidence of a problem by itself. The more revealing question is whether changing one reservation behavior requires coordinated edits across distant places—and whether those edits expose one module’s internal decisions to another.


Read the two axes together

The matrix below is useful because improving one axis does not automatically improve the other.

A matrix with coupling increasing upward and cohesion increasing rightward. It contrasts the desirable low-coupling, high-cohesion region with poorly chosen boundaries, a highly connected “God Object,” and fragmentation caused by decoupling without cohesion.

Consider its two less obvious warnings. A highly cohesive unit can still have too much external coupling: many consumers may depend on its internal details or broad interface. Conversely, splitting code into many pieces can reduce an obvious dependency while leaving related behavior scattered. The diagram calls that destructive decoupling. Neither label is an automatic verdict on a codebase; each tells you what to investigate.

Here is a practical way to examine coupling. Suppose another module uses reservations. Does it call a stable operation such as confirmReservation, or must it know which status string to write, which tables to update, and which count determines available capacity? The latter connection carries more assumptions. If the confirmation policy changes, its consumers may need to change with it even if their own responsibility has not changed.

Cohesion asks a complementary question: why do the contents share a home? “They all use a database” describes a technical similarity. “They enforce and execute reservation confirmation” describes a business purpose. Neither guarantees perfect design, but the second gives you a more useful basis for deciding what should change together.

Read the following selected passages of Cohesion and Coupling in Software with Examples by The Valuable Dev. They add two forms of coupling that a simple import graph can miss.

Cohesion and Coupling in Software with Examples

Read this for a sharper account of dependencies across boundaries and related responsibilities within them.

In “The Evolution of Coupling,” begin where the article introduces structural forms of coupling. Read the coupling discussion through its descriptions of logical and semantic coupling. Pay particular attention to behavior duplicated in separate modules: it can force coordinated changes without a direct code reference. Then, under “Cohesion” and its subsection “The Goal of Cohesive Design,” start at the contrast between connections across and within boundaries. Read the cohesion explanation through the description of a module whose elements serve the same goal.

Structural coupling is often visible in calls, imports, database access, and interface shapes. Logical coupling can be less visible: two components encode the same rule independently, so a policy change requires editing both. Semantic coupling appears when a consumer relies on how another component happens to behave, even if its interface does not promise that behavior. You do not need to classify every dependency precisely; the categories help you look beyond file references.


Recognize symptoms, then seek evidence

A code smell is a reason to investigate, not proof that a refactor is required. In Patterns in Practice: Cohesion and Coupling, the examples of divergent changes, feature envy, and shotgun surgery make useful starting points for an existing application.

Patterns in Practice: Cohesion And Coupling

Read the named smells and the author's class-level check for cohesion. The examples use another language, but the change patterns apply to PHP and TypeScript as well.

Under “Code Smells,” begin at the explanation that a smell calls for closer inspection. Read the three smells, noting whether each points to one unit doing too much or one idea spread across units. Then find “Increase Cohesion.” Within that subsection, read the naming check. Treat the class name as a clue rather than a sufficient test: a vague name can conceal unrelated responsibilities.

These symptoms become more persuasive when you can connect them to a change:

Symptom Evidence to look for
Divergent changes The same class is edited for unrelated reasons, such as changing publication rules and changing reservation confirmation.
Shotgun surgery One business-policy change repeatedly requires small edits in several components.
Feature envy A method repeatedly inspects another object’s fields or relations to make decisions that appear to belong with that object’s responsibility.

Do not mistake every multi-file change for shotgun surgery. A React display, a Laravel use case, and a database migration may all legitimately change for one feature. The warning is stronger when the same decision is separately implemented in several places, or when a consumer must understand another module’s internal representation to do its job.


Inspect a reservation structure

Imagine finding this structure while reviewing the reservation ADR:

app/
  Http/Controllers/ReservationController.php
  Workshops/WorkshopService.php
  Reporting/RevenueReport.php
resources/js/
  features/reservations/ConfirmButton.tsx

ReservationController delegates confirmation to WorkshopService. That service also publishes workshops and exports workshop revenue. Its confirmation method contains this excerpt:

public function confirm(Reservation $reservation): void
{
    $workshop = $reservation->workshop;
    $confirmedCount = $workshop->reservations()
        ->where('status', 'confirmed')
        ->count();

    if ($confirmedCount >= $workshop->booking_limit) {
        throw new BookingLimitReached();
    }

    $reservation->update(['status' => 'confirmed']);
}

Elsewhere, ConfirmButton.tsx uses a similar comparison to disable its button:

const atCapacity =
  workshop.confirmedCount >= workshop.bookingLimit;

Finally, RevenueReport directly queries the reservation table using the literal status confirmed.

Start with cohesion. Publishing a workshop, confirming a reservation, and exporting revenue all mention workshops, but do they have the same reason to change? Publication policy, booking limits, and reporting requirements may evolve independently. WorkshopService is therefore a candidate for poor class-level cohesion. Its broad name does not explain why those three behaviors must live together. This is a finding to verify by inspecting its methods and change history, not a conclusion drawn from its name alone.

Next inspect coupling across the intended module boundary. The ADR proposed that reservation confirmation remain behind a reservation-module interface. Here, WorkshopService knows the reservation status value, reads reservation records to calculate capacity, and writes reservation state. Those are concrete signs that a workshop component may own part of the reservation decision in practice. RevenueReport’s query is another dependency on reservation storage details. Whether that reporting dependency is acceptable depends on its purpose and stability; a read-only report is not equivalent to an outside module changing reservation state.

The React comparison needs a more careful judgment. Disabling an apparently unavailable action can improve the interface, but the browser’s count may be stale. It cannot enforce the booking limit. The server must still make the authoritative decision. The comparison becomes a logical-coupling concern if the confirmation rule grows—for example, to account for holds—and the UI and server implement different versions of eligibility. An API-provided eligibility hint could reduce duplicated policy, but it would remain a hint at the time it was fetched, not a guarantee that confirmation will succeed later.

There is also a correctness issue distinct from the structural diagnosis: the PHP excerpt does not show protection against two concurrent requests reading the same count and both confirming. Moving the method to a better-named class would not fix that race. As the ADR noted, the invariant still needs appropriate transaction and concurrency control. A cohesion diagnosis is not a correctness proof, and a refactor should not be presented as one.


Make the diagnosis useful to another engineer

For an existing application, a useful finding names a boundary, cites observable evidence, and explains a likely change cost. A repeatable inspection can proceed in this order:

  1. Choose a behavior and boundary. For example, inspect reservation confirmation at class and module scale, using the ADR’s intended owner as a point of comparison.
  2. Trace one request and its decision. Find where confirmation is invoked, where capacity is checked, and where reservation state is written. Record every other place that implements or assumes that decision.
  3. Test the grouping against reasons to change. Ask whether methods in one class change for the same business reason, and whether one policy change forces edits across multiple modules.
  4. Check the cost before proposing a remedy. Distinguish a stable public dependency from knowledge of internal tables, status values, or decision order. Note any correctness obligations the proposed restructuring must preserve.

Repository searches can help locate candidates. Searching for booking_limit, confirmedCount, and status comparisons may expose duplicate checks; Git history can show files that repeatedly change together. Neither is conclusive. A term may be generated, represented differently, or used only for display. Read the calling code and its purpose before labeling it a problem.

For the hypothetical structure, a defensible review note would be:

Potential boundary and cohesion issue: WorkshopService combines publication, confirmation, and revenue export, which have different reasons to change. Its confirmation method also reads and writes reservation state despite the intended reservation-module boundary. The client repeats the current capacity comparison, while reporting depends on a reservation status literal. Trace a booking-policy change across these locations before restructuring; preserve server-side enforcement and verify concurrent confirmation separately.

That note does more than say “tight coupling.” It identifies what is coupled, why it matters, and what remains uncertain. It also avoids prescribing a service extraction or a large refactor before the evidence supports one.


What to carry forward

Cohesion concerns what belongs together; coupling concerns what must be known across boundaries. Detect problems by examining actual responsibilities, dependencies, and change patterns—not by counting files, banning layers, or demanding zero dependencies. Divergent changes, shotgun surgery, and knowledge of another module’s internals are useful signals when tied to a concrete business change.

You have now compared an architectural decision with an application’s likely structure. In the next module, you’ll turn that diagnosis into maintainable Laravel design, beginning with a use case that can express its application behavior independently of HTTP and Eloquent.

Can't find a good explanation? Sign up and we'll make it for you

Sign up