Practice Solutions and Feedback
Attempt first; valid alternative designs are accepted when their contracts and tradeoffs are supported.
P01: Separate evidence from taste
Pricing, storage and presentation can vary independently; a renderer or repository boundary is plausible. For a small stable script, extra objects can increase navigation without reducing a current problem. Name the invariant and change pressure, not only SRP.
P02: Track the alias
Retain a tuple holding a dictionary, then change the dictionary amount through the original alias. The tuple observes the change. A snapshot of immutable signed values can meet a stable-history contract; copying adds work and may require an API migration.
P03: Write a transition contract
Rejected withdrawal returns False with balance 10/history (); deposit 5 returns None with 15/(5,); withdraw 3 returns True with 12/(5,-3). Zero is accepted with no event. Invalid input raises ValueError before mutation. Currency and numeric domain must be explicit.
P04: Substitution under a real contract
Set width 10 then height 5 and require width 10, height 5, area 50. A square that forces both to 5 breaks the contract. A read-only area capability can accept both if its dimension/area semantics agree; geometry alone does not settle behavioral subtyping.
P05: Test a client role
The fake can expose only balance and count calls. The consumer should use it once and pass the result to the formatter. This tests the consumer dependency; it does not prove arbitrary providers satisfy lifetime, state-history or performance contracts.
P06: Rename without migrating data
Use meaningful variable/function names while retaining customer[0] and customer[2], or unpack named local bindings with the exact row-shape assumption stated. Attribute access requires a representation adapter or coordinated migration plus failure and compatibility checks.
P07: Audit a misleading rationale
An attempt count has no time dimension. Concurrent callers, the initial attempt and delay policy affect the rate. State that the count caps work; a separate shared limiter enforces a per-time-window contract. Do not invent an upstream policy.
P08: Extract with effects visible
Both sketches assume reusable input; the extracted version can emit a header/rows before totaling fails. A generator is consumed once. A valid repair first specifies materialisation and failure policy; it may intentionally change legacy behavior and memory usage.
P09: Keep useful documentation
Document nonnegative same-currency Money, minor units, zero no-op, True/False outcomes, invalid ValueError and unchanged state on declared failures. Keep concurrency and transfer atomicity outside this sequential contract.
P10: Check published compatibility
Reflection/string lookups, saved JSON keys, plugins or external clients may retain the old name. Inventory owners and usage, then use an alias, explicit data migration or versioned API as needed. A compiler sees only its compilation boundary.
P11: Challenge a smell diagnosis
A projection naturally reads fields; no incorrect location is established by getter count. Duplicated domain policy, change history or representation leakage could justify a move. Moving all presentation into the record may worsen dependency direction.
P12: Discriminate extension axes
A hierarchy localises a new variant but adding an operation can touch every class. A closed match can make operation addition local but a new variant needs all relevant matches updated. Neither removes construction/validation/testing obligations.
P13: Inspect a change set
Schema and UI changes can be essential end-to-end work. Repeated preference-default logic copied into several consumers is a candidate for consolidation. A single PR file count cannot establish the causal source of maintenance cost.
P14: Evaluate a single-provider seam
The clock seam provides present deterministic testing and can protect time policy. The wrapper may add no useful boundary, but inspect public compatibility and ownership before removing it. One implementation alone does not decide either case.
P15: Clarify value semantics
Use exact integer minor units and explicit ValueError checks; bool and floats are excluded. Signed Money supports changes, while deposits require nonnegative values. Assertions can be disabled, and a wrapper enforces only rules its code actually implements.
P16: Implement two representations
After each operation compare result, balance and complete history to a scalar transition model, including rejected operations and distinct accounts. Equal finite traces support compatibility but not all-input proof. Review one cached and one derived representation manually.
P17: Inject a policy
A positive integer weight calls rate once; invalid weight calls it zero times. A provider exception propagates. A negative or non-Money result is invalid; a conforming EUR result is valid because quote does not fix currency. No inheritance is required for callables.
P18: Implement a reliable snapshot
An earlier history tuple of integers remains unchanged after deposit/withdraw, and a second account remains independent. A tuple of mutable records does not offer the same guarantee. Document snapshot creation cost and ordinary API scope.
P19: Specify a report boundary
Validate and consume once, escape text, then write exactly one string. A generator failure or invalid late row must cause zero sink calls. Once sink.write starts, an error can leave partial external output; there is no promised I/O transaction.
P20: Write a reviewable decision
Accept any specific, reproducible decision with a supported tradeoff. Reject unsupported universal rules, fabricated observations or “fewer classes is better” as the whole argument. A justified decision to keep the current structure is valid.