Chapter 2 — A Pragmatic Approach
Topics 8–15 | Pages 73–142
Core Idea
Universal principles that apply at all levels of software development: design, architecture, delivery, and estimation. All of them flow from one root idea — make things easy to change.
Topic 8 — The Essence of Good Design (p75)
Tip 14: Good Design Is Easier to Change Than Bad Design
The ETC principle (Easier to Change) unifies every design principle:
- Why is decoupling good? → Isolated concerns are easier to change.
- Why is SRP useful? → A requirement change maps to one module change.
- Why does naming matter? → You must read code to change it.
ETC is a value, not a rule. It guides choices. When uncertain which path is more changeable: write replaceable code (decoupled, cohesive), and note your reasoning in your daybook to build instincts over time.
Topic 9 — DRY — The Evils of Duplication (p79)
Tip 15: DRY — Don’t Repeat Yourself
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
DRY is not about code duplication — it’s about knowledge duplication. The acid test: when a single fact about the domain changes, how many places in the code must you update?
Types of duplication:
| Type | Example | Fix |
|---|---|---|
| Code duplication | Repeated formatting logic | Extract function |
| Documentation duplication | Comment restates what code already says | Delete the comment; improve naming |
| Data structure duplication | length stored alongside start/end | Make it a computed field |
| API/external schema duplication | Code that mirrors an external spec | Use code generation, OpenAPI, or schema introspection |
| Interdeveloper duplication | Same logic written by two teams | Foster communication, appoint a “project librarian,” make reuse easy |
Important distinction: Two functions with the same body but different intent are NOT a DRY violation. The coincidental similarity doesn’t mean they represent the same knowledge.
Tip 16: Make It Easy to Reuse
If reusing existing code is harder than writing new code, developers won’t do it, and duplication spreads.
Topic 10 — Orthogonality (p92)
Two things are orthogonal if changing one has no effect on the other. The goal: self-contained components with a single, well-defined purpose (cohesion).
Tip 17: Eliminate Effects Between Unrelated Things
Benefits:
- Productivity: Changes are localized; each component can be developed and tested independently; combined orthogonal components multiply rather than overlap in capability.
- Risk reduction: Diseased code is isolated; fixes don’t ripple; third-party dependencies are bounded.
Orthogonality test: If a requirement changes, how many modules must change? The answer should be one.
Coding practices for orthogonality:
- Write shy code — don’t expose what’s unnecessary; don’t rely on others’ implementations (Law of Demeter)
- Avoid global data — globals couple every module that touches them
- Avoid similar functions — structural duplication signals poor separation; use Strategy pattern
- Use layering — each layer only depends on the layer below it
Orthogonality and testing: If a unit test requires importing half the system to run, the module is not orthogonal. Bug fixes that scatter changes across many files indicate poor orthogonality.
DRY vs. Orthogonality:
- DRY: minimize knowledge duplication
- Orthogonality: minimize interdependency
- Use both together
Topic 11 — Reversibility (p104)
Requirements, vendors, architectures, and environments all change — often in ways you can’t predict. Treating any decision as final is a mistake.
Tip 18: There Are No Final Decisions
Tip 19: Forgo Following Fads
Strategy: abstract away dependencies so swapping them doesn’t require rewriting everything.
- Hide third-party APIs behind your own abstraction layers
- Break code into components (even if deployed monolithically at first)
- Don’t chase the latest architectural trend; ensure code can “roll with the punches”
Architectural fashions (2000–2019): big iron → federation → commodity clusters → VMs → services → containers → serverless → back to iron. You can’t predict which will dominate. Build to swap.
Topic 12 — Tracer Bullets (p109)
Tip 20: Use Tracer Bullets to Find the Target
Tracer bullets = thin, end-to-end slices of functionality that traverse all layers of the system, built early to validate architecture and gather feedback. They are production code — not thrown away.
When to use: requirements are vague, tech stack is unfamiliar, or you need to show progress.
Advantages over “code in modules, then integrate”:
- Users see something working early and can correct direction
- Provides a skeleton that keeps the team consistent
- Enables daily (or more frequent) integration
- Always have something to demo
- Progress is visibly measurable use case by use case
Tracer Bullets vs. Prototypes:
| Tracer Bullets | Prototypes | |
|---|---|---|
| Purpose | Validate architecture end-to-end | Explore a specific risk/question |
| Code | Production-quality, kept | Throwaway |
| Scope | Thin slice of whole system | One aspect (UI, algorithm, etc.) |
Prototype is reconnaissance; tracer is the first real shot.
Topic 13 — Prototypes and Post-it Notes (p118)
Tip 21: Prototype to Learn
Value is in the lessons, not the code. Prototypes answer specific questions about risky/unknown areas: architecture, UI, third-party integrations, algorithms, performance.
What you can ignore in a prototype: correctness, completeness, robustness, style/docs.
Critical warning: Make it crystal clear prototypes are disposable. Stakeholders and managers may try to deploy prototype code. If the culture won’t accept that prototypes get thrown away, use tracer bullets instead.
Use high-level scripting languages for prototypes (Python, Ruby) — they get out of your way faster.
Topic 14 — Domain Languages (p124)
Tip 22: Program Close to the Problem Domain
Domain-specific languages (DSLs) let you express intent in terms of the problem domain.
| Type | Examples | Tradeoffs |
|---|---|---|
| Internal DSL | RSpec, Phoenix Router | Bound by host language syntax; no parser needed; can use all host language features |
| External DSL | Cucumber, Ansible (YAML) | Full syntax freedom; requires parser; more effort |
Practical advice:
- Use off-the-shelf external formats (YAML, JSON, CSV) when possible
- For internal DSLs, simple named functions often suffice — no metaprogramming magic needed
- Don’t spend more effort on the DSL than you save
Business users rarely read Cucumber specs — they want runnable software, not documents. Their real needs surface when they interact with code.
Topic 15 — Estimating (p133)
Tip 23: Estimate to Avoid Surprises
Tip 24: Iterate the Schedule with the Code
Estimation accuracy scales with time unit used:
| Duration | Express in |
|---|---|
| 1–15 days | Days |
| 3–6 weeks | Weeks |
| 8–20 weeks | Months |
| 20+ weeks | Think hard before estimating |
Estimation process:
- Understand what’s being asked — scope, implicit assumptions
- Build a model — mental sketch of the system or process
- Break model into components — identify interaction rules
- Give each parameter a value — focus accuracy on high-impact multipliers
- Calculate — use ranges, not single numbers; strange results reveal model errors
- Track your estimates — record and review; find out why estimates were wrong
For project schedules: don’t try to nail down the full timeline upfront. Use incremental development; refine the estimate each iteration based on actual velocity.
What to say when asked for an estimate: “I’ll get back to you.” Never estimate at the coffee machine.
Key Takeaways
- ETC (Easier to Change) is the root principle; all other design rules are special cases.
- DRY is about knowledge duplication, not code duplication — the acid test is how many places change when a fact changes.
- Orthogonality means independent components; changes stay local, tests stay small.
- No architectural decision is final — build to swap vendors, patterns, and platforms.
- Tracer bullets get end-to-end working fast, stay in production, and correct direction early.
- Prototypes are throwaway; make that explicit or use tracer bullets instead.
- When asked for an estimate, say “I’ll get back to you” — and iterate the schedule with the code.