Best for
- Code is hard to understand or maintain
- Functions/classes have grown too large
- Code smells are detected
smallnest/goal-workflow/skills/refactor/SKILL.md
Use it for engineering tasks; the detail page covers purpose, installation, and practical steps.
Decision brief
Surgical code refactoring based on Martin Fowler's (2nd Edition) catalog. Improve structure, readability, and maintainability without changing external behavior. Gradual evolution, not revolution.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/smallnest/goal-workflow --skill "skills/refactor"Inspect the Agent Skill "refactor" from https://github.com/smallnest/goal-workflow/blob/f7bb561169ec4fcde0d2769d01eb53010bb05cc8/skills/refactor/SKILL.md at commit f7bb561169ec4fcde0d2769d01eb53010bb05cc8. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
1. Write characterization tests if they don't exist. These capture current behavior — they don't need to be elegant, just comprehensive enough to catch regressions. 2. Record a baseline for the claimed problem: representative behavior, change spread, relevant callers, or a bench…
1. Write characterization tests if they don't exist. These capture current behavior — they don't need to be elegant, just comprehensive enough to catch regressions. 2. Record a baseline for the claimed problem: representative behavior, change spread, relevant callers, or a bench…
1. Smell the code. Use the smell catalog above to classify what's wrong, treating thresholds as candidate signals. 2. Understand the code. Read it thoroughly, map relevant callers and dependencies, and identify the concrete failure or change scenario. 3. Choose a primary princip…
For each step: 1. Make one small change. Address one verifiable part of the primary smell; do not bundle unrelated principle cleanups. 2. Compile. The code should compile after every change. 3. Run tests. All tests must pass. If they don't, you've changed behavior. 4. Check the…
1. Behavior. All tests, type checks, compilation, and a manual smoke test pass. 2. Structure. Compare dependency direction, relevant callers, duplicated knowledge, or change spread against the decision card. 3. Fail fast semantics. If validation moved earlier, preserve error typ…
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 98/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 237 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Surgical code refactoring based on Martin Fowler's (2nd Edition) catalog. Improve structure, readability, and maintainability without changing external behavior. Gradual evolution, not revolution.
This skill activates when:
These five rules are non-negotiable. Violating any of them turns refactoring into reckless editing.
Only how the code works changes, never what it does. If tests existed before, they must pass after. If the refactoring introduces a behavioral change, it's not refactoring — it's rewriting.
Each change should be the smallest possible transformation that compiles and passes tests. If a step breaks, you know exactly which change caused it. Refactoring is a series of tiny, safe transformations, not one big rewrite.
Commit before starting. Commit after each successful step. This gives you infinite undo. Branch from a clean state so you can abandon the refactoring without consequences.
"Without tests, you're not refactoring — you're just editing." If tests don't exist for the target code, write characterization tests first. These tests capture the current behavior so you can detect regressions.
Never mix refactoring with feature changes. Never refactor two unrelated things simultaneously. Each commit should contain exactly one refactoring operation.
| Scenario | Action |
|---|---|
| Code works and won't change again | Leave it alone |
| Critical production path with no tests | Write characterization tests first |
| Under tight deadline pressure | Document the smell, refactor later |
| No clear purpose or benefit | Don't refactor for refactoring's sake |
| Code is fundamentally wrong | This is a rewrite, not a refactoring |
Principles guide judgment; they are not independent reasons to rewrite code. Before choosing a Fowler technique, record one decision card:
| Field | Required answer |
|---|---|
| Primary smell | One root cause, not one entry per principle |
| Evidence | Location, behavior, callers, change history, or measurement |
| Primary principle | The most specific applicable principle |
| Related principles | Explanatory labels only; do not count separately |
| Expected impact | Observable reduction in change spread, cognitive load, duplicated knowledge, coupling, delayed failure, or measured runtime cost |
| Smallest refactoring | The least invasive Fowler technique that addresses the root cause |
| Baseline / success condition | How behavior preservation and the expected benefit will be verified |
Use these four decision lenses:
| Lens | Principles | Questions to answer |
|---|---|---|
| Responsibility and dependencies | SOLID, SRP, OCP, DIP, Separation of Concerns | Are reasons to change mixed? Does a real new variant repeatedly modify stable logic? Does high-level policy depend on concrete mechanism? Do concerns leak across a boundary? |
| Reuse and structure | DRY, Composition | Is the same knowledge duplicated, or merely similar syntax? Would composition localize a real variation better than inheritance? |
| Simplicity and scope | KISS, YAGNI | Is the proposed structure simpler for today's problem? Is every abstraction backed by an existing variation or boundary? |
| Runtime and feedback | Fail Fast, Measure First | Can invalid state fail nearer its source without changing error semantics? What baseline proves the problem and the result? |
SOLID is an umbrella. When evidence supports SRP, OCP, or DIP, use that specific lens and do not create a second SOLID issue. LSP and ISP may be labeled SOLID/LSP and SOLID/ISP. DRY means shared knowledge, not all similar code. Composition is preferred when inheritance creates real coupling, not by default. OCP and DIP never justify speculative layers that violate KISS or YAGNI.
Numeric thresholds in this skill—line counts, parameter counts, method counts, and nesting depth—are context-dependent candidate indicators. Confirm mixed responsibilities, cognitive cost, repeated change, duplicated knowledge, or measured runtime impact before acting.
Based on Fowler's taxonomy. Before refactoring, identify which smell is present.
| Smell | Description | Primary Refactoring |
|---|---|---|
| Long Method | Candidate: method > 10-15 lines; confirm mixed responsibilities or cognitive cost | Extract Method, Replace Temp with Query |
| Large Class | Candidate: many fields/methods; confirm independent reasons to change | Extract Class, Extract Subclass |
| Primitive Obsession | Using primitives instead of small objects | Replace Data Value with Object, Replace Type Code with Class |
| Long Parameter List | Candidate: > 3-4 parameters; confirm a missing concept or recurring data clump | Introduce Parameter Object, Preserve Whole Object |
| Data Clumps | Same group of data appearing together | Extract Class, Introduce Parameter Object |
| Smell | Description | Primary Refactoring |
|---|---|---|
| Switch Statements | Repeated switch/if-else on type codes | Replace Conditional with Polymorphism, Replace Type Code with Subclasses |
| Temporary Field | Field only set in certain circumstances | Extract Class, Introduce Null Object |
| Refused Bequest | Subclass doesn't use inherited members | Replace Inheritance with Delegation, Push Down Method/Field |
| Alternative Classes with Different Interfaces | Classes doing similar things with different names | Rename Method, Move Method, Extract Superclass |
| Smell | Description | Primary Refactoring |
|---|---|---|
| Divergent Change | One class changed for different reasons | Extract Class |
| Shotgun Surgery | One change requires many small changes across classes | Move Method, Move Field, Inline Class |
| Parallel Inheritance Hierarchies | Adding a subclass to one hierarchy forces adding to another | Move Method, Move Field |
| Smell | Description | Primary Refactoring |
|---|---|---|
| Comments | Comments explaining what code does (not why) | Extract Method, Rename Variable, Introduce Assertion |
| Duplicate Code | Same code structure in multiple places | Extract Method, Pull Up Method, Form Template Method |
| Lazy Class | Class doing too little to justify existence | Inline Class, Collapse Hierarchy |
| Data Class | Class with only fields and getters/setters | Move Method, Encapsulate Field, Encapsulate Collection |
| Dead Code | Unused code, imports, commented-out blocks | Delete it (git history has it) |
| Speculative Generality | Code built for "someday" that never came | Inline Class, Collapse Hierarchy, Remove Parameter |
| Smell | Description | Primary Refactoring |
|---|---|---|
| Feature Envy | Method uses another class's data more than its own | Move Method, Extract Method + Move Method |
| Inappropriate Intimacy | Classes know too much about each other's internals | Move Method, Move Field, Replace Delegation with Hidden Delegate |
| Message Chains | a.getB().getC().getD().doSomething() | Hide Delegate, Extract Method |
| Middle Man | Class delegates everything to another class | Remove Middle Man, Inline Method |
| Incomplete Library Class | Library missing methods you need | Introduce Foreign Method, Introduce Local Extension |
Organized by category, from Fowler's catalog. Each technique includes its mechanical steps.
Turn a code fragment into a method whose name explains its purpose.
Mechanics:
Before:
void printOwing() {
printBanner();
// Print details
System.out.println("name: " + _name);
System.out.println("amount: " + getOutstanding());
}
After:
void printOwing() {
printBanner();
printDetails(getOutstanding());
}
void printDetails(double outstanding) {
System.out.println("name: " + _name);
System.out.println("amount: " + outstanding);
}
Replace a method call with its body when the method body is as clear as the name.
Mechanics:
Put the result of an expression (or part of it) in a self-explanatory variable.
Before:
if (platform.toUpperCase().indexOf("MAC") > -1 &&
browser.toUpperCase().indexOf("IE") > -1 &&
wasInitialized() && resize > 0) {
// ...
}
After:
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
// ...
}
Replace a temp variable with its expression when the temp is only used once and the expression is clear.
Extract the expression into a method. Temps that are computed once and reused are replaced with method calls.
A temp assigned more than once (not loop/collecting) should be split into separate variables, one per responsibility.
Don't assign to parameters. Use a local variable instead.
When a long method uses many local variables that make Extract Method hard, turn the method into its own class, with locals as fields.
Replace an algorithm with a clearer one.
Move a method to the class where it's used most.
Mechanics:
Move a field to the class where it's used most.
When a class does the work of two, split it. Create a new class and move relevant fields and methods.
When a class does almost nothing, absorb it into the class that uses it most.
Create methods on the server to hide the delegate chain. manager = person.getDepartment().getManager() → manager = person.getManager().
When a class is doing too much delegation, call the delegate directly.
When a server class needs an additional method but you can't modify it, create a method on the client with the server instance as the first argument.
When you need multiple foreign methods, create an extension class (subclass or wrapper).
Access fields through getters and setters, even within the owning class.
When a data item needs additional data or behavior, turn it into an object.
Before:
class Order {
private String customer; // Just a string
}
After:
class Order {
private Customer customer; // Rich object with name, address, credit rating
}
When you need to share one instance of an object across multiple places.
When a reference object is small, immutable, and you want value semantics.
When an array holds heterogeneous data (String[] row = new String[3] — name, score, wins), replace with an object.
Domain data lives in a GUI control but domain logic needs it. Copy the data into a domain object and set up an observer to keep the two in sync (Observer pattern). Separates presentation from domain so each can evolve independently.
Two classes need each other's features but only one holds a reference. Add a back-pointer and make the modifiers on both ends keep the link consistent. Add the reference only when genuinely needed — bidirectional links raise coupling and risk inconsistency.
A two-way link exists but one side no longer uses the other. Drop the unneeded direction. Reduces coupling, simplifies lifecycle management, and avoids "zombie" objects kept alive only by a stale back-pointer.
Replace literal numbers/strings with named constants.
Make public fields private and provide accessors.
Never return the raw collection. Return a read-only view and provide add/remove methods.
Replace a numeric/string type code with a class that has meaningful behavior.
When type code affects behavior, use polymorphism instead of conditionals.
Similar to subclasses but uses composition when the type can change at runtime.
When subclasses vary only in constant data, replace them with fields on a single class.
Extract the condition, then-part, and else-part into separate methods.
Before:
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * _winterRate + _winterServiceCharge;
} else {
charge = quantity * _summerRate;
}
After:
if (isSummer(date)) {
charge = summerCharge(quantity);
} else {
charge = winterCharge(quantity);
}
Combine multiple conditionals that have the same result.
Move code that appears in every branch outside the conditional.
Replace control flags with break, continue, or return.
Use early returns for special cases instead of deep nesting.
Before (arrow code):
double getPayAmount() {
double result;
if (_isDead) {
result = deadAmount();
} else {
if (_isSeparated) {
result = separatedAmount();
} else {
if (_isRetired) {
result = retiredAmount();
} else {
result = normalPayAmount();
}
}
}
return result;
}
After:
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
}
When a conditional chooses different behavior based on the type of an object, use subclasses.
Replace null checks with a null object that provides default behavior.
State assumptions explicitly with assertions.
The name should say what the method does. If you can't think of a good name, the method may have multiple responsibilities.
Add parameters when a method needs more info. Remove parameters when the method can get the info another way.
A method should either return a value OR change state, never both.
Several methods doing similar things with different values → one method with a parameter.
The inverse: when a parameter essentially selects different behavior, create separate methods.
Pass the whole object instead of pulling individual fields from it.
(refactoring.guru: Replace Parameter with Method Call) When a parameter can be computed from data the object already has, remove the parameter and let the method call the query itself.
Group parameters that naturally go together into an object.
Make a field immutable by removing its setter and setting it in the constructor.
Make methods private when they're not used outside the class.
When you need more flexibility than a simple constructor call.
Throw an exception instead of returning an error code.
Check the condition first instead of catching an exception.
Move identical fields/methods/constructor code from subclasses to superclass.
Move behavior from superclass to only the subclasses that use it.
Create a subclass for a subset of features used in some instances.
Create a superclass for shared features of similar classes.
Create an interface from a subset of a class's public methods.
Merge a superclass and subclass when they're not different enough.
Generalize an algorithm in the superclass, letting subclasses fill in the specifics.
When a subclass only uses part of the superclass, use composition instead.
When a delegating class needs access to all of the delegate's behavior.
For each step:
refactor: extract validateEmail method.Repeat until the smell is resolved.
any usage without qualificationfinal for locals that shouldn't changeconst over let for immutable bindings??) and optional chaining (?.) eliminate null-check noisedataclasses to replace tuple/data-class patterns@property to replace gettersResult and Option instead of error codes and nullFrom trait implementations clean up type conversions1. Characterization tests → capture what the code does now
2. Git commit → save a known-good state
3. Branch → isolate refactoring from other work
1. One change → one refactoring technique
2. Compile → must compile clean
3. Tests → every test must pass
4. Commit → message: "refactor: <technique> <what>"
1. Undo the last change
2. Understand what broke and why
3. Try a smaller step
4. If the test was wrong and behavior was correct, fix the test FIRST, then retry
1. Full test suite → all tests pass
2. Manual smoke test → quick sanity check
3. Self-review diff → catch unintended changes
4. Final commit → describe the overall transformation
Replace a conditional that chooses an algorithm. Smell: Switch on type code with different behavior per branch. Technique: Replace Conditional with Polymorphism + Extract Method.
Extract common algorithm skeleton to superclass, letting subclasses fill in the variants. Smell: Duplicate code with slight variations. Technique: Form Template Method.
Replace a state-based conditional by extracting each state's behavior into a class. Smell: Switch on status field with behavior variation. Technique: Replace Type Code with State/Strategy.
Treat individual objects and groups uniformly. Smell: Client code has special handling for single vs. collection cases. Technique: Extract Interface + Create Composite.
Add behavior dynamically by wrapping objects. Smell: Conditional logic for optional behaviors. Technique: Extract Class + use composition.
Replace null checks with a default object. Smell: Repeated if (x == null) checks. Technique: Introduce Null Object.
| Scenario | Handling |
|---|---|
| No tests exist | Write characterization tests first. Run the code with various inputs, capture outputs. These are your safety net. |
| Refactoring breaks a distant test | FIRST understand why. Maybe the test relied on implementation detail. If so, fix the test to test behavior, not implementation. Then resume. |
| User wants behavior change + refactor together | REFUSE. Do them separately. Refactor first to make the behavior change easy, commit, then change behavior. |
| Method is too complex to step through | Use Replace Method with Method Object. Turn the whole method into a class where each step can be extracted. |
| Refactoring across a large codebase | Extract a micro-service or module boundary first. Then refactor within the boundary. "There is a refactoring for everything except too many refactorings." |
| IDE automated refactoring available | Use it. Modern IDEs can safely rename, extract method, introduce variable, etc. Only do it manually when the IDE can't. |
| Undo needed | git stash or git reset --hard back to last commit. Small commits make this painless. |
Frequently asked questions
Surgical code refactoring based on Martin Fowler's (2nd Edition) catalog. Improve structure, readability, and maintainability without changing external behavior. Gradual evolution, not revolution.
The source record exposes this install command: npx skills add https://github.com/smallnest/goal-workflow --skill "skills/refactor". Inspect the command and pinned source before running it.
Alternatives
github/awesome-copilot
Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.
testdouble/han
Restructure existing code without changing its behavior, through a test-gated refactoring loop: a named target, a green suite over that target before any edit, a planned sequence of small named refactorings, and the full suite re-run after every step. Use when the user wants to refactor, restructure, clean up, simplify, or improve the design of existing code, or to apply refactoring recommendations from a code-review or architectural-analysis report. This skill changes code; it does not review c
mgiovani/cc-arsenal
Restructures existing code without changing its behavior: maps callers and test coverage, adds characterization tests where coverage is thin, then applies the change in small steps verified against the full test suite after each one. Use when the user wants to refactor, extract a method or class, simplify logic, reduce duplication, improve naming, restructure modules, or pay down technical debt in code that already works. Not for adding new functionality (use implement-feature) or fixing broken
coreyhaines31/marketingskills
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program