simplification-pass
verified636bdff0-812e-43f9-a21d-8bad83a7dbe0
Use when reducing codebase complexity — find dead code with vulture/ruff, collapse single-implementation interfaces, inline premature indirection. The 'could the next reader delete this?' test.
Metadata
Skill file
# Simplification Pass
**Use when** a codebase has accumulated dead code, speculative generality, and indirection that makes it hard to understand. The goal: delete what doesn't earn its keep.
## Dead-Code Detection
### Automated tools
```bash
# Python: vulture finds unused functions, imports, variables
pip install vulture
vulture src/ --min-confidence 80
# Example output:
# src/orders/utils.py:15: unused function 'legacy_calculate'
# src/orders/models.py:42: unused import 'datetime'
# src/orders/service.py:88: unused variable 'old_total'
# Ruff finds unused imports and variables
ruff check src/ --select F401,F841
# JavaScript/TypeScript
npx tsc --noUnusedLocals --noUnusedParameters
npx eslint src/ --rule 'no-unused-vars: error'
```
### IDE signals
Greyed-out code (VS Code, PyCharm) = unused. Pay attention to it — it's free dead-code detection.
### Manual verification (before deleting anything)
```bash
# vulture says unused — verify with grep for callers
grep -rn "legacy_calculate" src/ tests/
# If no hits outside the definition -> truly dead -> delete
```
## Removing Speculative Generality
Speculative generality = abstractions built for futures that never arrived.
```python
# SPECULATIVE GENERALITY: an interface with ONE implementation
class PaymentProcessor(Protocol):
def process(self, amount): ...
class StripeProcessor(PaymentProcessor): # the ONLY implementation
def process(self, amount):
return stripe.charge(amount)
# The Protocol adds indirection with no benefit.
# COLLAPSE IT: use StripeProcessor directly, or keep the class but drop the Protocol.
# AFTER:
def process_payment(amount):
return stripe.charge(amount) # direct, obvious
```
### The "one implementation" test
```text
If an interface/abstract class has exactly ONE implementation,
and no test uses a fake for it, the abstraction is speculative.
Collapse it until a second real implementation arrives (YAGNI).
```
## Inlining Premature Indirection
```python
# PREMATURE INDIRECTION: a function that just wraps another with no added value
def get_user_name(user_id):
return fetch_user(user_id).name # one-liner wrapper
# The wrapper adds a name to remember without adding meaning.
# INLINE it:
name = fetch_user(user_id).name
```
### When is indirection NOT premature?
| Keep the indirection if... | Collapse it if... |
|---------------------------|-------------------|
| 2+ call sites that would duplicate | 1 call site |
| It hides a changing dependency | It wraps a stable function |
| It encodes a real business concept | It's a one-line passthrough |
| A test mocks it | No test references it |
## The "Could the next reader delete this?" Test
For every abstraction (class, function, interface, config), ask:
> If the next engineer deleted this, would they have to understand it first?
- **YES** → it's carrying meaning, keep it
- **NO** → it's ceremony, delete it
```python
# FAILS the test (deletable without understanding):
def calculate(a, b):
return a + b # just a rename of +
# PASSES the test (carries meaning):
def calculate_order_total(items):
"""Sum prices with tax and shipping."""
...
```
## Simplification Checklist (run top-down)
```text
1. Dead imports -> ruff --select F401
2. Dead functions -> vulture --min-confidence 80
3. Dead params -> check each function's args are all used
4. Single-impl interfaces -> collapse
5. One-line wrappers -> inline
6. Over-decomposed modules -> merge files under 200 lines that belong together
7. Unused config/feature flags -> grep for the flag name
8. Commented-out code -> delete (git history has it)
```
## Guardrails
- **Verify before deleting.** vulture and IDE greying are signals, not proof. `grep` for callers first.
- Delete in a SEPARATE commit (`refactor: remove dead code`), never mixed with features.
- Don't delete code that's a public API or referenced by other services/repos.
- Don't simplify code you don't have tests for — add a characterization test first (see working-with-legacy-code).
- Over-simplifying real requirements is worse than complexity. If it's complex because the DOMAIN is complex, leave it.
## Pitfalls
| Pitfall | Fix |
|---------|-----|
| Deleting "unused" code that's imported via `__init__` re-export or string eval | Grep for the symbol name + check `__all__` |
| Deleting code referenced in a migration or serialized data | Check for string references and pickle/db fixtures |
| Over-simplifying a real requirement | If a test needs the complexity, keep it |
| Mixing simplification with features | Separate commits: `refactor:` vs `feat:` |
| Deleting commented-out code "to be safe" | Fine — but commit it separately so it's findable |
## Verify / Checklist
- [ ] vulture + ruff run, output reviewed
- [ ] Each "dead" item grep-verified (no callers in src/ or tests/)
- [ ] Single-implementation interfaces collapsed or justified
- [ ] One-line passthrough wrappers inlined where no value added
- [ ] Tests still pass after each deletion (`pytest tests/ -x`)
- [ ] Simplification in its own `refactor:` commit, separate from features
Attached files
No attached files.