@luanpdd/kit-mcp 1.35.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +2 -2
- package/bin/mcp.js +6 -6
- package/bin/ui.js +74 -74
- package/gates/ai-prompt-stability.md +120 -120
- package/gates/budget-description.md +68 -68
- package/gates/confidence.md +29 -29
- package/gates/dependency-check.md +33 -33
- package/gates/dept-cycle-prevention.md +179 -179
- package/gates/golden-signals-coverage.md +133 -133
- package/gates/legacy-refactor-safety.md +178 -178
- package/gates/multi-tenant-rls-coverage.md +102 -102
- package/gates/no-personal-uuid.md +72 -72
- package/gates/obs-agents-mcp-supabase.md +86 -86
- package/gates/obs-skills-frontmatter.md +76 -76
- package/gates/observability-coverage.md +151 -151
- package/gates/omm-no-regression.md +83 -83
- package/gates/postmortem-template-required.md +127 -127
- package/gates/prr-checklist-coverage.md +128 -128
- package/gates/regression.md +32 -32
- package/gates/release-pipeline-policy.md +132 -132
- package/gates/secrets-scan.md +33 -33
- package/gates/service-role-not-in-user-facing.md +113 -113
- package/gates/skill-must-include.md +71 -71
- package/gates/sync-idempotent.md +62 -62
- package/gates/verify-phase-goal.md +34 -34
- package/kit/agents/designer-ui.md +216 -216
- package/kit/agents/workflow-generator.md +537 -167
- package/kit/commands/adicionar-backlog.md +1 -1
- package/kit/commands/adicionar-fase.md +1 -1
- package/kit/commands/adicionar-tarefa.md +1 -1
- package/kit/commands/auditar-observabilidade.md +103 -103
- package/kit/commands/auditar-toil.md +129 -129
- package/kit/commands/caracterizar-prompt.md +195 -195
- package/kit/commands/criar-workflow.md +158 -158
- package/kit/commands/definir-perfil.md +1 -1
- package/kit/commands/definir-slo.md +108 -108
- package/kit/commands/fio.md +1 -1
- package/kit/commands/golden-signals.md +142 -142
- package/kit/commands/instrumentar-fase.md +200 -200
- package/kit/commands/investigar-producao.md +162 -162
- package/kit/commands/observabilidade.md +118 -118
- package/kit/commands/postmortem.md +179 -179
- package/kit/commands/prr.md +205 -205
- package/kit/commands/publicar-rapido.md +207 -207
- package/kit/commands/risk-budget.md +220 -220
- package/kit/commands/sre.md +230 -230
- package/kit/file-manifest.json +424 -424
- package/kit/framework/references/output-style.md +22 -22
- package/kit/hooks/post-apply-migration.js +199 -199
- package/kit/hooks/sidecar-tool-publisher.js +210 -210
- package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
- package/kit/skills/_shared-legacy/glossary.md +389 -389
- package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
- package/kit/skills/_shared-observability/glossary.md +396 -396
- package/kit/skills/_shared-sre/glossary.md +712 -712
- package/kit/skills/_shared-supabase/glossary.md +234 -234
- package/kit/skills/blameless-postmortems/SKILL.md +340 -340
- package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
- package/kit/skills/cascading-failures/SKILL.md +311 -311
- package/kit/skills/core-analysis-loop/SKILL.md +352 -352
- package/kit/skills/distributed-tracing/SKILL.md +362 -362
- package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -223
- package/kit/skills/eliminating-toil/SKILL.md +243 -243
- package/kit/skills/event-based-slos/SKILL.md +296 -296
- package/kit/skills/four-golden-signals/SKILL.md +314 -314
- package/kit/skills/hermetic-builds/SKILL.md +323 -323
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
- package/kit/skills/llm-as-dependency/SKILL.md +436 -436
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
- package/kit/skills/observability-driven-development/SKILL.md +315 -315
- package/kit/skills/observability-maturity-model/SKILL.md +222 -222
- package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
- package/kit/skills/production-readiness-review/SKILL.md +305 -305
- package/kit/skills/release-engineering/SKILL.md +367 -367
- package/kit/skills/retry-strategies/SKILL.md +372 -372
- package/kit/skills/sre-risk-management/SKILL.md +221 -221
- package/kit/skills/structured-events/SKILL.md +265 -265
- package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
- package/kit/skills/supabase-database-functions/SKILL.md +332 -332
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
- package/kit/skills/supabase-storage/SKILL.md +234 -234
- package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
- package/kit/skills/telemetry-sampling/SKILL.md +256 -256
- package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
- package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
- package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
- package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
- package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
- package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
- package/kit/skills/ui-tipografia/SKILL.md +211 -211
- package/package.json +1 -1
- package/src/cli/index.js +1114 -1114
- package/src/cli/render.js +194 -194
- package/src/cli/upgrade-check.js +135 -135
- package/src/core/error-redaction.js +76 -76
- package/src/core/failures.js +153 -153
- package/src/core/gate-runner.js +205 -205
- package/src/core/gates.js +82 -82
- package/src/core/logger.js +170 -170
- package/src/core/manifest-verify.js +174 -174
- package/src/core/metrics.js +268 -268
- package/src/core/notify.js +60 -60
- package/src/core/path-safety.js +141 -141
- package/src/core/replays.js +120 -120
- package/src/core/ui.js +185 -185
- package/src/mcp-server/install.js +149 -149
- package/src/mcp-server/roots.js +124 -124
- package/src/ui/auto-spawn.js +113 -113
- package/src/ui/browser.js +78 -78
- package/src/ui/client.js +130 -130
- package/src/ui/events.js +65 -65
- package/src/ui/lockfile.js +191 -191
- package/src/ui/port.js +67 -67
- package/src/ui/server.js +547 -547
- package/src/ui/wrapper.js +129 -129
|
@@ -1,444 +1,444 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: legacy-monster-methods
|
|
3
|
-
description: Use ao refatorar método > 100 linhas sem testes — scratch refactoring, single-goal editing, safe extraction (cap 22 Feathers). Padrões para domesticar bulleted vs snarled methods.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Legacy — Monster Methods
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill quando o user encontra método absurdamente longo (> 100 linhas) que precisa ser modificado. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "esse método tem [N] linhas, preciso mudar"
|
|
13
|
-
- "monster method", "método monstro", "método gigante"
|
|
14
|
-
- "scratch refactoring", "refactor de rascunho"
|
|
15
|
-
- "single-goal editing", "edição com objetivo único"
|
|
16
|
-
- "extract method em método sem testes"
|
|
17
|
-
- "cap 22 Feathers"
|
|
18
|
-
- "bulleted method", "snarled method"
|
|
19
|
-
|
|
20
|
-
Carrega quando `legacy-characterization-tests` é insuficiente isoladamente — método é grande demais para enumerar inputs, mas precisa ser refatorado para ficar testável.
|
|
21
|
-
|
|
22
|
-
## Regras absolutas
|
|
23
|
-
|
|
24
|
-
- **Monster method = > 100 linhas OU > 5 níveis de aninhamento.** Pelo menos 1 dos dois. Heurística empírica do livro.
|
|
25
|
-
- **Distinguir bulleted vs snarled.** Bulleted = linhas longas mas planas (mais fácil). Snarled = nesting profundo, control flow complexo (mais difícil). Estratégias diferentes.
|
|
26
|
-
- **Scratch refactoring é DESCARTÁVEL.** Branch lixo, refactor estético para entender, depois `git checkout main` e descarte tudo. Só conhecimento adquirido viaja para a refatoração real.
|
|
27
|
-
- **Single-goal editing.** UMA mudança por commit/PR. Renomear OR extrair OR mover, NUNCA os 3 juntos. Cada commit é mecânico, isolado, revisável.
|
|
28
|
-
- **Extract method em monster sem teste = SAFE EXTRACTION.** Apenas levantar bloco contíguo + capturar variáveis usadas como parâmetros. SEM mover lógica entre escopos. SEM mudar comportamento. Lê → executa → escreve idênticamente antes/depois.
|
|
29
|
-
- **Compilação verde após CADA commit.** Passo grande demais = você não está fazendo single-goal. Quebre menor.
|
|
30
|
-
- **Não introduza tests "no meio" do refactor.** Refactor preserva comportamento → mesmo set de tests roda verde antes/durante/depois. Test novo significa novo comportamento (PR diferente).
|
|
31
|
-
- **Pequenos testes em método extraído saem GRÁTIS.** Após extract method, o método extraído é menor, tipicamente puro. Test acumulado durante refactor incremental.
|
|
32
|
-
|
|
33
|
-
## Patterns canônicos
|
|
34
|
-
|
|
35
|
-
### Pattern 1: Bulleted vs Snarled — diagnóstico
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
BULLETED METHOD
|
|
39
|
-
===============
|
|
40
|
-
public function processOrder(order) {
|
|
41
|
-
validateOrder(order)
|
|
42
|
-
computeTotals(order)
|
|
43
|
-
applyDiscounts(order)
|
|
44
|
-
saveOrder(order)
|
|
45
|
-
notifyCustomer(order)
|
|
46
|
-
publishEvent(order)
|
|
47
|
-
updateAnalytics(order)
|
|
48
|
-
returnReceipt(order)
|
|
49
|
-
}
|
|
50
|
-
↑ 8 linhas, todas no mesmo nível. Cada linha é praticamente extract-method-ready.
|
|
51
|
-
|
|
52
|
-
SNARLED METHOD
|
|
53
|
-
==============
|
|
54
|
-
public function processOrder(order) {
|
|
55
|
-
if (order != null) {
|
|
56
|
-
if (order.items != null && order.items.length > 0) {
|
|
57
|
-
var total = 0
|
|
58
|
-
for (var item of order.items) {
|
|
59
|
-
if (item.discount) {
|
|
60
|
-
if (item.discount.type === 'percent') {
|
|
61
|
-
total += item.price * (1 - item.discount.value / 100) * item.qty
|
|
62
|
-
} else if (item.discount.type === 'fixed') {
|
|
63
|
-
total += (item.price - item.discount.value) * item.qty
|
|
64
|
-
} else {
|
|
65
|
-
total += item.price * item.qty
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
total += item.price * item.qty
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// ... mais 80 linhas com nesting similar
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
↑ control flow profundamente aninhado. Extract method não é trivial — variáveis cruzam scopes.
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Diagnóstico:**
|
|
79
|
-
- Conte linhas no mesmo nível de indentação. Se > 70% das linhas estão no nível 1-2 → BULLETED.
|
|
80
|
-
- Conte profundidade máxima de aninhamento. Se ≥ 5 → SNARLED.
|
|
81
|
-
|
|
82
|
-
**Estratégias divergem** — abaixo.
|
|
83
|
-
|
|
84
|
-
### Pattern 2: Scratch refactoring (cap 22)
|
|
85
|
-
|
|
86
|
-
Refactor descartável para ENTENDER o método antes de mudá-lo.
|
|
87
|
-
|
|
88
|
-
```text
|
|
89
|
-
1. Cria branch `scratch/<method-name>-explore`
|
|
90
|
-
2. Refatora à vontade — extract aleatório, rename especulativo,
|
|
91
|
-
reformata estética. Goal: tornar legível para você.
|
|
92
|
-
3. Lê o resultado, entende o que método faz.
|
|
93
|
-
4. NÃO commita nem PR-a essa branch.
|
|
94
|
-
5. `git checkout main`. Volta ao código original intocado.
|
|
95
|
-
6. AGORA você tem mental model. Refatoração real começa com
|
|
96
|
-
passos pequenos disciplinados (single-goal editing).
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**Por que descartável:** scratch é estética + quebra contracts. Manter seria perigoso. Só conhecimento adquirido vai para a real.
|
|
100
|
-
|
|
101
|
-
**Quando vale o tempo:** método > 200 linhas + você nunca tinha mexido nele. < 100 linhas raramente vale (lê direto resolve).
|
|
102
|
-
|
|
103
|
-
### Pattern 3: Single-goal editing — atomic refactoring
|
|
104
|
-
|
|
105
|
-
Cada commit faz UMA coisa. Disciplina absoluta.
|
|
106
|
-
|
|
107
|
-
```text
|
|
108
|
-
✓ COMMIT 1: rename method (mecânico, IDE-assisted)
|
|
109
|
-
- Antes: processOrder
|
|
110
|
-
- Depois: processOrderLegacy
|
|
111
|
-
- Diff: rename refactor automático
|
|
112
|
-
|
|
113
|
-
✓ COMMIT 2: extract method (mecânico)
|
|
114
|
-
- Antes: linhas 42-58 inline em processOrderLegacy
|
|
115
|
-
- Depois: extracted como computeTotals(items)
|
|
116
|
-
- Diff: 17 linhas → 1 chamada
|
|
117
|
-
|
|
118
|
-
✓ COMMIT 3: extract method (mecânico)
|
|
119
|
-
- Antes: linhas 60-85 inline
|
|
120
|
-
- Depois: extracted como applyDiscounts(items, discounts)
|
|
121
|
-
- Diff: 26 linhas → 1 chamada
|
|
122
|
-
|
|
123
|
-
✗ COMMIT NÃO-FEITO: extract + rename + add validation
|
|
124
|
-
Múltiplos goals num commit = no single-goal. Difícil revisar.
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
**Princípio:** cada commit é REVERTIBLE individualmente. Se PR3 tem bug, revert PR3 só, não a sequência inteira.
|
|
128
|
-
|
|
129
|
-
### Pattern 4: Safe extraction em método sem testes
|
|
130
|
-
|
|
131
|
-
Extract method preservando comportamento sem ter test que valide.
|
|
132
|
-
|
|
133
|
-
```text
|
|
134
|
-
SAFE EXTRACTION CHECKLIST
|
|
135
|
-
=========================
|
|
136
|
-
□ Bloco a extrair é CONTÍGUO (sem control flow saindo do meio)
|
|
137
|
-
□ Variáveis lidas dentro do bloco mas declaradas fora → parâmetros
|
|
138
|
-
□ Variáveis ESCRITAS dentro do bloco mas usadas fora → return values
|
|
139
|
-
□ Bloco não tem `return`, `throw`, `break`, `continue` que afete escopo externo
|
|
140
|
-
(se tem: extract não é seguro, escolha menor)
|
|
141
|
-
□ Type signatures preservadas (parâmetros e retorno bem-tipados)
|
|
142
|
-
□ Comportamento idêntico — antes do extract: lê X, computa Y, escreve Z;
|
|
143
|
-
depois: chama método extraído que lê X, computa Y, escreve Z
|
|
144
|
-
|
|
145
|
-
PROCESSO MECÂNICO
|
|
146
|
-
=================
|
|
147
|
-
1. Selecionar bloco contíguo (10-30 linhas tipicamente)
|
|
148
|
-
2. Identificar variáveis lidas vs escritas no bloco
|
|
149
|
-
3. Lidas-mas-não-escritas → parâmetros (in)
|
|
150
|
-
4. Escritas-mas-usadas-fora → adicionar a return value (out)
|
|
151
|
-
5. IDE: extract method (Cmd+Opt+M no IntelliJ; "Refactor: Extract Function" no VS Code)
|
|
152
|
-
6. Compilar — verde?
|
|
153
|
-
7. Run smoke (qualquer comando manual que rodava antes) — comportamento idêntico?
|
|
154
|
-
8. Commit. Próximo bloco.
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**Insight:** safe extraction é seguro mesmo SEM testes porque é PURELY MECHANICAL. IDE faz com 100% de fidelidade. Risco residual = bug do IDE (raríssimo) ou erro humano em copiar manualmente (não use cópia manual — use extract automation).
|
|
158
|
-
|
|
159
|
-
### Pattern 5: Domando bulleted method
|
|
160
|
-
|
|
161
|
-
Bulleted é fácil. Workflow:
|
|
162
|
-
|
|
163
|
-
```text
|
|
164
|
-
1. Cada linha do método é uma "frase" → cada uma vira método extraído.
|
|
165
|
-
Antes: processOrder() com 30 linhas no nível 1
|
|
166
|
-
Depois: processOrder() com 8 chamadas, cada uma para um helper
|
|
167
|
-
|
|
168
|
-
2. Após extract, helpers são MENORES → tests acumulam grátis:
|
|
169
|
-
public computeTotals(order) {
|
|
170
|
-
// 8 linhas — testável isolado agora
|
|
171
|
-
}
|
|
172
|
-
public applyDiscounts(order, codes) {
|
|
173
|
-
// 12 linhas — testável isolado agora
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
3. Tests acumulados cobrem comportamento UNIT:
|
|
177
|
-
test('computeTotals — com 1 item', () => { ... })
|
|
178
|
-
test('computeTotals — com items vazios', () => { ... })
|
|
179
|
-
|
|
180
|
-
4. processOrder() agora é orquestração — test acaba sendo
|
|
181
|
-
integration tipo:
|
|
182
|
-
test('processOrder — happy path orchestration', () => {
|
|
183
|
-
processOrder(typicalOrder)
|
|
184
|
-
expect(saved).toBeDefined()
|
|
185
|
-
expect(notifications).toHaveLength(1)
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
5. Iterativamente, código vira pirâmide de tests bem definida.
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
**Esforço típico:** método 100 linhas bulleted → 6-8 commits, 1-2 dias para refactor + 5-10 testes acumulados grátis.
|
|
192
|
-
|
|
193
|
-
### Pattern 6: Domando snarled method
|
|
194
|
-
|
|
195
|
-
Snarled é difícil. Estratégia: APLAINAR primeiro, depois extrair.
|
|
196
|
-
|
|
197
|
-
```text
|
|
198
|
-
PASSO 1 — Achatar via guard clauses (early return)
|
|
199
|
-
Antes: Depois:
|
|
200
|
-
if (order != null) { if (order == null) return ERROR
|
|
201
|
-
if (order.items != null) { if (order.items == null) return ERROR
|
|
202
|
-
if (order.items.length > 0) { if (order.items.length === 0) return EMPTY
|
|
203
|
-
// 80 linhas // 80 linhas (agora sem nesting)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
↑ 3 níveis viraram 0 níveis. Pure mechanical. Sem mudança comportamental.
|
|
208
|
-
|
|
209
|
-
PASSO 2 — Extract method em loop interno
|
|
210
|
-
Antes: Depois:
|
|
211
|
-
for (item of items) { for (item of items) {
|
|
212
|
-
if (item.discount) { total += computeItemTotal(item)
|
|
213
|
-
if (item.discount.type === 'percent') { ↑ 1 chamada
|
|
214
|
-
total += item.price * (1 - ...) * item.qty
|
|
215
|
-
} else if (...) {
|
|
216
|
-
...
|
|
217
|
-
}
|
|
218
|
-
} else {
|
|
219
|
-
total += item.price * item.qty
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function computeItemTotal(item) {
|
|
224
|
-
if (!item.discount) return item.price * item.qty
|
|
225
|
-
if (item.discount.type === 'percent')
|
|
226
|
-
return item.price * (1 - item.discount.value / 100) * item.qty
|
|
227
|
-
if (item.discount.type === 'fixed')
|
|
228
|
-
return (item.price - item.discount.value) * item.qty
|
|
229
|
-
return item.price * item.qty
|
|
230
|
-
}
|
|
231
|
-
↑ Loop de 12 linhas → 3 linhas + função pura testável.
|
|
232
|
-
|
|
233
|
-
PASSO 3 — Iterar até nível máximo de aninhamento ≤ 2.
|
|
234
|
-
|
|
235
|
-
PASSO 4 — Tests no método extraído (puro, fácil de testar):
|
|
236
|
-
test('computeItemTotal — sem desconto', ...)
|
|
237
|
-
test('computeItemTotal — desconto percentual', ...)
|
|
238
|
-
test('computeItemTotal — desconto fixo', ...)
|
|
239
|
-
test('computeItemTotal — discount.type desconhecido', ...)
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
**Esforço típico:** método 150 linhas snarled → 12-20 commits, 3-7 dias para refactor + 15-25 testes acumulados.
|
|
243
|
-
|
|
244
|
-
### Pattern 7: Sequência canônica de tipos de single-goal commit
|
|
245
|
-
|
|
246
|
-
Em ordem do mais SEGURO ao mais ARRISCADO. Faça nessa ordem.
|
|
247
|
-
|
|
248
|
-
```text
|
|
249
|
-
SEGURO ↓
|
|
250
|
-
======================================================
|
|
251
|
-
1. RENAME (variable, method, class, file)
|
|
252
|
-
IDE-assisted, mecânico, comportamento idêntico
|
|
253
|
-
|
|
254
|
-
2. SAFE EXTRACTION (extract method/variable)
|
|
255
|
-
Selecione bloco contíguo, IDE extract, sem mover lógica entre scopes
|
|
256
|
-
|
|
257
|
-
3. MOVE METHOD (entre classes)
|
|
258
|
-
Apenas se método não usa state da origem (puro relativo à classe)
|
|
259
|
-
|
|
260
|
-
4. INTRODUCE PARAMETER OBJECT
|
|
261
|
-
Agrupar parâmetros relacionados em DTO. Mecânico.
|
|
262
|
-
|
|
263
|
-
5. INVERT DEPENDENCY (constructor injection)
|
|
264
|
-
New X() interno → recebe X externo. Quebra encapsulation, mas
|
|
265
|
-
comportamento permanece (se default-arg usado).
|
|
266
|
-
|
|
267
|
-
6. CHANGE METHOD SIGNATURE
|
|
268
|
-
Adicionar/remover parâmetro. Risk: callers podem passar wrong.
|
|
269
|
-
|
|
270
|
-
7. ALGORITHM REPLACEMENT
|
|
271
|
-
Mudar IMPLEMENTAÇÃO mantendo contrato. Risk médio — characterization
|
|
272
|
-
tests obrigatórios.
|
|
273
|
-
|
|
274
|
-
8. CONTRACT CHANGE
|
|
275
|
-
Mudar pre-condition/post-condition. Risk alto — todos os callers
|
|
276
|
-
precisam ser inspecionados.
|
|
277
|
-
↑ ARRISCADO
|
|
278
|
-
======================================================
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Stop em #5 ou #6 para refactor "limpa-e-vai". #7 e #8 = mudança comportamental, exigem characterization completa.
|
|
282
|
-
|
|
283
|
-
### Pattern 8: Effort budget de monster method
|
|
284
|
-
|
|
285
|
-
| Tamanho | Tipo | Esforço de refactor | Output esperado |
|
|
286
|
-
|---|---|---|---|
|
|
287
|
-
| 100-150 linhas, bulleted | extract methods + acumular tests | 1-2 dias | 5-10 helpers + 5-10 tests |
|
|
288
|
-
| 100-150 linhas, snarled | flatten + extract | 3-5 dias | 5-10 helpers + 10-15 tests |
|
|
289
|
-
| 150-300 linhas, bulleted | extract class após methods | 3-7 dias | 5-10 helpers + nova classe + 10-15 tests |
|
|
290
|
-
| 150-300 linhas, snarled | scratch refactor + flatten + extract | 1-2 semanas | classe + 15-25 tests |
|
|
291
|
-
| > 300 linhas | só com aprovação stakeholder, alocação dedicada | 2-4 semanas | reescrever via sprout class? |
|
|
292
|
-
|
|
293
|
-
**Heurística:** método > 300 linhas raramente vale refactor incremental. Considere sprout class — encapsular comportamento NOVO em classe nova, deixar legado intocado, eventually deprecate.
|
|
294
|
-
|
|
295
|
-
### Pattern 9: Cobertura emergente
|
|
296
|
-
|
|
297
|
-
Refactor de monster method NÃO precisa de characterization completa upfront se você usa safe extraction (mecânica). Mas teste se acumula:
|
|
298
|
-
|
|
299
|
-
```text
|
|
300
|
-
PRE-REFACTOR (T0)
|
|
301
|
-
Coverage: 0% (untested)
|
|
302
|
-
Confiança: baixa
|
|
303
|
-
|
|
304
|
-
DURANTE REFACTOR (T1-Tn)
|
|
305
|
-
Cada extract method → método extraído fica testável (puro/menor)
|
|
306
|
-
Adicione 1-3 unit tests do extracted antes de seguir
|
|
307
|
-
Coverage cresce 5-10% por extract
|
|
308
|
-
|
|
309
|
-
PÓS-REFACTOR (Tfinal)
|
|
310
|
-
Original 100 linhas → 1 método orquestrador + 8 helpers
|
|
311
|
-
Cobertura: 60-80% (helpers cobertos; orquestrador integration)
|
|
312
|
-
Confiança: alta
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
**Sem essa disciplina:** refactor termina, código mais limpo, mas cobertura ainda 0%. Próxima mudança volta ao mesmo dilema.
|
|
316
|
-
|
|
317
|
-
## Anti-patterns
|
|
318
|
-
|
|
319
|
-
### ANTI: refactor monstro em 1 PR
|
|
320
|
-
|
|
321
|
-
```text
|
|
322
|
-
ANTI: PR de 1500 linhas — extracted 12 methods + renamed 8 variables
|
|
323
|
-
+ moved 3 fields + fixed 2 bugs + adicionou 25 tests.
|
|
324
|
-
|
|
325
|
-
PROBLEMA: PR não-revisável. Reviewer aprova "no fé". CI verde diz
|
|
326
|
-
pouco — branch coverage caiu. Revert é all-or-nothing.
|
|
327
|
-
|
|
328
|
-
CERTO: 12-25 commits/PRs em sequência. Cada um single-goal,
|
|
329
|
-
≤ 100 linhas, mecânico, revertível, com proof of correctness
|
|
330
|
-
(compila + roda).
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### ANTI: misturar refactor + bug fix + feature
|
|
334
|
-
|
|
335
|
-
```text
|
|
336
|
-
ANTI: enquanto refatora, "ah esse if pode ser melhor", "esse loop
|
|
337
|
-
podia usar reduce", "ah aqui tem um bug, conserto".
|
|
338
|
-
|
|
339
|
-
PROBLEMA: você quebrou single-goal em ~5 lugares. Reviewer não consegue
|
|
340
|
-
identificar o que é refactor (preserva) vs bug fix (muda).
|
|
341
|
-
Se algo quebra, bisect aponta para PR mas não isola causa.
|
|
342
|
-
|
|
343
|
-
CERTO: anote bugs encontrados num arquivo `BUGS-FOUND.md`. Não
|
|
344
|
-
conserte agora. Após refactor terminar, faz PRs separados
|
|
345
|
-
para cada fix com test do comportamento correto.
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### ANTI: extract method + mover lógica
|
|
349
|
-
|
|
350
|
-
```text
|
|
351
|
-
ANTI: extract method, mas durante extraction "noto" que lógica é
|
|
352
|
-
melhor em outro escopo, então move pra lá durante o extract.
|
|
353
|
-
|
|
354
|
-
PROBLEMA: comportamento mudou. Não é mais SAFE extraction. Você
|
|
355
|
-
precisava de characterization mas pulou.
|
|
356
|
-
|
|
357
|
-
CERTO: 2 PRs sequenciais.
|
|
358
|
-
PR1 — extract method (idêntico, no mesmo escopo)
|
|
359
|
-
PR2 — move method (com test que valida em ambos os contextos)
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
### ANTI: scratch refactoring committed
|
|
363
|
-
|
|
364
|
-
```text
|
|
365
|
-
ANTI: scratch ficou bom, mantenho-o em PR.
|
|
366
|
-
|
|
367
|
-
PROBLEMA: scratch fez mudanças não-mecânicas (estéticas, especulativas).
|
|
368
|
-
Sem characterization, não há prova de comportamento idêntico.
|
|
369
|
-
Você acabou de fazer "edit and pray" disfarçado de refactor.
|
|
370
|
-
|
|
371
|
-
CERTO: scratch é descartável. SEMPRE. Real refactor recomeça do
|
|
372
|
-
código original com passos disciplinados.
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### ANTI: extract APENAS para mais legibilidade, sem reduzir tamanho
|
|
376
|
-
|
|
377
|
-
```text
|
|
378
|
-
ANTI: extract de 1 linha para método com nome descritivo "para ficar
|
|
379
|
-
mais claro". Original tinha 200 linhas, agora tem 195 + 1 linha
|
|
380
|
-
em método novo.
|
|
381
|
-
|
|
382
|
-
PROBLEMA: 5 minutos para reviewer entender a chamada extra. Tamanho
|
|
383
|
-
do monster diminuiu 1%. Trade desfavorável.
|
|
384
|
-
|
|
385
|
-
CERTO: extract de 10-30 linhas mínimo. Bloco coeso e separável,
|
|
386
|
-
não single statement. Linha solta com nome longo é
|
|
387
|
-
refactoring teatral.
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### ANTI: tentar testar tudo upfront
|
|
391
|
-
|
|
392
|
-
```text
|
|
393
|
-
ANTI: "vou characterize completo das 200 linhas em todos os 30
|
|
394
|
-
inputs antes de tocar uma vírgula".
|
|
395
|
-
|
|
396
|
-
PROBLEMA: 200 linhas com 30 inputs = 1-2 semanas de characterization.
|
|
397
|
-
Stakeholder cancela. Refactor nunca acontece. Status quo
|
|
398
|
-
eterno.
|
|
399
|
-
|
|
400
|
-
CERTO: characterization MÍNIMA viável (5-10 inputs nos pontos óbvios).
|
|
401
|
-
Refactor mecânico (safe extraction, rename) que PRESERVA
|
|
402
|
-
comportamento. Acumula testes em helpers extraídos. Cobertura
|
|
403
|
-
emerge organicamente.
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
## Verificação
|
|
407
|
-
|
|
408
|
-
Antes de declarar refactor de monster method completo:
|
|
409
|
-
|
|
410
|
-
1. **Tipo identificado** — bulleted vs snarled
|
|
411
|
-
2. **Tamanho original < 100 linhas após refactor** — se ainda > 100, refactor não terminou
|
|
412
|
-
3. **Cada commit é single-goal** — rename OR extract OR move, nunca múltiplos
|
|
413
|
-
4. **Compilação verde a cada commit** — passos pequenos, mecânicos
|
|
414
|
-
5. **Smoke run após cada commit** — comportamento preservado
|
|
415
|
-
6. **Tests acumulados nos helpers extraídos** — coverage cresceu de 0% para ≥ 50%
|
|
416
|
-
7. **Bugs encontrados anotados, NÃO consertados durante refactor** — fix em PRs separados
|
|
417
|
-
8. **Sem scratch committed** — só conhecimento adquirido viajou
|
|
418
|
-
|
|
419
|
-
## Limiar de "pronto para feature change pós-refactor"
|
|
420
|
-
|
|
421
|
-
```text
|
|
422
|
-
Linhas do método principal: ≤ 100 (idealmente ≤ 50)
|
|
423
|
-
Profundidade máxima de aninhamento: ≤ 3
|
|
424
|
-
Helpers extraídos: 5-15 (cada ≤ 30 linhas)
|
|
425
|
-
Coverage do método principal: ≥ 50%
|
|
426
|
-
Coverage dos helpers: ≥ 70%
|
|
427
|
-
Bugs encontrados: anotados em BUGS-FOUND.md
|
|
428
|
-
PRs: cada single-goal, ≤ 100 linhas, revertíveis
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
Atingidos? Agora a feature change pode acontecer com confiança normal de TDD.
|
|
432
|
-
|
|
433
|
-
---
|
|
434
|
-
|
|
435
|
-
## Ver também
|
|
436
|
-
|
|
437
|
-
- [`_shared-legacy/glossary.md`](../_shared-legacy/glossary.md) — vocabulário (monster method, bulleted vs snarled, scratch, single-goal, safe extraction)
|
|
438
|
-
- [`legacy-characterization-tests`](../legacy-characterization-tests/SKILL.md) — para mudanças COMPORTAMENTAIS, characterization é obrigatório (não basta safe extraction)
|
|
439
|
-
- [`legacy-seams-and-test-harness`](../legacy-seams-and-test-harness/SKILL.md) — break-deps é pré-requisito quando helpers extraídos têm I/O
|
|
440
|
-
- [`legacy-effect-analysis`](../legacy-effect-analysis/SKILL.md) — sketch dentro do monster ajuda a escolher onde extrair
|
|
441
|
-
- [`legacy-sprout-wrap-techniques`](../legacy-sprout-wrap-techniques/SKILL.md) — quando monster > 300 linhas, sprout class para novo comportamento sem refatorar
|
|
442
|
-
- [`pre-refactor-characterization`](../pre-refactor-characterization/SKILL.md) — gate distingue safe extraction (livre) de behavioral change (requer characterization)
|
|
443
|
-
|
|
444
|
-
*Material-fonte: Working Effectively with Legacy Code — Feathers, 2004 — Cap 22: "I Need to Change a Monster Method and I Can't Write Tests for It".*
|
|
1
|
+
---
|
|
2
|
+
name: legacy-monster-methods
|
|
3
|
+
description: Use ao refatorar método > 100 linhas sem testes — scratch refactoring, single-goal editing, safe extraction (cap 22 Feathers). Padrões para domesticar bulleted vs snarled methods.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Legacy — Monster Methods
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando o user encontra método absurdamente longo (> 100 linhas) que precisa ser modificado. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "esse método tem [N] linhas, preciso mudar"
|
|
13
|
+
- "monster method", "método monstro", "método gigante"
|
|
14
|
+
- "scratch refactoring", "refactor de rascunho"
|
|
15
|
+
- "single-goal editing", "edição com objetivo único"
|
|
16
|
+
- "extract method em método sem testes"
|
|
17
|
+
- "cap 22 Feathers"
|
|
18
|
+
- "bulleted method", "snarled method"
|
|
19
|
+
|
|
20
|
+
Carrega quando `legacy-characterization-tests` é insuficiente isoladamente — método é grande demais para enumerar inputs, mas precisa ser refatorado para ficar testável.
|
|
21
|
+
|
|
22
|
+
## Regras absolutas
|
|
23
|
+
|
|
24
|
+
- **Monster method = > 100 linhas OU > 5 níveis de aninhamento.** Pelo menos 1 dos dois. Heurística empírica do livro.
|
|
25
|
+
- **Distinguir bulleted vs snarled.** Bulleted = linhas longas mas planas (mais fácil). Snarled = nesting profundo, control flow complexo (mais difícil). Estratégias diferentes.
|
|
26
|
+
- **Scratch refactoring é DESCARTÁVEL.** Branch lixo, refactor estético para entender, depois `git checkout main` e descarte tudo. Só conhecimento adquirido viaja para a refatoração real.
|
|
27
|
+
- **Single-goal editing.** UMA mudança por commit/PR. Renomear OR extrair OR mover, NUNCA os 3 juntos. Cada commit é mecânico, isolado, revisável.
|
|
28
|
+
- **Extract method em monster sem teste = SAFE EXTRACTION.** Apenas levantar bloco contíguo + capturar variáveis usadas como parâmetros. SEM mover lógica entre escopos. SEM mudar comportamento. Lê → executa → escreve idênticamente antes/depois.
|
|
29
|
+
- **Compilação verde após CADA commit.** Passo grande demais = você não está fazendo single-goal. Quebre menor.
|
|
30
|
+
- **Não introduza tests "no meio" do refactor.** Refactor preserva comportamento → mesmo set de tests roda verde antes/durante/depois. Test novo significa novo comportamento (PR diferente).
|
|
31
|
+
- **Pequenos testes em método extraído saem GRÁTIS.** Após extract method, o método extraído é menor, tipicamente puro. Test acumulado durante refactor incremental.
|
|
32
|
+
|
|
33
|
+
## Patterns canônicos
|
|
34
|
+
|
|
35
|
+
### Pattern 1: Bulleted vs Snarled — diagnóstico
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
BULLETED METHOD
|
|
39
|
+
===============
|
|
40
|
+
public function processOrder(order) {
|
|
41
|
+
validateOrder(order)
|
|
42
|
+
computeTotals(order)
|
|
43
|
+
applyDiscounts(order)
|
|
44
|
+
saveOrder(order)
|
|
45
|
+
notifyCustomer(order)
|
|
46
|
+
publishEvent(order)
|
|
47
|
+
updateAnalytics(order)
|
|
48
|
+
returnReceipt(order)
|
|
49
|
+
}
|
|
50
|
+
↑ 8 linhas, todas no mesmo nível. Cada linha é praticamente extract-method-ready.
|
|
51
|
+
|
|
52
|
+
SNARLED METHOD
|
|
53
|
+
==============
|
|
54
|
+
public function processOrder(order) {
|
|
55
|
+
if (order != null) {
|
|
56
|
+
if (order.items != null && order.items.length > 0) {
|
|
57
|
+
var total = 0
|
|
58
|
+
for (var item of order.items) {
|
|
59
|
+
if (item.discount) {
|
|
60
|
+
if (item.discount.type === 'percent') {
|
|
61
|
+
total += item.price * (1 - item.discount.value / 100) * item.qty
|
|
62
|
+
} else if (item.discount.type === 'fixed') {
|
|
63
|
+
total += (item.price - item.discount.value) * item.qty
|
|
64
|
+
} else {
|
|
65
|
+
total += item.price * item.qty
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
total += item.price * item.qty
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// ... mais 80 linhas com nesting similar
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
↑ control flow profundamente aninhado. Extract method não é trivial — variáveis cruzam scopes.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Diagnóstico:**
|
|
79
|
+
- Conte linhas no mesmo nível de indentação. Se > 70% das linhas estão no nível 1-2 → BULLETED.
|
|
80
|
+
- Conte profundidade máxima de aninhamento. Se ≥ 5 → SNARLED.
|
|
81
|
+
|
|
82
|
+
**Estratégias divergem** — abaixo.
|
|
83
|
+
|
|
84
|
+
### Pattern 2: Scratch refactoring (cap 22)
|
|
85
|
+
|
|
86
|
+
Refactor descartável para ENTENDER o método antes de mudá-lo.
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
1. Cria branch `scratch/<method-name>-explore`
|
|
90
|
+
2. Refatora à vontade — extract aleatório, rename especulativo,
|
|
91
|
+
reformata estética. Goal: tornar legível para você.
|
|
92
|
+
3. Lê o resultado, entende o que método faz.
|
|
93
|
+
4. NÃO commita nem PR-a essa branch.
|
|
94
|
+
5. `git checkout main`. Volta ao código original intocado.
|
|
95
|
+
6. AGORA você tem mental model. Refatoração real começa com
|
|
96
|
+
passos pequenos disciplinados (single-goal editing).
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Por que descartável:** scratch é estética + quebra contracts. Manter seria perigoso. Só conhecimento adquirido vai para a real.
|
|
100
|
+
|
|
101
|
+
**Quando vale o tempo:** método > 200 linhas + você nunca tinha mexido nele. < 100 linhas raramente vale (lê direto resolve).
|
|
102
|
+
|
|
103
|
+
### Pattern 3: Single-goal editing — atomic refactoring
|
|
104
|
+
|
|
105
|
+
Cada commit faz UMA coisa. Disciplina absoluta.
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
✓ COMMIT 1: rename method (mecânico, IDE-assisted)
|
|
109
|
+
- Antes: processOrder
|
|
110
|
+
- Depois: processOrderLegacy
|
|
111
|
+
- Diff: rename refactor automático
|
|
112
|
+
|
|
113
|
+
✓ COMMIT 2: extract method (mecânico)
|
|
114
|
+
- Antes: linhas 42-58 inline em processOrderLegacy
|
|
115
|
+
- Depois: extracted como computeTotals(items)
|
|
116
|
+
- Diff: 17 linhas → 1 chamada
|
|
117
|
+
|
|
118
|
+
✓ COMMIT 3: extract method (mecânico)
|
|
119
|
+
- Antes: linhas 60-85 inline
|
|
120
|
+
- Depois: extracted como applyDiscounts(items, discounts)
|
|
121
|
+
- Diff: 26 linhas → 1 chamada
|
|
122
|
+
|
|
123
|
+
✗ COMMIT NÃO-FEITO: extract + rename + add validation
|
|
124
|
+
Múltiplos goals num commit = no single-goal. Difícil revisar.
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Princípio:** cada commit é REVERTIBLE individualmente. Se PR3 tem bug, revert PR3 só, não a sequência inteira.
|
|
128
|
+
|
|
129
|
+
### Pattern 4: Safe extraction em método sem testes
|
|
130
|
+
|
|
131
|
+
Extract method preservando comportamento sem ter test que valide.
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
SAFE EXTRACTION CHECKLIST
|
|
135
|
+
=========================
|
|
136
|
+
□ Bloco a extrair é CONTÍGUO (sem control flow saindo do meio)
|
|
137
|
+
□ Variáveis lidas dentro do bloco mas declaradas fora → parâmetros
|
|
138
|
+
□ Variáveis ESCRITAS dentro do bloco mas usadas fora → return values
|
|
139
|
+
□ Bloco não tem `return`, `throw`, `break`, `continue` que afete escopo externo
|
|
140
|
+
(se tem: extract não é seguro, escolha menor)
|
|
141
|
+
□ Type signatures preservadas (parâmetros e retorno bem-tipados)
|
|
142
|
+
□ Comportamento idêntico — antes do extract: lê X, computa Y, escreve Z;
|
|
143
|
+
depois: chama método extraído que lê X, computa Y, escreve Z
|
|
144
|
+
|
|
145
|
+
PROCESSO MECÂNICO
|
|
146
|
+
=================
|
|
147
|
+
1. Selecionar bloco contíguo (10-30 linhas tipicamente)
|
|
148
|
+
2. Identificar variáveis lidas vs escritas no bloco
|
|
149
|
+
3. Lidas-mas-não-escritas → parâmetros (in)
|
|
150
|
+
4. Escritas-mas-usadas-fora → adicionar a return value (out)
|
|
151
|
+
5. IDE: extract method (Cmd+Opt+M no IntelliJ; "Refactor: Extract Function" no VS Code)
|
|
152
|
+
6. Compilar — verde?
|
|
153
|
+
7. Run smoke (qualquer comando manual que rodava antes) — comportamento idêntico?
|
|
154
|
+
8. Commit. Próximo bloco.
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Insight:** safe extraction é seguro mesmo SEM testes porque é PURELY MECHANICAL. IDE faz com 100% de fidelidade. Risco residual = bug do IDE (raríssimo) ou erro humano em copiar manualmente (não use cópia manual — use extract automation).
|
|
158
|
+
|
|
159
|
+
### Pattern 5: Domando bulleted method
|
|
160
|
+
|
|
161
|
+
Bulleted é fácil. Workflow:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
1. Cada linha do método é uma "frase" → cada uma vira método extraído.
|
|
165
|
+
Antes: processOrder() com 30 linhas no nível 1
|
|
166
|
+
Depois: processOrder() com 8 chamadas, cada uma para um helper
|
|
167
|
+
|
|
168
|
+
2. Após extract, helpers são MENORES → tests acumulam grátis:
|
|
169
|
+
public computeTotals(order) {
|
|
170
|
+
// 8 linhas — testável isolado agora
|
|
171
|
+
}
|
|
172
|
+
public applyDiscounts(order, codes) {
|
|
173
|
+
// 12 linhas — testável isolado agora
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
3. Tests acumulados cobrem comportamento UNIT:
|
|
177
|
+
test('computeTotals — com 1 item', () => { ... })
|
|
178
|
+
test('computeTotals — com items vazios', () => { ... })
|
|
179
|
+
|
|
180
|
+
4. processOrder() agora é orquestração — test acaba sendo
|
|
181
|
+
integration tipo:
|
|
182
|
+
test('processOrder — happy path orchestration', () => {
|
|
183
|
+
processOrder(typicalOrder)
|
|
184
|
+
expect(saved).toBeDefined()
|
|
185
|
+
expect(notifications).toHaveLength(1)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
5. Iterativamente, código vira pirâmide de tests bem definida.
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Esforço típico:** método 100 linhas bulleted → 6-8 commits, 1-2 dias para refactor + 5-10 testes acumulados grátis.
|
|
192
|
+
|
|
193
|
+
### Pattern 6: Domando snarled method
|
|
194
|
+
|
|
195
|
+
Snarled é difícil. Estratégia: APLAINAR primeiro, depois extrair.
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
PASSO 1 — Achatar via guard clauses (early return)
|
|
199
|
+
Antes: Depois:
|
|
200
|
+
if (order != null) { if (order == null) return ERROR
|
|
201
|
+
if (order.items != null) { if (order.items == null) return ERROR
|
|
202
|
+
if (order.items.length > 0) { if (order.items.length === 0) return EMPTY
|
|
203
|
+
// 80 linhas // 80 linhas (agora sem nesting)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
↑ 3 níveis viraram 0 níveis. Pure mechanical. Sem mudança comportamental.
|
|
208
|
+
|
|
209
|
+
PASSO 2 — Extract method em loop interno
|
|
210
|
+
Antes: Depois:
|
|
211
|
+
for (item of items) { for (item of items) {
|
|
212
|
+
if (item.discount) { total += computeItemTotal(item)
|
|
213
|
+
if (item.discount.type === 'percent') { ↑ 1 chamada
|
|
214
|
+
total += item.price * (1 - ...) * item.qty
|
|
215
|
+
} else if (...) {
|
|
216
|
+
...
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
total += item.price * item.qty
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function computeItemTotal(item) {
|
|
224
|
+
if (!item.discount) return item.price * item.qty
|
|
225
|
+
if (item.discount.type === 'percent')
|
|
226
|
+
return item.price * (1 - item.discount.value / 100) * item.qty
|
|
227
|
+
if (item.discount.type === 'fixed')
|
|
228
|
+
return (item.price - item.discount.value) * item.qty
|
|
229
|
+
return item.price * item.qty
|
|
230
|
+
}
|
|
231
|
+
↑ Loop de 12 linhas → 3 linhas + função pura testável.
|
|
232
|
+
|
|
233
|
+
PASSO 3 — Iterar até nível máximo de aninhamento ≤ 2.
|
|
234
|
+
|
|
235
|
+
PASSO 4 — Tests no método extraído (puro, fácil de testar):
|
|
236
|
+
test('computeItemTotal — sem desconto', ...)
|
|
237
|
+
test('computeItemTotal — desconto percentual', ...)
|
|
238
|
+
test('computeItemTotal — desconto fixo', ...)
|
|
239
|
+
test('computeItemTotal — discount.type desconhecido', ...)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Esforço típico:** método 150 linhas snarled → 12-20 commits, 3-7 dias para refactor + 15-25 testes acumulados.
|
|
243
|
+
|
|
244
|
+
### Pattern 7: Sequência canônica de tipos de single-goal commit
|
|
245
|
+
|
|
246
|
+
Em ordem do mais SEGURO ao mais ARRISCADO. Faça nessa ordem.
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
SEGURO ↓
|
|
250
|
+
======================================================
|
|
251
|
+
1. RENAME (variable, method, class, file)
|
|
252
|
+
IDE-assisted, mecânico, comportamento idêntico
|
|
253
|
+
|
|
254
|
+
2. SAFE EXTRACTION (extract method/variable)
|
|
255
|
+
Selecione bloco contíguo, IDE extract, sem mover lógica entre scopes
|
|
256
|
+
|
|
257
|
+
3. MOVE METHOD (entre classes)
|
|
258
|
+
Apenas se método não usa state da origem (puro relativo à classe)
|
|
259
|
+
|
|
260
|
+
4. INTRODUCE PARAMETER OBJECT
|
|
261
|
+
Agrupar parâmetros relacionados em DTO. Mecânico.
|
|
262
|
+
|
|
263
|
+
5. INVERT DEPENDENCY (constructor injection)
|
|
264
|
+
New X() interno → recebe X externo. Quebra encapsulation, mas
|
|
265
|
+
comportamento permanece (se default-arg usado).
|
|
266
|
+
|
|
267
|
+
6. CHANGE METHOD SIGNATURE
|
|
268
|
+
Adicionar/remover parâmetro. Risk: callers podem passar wrong.
|
|
269
|
+
|
|
270
|
+
7. ALGORITHM REPLACEMENT
|
|
271
|
+
Mudar IMPLEMENTAÇÃO mantendo contrato. Risk médio — characterization
|
|
272
|
+
tests obrigatórios.
|
|
273
|
+
|
|
274
|
+
8. CONTRACT CHANGE
|
|
275
|
+
Mudar pre-condition/post-condition. Risk alto — todos os callers
|
|
276
|
+
precisam ser inspecionados.
|
|
277
|
+
↑ ARRISCADO
|
|
278
|
+
======================================================
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Stop em #5 ou #6 para refactor "limpa-e-vai". #7 e #8 = mudança comportamental, exigem characterization completa.
|
|
282
|
+
|
|
283
|
+
### Pattern 8: Effort budget de monster method
|
|
284
|
+
|
|
285
|
+
| Tamanho | Tipo | Esforço de refactor | Output esperado |
|
|
286
|
+
|---|---|---|---|
|
|
287
|
+
| 100-150 linhas, bulleted | extract methods + acumular tests | 1-2 dias | 5-10 helpers + 5-10 tests |
|
|
288
|
+
| 100-150 linhas, snarled | flatten + extract | 3-5 dias | 5-10 helpers + 10-15 tests |
|
|
289
|
+
| 150-300 linhas, bulleted | extract class após methods | 3-7 dias | 5-10 helpers + nova classe + 10-15 tests |
|
|
290
|
+
| 150-300 linhas, snarled | scratch refactor + flatten + extract | 1-2 semanas | classe + 15-25 tests |
|
|
291
|
+
| > 300 linhas | só com aprovação stakeholder, alocação dedicada | 2-4 semanas | reescrever via sprout class? |
|
|
292
|
+
|
|
293
|
+
**Heurística:** método > 300 linhas raramente vale refactor incremental. Considere sprout class — encapsular comportamento NOVO em classe nova, deixar legado intocado, eventually deprecate.
|
|
294
|
+
|
|
295
|
+
### Pattern 9: Cobertura emergente
|
|
296
|
+
|
|
297
|
+
Refactor de monster method NÃO precisa de characterization completa upfront se você usa safe extraction (mecânica). Mas teste se acumula:
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
PRE-REFACTOR (T0)
|
|
301
|
+
Coverage: 0% (untested)
|
|
302
|
+
Confiança: baixa
|
|
303
|
+
|
|
304
|
+
DURANTE REFACTOR (T1-Tn)
|
|
305
|
+
Cada extract method → método extraído fica testável (puro/menor)
|
|
306
|
+
Adicione 1-3 unit tests do extracted antes de seguir
|
|
307
|
+
Coverage cresce 5-10% por extract
|
|
308
|
+
|
|
309
|
+
PÓS-REFACTOR (Tfinal)
|
|
310
|
+
Original 100 linhas → 1 método orquestrador + 8 helpers
|
|
311
|
+
Cobertura: 60-80% (helpers cobertos; orquestrador integration)
|
|
312
|
+
Confiança: alta
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Sem essa disciplina:** refactor termina, código mais limpo, mas cobertura ainda 0%. Próxima mudança volta ao mesmo dilema.
|
|
316
|
+
|
|
317
|
+
## Anti-patterns
|
|
318
|
+
|
|
319
|
+
### ANTI: refactor monstro em 1 PR
|
|
320
|
+
|
|
321
|
+
```text
|
|
322
|
+
ANTI: PR de 1500 linhas — extracted 12 methods + renamed 8 variables
|
|
323
|
+
+ moved 3 fields + fixed 2 bugs + adicionou 25 tests.
|
|
324
|
+
|
|
325
|
+
PROBLEMA: PR não-revisável. Reviewer aprova "no fé". CI verde diz
|
|
326
|
+
pouco — branch coverage caiu. Revert é all-or-nothing.
|
|
327
|
+
|
|
328
|
+
CERTO: 12-25 commits/PRs em sequência. Cada um single-goal,
|
|
329
|
+
≤ 100 linhas, mecânico, revertível, com proof of correctness
|
|
330
|
+
(compila + roda).
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### ANTI: misturar refactor + bug fix + feature
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
ANTI: enquanto refatora, "ah esse if pode ser melhor", "esse loop
|
|
337
|
+
podia usar reduce", "ah aqui tem um bug, conserto".
|
|
338
|
+
|
|
339
|
+
PROBLEMA: você quebrou single-goal em ~5 lugares. Reviewer não consegue
|
|
340
|
+
identificar o que é refactor (preserva) vs bug fix (muda).
|
|
341
|
+
Se algo quebra, bisect aponta para PR mas não isola causa.
|
|
342
|
+
|
|
343
|
+
CERTO: anote bugs encontrados num arquivo `BUGS-FOUND.md`. Não
|
|
344
|
+
conserte agora. Após refactor terminar, faz PRs separados
|
|
345
|
+
para cada fix com test do comportamento correto.
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### ANTI: extract method + mover lógica
|
|
349
|
+
|
|
350
|
+
```text
|
|
351
|
+
ANTI: extract method, mas durante extraction "noto" que lógica é
|
|
352
|
+
melhor em outro escopo, então move pra lá durante o extract.
|
|
353
|
+
|
|
354
|
+
PROBLEMA: comportamento mudou. Não é mais SAFE extraction. Você
|
|
355
|
+
precisava de characterization mas pulou.
|
|
356
|
+
|
|
357
|
+
CERTO: 2 PRs sequenciais.
|
|
358
|
+
PR1 — extract method (idêntico, no mesmo escopo)
|
|
359
|
+
PR2 — move method (com test que valida em ambos os contextos)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### ANTI: scratch refactoring committed
|
|
363
|
+
|
|
364
|
+
```text
|
|
365
|
+
ANTI: scratch ficou bom, mantenho-o em PR.
|
|
366
|
+
|
|
367
|
+
PROBLEMA: scratch fez mudanças não-mecânicas (estéticas, especulativas).
|
|
368
|
+
Sem characterization, não há prova de comportamento idêntico.
|
|
369
|
+
Você acabou de fazer "edit and pray" disfarçado de refactor.
|
|
370
|
+
|
|
371
|
+
CERTO: scratch é descartável. SEMPRE. Real refactor recomeça do
|
|
372
|
+
código original com passos disciplinados.
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### ANTI: extract APENAS para mais legibilidade, sem reduzir tamanho
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
ANTI: extract de 1 linha para método com nome descritivo "para ficar
|
|
379
|
+
mais claro". Original tinha 200 linhas, agora tem 195 + 1 linha
|
|
380
|
+
em método novo.
|
|
381
|
+
|
|
382
|
+
PROBLEMA: 5 minutos para reviewer entender a chamada extra. Tamanho
|
|
383
|
+
do monster diminuiu 1%. Trade desfavorável.
|
|
384
|
+
|
|
385
|
+
CERTO: extract de 10-30 linhas mínimo. Bloco coeso e separável,
|
|
386
|
+
não single statement. Linha solta com nome longo é
|
|
387
|
+
refactoring teatral.
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### ANTI: tentar testar tudo upfront
|
|
391
|
+
|
|
392
|
+
```text
|
|
393
|
+
ANTI: "vou characterize completo das 200 linhas em todos os 30
|
|
394
|
+
inputs antes de tocar uma vírgula".
|
|
395
|
+
|
|
396
|
+
PROBLEMA: 200 linhas com 30 inputs = 1-2 semanas de characterization.
|
|
397
|
+
Stakeholder cancela. Refactor nunca acontece. Status quo
|
|
398
|
+
eterno.
|
|
399
|
+
|
|
400
|
+
CERTO: characterization MÍNIMA viável (5-10 inputs nos pontos óbvios).
|
|
401
|
+
Refactor mecânico (safe extraction, rename) que PRESERVA
|
|
402
|
+
comportamento. Acumula testes em helpers extraídos. Cobertura
|
|
403
|
+
emerge organicamente.
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Verificação
|
|
407
|
+
|
|
408
|
+
Antes de declarar refactor de monster method completo:
|
|
409
|
+
|
|
410
|
+
1. **Tipo identificado** — bulleted vs snarled
|
|
411
|
+
2. **Tamanho original < 100 linhas após refactor** — se ainda > 100, refactor não terminou
|
|
412
|
+
3. **Cada commit é single-goal** — rename OR extract OR move, nunca múltiplos
|
|
413
|
+
4. **Compilação verde a cada commit** — passos pequenos, mecânicos
|
|
414
|
+
5. **Smoke run após cada commit** — comportamento preservado
|
|
415
|
+
6. **Tests acumulados nos helpers extraídos** — coverage cresceu de 0% para ≥ 50%
|
|
416
|
+
7. **Bugs encontrados anotados, NÃO consertados durante refactor** — fix em PRs separados
|
|
417
|
+
8. **Sem scratch committed** — só conhecimento adquirido viajou
|
|
418
|
+
|
|
419
|
+
## Limiar de "pronto para feature change pós-refactor"
|
|
420
|
+
|
|
421
|
+
```text
|
|
422
|
+
Linhas do método principal: ≤ 100 (idealmente ≤ 50)
|
|
423
|
+
Profundidade máxima de aninhamento: ≤ 3
|
|
424
|
+
Helpers extraídos: 5-15 (cada ≤ 30 linhas)
|
|
425
|
+
Coverage do método principal: ≥ 50%
|
|
426
|
+
Coverage dos helpers: ≥ 70%
|
|
427
|
+
Bugs encontrados: anotados em BUGS-FOUND.md
|
|
428
|
+
PRs: cada single-goal, ≤ 100 linhas, revertíveis
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Atingidos? Agora a feature change pode acontecer com confiança normal de TDD.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Ver também
|
|
436
|
+
|
|
437
|
+
- [`_shared-legacy/glossary.md`](../_shared-legacy/glossary.md) — vocabulário (monster method, bulleted vs snarled, scratch, single-goal, safe extraction)
|
|
438
|
+
- [`legacy-characterization-tests`](../legacy-characterization-tests/SKILL.md) — para mudanças COMPORTAMENTAIS, characterization é obrigatório (não basta safe extraction)
|
|
439
|
+
- [`legacy-seams-and-test-harness`](../legacy-seams-and-test-harness/SKILL.md) — break-deps é pré-requisito quando helpers extraídos têm I/O
|
|
440
|
+
- [`legacy-effect-analysis`](../legacy-effect-analysis/SKILL.md) — sketch dentro do monster ajuda a escolher onde extrair
|
|
441
|
+
- [`legacy-sprout-wrap-techniques`](../legacy-sprout-wrap-techniques/SKILL.md) — quando monster > 300 linhas, sprout class para novo comportamento sem refatorar
|
|
442
|
+
- [`pre-refactor-characterization`](../pre-refactor-characterization/SKILL.md) — gate distingue safe extraction (livre) de behavioral change (requer characterization)
|
|
443
|
+
|
|
444
|
+
*Material-fonte: Working Effectively with Legacy Code — Feathers, 2004 — Cap 22: "I Need to Change a Monster Method and I Can't Write Tests for It".*
|