Skip to main content

Module 1: OOD Foundations & Code Smells: Case Studies

These are synthetic teaching scenarios. Linked sources provide background, not documented incident evidence. Each proposed improvement is a candidate: test its contract and compare an alternative before accepting it.

Use these cases to practice recognizing design pressure before the code becomes hard to change. Each case asks for a concrete artifact that can be reviewed with another engineer.


Case Study 1: The Checkout Service That Knows Every Rule​

Scenario: An ecommerce CheckoutService validates coupons, calculates tax, applies loyalty points, checks inventory, sends email, and writes the order. Every new promotion changes the same file, and every bug fix risks breaking a different checkout path.

Source anchor: Code Smell - Martin Fowler, plus the module's SRP and large-class concepts.

Module concepts:

  • Single Responsibility Principle
  • large class
  • divergent change
  • cohesion and reasons to change

Wrong Approach​

Split the file by method name only: CheckoutValidation, CheckoutCalculation, CheckoutPersistence. The class count rises, but the same policy decisions remain tangled across the workflow.

Better Approach​

Identify independent reasons to change: pricing policy, inventory reservation, payment authorization, order persistence, and notification. Move policy behavior near the data it owns, leave orchestration in a thin application service, and add behavior tests for representative checkout paths before moving code.

Tradeoff Table​

ChoiceGainCost
Keep one serviceSimple call pathHigh regression risk and merge conflicts
Split by technical layerFamiliar namingBusiness rules still scatter
Split by responsibilityClear ownershipRequires tests and careful interface design

Failure Mode​

A loyalty-points change modifies tax calculation because both rules share temporary variables in the same long method.

Required Artifact​

Create a responsibility map that lists each checkout rule, its reason to change, its owning object or service, and the tests that protect the move.

Project / Capstone Connection​

Use the same responsibility map for the Semester 3 refactoring project before applying patterns.


Case Study 2: The Anemic Order Model​

Scenario: Order, OrderLine, and Customer classes contain fields and getters only. All rules live in procedural services: OrderService.cancel(order), PricingService.total(order), ReturnService.canReturn(order). Engineers keep adding flags because there is no obvious place for behavior.

Source anchor: Anemic Domain Model - Martin Fowler.

Module concepts:

  • encapsulation
  • tell, do not ask
  • domain behavior
  • feature envy

Wrong Approach​

Duplicate cancellation and pricing policy in independent services without assigning an owner or checking consistent transitions. Data records alone do not establish this failure: immutable records with pure transition functions can protect the same invariants.

Better Approach​

Move behavior that depends on Order invariants into the model: cancellation rules, total calculation that belongs to order lines, and return eligibility that depends on order state. Keep infrastructure concerns outside the entity, but let the domain object protect its own rules.

Tradeoff Table​

ChoiceGainCost
Records with procedural servicesExplicit data and transformationsRequires one policy owner and controlled mutation; duplication can drift
Behavior-rich modelStrong invariantsNeeds clear boundaries around I/O
Hybrid modelIncremental adoptionCan become inconsistent if ownership is vague

Failure Mode​

One service allows cancellation after shipment while another denies it because both reimplemented the rule.

Required Artifact​

Write a before/after responsibility diagram and three behavior tests that exercise the invariants, including an invalid transition. Explain why the design preserves them beyond those finite cases. Compare a behavior-rich object with immutable records and a pure transition function.

Project / Capstone Connection​

Use this case when deciding whether a class in the capstone should be a real domain object or a transport record.


Case Study 3: Primitive Obsession in Billing​

Scenario: A subscription system passes amount: number, currency: string, email: string, and country: string through dozens of methods. A refund accidentally mixes cents and dollars, and support finds invoices sent to invalid addresses.

Source anchor: Refactoring catalog - Martin Fowler, especially value-oriented refactorings such as introducing parameter objects and replacing primitives with richer domain concepts.

Module concepts:

  • primitive obsession
  • value objects
  • invalid states
  • interface clarity

Wrong Approach​

Rename parameters but leave the observed units mismatch and missing boundary validation unchanged. Clear names help readers; they do not by themselves reject invalid combinations.

Better Approach​

Introduce small immutable value objects such as Money, EmailAddress, and CountryCode. Put validation at construction boundaries, make illegal combinations unrepresentable where practical, and keep serialization adapters at the system edge.

Tradeoff Table​

ChoiceGainCost
Raw primitivesFast to pass aroundEasy to mix invalid values
Value objectsSafer domain APIMore types and conversion code
Validation only at UIFewer backend changesBackground jobs and APIs can bypass it

Failure Mode​

A background refund job bypasses UI validation and sends 19.99 to an API that expects cents.

Required Artifact​

Design a Money value object API with construction rules, equality behavior, serialization boundaries, and tests for invalid input.

Project / Capstone Connection​

Apply the same value-object audit to identifiers, dates, money, and status fields in the project codebase.


Case Study 4: The Train-Wreck API Call​

Scenario: A reporting controller computes customer risk with order.customer.account.profile.preferences.region.policy.riskTier. A schema change in the customer profile breaks reporting, recommendation, and fraud code at once.

Source anchor: Code Smell - Martin Fowler, connected to message chains and excessive coupling.

Module concepts:

  • coupling
  • Law of Demeter
  • message chains
  • information hiding

Wrong Approach​

Copy the same traversal into several helpers without assigning ownership of the schema dependency. A single controller adapter can be a valid boundary when its responsibility is precisely to translate that schema.

Better Approach​

Ask which object owns the policy decision. Expose a stable query such as order.customerRiskTier(policyDate) or a domain service that accepts the minimal facts it needs. Hide profile traversal behind a boundary that can change without forcing callers to know the shape.

Tradeoff Table​

ChoiceGainCost
Direct traversalQuick accessCallers depend on internal structure
Helper functionReduces duplicationCan hide the same coupling
Owner-level methodStable contractRequires naming the real responsibility

Failure Mode​

A profile migration breaks unrelated features because callers reached through internal object graphs.

Required Artifact​

Create a dependency sketch showing the current message chain, the proposed owner, and the public method each caller should use.

Project / Capstone Connection​

Use this sketch during code review when a feature reaches through three or more collaborators.


Case Study 5: Inheritance That Violates Substitution​

Scenario: Square extends Rectangle in a drawing library because both have width and height. A layout algorithm sets width and height independently; squares override setters and surprise callers.

Source anchor: The module's Liskov Substitution lesson and the classic subtype-contract problem.

Module concepts:

  • Liskov Substitution Principle
  • subtype contracts
  • composition over inheritance
  • behavioral compatibility

Wrong Approach​

Patch the layout algorithm with if shape is Square checks.

Better Approach​

Model shared behavior through an interface such as ResizableShape only when the contract is valid for every implementer. Use composition or separate types when invariants differ. Tests should assert substitutability, not just constructor behavior.

Tradeoff Table​

ChoiceGainCost
Inherit for shared fieldsLess code initiallyBroken behavioral contract
Type checksQuick patchSpreads special cases
Smaller interfacesHonest contractsMore explicit design work

Failure Mode​

Any caller that assumes independent width and height changes fails when passed a square.

Required Artifact​

Write a subtype contract table for Rectangle, Square, and the replacement interface, including preconditions, postconditions, and tests.

Project / Capstone Connection​

Use the same contract table before introducing inheritance in the semester project.


Source Map​

SourceUse it for
Code Smell - Martin FowlerFraming smells as signals of deeper design problems.
Anemic Domain Model - Martin FowlerDistinguishing data containers from behavior-rich domain objects.
Refactoring catalog - Martin FowlerNaming refactorings that move from primitive-heavy code toward clearer design.