Consolidate duplicate code while preserving business rules
Two similar methods are not necessarily one rule. The order and return processors share validation mechanics but use different prefixes, shipping thresholds, and inventory directions. Removing those differences would be a behavior change.
Lab briefing

Original concept illustration (SVG)
| At a glance | Your route |
|---|---|
| Level and time | 200; 55 minutes (facilitation estimate) |
| Starting action | Capture equality thresholds before extracting one helper. |
| Learner materials | Download 07-duplication.zip |
| Workspace | Open the extracted kit root; run the baseline from . relative to that root |
| Expected initial check | The selected project builds. Compilation alone does not prove behavior. |
| Setup help | Download, extract, local Git and optional GitHub |
[!NOTE] Similar code does not mean the same shipping or inventory policy.
Concepts · First task · Evidence checklist · Reset
Learning objectives
- Identify exact versus semantic duplication using real code.
- Capture current outputs and side effects before extraction.
- Choose a small shared abstraction without over-generalizing policy.
- Detect a refactor that accidentally changes a boundary.
Before you start
Prepare 07-duplication with the common setup.
The source is the bundled
ECommerceOrderAndReturn fixture.
Use its .NET SDK target and one build process.
Concepts and use cases
| Candidate | Possible shared mechanic | Keep explicit |
|---|---|---|
Validate |
Blank/length checks | ORD versus RET prefixes |
CalculateShipping |
Applying a policy | Order and return thresholds/amounts |
| Notifications | Formatting and output | Message purpose and recipients |
| Inventory | Bounds and logging | Reserve decreases; restore increases |
The processors catch and log exceptions. A zero exit code from the demo therefore does not prove that an expected exception was thrown. This is a characterization exercise, not proof of production security.
Exercise scenario
You need to reduce maintenance duplication without changing totals, inventory, validation messages, or event ordering. A separate business change would need its own acceptance criteria and review.
Task 1 - Capture a real baseline
-
Read
OrderProcessor.cs,ReturnProcessor.cs,Configuration/AppConfig.cs, and the services they call. -
Build the copied project:
dotnet build ECommerceOrderAndReturn.csproj -m:1 -p:UseSharedCompilation=false dotnet run --no-build --project ECommerceOrderAndReturn.csproj -
Record current order/return shipping totals, inventory before/after, and rejected IDs. Treat
EXPECTED_OUTPUT.mdas a historical illustration, not today’s output. -
Identify timestamps or generated identifiers before comparing logs. Do not remove business values merely to make a diff look equal.
Task 2 - Analyze duplication with Ask
Compare OrderProcessor.Validate and ReturnProcessor.Validate. Cite the shared
mechanics and the policy differences. Do the same for shipping and inventory.
Do not edit. Flag swallowed exceptions separately from the refactoring scope.
Check the answer against these concrete shipping rules:
| Rule | Orders | Returns |
|---|---|---|
| Base | 5.00 | 3.00 |
| Weight surcharge | Above 10: +2.00 | Above 5: +1.50 |
| Value discount | Above 50: -1.00 | Above 30: -0.50 |
| Special handling | Fragile: +3.00 | Oversized: +4.00 |
Do not replace “above” with “at least.” Test values immediately below, at, and above the thresholds before moving code.
Task 3 - Plan one extraction
- In Plan, choose either validation or shipping for the first change.
- Require a table of preserved rules, proposed parameters, existing callers, regression cases, and rollback.
- Explain why a shared helper is simpler than a new class hierarchy.
- Keep the original public processing methods and side-effect order.
- Define a stopping point after one behavior-safe extraction.
Task 4 - Implement and challenge the result
- Ask Agent to implement only that extraction.
- Inspect all callers; unused helpers do not constitute consolidation.
- Add assertions around deterministic values or a narrowly scoped characterization harness. Do not rename a console transcript “unit tests.”
- Re-run the same build/demo and the added assertions.
- Temporarily invert one threshold in the disposable copy. Confirm the relevant assertion fails, then restore it.
- Consider a second extraction only after the first one is verified.
Verify your work
- Public entry points and observable rules remain unchanged.
- Weight/value equality boundaries are tested.
- Reserve and restore still have opposite inventory effects.
- New assertions reject an intentionally wrong threshold.
- The diff contains actual reuse rather than an unused abstraction.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Every run has a different log | Dynamic time/IDs; compare stable fields explicitly |
| Order and return prices converge | Distinct policies were merged accidentally |
| All demo scenarios exit zero | Exceptions are logged internally; add discriminating assertions |
| Refactor expands across many layers | Return to one extraction and state non-goals |
Independent practice
Refactor one notification helper while preserving the public methods and messages. Explain why changing retry behavior would be a separate feature.
Reset
Stop the demo, save the baseline and comparison evidence, and restore only named exercise files in the disposable copy. Do not overwrite the source fixture.