Skip to main content
ClaudeWave
Skill1.3k estrellas del repoactualizado 2d ago

working-with-legacy-code

# ClaudeWave: working-with-legacy-code This Claude Code skill provides a structured approach for safely modifying untested codebases using Michael C. Feathers' legacy code techniques. Use it when working with code lacking tests, getting classes under test despite constructor or singleton barriers, or planning incremental test coverage through characterization tests, seams, and dependency-breaking methods before making changes.

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/wondelai/skills /tmp/working-with-legacy-code && cp -r /tmp/working-with-legacy-code/working-with-legacy-code ~/.claude/skills/working-with-legacy-code
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

# Working Effectively with Legacy Code

A field manual for changing code that has no tests, distilled from Michael C. Feathers' *Working Effectively with Legacy Code*. Use it to get untestable classes into a harness, pin down current behavior with characterization tests, and make changes one safe, verifiable step at a time — without resorting to a rewrite.

## Core Principle

**Legacy code is simply code without tests.** Not old code, not ugly code — untested code: without tests you cannot know whether a change preserves behavior, so every edit is a gamble. The craft is breaking dependencies just enough to get tests in place before changing anything — cover and modify, never edit and pray.

## Scoring

**Goal: 10/10.** Rate changes to untested code 0-10 against the principles below. Report the current score and the specific steps needed to reach 10/10.

- **9-10:** Change points covered by characterization tests before any edit; behavior changes and refactoring shipped as separate verified steps; dependencies broken with the least invasive technique
- **7-8:** Tests at most change points, but occasional mixed refactor-plus-behavior commits or heavier dependency surgery than needed
- **5-6:** Some characterization tests, yet key paths still changed on faith; sprouted code accumulating with no payback plan
- **3-4:** Edit-and-pray with manual verification; tests written after the change, asserting whatever the new code happens to do
- **0-2:** Untested edits straight into tangled code, refactoring and behavior change mixed in one commit, rewrite proposed instead of tests

## Framework

### 1. The Legacy Code Dilemma and Change Algorithm

**Core concept:** The dilemma: to change code safely we need tests, but to get tests in place we have to change code. The way out is a fixed sequence — identify change points, find test points, break dependencies, write tests, then make changes and refactor — where the pre-test edits are conservative and mechanical, and the real change happens only inside the safety net.

**Why it works:** Edit-and-pray substitutes care for feedback, and care doesn't scale to code you don't fully understand. Cover-and-modify clamps existing behavior in a vise of tests, so any unintended change announces itself immediately on your machine instead of later in production.

**Key insights:**
- There are two reasons to change code — changing behavior (feature, bug fix) and improving structure (refactoring) — and mixing them in one step makes failures undiagnosable
- Test points are rarely the change points: effects propagate, so you often test where the change's effects surface, not where the edit happens
- Dependency-breaking edits made before tests exist must preserve signatures exactly and lean on the compiler to find every affected site
- Coverage grows along the paths you actually change — that beats any dedicated "testing project" that never gets funded
- "Programming is the art of doing one thing at a time": each step of the algorithm is separately verifiable

**Applications:**

| Context | Application | Example |
|---------|-------------|---------|
| Bug fix in an untested module | Run the five steps before touching the bug | Pin `parseInvoice()` with tests, then fix the rounding error |
| PR mixing cleanup and a feature | Split into structure-only and behavior-only commits | Extract and rename first, tests green, then add the discount rule |
| "It's just a one-line change" | Find the nearest test point first | One pin test at the public method that calls the private one you edit |

See: [references/change-algorithm.md](references/change-algorithm.md)

### 2. Seams: Where to Pry Code Apart

**Core concept:** A seam is a place where you can alter behavior in your program without editing in that place. Every seam has an enabling point — where you decide which behavior runs. Getting legacy code under test is largely a hunt for seams: spots where a test can substitute a slow, global, or external dependency while the production source stays untouched.

**Why it works:** If you must edit code to test it, you risk changing the very behavior you are trying to pin down. Seams move the substitution to a distance — a subclass, an import, a build flag — so the code under test runs exactly as in production while the test controls its dependencies from the enabling point.

**Key insights:**
- Object seams are the default in OO code: every overridable call is a seam, and its enabling point is wherever the object is created or passed in
- Link and import seams swap implementations at build or load time — `jest.mock` and `unittest.mock.patch` are link seams in modern clothing
- Preprocessing seams (C/C++ macros) are the bluntest instrument; reach for them last
- `new Database()` inside a method body is a seam that never got built — constructors doing real work, globals, statics, and hard-wired I/O are where seams die
- A seam without a reachable enabling point is useless: if the test can't make the decision, keep hunting
- Dynamic languages make nearly every name lookup a seam — cheap, but patching internals couples tests to file layout

**Applications:**

| Context | Application | Example |
|---------|-------------|---------|
| Class constructs its own DB client | Object seam via constructor parameter | `constructor(db: Db = new ProdDb())` — tests pass a fake |
| Module calls a top-level `send_email()` | Import/link seam | `mocker.patch("billing.send_email")` or `jest.mock("./mailer")` |
| Logic reads the wall clock directly | Seam at the clock | Inject a `now()` provider; tests freeze time |

See: [references/seams.md](references/seams.md)

### 3. Characterization Tests

**Core concept:** A characterization test documents what the code actually does right now — not what the spec, the comments, or anyone's memory says it should do. Write a probe you know will fail, let the failure message reveal the real behavior, then change the assertion to pin that behavior in place.

**Why it work
37signals-waySkill

Build lean, opinionated products using the 37signals philosophy from Getting Real, Rework, and Shape Up. Use when the user mentions "Getting Real", "Rework", "Shape Up", "37signals", "Basecamp method", "six-week cycles", "fixed time variable scope", "appetite vs estimates", "betting table", "breadboarding", "fat marker sketch", "build less", "underdo the competition", or "opinionated software". Also trigger when cutting scope to ship faster, running small teams, avoiding long-term roadmaps, or eliminating meetings. Covers shaping, betting, building, and the art of saying no. For MVP validation, see lean-startup. For design sprints, see design-sprint.

blue-ocean-strategySkill

Create uncontested market space using value innovation instead of competing head-to-head. Use when the user mentions "blue ocean", "red ocean", "strategy canvas", "ERRC framework", "value innovation", "non-customers", "buyer utility map", "eliminate-reduce-raise-create", or "uncontested market". Also trigger when comparing pricing strategies, exploring new market categories, finding underserved customer segments, or asking how to stop competing on price. Covers the Four Actions Framework, buyer utility map, and value-cost trade-offs. For tech adoption strategy, see crossing-the-chasm. For product positioning, see obviously-awesome.

clean-architectureSkill

Structure software around the Dependency Rule: source code dependencies point inward from frameworks to use cases to entities. Use when the user mentions "architecture layers", "dependency rule", "ports and adapters", "hexagonal architecture", "use case boundary", "onion architecture", "screaming architecture", or "framework independence". Also trigger when decoupling business logic from databases or frameworks, defining module boundaries, or debating where to put business rules. Covers component principles, boundaries, and SOLID. For code quality, see clean-code. For domain modeling, see domain-driven-design.

clean-codeSkill

Write readable, maintainable code through disciplined naming, small functions, and clean error handling. Use when the user mentions "code review", "naming conventions", "function too long", "code smells", "readable code", "boy scout rule", "single responsibility", or "unit test quality". Also trigger when reviewing pull requests for readability, refactoring messy functions, debating comment styles, or improving error handling patterns. Covers SRP, comment discipline, formatting, and unit testing. For refactoring techniques, see refactoring-patterns. For architecture, see clean-architecture.

contagiousSkill

Engineer word-of-mouth and virality using the STEPPS framework (Social Currency, Triggers, Emotion, Public, Practical Value, Stories). Use when the user mentions "go viral", "word of mouth", "shareable content", "social currency", "why people share", "viral loop", "referral program", or "organic growth". Also trigger when designing shareable features, crafting social media campaigns, or building products that spread through peer recommendation. Covers environmental triggers and high-arousal emotional content. For sticky messaging, see made-to-stick. For persuasion tactics, see influence-psychology.

continuous-discoverySkill

Build a weekly cadence of customer touchpoints using Opportunity Solution Trees, assumption mapping, and interview snapshots. Use when the user mentions "continuous discovery", "opportunity solution tree", "weekly interviews", "assumption testing", "discovery habits", "product trio", or "outcome-based roadmap". Also trigger when setting up regular customer feedback loops, prioritizing which experiments to run, or connecting discovery insights to delivery work. Covers experience mapping, co-creation, and prioritizing opportunities. For interview technique, see mom-test. For team structure, see inspired-product.

cro-methodologySkill

Audit websites and landing pages for conversion issues and design evidence-based A/B tests. Use when the user mentions "landing page isnt converting", "conversion rate", "A/B test", "why visitors leave", "objection handling", "bounce rate", "split testing", or "conversion funnel". Also trigger when diagnosing why signups are low, designing experiment hypotheses, or auditing checkout flows for friction points. Covers funnel mapping, persuasion assets, and objection/counter-objection frameworks. For overall marketing strategy, see one-page-marketing. For usability issues, see ux-heuristics.

crossing-the-chasmSkill

Navigate the technology adoption lifecycle from early adopters to mainstream market. Use when the user mentions "crossing the chasm", "beachhead segment", "whole product", "early adopters vs. mainstream", "tech go-to-market", "bowling pin strategy", "technology adoption lifecycle", or "pragmatist buyers". Also trigger when a startup has early traction but struggles to grow beyond initial users, or when planning go-to-market for technical products. Covers D-Day analogy, bowling-pin strategy, and positioning against incumbents. For product positioning, see obviously-awesome. For new market creation, see blue-ocean-strategy.