From code-foundations
Safely modifies untested legacy code by getting it under test first using characterization tests, seam identification, sprout/wrap techniques, and pinch-point testing before making production changes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/code-foundations:welc-legacy-codeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> "Legacy code is simply code without tests." - Michael Feathers
"Legacy code is simply code without tests." - Michael Feathers
Get code under test before changing it. This is the core principle.
| Symptom | Solution |
|---|---|
| Can't get class into test harness | Decision Tree: Can't Instantiate |
| Can't run method in test harness | Decision Tree: Can't Call Method |
| Don't know what tests to write | Characterization Tests |
| Under time pressure, need to change now | Sprout/Wrap Techniques |
| Need to change many classes in one area | Pinch Point Testing |
| Class is too big / monster method | Effect Sketching → find clusters |
| Afraid I'll break something | Single-Goal Editing, Preserve Signatures |
Use this for every change to untested code.
1. IDENTIFY change points
- Where in the code do you need to make modifications?
2. FIND test points
- Where can you write tests to cover the change?
- Look for pinch points where tests cover multiple changes
3. BREAK dependencies
- Remove obstacles that prevent testing
- Use dependency-breaking techniques (catalog below)
4. WRITE characterization tests
- Document what the code DOES, not what it SHOULD do
- Process: write assertion you know will fail → let failure
tell you actual behavior → change test to match
5. MAKE changes and refactor
- Implement the change with test safety net
When you can't test everything but need to change code now:
These keep new code separate and testable even when old code isn't.
When you don't know what tests to write:
You're documenting what the code does, not what it should do. This is the test safety net for refactoring.
When you need to change many classes in one area:
| Technique | When to Use |
|---|---|
| Adapt Parameter | Parameter class hard to fake |
| Break Out Method Object | Long method with local state |
| Encapsulate Global References | Global variables block testing |
| Expose Static Method | Method uses no instance state |
| Extract and Override Call | Single problematic call |
| Extract and Override Factory Method | Constructor creates dependencies |
| Extract and Override Getter | Instance variable holds problem |
| Extract Implementer | Turn class into interface |
| Extract Interface | Need to fake a class |
| Introduce Instance Delegator | Static method blocks testing |
| Introduce Static Setter | Singleton blocks testing |
| Parameterize Constructor | Constructor creates its dependencies |
| Parameterize Method | Method obtains dependency internally |
| Primitivize Parameter | Parameter hard to create, only data needed |
| Pull Up Feature | Test feature in isolation |
| Push Down Dependency | Separate testable logic from dependencies |
| Replace Global Reference with Getter | Global variable access |
| Subclass and Override Method | Method behavior blocks testing |
| Supersede Instance Variable | Replace after construction |
Is the problem in the constructor?
├── YES: Does constructor CREATE objects?
│ ├── YES: Parameterize Constructor or Extract and Override Factory Method
│ └── NO: Does constructor USE passed objects?
│ ├── YES: Extract Interface on parameter, pass fake
│ └── NO: Hidden dependency — find the `new` and externalize it
└── NO: Is it a singleton/global?
├── YES: Introduce Static Setter
└── NO: Include/import dependency chain
└── Extract Interface or Extract Implementer
Is the method private?
├── YES: Make protected, create testing subclass
└── NO: Is a parameter hard to create?
├── YES: Is parameter sealed/final?
│ ├── YES: Adapt Parameter
│ └── NO: Extract Interface from parameter
└── NO: Does method have invisible side effects?
└── YES: Extract side effects to separate methods, override in test
Can you test the new code in isolation?
├── YES: Sprout Method or Sprout Class
└── NO: Does new behavior go before/after existing?
├── YES: Wrap Method
└── NO: Can you not modify the class at all?
└── YES: Wrap Class (Decorator)
| After | Next |
|---|---|
| Code under test | Skill(code-foundations:cc-refactoring-guidance) (safe refactoring process) |
| Dependencies broken | Continue with Legacy Code Change Algorithm step 4-5 |
npx claudepluginhub ryanthedev/code-foundationsApplies Feathers' legacy code change algorithm: break dependencies, write characterization tests, then safely modify untested codebases. Use for untestable classes, tangled modules, or code you're afraid to change.
Guides safe refactoring of untested legacy code using RGR workflow and characterization tests. Use when modifying code without test coverage.
Modernizes legacy codebases via characterization tests, golden master testing, incremental refactoring, dependency upgrades, and dead code removal. Activates on tech debt, untested code, or deprecated deps.