🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Safe Legacy Editing Discipline

by @quochungto

Apply 4 editing disciplines when modifying legacy code: Hyperaware Editing, Single-Goal Editing, Preserve Signatures, Lean on the Compiler. Use whenever a de...

Versionv1.0.0
⚑ When to Use
TriggerAction
Apply this skill when any of the following are true:
- You are about to make an edit and there are no automated tests covering the code
- You are mid-dependency-breaking and tempted to also "clean up" something you notice
- You catch yourself reasoning: "I'll just tweak it here β€” it's a small change"
- Your team has stopped touching code because "if it's not broke, don't fix it"
- You recognize the Edit-and-Pray pattern in your own recent work
**Relationship to the Legacy Code Change Algorithm:** These disciplines activate at Step 3 (break dependencies) and continue through Step 5 (make the change). They are the behavioral layer underneath the algorithm β€” the how of editing when the safety net is thin.
πŸ’‘ Examples

(a) Java developer about to add a parameter to a legacy method with no tests

Situation: A Java developer needs to add a locale parameter to a 200-line method InvoiceFormatter.format(Invoice invoice) that has no tests.

Step 1 (context): Untested-bootstrap. There are no tests. This is initial dependency-breaking work.

Step 2 (anti-pattern): Edit-and-Pray risk β€” the developer plans to add the parameter and then "test it manually by running the UI." Name the pattern and flag it.

Step 3 (Hyperaware): Adding a new parameter is behavior-changing. Every call site that currently compiles with one argument will break. Every place that constructs the string output may now differ. High-risk edits β€” note them explicitly.

Step 4 (Single-Goal): Write: "Goal: add Locale locale parameter to format() and thread it to the one callsite that needs it." While reading the method, the developer notices three other parameters that "should really be an object." Write them on the deferred list. Do not act on them now.

Step 5 (Preserve Signatures): Copy Invoice invoice from the current signature. Type public String format( then paste, add , Locale locale). Do not retype. Do not reorder. Do not rename invoice to inv.

Step 6 (Lean on the Compiler): The compiler error list after adding the parameter reveals 7 call sites, not the 2 the developer thought existed. Update all 7 before checking inheritance (no superclass has a format() method β€” confirmed via Grep).

Result: The parameter is threaded through all 7 call sites. No signature mutations were introduced. 5 deferred cleanup items are on paper. The code compiles. The developer can now write a characterization test.


(b) Team stuck in Minimization Freeze rationalization

Situation: A tech lead says: "Our core billing class has 800 lines. We can't add tests without refactoring, but we don't want to refactor because something might break. The policy is: if it's not broke, don't fix it."

Step 2 (anti-pattern): This is Minimization Freeze combined with Legacy Code Dilemma. Name both.

The inversion to surface: "It involves less editing, and it's safer" is backwards. Every future change to the 800-line class is made without any safety net. The cumulative risk grows with every undisciplined edit. True safety requires more edits (to create seams), not fewer.

The path out of Legacy Code Dilemma: Conservative dependency-breaking techniques (from Chapter 25) are designed specifically to be safe enough to apply without tests. They are not general refactoring β€” they are a bootstrap protocol. Apply one conservative technique (e.g., Parameterize Constructor), confirm it compiles, write the first test against the newly isolated code. The circularity is broken at the first test.

Single-Goal application: The first goal is not "clean up the billing class." It is: "break one dependency so we can write one test." Write that as the goal. Everything else is deferred.


(c) C++ developer encapsulating globals via Lean on the Compiler

Situation: A C++ developer wants to encapsulate two global variables (domestic_exchange_rate, foreign_exchange_rate) into a class so they can be substituted in tests.

Step 1 (context): Untested-bootstrap. Encapsulating globals IS a dependency-breaking technique. No tests yet.

Step 5 (Preserve Signatures): The globals are variables, not methods. The principle still applies: the new class fields should have identical names (domestic_exchange_rate, foreign_exchange_rate), not renamed versions.

Step 6 (Lean on the Compiler): 1. Comment out both global variable declarations 2. Compile β€” 34 errors across 9 files 3. Navigate to each error; prefix the reference with exchange. 4. Re-compile β€” 0 errors

Inheritance check: These are global variables, not class methods. Inheritance masking does not apply. The compiler's 34 errors are the complete set of usages.

Result: All 34 usages updated mechanically. No manual search required. The change is ready for a characterization test of the exchange rate behavior.

View on ClawHub
TERMINAL
clawhub install bookforge-safe-legacy-editing-discipline

πŸ§ͺ Use this skill with your agent

Most visitors already have an agent. Pick your environment, install or copy the workflow, then run the smoke-test prompt above.

πŸ” Can't find the right skill?

Search 60,000+ AI agent skills β€” free, no login needed.

Search Skills β†’