Token导航 LogoToken导航TokenDH.com
研究检索external-serviceclawhub未标认证来源可访问clear审计通过

bookforge-legacy-code-addition-techniquesBookforge 遗留代码添加技术

Agent Skill

bookforge-legacy-code-addition-techniques 用于查找、检索和筛选相关信息,适合在 OpenClaw 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

10,942

周安装

466

GitHub Stars

公开资料未说明

下载量

3,444
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:bookforge-legacy-code-addition-techniques(Bookforge 遗留代码添加技术)
来源仓库:https://github.com/quochungto/bookforge-legacy-code-addition-techniques
安装命令:
openclaw skills install bookforge-legacy-code-addition-techniques
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install bookforge-legacy-code-addition-techniques

简介

向未测试遗留代码安全添加新功能的技术指南。

  • 适合维护老系统的开发人员规避破坏风险。
  • 推荐 Sprout 或 Wrap 类/方法隔离变更影响。
  • 必须先行创建回归测试保障原有逻辑稳定。
  • 优先选择侵入性最小的方案逐步重构。bookforge-legacy-code-addition-techniques 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

name
legacy-code-addition-techniques
description
Add new functionality to untested legacy code using Sprout Method, Sprout Class, Wrap Method, or Wrap Class — whichever best fits the dependency profile. Use whenever a developer needs to add a feature, log statement, validation, or any new behavior to legacy code that they can't easily test — 'I have to add this feature fast', 'no time for a big refactor', 'just need to log this', 'add a check to existing method', 'need to add behavior without breaking legacy', 'sprout method', 'sprout class', 'wrap method', 'wrap class', 'decorator for legacy'. Activates for 'quick change to legacy', 'under time pressure', 'can't test this class but need to add a feature', 'extend without editing'.
version
1.0.0
homepage
https://github.com/bookforge-ai/bookforge-skills/tree/main/books/working-effectively-with-legacy-code/skills/legacy-code-addition-techniques
metadata
{"openclaw":{"emoji":"📚","homepage":"https://github.com/bookforge-ai/bookforge-skills"}}
status
draft
source-books
title
Working Effectively with Legacy Code
authors
["Michael C. Feathers"]
chapters
[6]
domain
software-engineering
tags
[legacy-code, refactoring, testing, software-engineering, feature-addition]
depends-on
execution
tier
1
mode
full
inputs
description
Existing source code + description of the new behavior to add
tools-required
[Read, Edit, Bash]
tools-optional
[Grep]
mcps-required
[]
environment
Codebase in an OO language. Test framework configured.
discovery
goal
Add new behavior to legacy code using the least-invasive Sprout or Wrap technique.
tasks
audience
roles
[software-engineer, backend-developer, senior-developer]
experience
intermediate
when_to_use
triggers
prerequisites
why
Sprout/Wrap is Step 5 (make change) when you can't break enough dependencies upfront
why
Preserve Signatures and Single-Goal Editing apply during the wrapping step
not_for
environment
codebase_required
true
codebase_helpful
true
works_offline
true
quality
scores
{with_skill: null, baseline: null, delta: null}
tested_at
null
eval_count
null
assertion_count
13
iterations_needed
null

Legacy Code Addition Techniques (Sprout & Wrap)

When to Use

You need to add new behavior to a legacy codebase — a feature, a logging statement, a validation check, an integration hook — but you cannot get the surrounding code under test right now. The existing method or class is too entangled to test directly.

Any of these conditions apply:

  • You need to add a feature today, and breaking dependencies on the source class would take hours you don't have
  • The source method contains old logic you must not disturb, and the new behavior is conceptually distinct
  • Adding code inline would mingle two separate operations into one method, making future changes harder
  • The source class cannot be instantiated in a test harness at all (constructor dependencies are too severe)
  • New behavior must execute every time the existing method is called (temporal coupling concern)

This skill is Step 5 of legacy-code-change-algorithm. If you haven't identified change points and test points yet, start there. Return here when you've determined that the change must be made without full test coverage of the source class.

Before executing, have:

  1. Target class/method name(s) where the new behavior must be introduced
  2. A one-sentence description of the new behavior
  3. An answer to: "Does this behavior need to execute alongside the existing call, or is it standalone?"
  4. An answer to: "Can the source class be instantiated in a test harness right now?"

Context & Input Gathering

Required

  • Target class and method: Where in the codebase does the change happen?

-> Read the file. Identify the specific method where you'd otherwise add the inline code.

  • New behavior description: What exactly should the new code do?

-> Must be specific enough to write a test for. "Log the payment" or "filter duplicate entries" — not "add some validation."

  • Testability of source class: Can you construct an instance of the source class in a test harness within the time available?

-> Check for: multi-argument constructors with DB connections, HTTP clients, file handles, singletons, or global state. -> Default assumption: source class is not easily testable (else you wouldn't be here).

  • Temporal coupling: Must the new behavior fire every time the existing method is called?

-> YES → the new behavior is temporally coupled → lean toward Wrap -> NO → the new behavior can stand alone → lean toward Sprout

Observable from Codebase

  • Constructor signature of the source class (how hard is instantiation?)
  • Whether the method already returns a value (affects Sprout Method's return handling)
  • Whether the source class implements an interface (makes Wrap Class easier)
  • Language (Java/C#/C++ → all four techniques; dynamic languages → Wrap Method simpler)

Process

Step 1: Classify Scope

ACTION: Determine whether the new behavior is *method-level* (a single new operation added at one call site in one method) or *class-level* (the behavior is logically a new abstraction, or the source class is so heavily coupled that even a sprouted method can't be tested on it).

WHY: Technique selection depends on whether you can get *any* testable unit out of the source class. Method-level scope means you can stay inside the source class. Class-level scope means you need to leave the source class entirely.

Rule of thumb:

  • If you can write new SourceClass(...) in a test harness in under 30 minutes → method-level scope is viable.
  • If constructor dependencies (DB, network, file system, singletons) would take hours to untangle → class-level scope; you must move to a new class.

ARTIFACT: Declare scope in your working notes: scope = method-level or scope = class-level.


Step 2: Classify Temporal Coupling

ACTION: Determine whether the new behavior is *independent* (it happens separately, can be called on its own) or *temporally coupled* (it must co-execute every time the original method is called).

WHY: Temporal coupling is the reason you'd be tempted to add code inline at the bottom of an existing method — "it has to happen at the same time." Wrap techniques explicitly address this by making the co-execution visible and deliberate at the callsite rather than buried inside the method. Sprout techniques assume the new behavior stands alone.

Rule of thumb:

  • "Every call to pay() must also log" → temporally coupled → Wrap
  • "I need to add duplicate-detection as a new step, but it could be called separately" → independent → Sprout

ARTIFACT: Declare coupling in your working notes: coupling = temporally-coupled or coupling = independent.


Step 3: Apply the 2×2 Selector

ACTION: Cross scope and coupling to select the technique:

                    SPROUT              WRAP
                 (independent)     (co-executes)
                ┌─────────────────┬──────────────────┐
 METHOD-LEVEL   │ Sprout Method   │  Wrap Method     │
                ├─────────────────┼──────────────────┤
 CLASS-LEVEL    │ Sprout Class    │  Wrap Class      │
                └─────────────────┴──────────────────┘

Why each quadrant:

TechniqueWhen to preferKey advantageKey disadvantage
Sprout MethodMethod-level + independentClearly separates new code from old; new method is fully testableGives up on getting source method under test; leaves source method in odd state
Sprout ClassClass-level + independentLets you TDD even when source class can't be constructedConceptually fragmenting — new class may seem disconnected
Wrap MethodMethod-level + co-executesMakes temporal coupling explicit; does not grow the original methodMust invent a new name for the original method's logic
Wrap ClassClass-level + co-executesFully separates new behavior from old using the Decorator patternMore structural overhead for simple additions

Additional selection rules:

  • Prefer Wrap Method over Sprout Method when the new feature is as important as the original method's purpose — Wrap makes it a first-class operation visible in the calling interface.
  • Use Wrap Class when the same new behavior needs to apply across many callers uniformly (Decorator pattern), or when the source class has grown so large that any addition makes it worse.
  • Use Sprout Class when you initially tried Sprout Method but couldn't even pass constructor arguments in a test (e.g., the class needs new DatabaseConnection() that you cannot fake quickly).

ARTIFACT: Decision recorded: technique = [Sprout Method | Sprout Class | Wrap Method | Wrap Class].


Step 4: Execute the Chosen Technique

Execute the step-by-step mechanics for your chosen technique. Full reference mechanics for all four are in references/four-techniques-mechanics.md. The most common two cases are inlined below.

Sprout Method (method-level + independent)

  1. Identify the exact location in the source method where the new functionality must happen.
  2. Write (but comment out) a call to a new method that will do the work. Decide its name and arguments now, before writing it. This forces you to think about its interface in context.
  3. Determine which local variables the new method needs from the source method. These become its parameters.
  4. Determine whether the new method must return a value to the source method. If yes, assign its return value to a variable in the call.
  5. Develop the new sprouted method using test-driven development — write tests for the sprouted method in isolation; make them pass.
  6. Uncomment the call in the source method to activate the integration.

WHY each step matters:

  • Step 2 (comment first): writing the call before the method locks in the right interface and avoids overbuilding.
  • Step 5 (TDD the sprout): you may not be able to test the source method, but you can always test the sprouted method because it has no legacy dependencies.
  • Step 6 last: the source method is only modified after the new code is fully tested — minimizing risk in the untested zone.

Wrap Method (method-level + co-executes)

  1. Identify the method whose every call must include the new behavior.
  2. Rename the existing method to something that describes what it actually does (e.g., pay()dispatchPayment()). Apply Preserve Signatures: copy the signature exactly — same parameter types, same return type. Make the renamed method private.
  3. Create a new method with the original name and signature. This is the new public entry point.
  4. In the new method, call both the renamed original method and a new method that you develop using TDD for the new behavior. Order (before or after) depends on the requirement.

WHY each step matters:

  • Step 2 (rename, don't copy): keeps the original logic in one place; renaming rather than duplicating prevents divergence.
  • Step 2 (Preserve Signatures): you are editing without tests; any signature change that breaks callers is a regression you won't catch. Copy-paste the signature verbatim.
  • Step 3 (same original name): all existing callers continue to work — they call pay() and get both behaviors transparently.
  • Step 4 (TDD the new method): the new behavior is tested even though pay() itself cannot be tested in isolation.

For Sprout Class and Wrap Class step-by-step mechanics, see references/four-techniques-mechanics.md.


Step 5: Develop New Code with TDD

ACTION: Regardless of technique chosen, write and pass tests for the new sprouted method, new sprouted class, or new wrapped method *before* integrating.

WHY: The whole point of Sprout/Wrap is to create a seam between tested new code and untested old code. If you skip tests on the new code, you lose the only testing benefit these techniques provide. The surrounding code has no tests — but the new code can and must have tests.

HOW:

  1. Write the simplest test that fails because the new method/class doesn't exist yet.
  2. Implement just enough to make it pass.
  3. Refactor the new code. It is clean code; you can afford to refactor it.
  4. Repeat until the behavior described in your requirement is fully tested.

Step 6: Integrate

ACTION: Activate the new code within the legacy call site.

  • Sprout Method: Uncomment the call in the source method.
  • Sprout Class: Uncomment the object creation and method call in the source method.
  • Wrap Method: The renamed+new structure is already in place from Step 4; verify callers still compile.
  • Wrap Class: Replace object instantiation site(s) with the wrapper class. If source class implements an interface, the wrapper implements the same interface — all callers remain unchanged.

Run the full build and any available tests (even characterization tests for the legacy code, if they exist) to confirm no regressions.


Step 7: Document the Refactoring Debt

ACTION: Add an entry to refactor-backlog.md immediately.

WHY: Sprout and Wrap are *intentionally temporary*. They leave old code in limbo — the source method or class has not been cleaned up, its responsibilities are now split, and the design is arguably worse than a proper refactoring would achieve. Documenting the debt ensures future work on this area includes a plan to get the source class under test and integrate the sprouted/wrapped logic properly.

Entry format:

## [ClassName / method] — Sprout/Wrap debt
- Technique applied: [Sprout Method | Sprout Class | Wrap Method | Wrap Class]
- New code location: [method or class name]
- Source method/class: [name] in [file path]
- What still needs doing: Get [SourceClass] under test, inline [NewMethod/NewClass] into proper location, eliminate the split responsibility.
- Date introduced: [today]

Inputs

InputRequiredDescription
Source class and methodYesThe legacy code where new behavior must appear
New behavior descriptionYesWhat the new code must do (specific enough to test)
Temporal coupling answerYesMust new behavior fire on every existing call?
Constructor testability answerYesCan source class be instantiated in a test harness quickly?
Test frameworkYesMust be configured to run tests on new isolated code

Outputs

OutputDescription
New method or new classThe new behavior, fully tested in isolation
Modified source methodOne-line integration call added (Sprout) or rename+delegate (Wrap)
refactor-backlog.md entryTracks the remaining design debt
Test fileTDD tests for the new method/class

Key Principles

  • Sprout/Wrap leaves OLD code in place — this is temporary. The source method is not improved; you are adding tested code beside or around it. Document the debt immediately. The techniques buy time; eventual refactoring of the source class is still required.
  • Wrap when new behavior must co-execute; Sprout when it stands alone. Temporal coupling is the deciding signal. Adding code inside a method "because it runs at the same time" is exactly the pattern that creates tangled legacy code. Wrap makes the coupling explicit and separable.
  • Class-level when constructor dependencies block method-level. If you cannot construct the source class in a test harness at all, move to a new class (Sprout Class) or wrap at the class level (Wrap Class). Do not try to sprout a method in a class you can't test.
  • Develop new code with TDD, even though the surrounding code has no tests. The seam between old and new code is a testing opportunity. The new method/class has clean dependencies — you chose them. This is the one place in the legacy codebase where you can practice full red-green-refactor.
  • Preserve Signatures during rename (Wrap Method). When renaming the original method, copy its signature verbatim — same parameter names, types, and return type. You are changing an untested method; any accidental signature modification will break callers silently.
  • Name the sprouted/wrapped code for what it actually does. dispatchPayment(), uniqueEntries(), QuarterlyReportTableHeaderProducer — not payOld() or doWork2(). The sprout or wrap will likely persist longer than you expect.

Examples

Sprout Method: Duplicate-entry detection in TransactionGate.postEntries() (Java)

Situation: postEntries(List entries) posts dates and adds entries to a bundle. A new requirement: skip entries already in the bundle. Adding the check inline mingles duplicate-detection with date-posting in one loop.

Analysis: Method-level + independent → Sprout Method.

Before (inline attempt — avoided):

public void postEntries(List entries) {
    List entriesToAdd = new LinkedList();
    for (Iterator it = entries.iterator(); it.hasNext(); ) {
        Entry entry = (Entry)it.next();
        if (!transactionBundle.getListManager().hasEntry(entry)) { // new check mixed in
            entry.postDate();
            entriesToAdd.add(entry);
        }
    }
    transactionBundle.getListManager().add(entriesToAdd);
}

This mingles two operations: date-posting and duplicate detection. It also introduces a temporary variable that will attract more code.

After (Sprout Method):

// New sprouted method — fully tested in isolation
List uniqueEntries(List entries) {
    List result = new ArrayList();
    for (Iterator it = entries.iterator(); it.hasNext(); ) {
        Entry entry = (Entry)it.next();
        if (!transactionBundle.getListManager().hasEntry(entry)) {
            result.add(entry);
        }
    }
    return result;
}

// Source method: single integration call added
public void postEntries(List entries) {
    List entriesToAdd = uniqueEntries(entries);   // Step 6: uncommented
    for (Iterator it = entriesToAdd.iterator(); it.hasNext(); ) {
        Entry entry = (Entry)it.next();
        entry.postDate();
    }
    transactionBundle.getListManager().add(entriesToAdd);
}

uniqueEntries() is tested with a FakeListManager before the call in postEntries() is uncommented.


Wrap Method: Payment logging in Employee.pay() (Java)

Situation: pay() calculates timecard totals and dispatches payment. New requirement: log every payment. Logging must happen every time pay() is called.

Analysis: Method-level + temporally coupled → Wrap Method.

Before:

public void pay() {
    Money amount = new Money();
    for (Iterator it = timecards.iterator(); it.hasNext(); ) {
        Timecard card = (Timecard)it.next();
        if (payPeriod.contains(date)) {
            amount.add(card.getHours() * payRate);
        }
    }
    payDispatcher.pay(this, date, amount);
}

After (Wrap Method — rename + delegate):

// Original logic, renamed, made private — Preserve Signatures applied
private void dispatchPayment() {
    Money amount = new Money();
    for (Iterator it = timecards.iterator(); it.hasNext(); ) {
        Timecard card = (Timecard)it.next();
        if (payPeriod.contains(date)) {
            amount.add(card.getHours() * payRate);
        }
    }
    payDispatcher.pay(this, date, amount);
}

// New public entry point — callers are unchanged
public void pay() {
    logPayment();           // new behavior — TDD'd in isolation
    dispatchPayment();      // delegate to original
}

private void logPayment() { ... }  // TDD'd, tested independently

All existing callers of pay() continue to work. The two behaviors — logging and dispatch — are independently testable.


Sprout Class: HTML table header in QuarterlyReportGenerator (C++)

Situation: QuarterlyReportGenerator::generate() builds an HTML report. New requirement: add a header row to the HTML table. The class is a large legacy class that would take a day to get into a test harness.

Analysis: Class-level + independent → Sprout Class.

New class developed with TDD:

class QuarterlyReportTableHeaderProducer {
public:
    string makeHeader();
};

string QuarterlyReportTableHeaderProducer::makeHeader() {
    return "<tr><td>Department</td><td>Manager</td>"
           "<td>Profit</td><td>Expenses</td></tr>";
}

Integration into source method (uncommented after TDD passes):

// Inside QuarterlyReportGenerator::generate()
QuarterlyReportTableHeaderProducer producer;
pageText += producer.makeHeader();   // Step 6: uncommented

QuarterlyReportTableHeaderProducer is tested completely independently of QuarterlyReportGenerator. The legacy class is not touched beyond the one integration line.

Design note: The class name initially seems disconnected. Over time it can be renamed QuarterlyReportTableHeaderGenerator and unified under an HTMLGenerator interface — but that refactoring happens later, when the source class is finally brought under test.

References

Full step-by-step mechanics for all four techniques, including Sprout Class (6 steps) and Wrap Class (4 steps + Decorator pattern guidance):

  • references/four-techniques-mechanics.md

License

This skill is licensed under CC-BY-SA-4.0. Source: BookForge — Working Effectively with Legacy Code by Michael C. Feathers (2004, Prentice Hall), Chapter 6.

Related BookForge Skills

Dependencies (must be installed for full value):

  • legacy-code-change-algorithm — The 5-step framework that leads to this skill. Sprout/Wrap is used at Step 5 when you can't break enough dependencies upfront. IF not installed → use this skill standalone, but know that you are skipping test point identification and dependency analysis.
  • safe-legacy-editing-discipline — The 4 safety constraints (Preserve Signatures, Single-Goal Editing, Hyperaware Editing, Lean on the Compiler) that govern Step 4's rename operation. IF not installed → apply Preserve Signatures manually: copy-paste signatures verbatim, make zero other changes during the rename step.

Cross-references:

  • characterization-test-writing — When the source class finally gets under test (Step 7's future work), use this to write characterization tests that lock in its current behavior before you clean up the sprout/wrap debt.
  • dependency-breaking-technique-executor — When you try Sprout Method but discover the source class can't be instantiated even for a method-level test, this skill applies the full catalog of 24 dependency-breaking techniques to make the class testable.
  • seam-type-selector — Helps identify which kind of seam (object seam, link seam, preprocessing seam) is available in the source class; useful before choosing between Sprout Method and Sprout Class.

Install the full book skill set: bookforge-skills — working-effectively-with-legacy-code

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

85.74%
按下载量换算2,953

安全审计

VirusTotal

通过

ClawScan

通过

Static analysis

通过

权限和风险

external-service

该 Skill 可能调用第三方服务、云服务或外部模型 API,使用前需要确认账号、额度、数据发送范围和服务条款。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills