| name | legacy-code-expert |
| description | Safely modify legacy code lacking tests. Identifies seams, breaks dependencies, plans characterization tests. Invoke before verification-protocol when touching untested code with tangled dependencies. |
| allowed-tools | ["Read","Grep","Glob","Bash","Edit","Write","AskUserQuestion"] |
ABOUTME: Feathers' dependency-breaking techniques for safely modifying untested legacy code
ABOUTME: Seam identification, change point analysis, characterization test planning
Legacy Code Expert
When to invoke: before verification-protocol (TDD), when the target code lacks tests and has dependencies that block testing. This skill produces the analysis; verification-protocol drives the test-first cycle that follows.
Workflow
- Identify change point (where the modification goes)
- Map dependencies blocking testability
- Find seams (places to alter behavior without editing production code)
- Select techniques (simplest that works, minimal ripple)
- Plan characterization tests (capture current behavior)
- Define transformation steps (small, reversible)
- Hand off to verification-protocol for TDD execution
Seam Types
| Seam | Mechanism | Best For | Example |
|---|
| Object | Polymorphism, interfaces | OOP languages (Java, C#, Go, Ruby) | Extract interface, subclass and override |
| Preprocessing | Macros, conditional compilation | C/C++ | #ifdef TESTING to swap implementations |
| Link | Linker substitution, module replacement | C/C++, dynamic languages | Replace .o file, monkey-patch module |
Seam identification questions:
- Can I substitute this dependency via a constructor or method parameter?
- Is there a virtual/overridable method I can intercept?
- Can I extract an interface the caller depends on?
- Does the language support module-level replacement (mocks, monkey-patching)?
Dependency-Breaking Techniques
Organized by primary seam type. Prefer techniques higher in each list (simpler, less invasive).
Object Seams
| Technique | When to Use |
|---|
| Extract Interface | Class has concrete dependency; create interface for substitution |
| Parameterize Constructor | Dependency created internally; pass it in instead |
| Parameterize Method | Single method needs a dependency; pass as argument |
| Extract and Override Call | Isolate one problematic call into a virtual method |
| Extract and Override Factory Method | Object creation blocks testing; make it overridable |
| Extract and Override Getter | Field access blocks testing; route through virtual getter |
| Subclass and Override Method | Create test subclass that overrides problematic methods |
| Extract Implementer | Move implementation to new class, keep interface in original |
| Break Out Method Object | Large method with many dependencies; extract to own class |
| Adapt Parameter | Wrap parameter type to break dependency on problematic type |
| Pull Up Feature | Move method to superclass to enable override in subclass |
| Push Down Dependency | Move dependency to subclass, keep parent testable |
| Introduce Instance Delegator | Instance method delegates to static (enables override) |
Preprocessing/Text Seams
| Technique | When to Use |
|---|
| Template Redefinition | Use generics/templates to inject test dependencies |
| Text Redefinition | Use macros to redefine dependencies at compile time |
| Definition Completion | Provide test implementation for incomplete type |
Link/Global Seams
| Technique | When to Use |
|---|
| Encapsulate Global References | Wrap globals in a class for controlled access |
| Replace Global Reference with Getter | Access global through virtual getter |
| Introduce Static Setter | Add setter for singleton/global (test-only) |
| Replace Function with Function Pointer | Use function pointers for runtime substitution |
| Link Substitution | Replace at link time (C/C++ only) |
| Supersede Instance Variable | Add setter to replace dependency post-construction |
| Primitivize Parameter | Replace object params with primitives to cut dependency |
Output Format
Produce this analysis, then hand off to verification-protocol:
## Legacy Code Analysis: [File/Class]
### Change Point
[Where the modification goes and what behavior changes]
### Dependencies Blocking Testing
- [dependency]: [why it blocks tests]
### Identified Seams
| Seam Type | Location | Exploitation Strategy |
|-----------|----------|-----------------------|
| [type] | [where] | [how to use it] |
### Recommended Techniques
1. **[Technique]**: [one-line rationale]
- Steps: [ordered list of mechanical changes]
- Risk: [what could go wrong]
### Characterization Tests
[Tests to lock current behavior BEFORE any changes]
- [test]: calls X with Y, expects Z (current behavior, not ideal)
### Transformation Steps
1. [small, reversible step]
2. [next step]
...
N. Hand off to verification-protocol: write failing test for new behavior
Guidelines
- Safety over elegance: smaller, safer changes win
- Characterization tests capture ACTUAL behavior (bugs included), not ideal behavior
- Goal: get tests in place, not perfect design
- Some debt is acceptable if it enables testing
- When full rewrite is cheaper than incremental change, say so (R4 deviation)
- Document reasoning for future maintainers