Simplify complex code through the Error Reduction Hierarchy: Define out > Mask > Aggregate > Crash...
The best way to deal with exceptions is to define errors out of existence.
Priority order: Define out β Mask β Aggregate β Crash (app-level only)
Before adding complexity to an interface (new parameters, new exceptions, new caller responsibilities):
1. Is this complexity closely related to the module's existing functionality?
NO β Should it be pulled into a DIFFERENT module?
YES β Identify correct module, pull there
NO β Leave in place (may be inherent to caller's domain)
YES β Continue
2. Will pulling down simplify code elsewhere in the application?
NO β Do not pull down (no benefit)
YES β Continue
3. Will pulling down simplify the module's interface?
NO β Do not pull down (risk of leakage)
YES β Pull complexity down
All three conditions must be YES to pull down.
Critical Constraint: Pulling down UNRELATED complexity creates information leakage. If the complexity isn't intrinsic to the module's core abstraction, it doesn't belong thereβfind the right home or leave it with the caller.
| Situation | Wrong Approach | Right Approach |
|---|---|---|
| Uncertain what value to use | Export parameter | Compute automatically |
| Different contexts need different values | Export parameter | Use reasonable default, expose only for exceptions |
| Policy decision unclear | Let user decide | Make a decision and own it |
Configuration parameters represent incomplete solutions. Every parameter pushes complexity to every user/administrator. Prefer dynamic computation over static configuration.
Apply in order of preference:
| Priority | Technique | How It Works | Example |
|---|---|---|---|
| 1 | Define out | Change semantics so error is impossible | unset(x) = "ensure x doesn't exist" (not "delete existing x") |
| 2 | Mask | Handle at low level, hide from callers | TCP retransmits lost packets internally |
| 3 | Aggregate | Single handler for multiple exceptions | One catch block in dispatcher handles all NoSuchParameter |
| Special | Crash | Print diagnostic and abort (app-level only) | malloc failure in non-recoverable contexts |
Note on "Crash": This is NOT level 4 of a hierarchyβit's a special case for truly unrecoverable errors in application code. Libraries should NEVER crash; they expose errors for callers to decide.
When facing an exception handling decision:
1. Can semantics be redefined to eliminate the error condition?
YES β Define out of existence
NO β Continue
2. Can exception be handled at low level without exposing?
YES β Mask
NO β Continue
3. Can multiple exceptions share the same handling?
YES β Aggregate
NO β Continue
4. Is error rare, unrecoverable, and non-value-critical?
YES β Just crash (app-level only)
NO β Must expose (exception information needed outside module)
| Exception Case | Why | What to Do Instead |
|---|---|---|
| Security-critical errors | Aggregating auth errors loses security-relevant distinctions | Keep distinct types for audit/logging |
| Retry-differentiated errors | Callers need different retry strategies per error type | Expose type info for retry decisions |
| Silent data loss risk | Define-out can mask user errors, complicate debugging | Fail fast for essential data errors |
| Library code | Callers should decide crash policy, not library | Expose errors; let app-level code crash |
| Technique | Gate Question |
|---|---|
| Define out | Does anyone NEED to detect this error case? |
| Mask | Does the caller have ANY useful response to this error? |
| Aggregate | Do callers handle these errors identically? |
| Crash | Is this (a) application-level code, (b) truly unrecoverable, AND (c) crash acceptable? |
Before defining an error out of existence, verify it's an incidental error (safe) not an essential error (must fail fast):
| Question | If YES β | If NO β |
|---|---|---|
| Would this state occur in normal, correct operation? | Safe to define out | Fail fast |
| Can the caller proceed meaningfully with the "defined out" state? | Safe | Expose error |
| Does the user/system have another way to detect this condition if needed? | Safe | Consider exposing |
| Technique | How | When to Use |
|---|---|---|
| Reduce information needed | Abstraction, eliminate special cases | Design-level changes |
| Leverage reader knowledge | Follow conventions, meet expectations | Incremental improvements |
| Present explicitly | Good names, strategic comments | When other techniques insufficient |
If a code reviewer says your code is not obvious, it is not obviousβregardless of how clear it seems to you.
| Problem | Why Nonobvious | Fix |
|---|---|---|
| Generic containers (Pair, Tuple) | getKey() obscures meaning |
Define specific class with named fields |
| Event-driven handlers | Control flow hidden | Document invocation context |
| Type mismatches | List declared, ArrayList allocated |
Match declaration to allocation |
| Violated expectations | Code doesn't do what reader assumes | Document or refactor to meet expectations |
Before presenting simplified code, produce a technique analysis table β this is the evidence that the hierarchy was applied:
| Error Condition | Technique | Gate Check | Reasoning |
|-----------------|-----------|------------|-----------|
| [each error] | [1-4] | [PASS/FAIL]| [why] |
Proceed only when the following are true:
If a criterion legitimately cannot be satisfied, present the code with the failed criterion and reason stated.
| Conflict | Resolution Heuristic |
|---|---|
| Define Out vs Fail Fast | Define out for incidental errors. Fail fast for essential errors. |
| Mask vs Explicit Handling | Mask when caller has no useful response. Expose when caller's response differs. |
| Aggregate vs Specific Messages | Aggregate the HANDLING, preserve specificity in the MESSAGE. |
| Pull Down vs Single Responsibility | Only pull down complexity RELATED to module's core purpose. |
| Obviousness vs Brevity | When define-out creates non-obvious behavior, add explanatory comment. |
| Simplify vs Performance | Prefer simplicity unless profiling proves performance-critical. |
| Red Flag | Symptom | Transformation |
|---|---|---|
| Scattered exceptions | Same error handled in many places | Aggregate to single handler |
| Configuration explosion | Many parameters exported | Compute automatically, provide defaults |
| Caller doing module's work | Logic outside that belongs inside | Pull complexity down |
| Over-defensive code | Checks for impossible conditions | Define errors out |
| Generic containers | Pair<X,Y> obscures meaning |
Create named structure |
| Comment-dependent understanding | Code unreadable without comments | Refactor for obviousness |
| Masked error without observability | Applying Mask or Define-out but no logging, metrics, or alternate signal when the error actually occurs | Every masked error needs an observability escape hatch (log, metric, health check) so operators can detect when masking hides a real problem |
Detailed checklists: Read(${CLAUDE_SKILL_DIR}/checklists.md)
| After | Next |
|---|---|
| Simplification done | Skill(code-foundations:aposd-verifying-correctness) β verify interface simplified |