Install
openclaw skills install bookforge-monster-method-decompositionDecompose a very large method (100+ lines, deeply nested) safely using automated refactoring and Feathers' Bulleted/Snarled classification. Use whenever a developer faces 'a huge method', 'I have a 500-line function', 'deeply nested conditionals', 'monster method', 'god method', 'need to break up this giant method', 'can't test this method it's too big', 'where do I even start with this method'. Activates for 'method extraction', 'IDE refactoring', 'automated extract method', 'introduce sensing variable', 'find sequences', 'skeletonize', 'coupling count', 'bulleted method', 'snarled method', 'break out method object'.
openclaw skills install bookforge-monster-method-decompositionA monster method is one so long and so complex that you genuinely do not feel comfortable touching it. It may be hundreds or thousands of lines with scattered indentation that makes navigation nearly impossible. You are tempted to print it out and lay it across the hallway to understand it.
Use this skill when any of the following are true:
Anti-pattern warning: Reading the method top-to-bottom attempting to comprehend it in full before doing anything is the primary failure mode. Monster methods overwhelm working memory. The structural attack — classify, pick strategy, extract small pieces — is always the right starting point.
Relationship to prerequisites: safe-legacy-editing-discipline applies throughout (Single-Goal Editing, Preserve Signatures). dependency-breaking-technique-executor handles escalation to Break Out Method Object when in-place decomposition stalls.
Before starting, collect:
Read the method structurally, not semantically. Do not try to understand what it does — only observe its shape.
Bulleted Method — nearly no dominant indentation. The method reads like a bulleted list of actions. When you squint, you see flat horizontal blocks. Sections may be separated by blank lines or comments. The hidden challenge: sections look separable but often share temporary variables declared in one chunk and used in the next.
Snarled Method — dominated by a single large, deeply indented section. The method contains one or more long conditional or loop structures that nest most of the code. Try to align the blocks visually — if you feel vertigo, you have a snarl. Logic buried deep in nesting is nearly impossible to test in isolation.
Hybrid (most real methods) — a snarled outer shell with bulleted sections hidden deep inside the nesting, or a bulleted surface with snarled interior chunks. Treat as snarled-first when the dominant indentation level is deep; bulleted-first when the top level reads flat.
Document the classification: BULLETED | SNARLED | HYBRID (dominant: X).
The classification from Step 1 determines the primary strategy. You will likely use both strategies at different points — this is expected. Feathers himself notes going back and forth.
Find Sequences (for Bulleted methods)
Extract the condition and body together into a single named method. Goal: reveal the overarching sequence of operations that was hidden inside the method's bulk. After extraction, the monster method should read like a sequence of named operations.
// Before (extract condition + body together):
if (marginalRate() > 2 && order.hasLimit()) {
order.readjust(rateCalculator.rateForToday());
order.recalculate();
}
// After Find Sequences:
recalculateOrder(order, rateCalculator); // condition + body inside the new method
Use Find Sequences when you sense that there is an overarching sequence of steps that will become legible once the chunks are named.
Skeletonize (for Snarled methods)
Extract the condition and body separately into two different methods. Goal: leave a skeleton of control structure in the original method — the if/for/while bones — with named delegations inside each branch. This makes the control structure visible and refactorable without understanding every branch's logic.
// Before (deep nesting):
if (marginalRate() > 2 && order.hasLimit()) {
order.readjust(rateCalculator.rateForToday());
order.recalculate();
}
// After Skeletonize (condition and body separated):
if (orderNeedsRecalculation(order)) {
recalculateOrder(order, rateCalculator);
}
Use Skeletonize when the control structure itself needs to be refactored — when the nesting pattern is the primary problem, not just the size of each branch.
Before extracting any piece, count its coupling:
coupling count = number of parameters that would pass in
+ 1 if there is a return value
(instance variable accesses do NOT count — they stay on the object)
Example: a method taking (int a, int b) and returning an int has coupling count 3.
Prioritize extractions with the lowest coupling count first. Coupling count 0 (no parameters, no return) is the safest — you are issuing a command to the object to modify its own state. These extractions are nearly impossible to get wrong.
Coupling count 0 extractions are especially valuable as a first pass on any monster method regardless of type — they progressively clarify the method's structure without introducing parameter/return type errors.
When coupling count is greater than 0, consider introducing a sensing variable (Step 5) before extracting.
This is the most critical constraint in the entire process:
If automated refactoring support is available, use the tool exclusively. Do not mix automated extractions with manual edits. Do not reorder statements. Do not split expressions. Do not rename during extraction.
Why: automated Extract Method performs variable flow analysis that would be error-prone by hand. Mixing manual edits with automated ones eliminates the safety guarantee — you no longer know which changes are tool-verified and which are hand-written. The price of this constraint is that extracted method names will sometimes be awkward. Accept this — names are moved later.
Extraction sequence:
If no automated refactoring tool is available, proceed more conservatively:
When you need to write a test for a piece of logic that is currently inside the monster method — and that logic does not yet have a way to be observed externally — introduce a sensing variable:
public boolean nodeAdded = false;) to the classKeep sensing variables in place across the entire refactoring session, deleting them only at the end. This lets you undo extractions easily if a better decomposition direction emerges.
Sensing variables are especially valuable for snarled methods — they allow you to add test coverage deep inside nested logic before you can reach it via the public interface.
After each batch of extractions, write characterization tests for the methods you extracted. These tests document actual current behavior. Use characterization-test-writing if this is your first time writing characterization tests.
At minimum for each extracted method:
These tests become the safety net for the next round of extractions.
If, after applying Steps 1–6, the method is still too monstrous to decompose in place — particularly if it has dozens of local variables that would all become parameters, or if the logic is so entangled that no low-coupling extraction exists — escalate.
Invoke dependency-breaking-technique-executor with the Break Out Method Object technique:
run() or execute() method on the new classBreak Out Method Object is more drastic than in-place decomposition but is often the right call when local variables are so numerous that sensing variables become impractical, or when the method's logic genuinely belongs to a concept that deserves its own type.
| Input | Required | Notes |
|---|---|---|
| Monster method source | Yes | Full text including signature |
| Language | Yes | Determines tool availability |
| Automated refactoring tool | Yes | Yes/no/partial |
| Existing tests | Yes | Drives sensing variable decision |
| Target change | Yes | Guides which section to prioritize |
| Class instantiability in test harness | No | Needed if writing new tests for extracted methods |
| Output | Description |
|---|---|
decomposition-plan.md | Classification, strategy choice, ordered extraction list with coupling counts |
| Extracted methods | Named private methods in the current class |
| Sensing variable tests | Temporary test file for coverage during extraction |
| Characterization tests | Persistent tests for extracted methods |
| Updated monster method | The original method, now a skeleton of delegating calls |
Classify first — strategy depends on type. Bulleted method → Find Sequences (condition + body together, reveal overarching sequence). Snarled method → Skeletonize (condition and body separately, leave control structure visible). Hybrid → lead with the dominant type.
Use the IDE exclusively. Never mix automated extraction with manual edits in the same session. No reordering. No expression splitting. No inline renames. The tool's safety guarantee is invalidated the moment you interleave manual edits.
Coupling count ordering (low → high) reduces risk. Extract 0-count methods first. They carry no parameter/return-type risk. Even one round of 0-count extractions gives you insight into the method's structure that top-to-bottom reading never could.
Extract to the current class first with awkward names. The name recalculateOrder is awkward when the logic belongs on Order — but it is correct enough to move forward safely. Move the method to its better home only after tests exist.
Top-to-bottom reading is the anti-pattern. The structural attack (classify → strategy → extract by coupling count) always proceeds faster and more safely than attempting full comprehension before touching anything.
Be prepared to redo. The first round of extractions produces insight that often reveals a better decomposition. Redoing is not wasted — it is the design process working correctly.
processRequest() is 300 lines with 12 flat sections separated by blank lines. Each section handles a different request type. Visual inspection: nearly no dominant indentation, flat shape — this is a Bulleted method.
Strategy: Find Sequences.
processRequest() reads as a sequence: validateRequest(), parseHeaders(), routeToHandler(), formatResponse(), etc.The resulting method reveals the overarching sequence that was buried in bulk.
updateCommodities() is 150 lines. The outer body is a single if over a for loop with nested conditions 5 levels deep. Visual inspection: deep nesting dominates — this is a Snarled method.
Strategy: Skeletonize.
commoditiesAreReadyForUpdate()if alone: runCommodityUpdate()if (commoditiesAreReadyForUpdate()) { runCommodityUpdate(); }runCommodityUpdate() — apply the same skeletonize pass to its inner loopsThe resulting method is a skeleton of control structure — the nesting pattern is now visible and refactorable.
generateReport() is 600 lines with 40 local variables. Every candidate extraction has coupling count 5+ because the logic is a deeply interconnected computation. No 0-count methods can be found. In-place decomposition would produce methods with enormous parameter lists that are harder to read than the original.
Decision: escalate to Break Out Method Object.
Invoke dependency-breaking-technique-executor with:
generateReport()ReportGeneratorReportGenerator.run()Now each local variable is an instance variable — sensing variables are no longer needed because the state is directly observable. Decompose ReportGenerator.run() using the same Bulleted/Snarled classification on what is now a class with proper state.
For deeper guidance on techniques referenced here:
characterization-test-writing skill; also Working Effectively with Legacy Code Ch 13Content derived from Working Effectively with Legacy Code (Michael C. Feathers, 2004). Skill text licensed under CC BY-SA 4.0.
| Skill | Relationship |
|---|---|
safe-legacy-editing-discipline | Prerequisite. Preserve Signatures and Single-Goal Editing apply at every extraction step |
dependency-breaking-technique-executor | Prerequisite. Break Out Method Object escalation path for in-place-infeasible cases |
characterization-test-writing | Produces the tests written after each extraction batch |
test-harness-entry-diagnostics | Diagnoses why the class cannot be instantiated in a test harness before starting decomposition |
scratch-refactoring-for-code-understanding | Alternative when the goal is comprehension, not safe production change |