Module 2: Refactoring Techniques: Case Studies
These cases focus on behavior-preserving change. The goal is not prettier code in isolation; it is a safer path from a hard-to-change design to a clearer one.
Case Study 1: Extract Method Without Changing Behavior
Scenario: A generateInvoice() method is 180 lines long. It loads customer data, calculates discounts, formats tax lines, writes audit events, and returns a PDF. A developer wants to split it quickly before adding a new discount.
Source anchor: Refactoring catalog - Martin Fowler, especially extract-method style refactorings.
Module concepts:
- refactoring as behavior preservation
- extract method
- characterization tests
- the two hats rule
Wrong Approach
Extract methods while also adding the new discount. When a test fails, nobody can tell whether the extraction or the feature changed behavior.
Better Approach
First pin current behavior with tests for normal invoice, discount invoice, tax-exempt invoice, and empty-line invoice. Extract one concept at a time, keep names domain-specific, and commit the pure refactor before adding the new rule.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Refactor and feature together | Fewer PRs | Hard to review and debug |
| Characterize then extract | Clear safety net | May preserve existing quirks temporarily |
| Rewrite from scratch | Clean design possible | High behavior regression risk |
Failure Mode
The PDF total changes by one cent because rounding moved to a different step.
Required Artifact
Produce a refactoring checklist with input examples, expected outputs, extraction order, and rollback point.
Project / Capstone Connection
Use this checklist before touching any long method in the Semester 3 project.
Case Study 2: Replacing a Shipping Switch With Polymorphism
Scenario: A shipping calculator has a switch carrier with FedEx, UPS, DHL, in-house courier, and freight. Each branch validates dimensions, calculates price, and formats the tracking number differently.
Source anchor: Refactoring catalog - Martin Fowler.
Module concepts:
- replace conditional with polymorphism
- strategy-like dispatch
- open/closed principle
- test matrix design
Wrong Approach
Add another case for every new carrier and copy an older branch as a starting point.
Better Approach
Create a CarrierRatePolicy interface and migrate one carrier at a time. Keep the old switch as an adapter during the transition, compare old and new outputs in tests, and remove the switch only after every branch has moved.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Keep switch | Easy local edit | Branches grow and duplicate |
| Big-bang replacement | Clean final shape | Large risky diff |
| Incremental polymorphism | Reviewable migration | Temporary adapter code |
Failure Mode
A new carrier changes tracking-number formatting and accidentally changes freight pricing because both live in the same conditional.
Required Artifact
Build a carrier test matrix with dimensions, destination, expected price, expected tracking format, and migration status.
Project / Capstone Connection
Use the same branch-by-branch migration plan for any capstone workflow driven by type codes.
Case Study 3: Introduce Parameter Object for Search Filters
Scenario: A product search function accepts twelve arguments: text, category, min price, max price, currency, sort, page, page size, availability, vendor, country, and personalization flag. Call sites pass ull` and boolean flags in different orders.
Source anchor: Refactoring catalog - Martin Fowler.
Module concepts:
- introduce parameter object
- preserve whole object
- data clumps
- API readability
Wrong Approach
Keep the signature and add comments at every call site.
Better Approach
Introduce a SearchQuery or ProductSearchCriteria object with named fields, validation, defaults, and focused construction helpers. Migrate call sites gradually and keep tests around pagination and default behavior.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Long parameter list | No new type | Call sites are fragile |
| Options object | Clear call sites | Needs validation rules |
| Builder | Handles complex defaults | Can be overbuilt for simple APIs |
Failure Mode
A caller swaps page and pageSize, causing expensive queries and bad UX.
Required Artifact
Write the new parameter object contract, including defaults, required fields, validation failures, and a migration list of affected call sites.
Project / Capstone Connection
Use this artifact for any function in the project with more than four related parameters.
Case Study 4: Branch by Abstraction for a Notification Provider
Scenario: A system sends email through Provider A. The team wants Provider B for reliability, but direct calls to Provider A are scattered across controllers, jobs, and admin tools.
Source anchor: The module's branch-by-abstraction concept and Google Engineering Practices: Small CLs.
Module concepts:
- branch by abstraction
- parallel change
- small reviewable changes
- rollback design
Wrong Approach
Create a long-lived feature branch that rewrites every call site and switches providers at the end.
Better Approach
Introduce a otificationSender` boundary, route existing Provider A through it, migrate call sites in small changes, add Provider B behind the same contract, then switch traffic with configuration and monitoring.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Long feature branch | One final diff | Merge conflicts and delayed feedback |
| Direct provider swap | Fast code change | Hard rollback |
| Branch by abstraction | Incremental safety | Temporary indirection |
Failure Mode
One admin job still calls Provider A directly and sends duplicate notifications during migration.
Required Artifact
Create a migration board with call sites, adapter status, tests, rollout flag, and rollback steps.
Project / Capstone Connection
Use this plan for any external dependency replacement in the capstone.
Case Study 5: Rename and Move Without Breaking Public API
Scenario: A package exposes UserManager.doStuff(user) publicly. Internally it actually provisions accounts. Several downstream services import it directly, and the team wants a clearer AccountProvisioner.provision(user) API.
Source anchor: Google Engineering Practices: Small CLs.
Module concepts:
- public contract
- deprecation path
- move method
- semantic naming
Wrong Approach
Rename the class and method in one PR and fix only the current repository.
Better Approach
Add the new API beside the old one, route old calls through the new implementation, mark the old symbol deprecated, update downstream users in small changes, and remove the old entry point only after usage is gone.
Tradeoff Table
| Choice | Gain | Cost |
|---|---|---|
| Immediate rename | Clean code now | Breaks consumers |
| Alias and deprecate | Safe migration | Temporary duplicated surface |
| Never rename | No migration | Misleading API persists |
Failure Mode
A scheduled job in another repository fails at runtime because it imported the old name.
Required Artifact
Write a deprecation plan with old API, new API, compatibility window, affected consumers, and removal criteria.
Project / Capstone Connection
Use this when cleaning names in shared modules during the final design review.
Source Map
| Source | Use it for |
|---|---|
| Refactoring catalog - Martin Fowler | Naming refactoring moves and sequencing behavior-preserving changes. |
| Google Engineering Practices: Small CLs | Keeping risky changes reviewable and easy to roll back. |