Push Ifs Up and Fors Down
Ifs vs matches and exhaustiveness
- Some argue the enum+
matchstyle is safer than if/else because the compiler enforces exhaustiveness; adding a new variant forces all matches to be updated. - Others counter that in simple cases this only adds boilerplate without extra safety, and compilers often generate identical machine code.
- Debate centers on whether future changes justify the extra abstraction and “double-entry bookkeeping” of reifying conditions as enums.
Pushing ifs up: clarity, invariants, guard clauses
- Supporters like centralizing branching in a higher-level function that “decides,” delegating straight‑line work to helpers.
- Hoisting conditions out of loops can:
- Make loop invariants explicit.
- Simplify reasoning, debugging, and sometimes enable vectorization/parallelization.
- Guard clauses and early returns are repeatedly praised as a way to avoid “arrow code” and deeply nested conditionals.
Arguments against universal “push ifs up”
- Many see this as a context‑dependent heuristic, not a rule. Over-application can:
- Violate DRY by forcing the same condition into many call sites.
- Obscure local preconditions and make functions easier to misuse.
- Some prefer validating close to where data is used, or keeping conditionals inside to guarantee idempotency or transaction boundaries.
- Several note examples where domain invariants or framework behavior (e.g. routing, middleware, options parsing) naturally keep checks “down”.
Pushing fors down and batching
- Strong agreement that APIs and functions should often operate on collections (“batch” style) rather than single items:
- Enables single DB/HTTP calls instead of N+1 queries.
- Better cache locality and easier loop optimization.
- However, callers may need both per-item and batch semantics; sometimes the caller has better information about how to parallelize or group work.
Static analysis and cyclomatic complexity
- Code-complexity tools often push the opposite direction (discouraging large, branchy “control centers”).
- Many find cyclomatic complexity warnings noisy: they can fragment logic into many tiny “poltergeist” functions that are harder to follow.
- Consensus: treat such tools as hints, not gospel; useful mainly to catch extreme cases (huge, deeply nested functions).
Types, input boundaries, and “parse don’t validate”
- A recurring theme is moving checks toward input boundaries and encoding assumptions in types (e.g.
Option<T>vsT, or separate “verified” vs “unchecked” types). - This reduces repeated conditionals in inner logic while preserving safety, especially in languages with rich type systems.
- Some link this to the “parse, don’t validate” idea: normalize data once at the edges, then operate on stronger types internally.
Performance vs readability and context
- There’s disagreement on how performance‑driven the advice is:
- Some read it as primarily about clarity and expressing intent; performance is a side effect.
- Others emphasize that in many domains (hot loops, data pipelines, SwiftUI rendering, SIMD) hoisting branches and batching are crucial to throughput.
- Several commenters insist that in typical application/server code, readability and maintainability dominate small performance gains.
General sentiment
- Many like the heuristic (“push ifs up, fors down”) as a mental nudge to reconsider structure, not as doctrine.
- Others see it as oversimplified, similar to other programming “fads”: useful in certain performance‑sensitive or data‑processing contexts, but dangerous if applied blindly.