Pragmatic clean code: write code the next person can change safely Intermediate
This cluster answers: what “clean” means when measured by the cost of changing code, and how to refactor a real service step by step without breaking production.
The problem
Section titled “The problem”“Clean” doesn’t mean pretty or short. This cluster’s working definition:
- Cheap to change: a business change touches few files and few branches.
- Safe to change: after the change, tests catch regressions and errors aren’t swallowed.
- Easy to review: a reviewer grasps the intent in a few minutes, without running the code in their head.
Every rule must answer: “where does it make the next change cheaper or safer?” If it can’t, it’s dogma; skip it.
Reading order
Section titled “Reading order”Loading diagram…
In this cluster
Section titled “In this cluster”| Part | Question it answers |
|---|---|
| Safe refactoring | What’s wrong with the “before” code? How do you refactor while proving behavior doesn’t change? |
| Functions and side effects | How to use guard clauses, drop flags, add constants with units, separate a pure core from I/O? |
| Errors, comments, DRY | Which errors are results and which are exceptions? What to comment? When is duplication better than abstraction? |
| Common mistakes | BAD/GOOD code that devs often write, and exactly when it blows up |
Decision summary
Section titled “Decision summary”| Symptom in code | Default fix | Why |
|---|---|---|
if nested 3-4 levels |
Guard clauses, return early | The main path reads straight through |
boolean parameters (express, dryRun) |
Split the function, or use a union type | Nobody can read the call site checkout(x, true, false) |
| More than ~3 parameters traveling together | Parameter object | Adding a field doesn’t break every call site |
500000, 0.9, 12 in logic |
Named constants with units | Change in one place, greppable |
Computation mixed with DB/HTTP/new Date() |
Functional core, imperative shell | Test the core without mocks |
catch (e) { console.log(e) } |
Rethrow with context + cause, or actually handle it |
A swallowed error = silently wrong data |
| Expected business branches (declined, out of stock) | Return a typed result | Exceptions are for the unexpected |
| Two similar pieces of code | Wait for the third | The wrong abstraction costs more than duplication |
| Comments narrating what the code does | Delete them, name things better; comment why | “What” comments go stale quickly |
Code review checklist
Section titled “Code review checklist”- Are refactor commits and behavior-change commits separate? Does the refactor commit change tests (it shouldn’t)?
- Is there any
catchthat doesn’t rethrow, doesn’t really handle, and only logs? - Do thrown errors carry enough context (id, amount, key) and keep the
cause? - Are expected business branches using exceptions as control flow?
- Are there
booleanparameters on public methods? Can the call site be read without opening the definition? - Do business numbers and strings have names and units? Is any value repeated in several places?
- Can the decision logic be tested without mocking DB/HTTP/the clock?
- Is there a new abstraction with only one caller, or a shared function that just got a flag added to “fit” a new caller?
Summary
Section titled “Summary”Next: Safe refactoring