2.2.2 · D4Design Principles

Exercises — KISS — Keep It Simple

3,748 words17 min readBack to topic

Before we start, we earn two tools from zero: what counts as a moving part, and the load formula .

Figure 1.

Figure — KISS — Keep It Simple


L1 — Recognition

Recall Solution 1.1

B violates KISS. The smell is cleverness over clarity (a "slick one-liner").

  • x & 1 is a bit-trick for parity that most readers must decode.
  • The generator-that-throws ((_ for _ in ()).throw(...)) is a cryptic way to raise an error. WHAT we did: compared two solutions to the same problem. WHY: KISS is about the simplest thing that solves the problem — both are correct, but A reads like English. Fix: keep A.
Recall Solution 1.2

Apply the rule bullet by bullet: score is 1 variable (bullet 1); the two ifs are 2 branch points (bullet 2); no other function/class is opened (bullet 3 contributes 0); return keywords are not counted. Total . WHY this is fine: is tiny — this is already KISS-clean via early returns.


L2 — Application

Recall Solution 2.1
def can_ship(order):
    if not order:          return False
    if not order.paid:     return False
    if not order.in_stock: return False
    return True

Counting. By the rule, each version has 3 branch points (the three conditions) and no extra variables/units, so and for both. A sharper metric — "live pairs" . The total counts every pair across the whole function; but a reader only truly juggles the branches that are open at once. So define a second quantity.

  • == = the number of if blocks that are still open (entered but not yet exited) at the single deepest point of the function.== It is a plain integer you get by pointing at the most-indented line and counting the ifs stacked above it.
  • ==Live pairs == — the pair-count among only those simultaneously-open branches. (Same machine as , but fed instead of .) Now measure both versions:
  • Nested version: at the innermost line, all 3 if blocks are open .
  • Guard-clause version: each if returns immediately, so at most one if is open at any moment . Same total on paper, but live pairs drop from 3 to 0 — that is the concrete, countable win of guard clauses.
Recall Solution 2.2

Delete verbose and the if.

def save(user):
    db.write(user)

Principle: YAGNI ("You Aren't Gonna Need It") — a way to achieve KISS by not building for imagined futures. Counting the win (by the rule): before, user and verbose are 2 variables and the if verbose is 1 branch point . After, only user remains, . We removed all 3 tracked interactions.


L3 — Analysis

Recall Solution 3.1

Before (one big function): After (five functions, each): Reduction factor: WHY it works: we kept the same total pieces (40) but broke the giant pair-web into 5 small isolated webs. Because is quadratic, five small squares are far smaller than one big square. This is the mathematical heart of Single Responsibility Principle.

Figure 2.

Figure — KISS — Keep It Simple

Recall Solution 3.2

(a) Internal. Each function has , so internal interactions. (b) Cross-function. The coordinator must now hold 40 sub-calls in its head — by the counting rule those 40 called units are 40 moving parts of the coordinator. Their interaction count is The lesson, now numeric: we drove internal load to 0 but recreated the exact same 780 as coordinator load — complexity was moved, not destroyed (indeed here, not even reduced). You cannot win by splitting into dust; you only relocate the tangle to the glue. This is Premature Optimization applied to structure.

Recall Solution 3.3

(a) Internal after split: two groups of 3 internal pairs. (b) Cross-function pairs: the coordinator tracks 2 sub-calls pair. (c) Grand total: . Confirming "moved, not deleted": the original 15 internal pairs did not vanish — 9 of them dissolved because pieces in different groups can no longer interact, but a new cross-function pair (the coordinator's 1) appeared to glue the groups back together. Complexity dropped from 15 to 7 here because the isolation saving beat the glue cost — but that glue cost is real and non-zero (and in Ex 3.2 it swallowed the entire saving), which is exactly why infinite splitting backfires.


L4 — Synthesis

Recall Solution 4.1

Joint recommendation: remove the duplication (DRY) with the simplest thing that works (KISS), and refuse the factory (YAGNI) because there is exactly one tax type today.

GST_RATE = 0.18
def with_gst(base):
    return base + base * GST_RATE

Counting the simple version (by the rule): base (variable) + GST_RATE (variable) = 2 parts, no branches, no other units opened . Counting the factory version — apply the rule to high-level constructs. Bullet 3 of the counting rule says each named unit the reader must open counts +1. A class, a factory method, and an interface are each such a named unit, so they each cost +1 (a reader genuinely must open every one to trace how a price is computed). Enumerating them:

  1. TaxStrategy interface — a named unit (bullet 3)
  2. GSTStrategy concrete class — a named unit (bullet 3)
  3. TaxCalculatorFactory class — a named unit (bullet 3)
  4. create() factory method — a named unit (bullet 3)
  5. the registry dict mapping tax-type → strategy — a variable (bullet 1)
  6. calculate() strategy method — a named unit (bullet 3)
  7. base — the input variable still threaded through (bullet 1) That is . So the factory costs 21 vs 1 — 21× the interactions to deliver one fixed 18% rate. Why DRY and KISS agree here: DRY says "one source of truth," KISS says "make that source the simplest possible" — a single function with a named constant, not a framework.
Recall Solution 4.2

Not KISS — it's over-simplification (a bug). The "insufficient funds" check is a needed piece of the problem, not accidental complexity. Removing it makes the code simpler and wrong. Corrected statement (Einstein's razor): as simple as possible, but no simpler. KISS removes unnecessary complexity; a required correctness check is necessary complexity. Rule of thumb: if deleting a piece changes what the program does on valid inputs, it wasn't excess — leave it in.


L5 — Mastery

Recall Solution 5.1

Version X (one function): Version Y (sum over 3 leaves + 1 coordinator): Verdict: is simpler by the count. Factor . What the coordinator term teaches: the split halves total pair-load here — but the coordinator's is the price of splitting (the same glue we counted in Ex 3.2–3.3). If the leaves had been smaller, that fixed coordinator cost could have eaten the saving. So the mastery habit is: always add the coordinator into the total, never compare leaves alone.

Recall Solution 5.2

Total: , with .

  • (groups of 10): leaves ; coord ; total .
  • (groups of 5): leaves ; coord ; total .
  • (groups of 4): leaves ; coord ; total . Minimum here: (total 40). WHAT this teaches: as rises, leaf-load falls fast (quadratic shrink of each group) but coordinator-load rises. There is a sweet spot, not "split forever." The curve below plots — it dips then eventually turns back up. This is the quantitative version of "split until each unit has one job, then stop."

Figure 3.

Figure — KISS — Keep It Simple


Recall Grand recap — the ladder you just climbed
  • L1: Spot cleverness/over-flagging; complexity ≠ line-count.
  • L2: Apply guard clauses and YAGNI to drop ; live pairs fall from 3 to 0.
  • L3: Splitting cuts super-linearly — but you move load to coordinator glue, never delete it (split into 40 → all 780 pairs land on the coordinator).
  • L4: DRY, KISS, YAGNI cooperate; "no simpler" protects needed edge cases.
  • L5: There's a mathematical sweet spot to splitting (even for uneven ); let responsibility, not the raw number, pick boundaries.