@lvlup-sw/axiom 0.2.2 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/backend-quality/references/deterministic-checks.md +24 -0
- package/skills/critique/SKILL.md +12 -1
- package/skills/critique/references/readability-patterns.md +277 -0
- package/skills/distill/references/simplification-guide.md +45 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axiom",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Backend code quality skills for Claude Code. Six skills that audit, critique, harden, and simplify your architecture — the backend half of impeccable.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "LevelUp Software"
|
package/package.json
CHANGED
|
@@ -108,6 +108,30 @@ Each check has: ID, pattern, what it detects, severity, and false-positive guida
|
|
|
108
108
|
|
|
109
109
|
## DIM-6: Architecture
|
|
110
110
|
|
|
111
|
+
### T-6.1a: Deep nesting
|
|
112
|
+
- **Pattern:** Count indentation levels per function; flag functions with >3 levels of nesting
|
|
113
|
+
- **Severity:** MEDIUM
|
|
114
|
+
- **Detects:** Functions where business logic is buried under layers of conditional checks
|
|
115
|
+
- **False positives:** Nesting from resource scoping (`try`/`with` blocks) or framework-required patterns
|
|
116
|
+
|
|
117
|
+
### T-6.1b: Long functions
|
|
118
|
+
- **Pattern:** Functions exceeding 50 lines (measured from signature to closing brace)
|
|
119
|
+
- **Severity:** MEDIUM
|
|
120
|
+
- **Detects:** Functions doing too much — multiple concerns in a single body
|
|
121
|
+
- **False positives:** Functions with long but simple data declarations (mapping tables, configuration objects)
|
|
122
|
+
|
|
123
|
+
### T-6.1c: Long parameter lists
|
|
124
|
+
- **Pattern:** Functions with more than 4 parameters in the signature
|
|
125
|
+
- **Severity:** MEDIUM
|
|
126
|
+
- **Detects:** Functions with too many inputs, suggesting they handle multiple concerns or need an options object
|
|
127
|
+
- **False positives:** Constructor functions in DI-heavy codebases where parameters are injected dependencies; callback-heavy APIs where signature is dictated by framework
|
|
128
|
+
|
|
129
|
+
### T-6.1d: Deep inheritance
|
|
130
|
+
- **Pattern:** `class\s+\w+\s+extends\s+` — flag when inheritance chain exceeds 2 levels
|
|
131
|
+
- **Severity:** MEDIUM
|
|
132
|
+
- **Detects:** Class hierarchies that would be simpler as composition
|
|
133
|
+
- **False positives:** Framework-required inheritance (e.g., extending base controller classes); sealed hierarchies with exhaustive matching
|
|
134
|
+
|
|
111
135
|
### T-6.1: Circular imports
|
|
112
136
|
- **Pattern:** Build import graph; detect cycles
|
|
113
137
|
- **Severity:** HIGH
|
package/skills/critique/SKILL.md
CHANGED
|
@@ -100,7 +100,17 @@ Identify modules with too many responsibilities:
|
|
|
100
100
|
- Classes or modules that are modified in every feature branch (shotgun surgery indicator)
|
|
101
101
|
- Modules that import from many unrelated domains
|
|
102
102
|
|
|
103
|
-
#### 3e.
|
|
103
|
+
#### 3e. Readability Patterns
|
|
104
|
+
|
|
105
|
+
Evaluate code-level readability and structural clarity:
|
|
106
|
+
|
|
107
|
+
- **DRY violations:** Duplicated logic across multiple locations that should be extracted
|
|
108
|
+
- **Deep nesting:** Functions with excessive indentation that could use guard clauses / early returns
|
|
109
|
+
- **Composition over inheritance:** Class hierarchies that would be simpler as composed components
|
|
110
|
+
- **Parameter discipline:** Long parameter lists and boolean flags that obscure call-site meaning
|
|
111
|
+
- For definitions, violation signals, and refactoring approaches, see `@skills/critique/references/readability-patterns.md`
|
|
112
|
+
|
|
113
|
+
#### 3f. Circular Dependency Identification
|
|
104
114
|
|
|
105
115
|
Detect import cycles between modules:
|
|
106
116
|
|
|
@@ -130,3 +140,4 @@ Format all findings per `@skills/backend-quality/references/findings-format.md`:
|
|
|
130
140
|
- `@skills/backend-quality/references/findings-format.md` — Standard output format for findings
|
|
131
141
|
- `@skills/critique/references/solid-principles.md` — SOLID principle definitions, violation signals, severity guide, and detection heuristics
|
|
132
142
|
- `@skills/critique/references/dependency-patterns.md` — Dependency pattern catalog, coupling metrics, circular dependency detection, and layered architecture guidance
|
|
143
|
+
- `@skills/critique/references/readability-patterns.md` — DRY, early returns, composition over inheritance, and parameter discipline
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Readability Patterns Reference
|
|
2
|
+
|
|
3
|
+
Detection heuristics and severity guidance for code readability concerns beyond SOLID. These patterns address structural clarity at the function and expression level.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## DRY — Don't Repeat Yourself
|
|
8
|
+
|
|
9
|
+
### Definition
|
|
10
|
+
|
|
11
|
+
Every piece of knowledge should have a single, authoritative representation in the system. When the same logic appears in multiple places, a change to that logic requires finding and updating every copy — and missing one creates a bug.
|
|
12
|
+
|
|
13
|
+
### Violation Signals
|
|
14
|
+
|
|
15
|
+
1. Two or more functions with near-identical bodies differing only in variable names or literals
|
|
16
|
+
2. Copy-pasted conditional chains (same `if/else` structure with different field names)
|
|
17
|
+
3. Parallel data transformations that follow the same pattern but aren't abstracted
|
|
18
|
+
4. Test setup code duplicated across multiple test files without extraction into a shared fixture
|
|
19
|
+
5. Configuration or mapping objects repeated in multiple modules instead of imported from one source
|
|
20
|
+
|
|
21
|
+
### When Duplication Is Acceptable
|
|
22
|
+
|
|
23
|
+
Not all duplication violates DRY. Apply the **three uses rule** (see `@skills/distill/references/simplification-guide.md`):
|
|
24
|
+
|
|
25
|
+
- **First occurrence:** Write it inline. Do not extract.
|
|
26
|
+
- **Second occurrence:** Note the duplication but tolerate it. The pattern may be coincidental.
|
|
27
|
+
- **Third occurrence:** Extract. You now have enough examples to see the true shape of the abstraction.
|
|
28
|
+
|
|
29
|
+
**Also acceptable:**
|
|
30
|
+
- Test assertions that happen to look similar but test different behaviors — readability in tests often outweighs DRY
|
|
31
|
+
- Protocol handlers or route definitions that share structure but represent distinct endpoints — premature abstraction here creates "magic" dispatchers that are harder to understand
|
|
32
|
+
- Two-line utility patterns (e.g., null-check-and-return) — the abstraction would be longer than the duplication
|
|
33
|
+
|
|
34
|
+
### Severity Guide
|
|
35
|
+
|
|
36
|
+
| Severity | When to assign |
|
|
37
|
+
|----------|---------------|
|
|
38
|
+
| **HIGH** | Duplicated business logic where a bug fix applied to one copy but not the other would cause incorrect behavior. |
|
|
39
|
+
| **MEDIUM** | Duplicated structural patterns (>10 lines) across 3+ locations. The abstraction is clear but hasn't been extracted. |
|
|
40
|
+
| **LOW** | Minor duplication (<10 lines) in 2 locations. Extraction would add more complexity than it removes. |
|
|
41
|
+
|
|
42
|
+
### Detection Heuristics
|
|
43
|
+
|
|
44
|
+
- Look for functions with the same control flow structure but different variable names
|
|
45
|
+
- Search for identical error handling blocks across different catch clauses
|
|
46
|
+
- Check for repeated object construction patterns (same keys, different values)
|
|
47
|
+
- Compare test files for duplicated setup/teardown logic
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Early Returns and Guard Clauses
|
|
52
|
+
|
|
53
|
+
### Definition
|
|
54
|
+
|
|
55
|
+
Guard clauses handle edge cases and preconditions at the top of a function, returning or throwing early. This eliminates nesting and keeps the main logic path at the lowest indentation level.
|
|
56
|
+
|
|
57
|
+
### The Problem with Deep Nesting
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Deep nesting — hard to follow
|
|
61
|
+
function processOrder(order: Order): Result {
|
|
62
|
+
if (order) {
|
|
63
|
+
if (order.items.length > 0) {
|
|
64
|
+
if (order.customer) {
|
|
65
|
+
if (order.customer.isActive) {
|
|
66
|
+
// ... actual business logic buried at 4 levels deep
|
|
67
|
+
const total = calculateTotal(order.items)
|
|
68
|
+
return { success: true, total }
|
|
69
|
+
} else {
|
|
70
|
+
return { success: false, error: 'Inactive customer' }
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
return { success: false, error: 'No customer' }
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
return { success: false, error: 'Empty order' }
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
return { success: false, error: 'No order' }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Guard clauses — preconditions handled upfront, main path is flat
|
|
86
|
+
function processOrder(order: Order): Result {
|
|
87
|
+
if (!order) return { success: false, error: 'No order' }
|
|
88
|
+
if (order.items.length === 0) return { success: false, error: 'Empty order' }
|
|
89
|
+
if (!order.customer) return { success: false, error: 'No customer' }
|
|
90
|
+
if (!order.customer.isActive) return { success: false, error: 'Inactive customer' }
|
|
91
|
+
|
|
92
|
+
const total = calculateTotal(order.items)
|
|
93
|
+
return { success: true, total }
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Violation Signals
|
|
98
|
+
|
|
99
|
+
1. Functions with more than 3 levels of nesting
|
|
100
|
+
2. `else` branches that contain only a return or throw (invert the condition and return early)
|
|
101
|
+
3. Long `if` blocks followed by a short `else { return }` — the return should come first
|
|
102
|
+
4. Nested `if` chains where each level checks a single condition (collapse into sequential guards)
|
|
103
|
+
5. Functions where the main logic is indented 3+ levels deep
|
|
104
|
+
|
|
105
|
+
### When to Keep Nesting
|
|
106
|
+
|
|
107
|
+
- **Mutually exclusive branches with equal weight:** When both branches contain substantial logic (not just a return), `if/else` is clearer than early return
|
|
108
|
+
- **Loop bodies with `continue`:** Using `continue` as a guard clause inside loops is the same pattern and equally valid, but excessive `continue` statements can fragment loop logic
|
|
109
|
+
- **Transaction scoping:** When nesting represents a resource scope (e.g., `try` blocks wrapping database transactions), the nesting communicates the scope boundary
|
|
110
|
+
|
|
111
|
+
### Severity Guide
|
|
112
|
+
|
|
113
|
+
| Severity | When to assign |
|
|
114
|
+
|----------|---------------|
|
|
115
|
+
| **HIGH** | Function with 5+ levels of nesting. Main business logic is buried and hard to trace. |
|
|
116
|
+
| **MEDIUM** | Function with 3-4 levels of nesting that could be flattened with guard clauses. |
|
|
117
|
+
| **LOW** | Occasional unnecessary `else` after a return statement. Cosmetic but worth noting. |
|
|
118
|
+
|
|
119
|
+
### Detection Heuristics
|
|
120
|
+
|
|
121
|
+
- Measure maximum indentation depth per function — flag functions exceeding 3 levels
|
|
122
|
+
- Search for `else { return` or `else { throw` patterns — these can almost always be inverted
|
|
123
|
+
- Look for `if (condition) { ... long block ... } else { return }` — invert the condition
|
|
124
|
+
- Count the ratio of guard-eligible checks to actual guards in functions with preconditions
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Composition Over Inheritance
|
|
129
|
+
|
|
130
|
+
### Definition
|
|
131
|
+
|
|
132
|
+
Favor assembling behavior from small, focused components rather than building deep class hierarchies. Inheritance creates tight coupling between parent and child classes; composition allows flexible recombination.
|
|
133
|
+
|
|
134
|
+
### Why Inheritance Creates Problems
|
|
135
|
+
|
|
136
|
+
- **Fragile base class:** Changes to the parent class ripple into all children, even when irrelevant to their specific behavior
|
|
137
|
+
- **Forced inheritance of unwanted behavior:** Subclasses inherit all parent methods, even ones that don't apply (ISP violation in class form)
|
|
138
|
+
- **Diamond problem:** Multiple inheritance paths create ambiguity about which parent's behavior to use
|
|
139
|
+
- **Deep hierarchies obscure behavior:** Understanding what a class does requires reading the entire chain from root to leaf
|
|
140
|
+
|
|
141
|
+
### Violation Signals
|
|
142
|
+
|
|
143
|
+
1. Class hierarchies deeper than 2 levels (grandchild classes)
|
|
144
|
+
2. Subclasses that override >50% of parent methods (the "is-a" relationship is wrong)
|
|
145
|
+
3. Abstract base classes with only one concrete implementation (premature abstraction)
|
|
146
|
+
4. Classes that extend a base class only to access a utility method (use composition or a standalone function)
|
|
147
|
+
5. `super` calls that require understanding the parent's implementation details (leaky inheritance)
|
|
148
|
+
6. "Template method" patterns where the base class controls flow and subclasses fill in hooks — often better expressed as a function accepting strategy callbacks
|
|
149
|
+
|
|
150
|
+
### Composition Alternatives
|
|
151
|
+
|
|
152
|
+
**Instead of inheritance for shared behavior:**
|
|
153
|
+
```typescript
|
|
154
|
+
// Inheritance approach — tight coupling
|
|
155
|
+
class BaseRepository {
|
|
156
|
+
async findById(id: string) { /* shared query logic */ }
|
|
157
|
+
async save(entity: unknown) { /* shared persistence logic */ }
|
|
158
|
+
}
|
|
159
|
+
class UserRepository extends BaseRepository {
|
|
160
|
+
async findByEmail(email: string) { /* user-specific */ }
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Composition approach — flexible, testable
|
|
166
|
+
function createRepository(tableName: string) {
|
|
167
|
+
return {
|
|
168
|
+
findById: (id: string) => query(tableName, { id }),
|
|
169
|
+
save: (entity: unknown) => upsert(tableName, entity),
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createUserRepository() {
|
|
174
|
+
const base = createRepository('users')
|
|
175
|
+
return {
|
|
176
|
+
...base,
|
|
177
|
+
findByEmail: (email: string) => query('users', { email }),
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Instead of inheritance for variation:**
|
|
183
|
+
```typescript
|
|
184
|
+
// Strategy pattern via composition
|
|
185
|
+
interface PricingStrategy {
|
|
186
|
+
calculate(items: Item[]): number
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const standardPricing: PricingStrategy = {
|
|
190
|
+
calculate: (items) => items.reduce((sum, i) => sum + i.price, 0)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const discountPricing: PricingStrategy = {
|
|
194
|
+
calculate: (items) => items.reduce((sum, i) => sum + i.price * 0.9, 0)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// No inheritance needed — strategies are interchangeable values
|
|
198
|
+
function checkout(items: Item[], pricing: PricingStrategy) {
|
|
199
|
+
return pricing.calculate(items)
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### When Inheritance Is Appropriate
|
|
204
|
+
|
|
205
|
+
- **Framework requirements:** When a framework requires extending a base class (e.g., React class components, some ORM patterns). Use inheritance because the framework demands it, not by choice.
|
|
206
|
+
- **True "is-a" relationships with shared state:** When the parent class manages state that all subclasses genuinely need, and subclasses extend rather than override behavior.
|
|
207
|
+
- **Sealed hierarchies:** When the set of subclasses is closed and known (e.g., AST node types, event types). Inheritance + exhaustive pattern matching is a valid design.
|
|
208
|
+
|
|
209
|
+
### Severity Guide
|
|
210
|
+
|
|
211
|
+
| Severity | When to assign |
|
|
212
|
+
|----------|---------------|
|
|
213
|
+
| **HIGH** | Class hierarchy 3+ levels deep where subclasses override most parent behavior. The hierarchy is fighting against itself. |
|
|
214
|
+
| **MEDIUM** | Abstract base class with one implementation, or base class used primarily as a bag of utility methods. Composition would be simpler. |
|
|
215
|
+
| **LOW** | Shallow inheritance (1 level) that works but could be expressed as composition. Not causing active problems. |
|
|
216
|
+
|
|
217
|
+
### Detection Heuristics
|
|
218
|
+
|
|
219
|
+
- Count `extends` depth — flag hierarchies deeper than 2 levels
|
|
220
|
+
- Look for abstract classes with exactly one concrete subclass
|
|
221
|
+
- Search for `super.` calls in methods that also override the parent method — sign of fragile coupling
|
|
222
|
+
- Check if subclasses use <50% of inherited methods — the inheritance may be for convenience, not design
|
|
223
|
+
- Look for classes that extend a base class and immediately override the constructor to do something unrelated
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Parameter Discipline
|
|
228
|
+
|
|
229
|
+
### Long Parameter Lists
|
|
230
|
+
|
|
231
|
+
Functions with many parameters are hard to call correctly. The caller must remember the order, and boolean flags in the middle of a parameter list are especially error-prone.
|
|
232
|
+
|
|
233
|
+
**Violation signals:**
|
|
234
|
+
- Functions with more than 4 parameters
|
|
235
|
+
- Boolean parameters that aren't self-documenting at the call site
|
|
236
|
+
- Parameters that are always passed together (should be grouped into an object)
|
|
237
|
+
|
|
238
|
+
**Refactoring approaches:**
|
|
239
|
+
```typescript
|
|
240
|
+
// Before: positional parameters, unclear at call site
|
|
241
|
+
function createUser(name: string, email: string, isAdmin: boolean,
|
|
242
|
+
sendWelcome: boolean, teamId: string | null) { ... }
|
|
243
|
+
|
|
244
|
+
createUser('Alice', 'a@b.com', true, false, 'team-1') // What do true, false mean?
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// After: options object, self-documenting
|
|
249
|
+
interface CreateUserOptions {
|
|
250
|
+
name: string
|
|
251
|
+
email: string
|
|
252
|
+
isAdmin?: boolean
|
|
253
|
+
sendWelcome?: boolean
|
|
254
|
+
teamId?: string
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function createUser(options: CreateUserOptions) { ... }
|
|
258
|
+
|
|
259
|
+
createUser({ name: 'Alice', email: 'a@b.com', isAdmin: true, teamId: 'team-1' })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Boolean Parameters
|
|
263
|
+
|
|
264
|
+
Boolean parameters are a special case of poor readability. At the call site, `true` and `false` carry no meaning without reading the function signature.
|
|
265
|
+
|
|
266
|
+
**Alternatives:**
|
|
267
|
+
- Use an options object (as above)
|
|
268
|
+
- Use separate functions: `enableFeature()` / `disableFeature()` instead of `setFeature(enabled: boolean)`
|
|
269
|
+
- Use string literals or enums: `format('compact')` instead of `format(true)`
|
|
270
|
+
|
|
271
|
+
### Severity Guide
|
|
272
|
+
|
|
273
|
+
| Severity | When to assign |
|
|
274
|
+
|----------|---------------|
|
|
275
|
+
| **HIGH** | Function with 6+ parameters, especially if multiple are booleans. Call sites are unreadable. |
|
|
276
|
+
| **MEDIUM** | Function with 4-5 parameters where grouping into an object would improve clarity. |
|
|
277
|
+
| **LOW** | Function with a boolean parameter that could be more expressive but is only called in 1-2 places. |
|
|
@@ -36,10 +36,54 @@ Reference guide for reducing code complexity, identifying vestigial patterns, an
|
|
|
36
36
|
### Reducing Conditional Complexity
|
|
37
37
|
|
|
38
38
|
- **Collapse nested conditionals:** Replace `if (a) { if (b) { ... } }` with `if (a && b) { ... }` when the nesting adds no clarity
|
|
39
|
-
- **Use early returns:** Convert deep nesting into guard clauses that return/throw early
|
|
40
39
|
- **Replace flag variables:** When a boolean flag is set and then checked once, inline the condition
|
|
41
40
|
- **Simplify boolean expressions:** `if (x === true)` becomes `if (x)`; `if (!x === false)` becomes `if (x)`
|
|
42
41
|
|
|
42
|
+
#### Early Returns and Guard Clauses
|
|
43
|
+
|
|
44
|
+
Convert deep nesting into guard clauses that return or throw early. This is one of the highest-impact readability improvements available.
|
|
45
|
+
|
|
46
|
+
**The principle:** Handle preconditions and edge cases at the top of the function, then let the main logic flow at the lowest indentation level.
|
|
47
|
+
|
|
48
|
+
**Before — nested conditionals:**
|
|
49
|
+
```typescript
|
|
50
|
+
function getDiscount(customer: Customer): number {
|
|
51
|
+
if (customer) {
|
|
52
|
+
if (customer.isActive) {
|
|
53
|
+
if (customer.orders > 10) {
|
|
54
|
+
return 0.15
|
|
55
|
+
} else {
|
|
56
|
+
return 0.05
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
throw new Error('Customer required')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**After — guard clauses:**
|
|
68
|
+
```typescript
|
|
69
|
+
function getDiscount(customer: Customer): number {
|
|
70
|
+
if (!customer) throw new Error('Customer required')
|
|
71
|
+
if (!customer.isActive) return 0
|
|
72
|
+
|
|
73
|
+
return customer.orders > 10 ? 0.15 : 0.05
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Patterns to look for:**
|
|
78
|
+
- `if (x) { ... long block ... } else { return y }` — invert: `if (!x) return y; ... long block ...`
|
|
79
|
+
- `if (x) { if (y) { if (z) { ... } } }` — flatten: `if (!x) return; if (!y) return; if (!z) return; ...`
|
|
80
|
+
- `else` after a `return` or `throw` — the `else` is unnecessary; remove it and dedent
|
|
81
|
+
|
|
82
|
+
**When not to flatten:**
|
|
83
|
+
- Both branches have substantial logic (not just returns) — `if/else` is clearer
|
|
84
|
+
- The nesting represents resource scoping (e.g., transaction boundaries)
|
|
85
|
+
- The conditions are not preconditions but genuinely branching logic paths
|
|
86
|
+
|
|
43
87
|
## Vestigial Pattern Identification
|
|
44
88
|
|
|
45
89
|
### Code Archaeology Approach
|