Skip to content

Build1 publisher3 min readPublished

One private helper turns Finance's discount edit into a silent change to Marketing's receipt

A dev.to walkthrough derives SOLID's five letters from two forces, cohesion and coupling. The SRP case it works in full turns on one private helper that two departments share and code review waves through.

The Engineer · Build desk

What happened

  • A dev.to post derives all five SOLID letters from two forces: high cohesion, keeping what changes together together, and low coupling, depending on stable abstractions instead of volatile details.
  • Every example sits in one system, the checkout slice of a payments product, where CheckoutService.checkout(cart) prices the cart, charges through a gateway, records the order and returns a result.
  • The SRP smell is a CheckoutManager whose total() answers to Finance and whose renderReceipt() answers to Marketing, both of them reading the same private rewardedItems() helper.
  • When Finance asks for gift-wrap fees to stop counting toward the loyalty discount, the edit lands in that shared helper and Marketing's receipt drops gift wrap from its points-earned line.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure A change that satisfies Finance can rewrite what Marketing's receipt says, and the reviewer approving it is looking at a class with one plausible topic and no interface that exposes the second reader.
  • constraint Naming the smell does not tell you how far to split, because the cure for the god class is the same move that produces the ten-file change.
  • decision If LSP is a correctness constraint, a subtype that breaks the base contract is a defect to fix, not a cost to weigh against the delivery date.
  • capability Deriving the letters from cohesion and coupling gives a reviewer grounds for declining to apply one, on evidence about who asks for changes.

The diff that ships this bug is correct on its own terms. The total drops by exactly the amount Finance asked for, every item is still charged, and nothing in the change looks wrong [15]. The helper doing the damage is private, so the dependency between two departments never appears in an interface anyone reviews [13].

The working definition of SRP in the piece is not "one thing per class"; it is one actor, one group of people who can ask for that change [10]. `CheckoutManager` fails on its two public methods, because `total()` answers to Finance and `renderReceipt()` answers to Marketing [11]. The article says no code review catches the edit "because the class has one name and one obvious topic" [16].

The instinct behind the fix is one most teams already follow. The layered split applies SRP without naming it: the controller changes when the API shape changes, the service when a business rule changes, the repository when storage changes [17]. Inside the service layer the same split gives pricing to `PriceCalculator`, receipt copy to `ReceiptFormatter`, and orchestration to `CheckoutService` [18]. In the companion repo the gift-wrap change is a `rewardGiftWrap` flag. `SrpTest` holds both versions of the rule side by side to show the customer is billed for every item either way, with the discount and Marketing's receipt as the things that move [19].

Splitting has its own failure mode. Under-apply SRP and you get the god class; over-apply it and one logical change forces edits across ten tiny files, which the article calls shotgun surgery [20]. Cohesion is what tells you how far to split, and splitting because "this method feels different" is how the ten-file version gets built [21].

Not every letter can be over-applied. ISP is the contested one: the textbook files it under coupling, and the article argues both readings are right [6]. LSP is filed under coupling for a specific reason: callers couple to the base contract and never to your subtype [7]. The article then sets it apart from the other four as "a detector rather than a design choice" [8]. On that account four letters carry an over-application cost and one carries none [1].

The article states that every principle has a cost and that SOLID applied without judgment "produces its own kind of unmaintainable code" [5]. The text available here works that cost out for SRP alone, breaking off mid-sentence in the trade-off section. For the two-force reduction to earn anything on your codebase, the actor question has to have a real answer. In the checkout example it does: Finance and Marketing ask for different changes to the same class [11]. Where a single product owner asks for everything, "one reason to change" has no second actor to point at, and the decision goes back to guessing which methods change together [10][21].

What to watch

  • Whether the repo carries a test as specific as SrpTest for the OCP, ISP and DIP sections, where the excess-abstraction complaint usually lands.
  • Whether the layered split holds up as an actor split in teams where controller, service and repository track three technologies and one requester.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories