Install
openclaw skills install bookforge-class-responsibility-realignmentRedistribute methods and fields to the classes that own them, repair broken inheritance hierarchies, and extend unmodifiable library classes. Use when: a met...
openclaw skills install bookforge-class-responsibility-realignmentYou have identified (via code-smell-diagnosis or direct inspection) that behavior or data is in the wrong class. The concrete signals are:
The core insight from Fowler: One of the most fundamental decisions in object design is deciding where to put responsibilities. Getting it wrong the first time is not a problem — refactoring exists to correct it. The moment you realize a method uses another class's data more than its own, you have the diagnosis and the prescription simultaneously: Move Method.
code-smell-diagnosis report. Why: responsibility decisions are made relative to the data each class owns — you cannot realign without knowing what each class contains.Before touching anything:
Scan targets:
- Which class does this method call getters on most? → Move Method candidate
- Which fields travel together in parameter lists? → Extract Class / Data Clumps
- How many files change together for one logical edit? → Shotgun Surgery scope
- How many methods on class A just call class B? → Middle Man threshold
- Which subclass methods are identical? → Pull Up Method candidates
- Does a subclass throw NotImplemented or ignore parent fields? → Refused Bequest
ACTION: Map the symptoms to one of the three problem clusters below.
WHY: Each cluster uses a different set of refactorings. Misclassification wastes a move or creates a new smell.
Cluster A — Misplaced behavior (flat class graph)
| Symptom | Refactoring |
|---|---|
| Method uses another class's data more than its own | Move Method |
| Field is referenced more by another class | Move Field |
| Class does too much; subset of fields/methods coheres separately | Extract Class |
| Class does too little; its responsibilities fit an absorbing class | Inline Class |
Client navigates a.getB().getC() chains | Hide Delegate (add delegating method on server) |
| Server delegates more than half its interface, adds no value | Remove Middle Man |
| Library class missing 1–2 methods | Introduce Foreign Method |
| Library class missing 3+ methods | Introduce Local Extension |
Cluster B — Hierarchy misalignment
| Symptom | Refactoring |
|---|---|
| Identical method/field in two or more subclasses | Pull Up Method / Pull Up Field |
| Constructors in subclasses with mostly identical bodies | Pull Up Constructor Body |
| Superclass method only meaningful in one subclass | Push Down Method |
| Superclass field only used in one subclass | Push Down Field |
| Subset of features used only in some instances | Extract Subclass |
| Two unrelated classes share common behavior | Extract Superclass |
| Several clients use only a subset of one class's interface | Extract Interface |
| Subclass and superclass have converged — barely different | Collapse Hierarchy |
| Two subclass methods do the same steps in the same order, but differently | Form Template Method |
| Subclass uses only part of superclass interface, or inherits wrong data | Replace Inheritance with Delegation |
| Delegation covers the full interface, delegation methods are boilerplate | Replace Delegation with Inheritance |
Cluster C — Encapsulation balance
| Symptom | Action |
|---|---|
| Clients know too much about delegate's internals | Add Hide Delegate methods on server |
| Server has grown into a pass-through layer | Remove Middle Man — let clients talk to delegate |
ACTION: Execute the mechanics for the identified refactoring(s).
WHY: Each step produces a compilable intermediate state; verifying after each move catches errors before they compound.
When a method is more interested in another class than its own:
target.methodName()). Compile and test.When a field is used more by another class:
When a class is doing work that should be done by two:
When a class is no longer pulling its weight:
Fowler's observation: these two are inverse refactorings and neither is permanently correct. "Refactoring means you never have to say you're sorry — you just fix it." Apply the rule by tracking:
manager = john.getDepartment().getManager(). Add getManager() directly to Person so the client does not need to know about Department. Do this for each method on the delegate that clients use. Remove the delegate accessor if clients no longer need it.The judgment call: Hide more when the delegate's implementation is likely to change (clients should not be coupled to it). Remove the middle man when the delegating layer adds no value and the cost of maintaining all those one-liners exceeds the coupling cost.
When you cannot modify a class but need additional behavior from it:
1–2 methods needed → Introduce Foreign Method
// foreign method; should be on [ServerClass]. Why: this marks it for migration if you later gain access to the source, and enables grep-based discovery of all foreign methods for a given class.3+ methods needed → Introduce Local Extension
a.equals(b) where a is original and b is wrapper) cannot be fully hidden.ACTION: Select and apply the hierarchy refactoring identified in Step 1.
WHY: Hierarchy smells compound — duplicate methods in subclasses create two maintenance points; misplaced fields make Pull Up Method harder. Address the fields before the methods.
Fields first, then methods. Why: Pull Up Method may depend on the field being in the superclass already.
super(...) from each subclass. If common code must run after subclass-specific initialization, use Extract Method on the common post-init logic and Pull Up Method to put it in the superclass, then call it explicitly from the subclass constructor.When superclass behavior is only relevant to some subclasses:
When a class has features used only in some instances:
When two classes share common behavior:
When several clients use only a subset of a class's interface, or when a class needs to work with any class that can handle certain requests:
When a subclass and superclass have converged:
When two subclass methods perform the same steps in the same order but the steps are implemented differently:
Result: the superclass holds the invariant algorithm; subclasses supply only the steps that vary. This is the Template Method pattern — adding a new variant requires only a new subclass that overrides the abstract steps.
When a subclass uses only part of the superclass interface or inherits data that does not make sense for it (Refused Bequest, strong form):
this so delegation and inheritance can coexist during the transition.super cannot be changed until the inheritance link is broken — they would recurse.this to a new instance of the former superclass.Contraindications: do not apply when the subclass uses all methods of the superclass (delegation is boilerplate without benefit), or when the delegate is shared and mutable (data sharing cannot be replicated with delegation).
When a class delegates to another class for the full interface and the delegating methods are pure boilerplate:
ACTION: Confirm the structural goals are achieved.
WHY: It is possible to complete all mechanics correctly but still have the smell — if the wrong refactoring was chosen for the symptom, or if a second smell was hidden beneath the first.
Verify each:
1. Move Field before Move Method when doing both. A method moved before its data is moved will still reference the old class via getter calls. Moving the field first stabilizes the data layout; the method move then resolves cleanly.
2. The encapsulation balance is not fixed. Hide Delegate protects clients from implementation changes. Remove Middle Man removes hollow layers. Apply whichever is appropriate given current coupling pressure — and reapply the inverse when conditions change.
3. Inheritance vs. delegation: the variation-dimensions rule. Fowler's criterion: if you need the class to vary in only one way, subclassing (Extract Subclass) is simpler. If you need it to vary in several different ways, you must use delegation for all but one of them — a class can only have one superclass, but it can hold multiple delegate objects.
4. Foreign methods are a workaround, not a destination.
Comment them as // foreign method; should be on [ServerClass] so they can be found and migrated if you gain access to the server class. If the count grows beyond 2, promote to a local extension.
5. Form Template Method is Extract Method + Pull Up. It is a composed refactoring, not a single step. The key is the decomposition: extracted methods must be either identical (candidates for Pull Up) or completely different (candidates for abstract methods). Partial overlap defeats the pattern.
Scenario: Account.overdraftCharge() calls _type.isPremium() and uses _type.daysOverdrawn() — most of its logic is about AccountType, not Account.
Diagnosis: Feature Envy. The method is more interested in AccountType than in Account.
Apply Move Method:
overdraftCharge to AccountType. Pass daysOverdrawn as a parameter (option d — pass the specific value).Account.overdraftCharge() body with return _type.overdraftCharge(_daysOverdrawn);account.overdraftCharge() still work — the source is now a delegating method.Account and redirect all callers to _type.overdraftCharge(_daysOverdrawn) directly.Result: AccountType owns overdraft logic. New account types require changes only in AccountType — Shotgun Surgery is eliminated.
Scenario: Person has _officeAreaCode and _officeNumber as separate fields. Both appear in method signatures throughout the codebase. Deleting _officeAreaCode makes _officeNumber meaningless without context.
Diagnosis: Data Clumps. These two fields form a natural object.
Apply Extract Class:
TelephoneNumber class.Person to TelephoneNumber: private TelephoneNumber _officeTelephone = new TelephoneNumber()._officeAreaCode to TelephoneNumber. Update Person's accessors to delegate: getOfficeAreaCode() → _officeTelephone.getAreaCode()._officeNumber. Move Method: move getTelephoneNumber() to TelephoneNumber.TelephoneNumber as a reference object (aliasing allowed) or make it immutable (safer).Result: TelephoneNumber is a named concept. It can be reused by HomeAddress, MobileContact, and others without duplicating the area-code/number pair.
Scenario: JobItem has an _isLabor boolean that controls behavior in getUnitPrice(). Labor items use employee.getRate(); parts items use _unitPrice. The _employee field is null for parts items.
Classification step:
Apply Extract Subclass (creating LaborItem):
LaborItem extends JobItem.getEmployee() moves to LaborItem._employee moves to LaborItem._isLabor; replace its getter with a polymorphic constant: JobItem.isLabor() returns false, LaborItem.isLabor() returns true.getUnitPrice(): JobItem returns _unitPrice; LaborItem returns _employee.getRate()._isLabor field.Contrast with delegation case: If job items needed to vary along a second axis (e.g., taxable vs. non-taxable), subclassing could cover only one axis. The second axis would require a delegate: JobItem holds a TaxStrategy delegate and a PricingStrategy delegate, with the hierarchy handling neither.
Scenario: TextStatement.value() and HtmlStatement.value() perform the same three steps (header, body loop, footer) but format them differently. The two methods are similar but not identical.
Apply Form Template Method:
Statement superclass.headerString(), eachRentalString(), footerString() — extracted with identical signatures in both subclasses, different bodies.value() becomes identical in both subclasses: it calls headerString(), loops calling eachRentalString(), and calls footerString().value() to Statement. Declare headerString(), eachRentalString(), footerString() as abstract on Statement.value() from both subclasses.Result: Adding a PdfStatement requires only a new subclass that overrides three methods — the algorithm structure is untouchable.
| File | Contents | When to read |
|---|---|---|
references/smell-catalog.md | Full detection criteria for all 22 smells | Confirming the smell classification in Step 1 |
references/refactoring-prescriptions.md | Full prescription trees per smell | Selecting the correct conditional branch for each refactoring |
Hub skill relationships:
code-smell-diagnosis — diagnose before realigning; this skill executes what that skill prescribesdata-organization-refactoring — when Data Clumps or Primitive Obsession are the primary smell (Extract Class for data, not behavior)type-code-refactoring-selector — when the hierarchy problem involves a type code (Replace Type Code with Subclasses / State / Strategy)conditional-simplification-strategy — when Replace Conditional with Polymorphism is needed after Extract Subclassbig-refactoring-planner — when multiple smells indicate a systemic design problem requiring coordinated multi-step refactoringThis skill is licensed under CC-BY-SA-4.0. Source: BookForge — Refactoring: Improving the Design of Existing Code by Martin Fowler and Kent Beck.
Install related skills from ClawhHub:
clawhub install bookforge-code-smell-diagnosisclawhub install bookforge-data-organization-refactoringclawhub install bookforge-type-code-refactoring-selectorclawhub install bookforge-conditional-simplification-strategyclawhub install bookforge-big-refactoring-plannerOr install the full book set from GitHub: bookforge-skills