@luanpdd/kit-mcp 1.10.0 → 1.12.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/gates/ai-prompt-stability.md +120 -0
- package/gates/legacy-refactor-safety.md +178 -0
- package/gates/observability-coverage.md +151 -0
- package/gates/release-pipeline-policy.md +132 -0
- package/kit/COMANDOS.md +15 -0
- package/kit/agents/ai-mutation-tester.md +298 -0
- package/kit/agents/cascading-failures-auditor.md +306 -0
- package/kit/agents/executor.md +13 -0
- package/kit/agents/legacy-characterizer.md +378 -0
- package/kit/agents/load-shedding-instrumenter.md +297 -0
- package/kit/agents/observability-coverage-auditor.md +325 -0
- package/kit/agents/omm-auditor.md +47 -0
- package/kit/agents/payload-capture-instrumenter.md +283 -0
- package/kit/agents/planner.md +29 -0
- package/kit/agents/prr-conductor.md +8 -0
- package/kit/agents/refactor-safety-auditor.md +414 -0
- package/kit/agents/release-pipeline-auditor.md +360 -0
- package/kit/agents/seam-finder.md +367 -0
- package/kit/agents/shotgun-surgery-detector.md +359 -0
- package/kit/agents/storytelling-analyst.md +309 -0
- package/kit/agents/supabase-edge-fn-writer.md +12 -0
- package/kit/agents/verifier.md +30 -0
- package/kit/commands/auditar-cascading.md +111 -0
- package/kit/commands/auditar-marco.md +44 -1
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -0
- package/kit/commands/auditar-refactor.md +219 -0
- package/kit/commands/auditar-release.md +109 -0
- package/kit/commands/capturar-payloads.md +193 -0
- package/kit/commands/caracterizar-prompt.md +195 -0
- package/kit/commands/caracterizar.md +212 -0
- package/kit/commands/concluir-marco.md +41 -1
- package/kit/commands/detectar-duplicacao.md +197 -0
- package/kit/commands/discutir-fase.md +41 -0
- package/kit/commands/encontrar-seams.md +136 -0
- package/kit/commands/forense.md +40 -1
- package/kit/commands/legacy.md +263 -0
- package/kit/commands/load-shedding.md +117 -0
- package/kit/commands/observabilidade.md +2 -0
- package/kit/commands/refactor-seguro.md +321 -0
- package/kit/commands/sre.md +3 -0
- package/kit/commands/storytelling.md +179 -0
- package/kit/skills/_shared-legacy/glossary.md +389 -0
- package/kit/skills/_shared-sre/glossary.md +139 -0
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -0
- package/kit/skills/cascading-failures/SKILL.md +307 -0
- package/kit/skills/four-golden-signals/SKILL.md +17 -0
- package/kit/skills/hermetic-builds/SKILL.md +323 -0
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -0
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -0
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -0
- package/kit/skills/legacy-extract-class/SKILL.md +203 -0
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -0
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -0
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -0
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -0
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -0
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -0
- package/kit/skills/llm-as-dependency/SKILL.md +436 -0
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -0
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -0
- package/kit/skills/release-engineering/SKILL.md +367 -0
- package/kit/skills/retry-strategies/SKILL.md +372 -0
- package/package.json +2 -2
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: legacy-extract-class
|
|
3
|
+
description: Use ao identificar classes "muito grandes" (cap 20 Feathers) com responsibility hot spots — extract class para separar responsabilidades. Aplicado a domain classes Supabase (OrderService → OrderValidator + Repository + Notifier).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Legacy — Extract Class
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando uma classe ultrapassa thresholds de tamanho/complexidade ou tem múltiplas responsabilidades distintas. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "essa classe é grande demais", "this class is too big"
|
|
13
|
+
- "extract class", "extrair classe", "split class"
|
|
14
|
+
- "responsibility hot spot", "single responsibility"
|
|
15
|
+
- "cap 20 Feathers"
|
|
16
|
+
- "OrderService faz validação, persistência E notificação"
|
|
17
|
+
- arquivo de classe > 300 linhas com múltiplos métodos públicos não-relacionados
|
|
18
|
+
|
|
19
|
+
## Regras absolutas
|
|
20
|
+
|
|
21
|
+
- **Heurísticas de "classe muito grande":** > 300 linhas; > 10 fields; > 12 métodos públicos; > 3 responsabilidades distintas (hot spots). Pelo menos 2 dos 4 = candidato.
|
|
22
|
+
- **Extract class != extract method.** Extract method move bloco contíguo dentro da mesma classe; extract class move responsabilidade INTEIRA para classe nova com state próprio.
|
|
23
|
+
- **Identifique o "hot spot" primeiro.** Cluster de fields + métodos que se referenciam mais entre si do que com o resto da classe = unidade extraível.
|
|
24
|
+
- **Compilação verde a cada commit.** Pequenos passos: (1) criar classe nova vazia; (2) mover 1 field; (3) ajustar callers; (4) repetir.
|
|
25
|
+
- **Não introduza herança especulativa.** Extract class produz composição (classe original tem field do tipo extraído); herança só se faz parte do design intencional.
|
|
26
|
+
- **Aplicação canônica em Supabase domain classes:** `OrderService` que faz validação + persistência + notificação + audit → extract para `OrderValidator`, `OrderRepository`, `OrderNotifier`, `OrderAuditor` (modernização: SRP em layer de domínio).
|
|
27
|
+
|
|
28
|
+
## Patterns canônicos
|
|
29
|
+
|
|
30
|
+
### Pattern 1: Detecção de hot spots
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
Para cada field e método da classe alvo:
|
|
34
|
+
Crie matriz de coupling — quem usa quem.
|
|
35
|
+
Cluster fields/métodos que SE REFERENCIAM mais.
|
|
36
|
+
Cluster com 2+ fields E 3+ métodos = hot spot extraível.
|
|
37
|
+
|
|
38
|
+
Exemplo — OrderService 450 linhas:
|
|
39
|
+
Cluster A (validação):
|
|
40
|
+
fields: validators[], strictMode
|
|
41
|
+
methods: validate, validateField, addCustomValidator
|
|
42
|
+
Cluster B (persistência):
|
|
43
|
+
fields: db, cache
|
|
44
|
+
methods: save, findById, findRecent
|
|
45
|
+
Cluster C (notificação):
|
|
46
|
+
fields: notifier, templateEngine
|
|
47
|
+
methods: notifyCustomer, formatNotification
|
|
48
|
+
|
|
49
|
+
→ 3 hot spots → 3 extract class candidatos.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Pattern 2: Workflow de extract class
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
1. Criar classe nova vazia com nome descritivo
|
|
56
|
+
class OrderValidator {}
|
|
57
|
+
|
|
58
|
+
2. Mover 1 field (e seus referentes diretos)
|
|
59
|
+
- Mover field
|
|
60
|
+
- Adicionar field correspondente na classe original APONTANDO para nova
|
|
61
|
+
- Ajustar todos os usos (this.X → this.validator.X)
|
|
62
|
+
- Compilar verde
|
|
63
|
+
|
|
64
|
+
3. Mover 1 método relacionado
|
|
65
|
+
- Mover método (com edits triviais para acessar fields da classe nova)
|
|
66
|
+
- Atualizar callers
|
|
67
|
+
- Compilar verde
|
|
68
|
+
|
|
69
|
+
4. Repetir para cada field/método do hot spot
|
|
70
|
+
5. Adicionar test harness para classe nova (agora menor → fácil testar)
|
|
71
|
+
6. (Opcional) extract interface se existem múltiplas implementações
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Pattern 3: Aplicação canônica Supabase
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// ANTES — OrderService 450 linhas, 4 responsabilidades
|
|
78
|
+
class OrderService {
|
|
79
|
+
constructor(private db: SupabaseClient, private notifier: SmtpClient) {}
|
|
80
|
+
|
|
81
|
+
// validation
|
|
82
|
+
validate(order: Order): ValidationResult { /* 80 linhas */ }
|
|
83
|
+
validateField(field: string, value: any) { /* ... */ }
|
|
84
|
+
|
|
85
|
+
// persistence
|
|
86
|
+
async save(order: Order) { await this.db.from('orders').insert(...) }
|
|
87
|
+
async findById(id: string) { return this.db.from('orders').select().eq('id', id) }
|
|
88
|
+
|
|
89
|
+
// notification
|
|
90
|
+
async notifyCustomer(order: Order) { /* template + smtp send */ }
|
|
91
|
+
|
|
92
|
+
// audit
|
|
93
|
+
async logAudit(action: string, order: Order) { /* db audit_log insert */ }
|
|
94
|
+
|
|
95
|
+
// orchestration (entrypoint)
|
|
96
|
+
async place(order: Order): Promise<Result> {
|
|
97
|
+
const v = this.validate(order)
|
|
98
|
+
if (!v.ok) return { error: v.errors }
|
|
99
|
+
await this.save(order)
|
|
100
|
+
await this.notifyCustomer(order)
|
|
101
|
+
await this.logAudit('placed', order)
|
|
102
|
+
return { ok: true }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// DEPOIS — 4 classes coesas + orquestrador enxuto
|
|
107
|
+
class OrderValidator { validate(order: Order): ValidationResult { /* 60 linhas */ } }
|
|
108
|
+
|
|
109
|
+
class OrderRepository {
|
|
110
|
+
constructor(private db: SupabaseClient) {}
|
|
111
|
+
async save(order: Order) { /* ... */ }
|
|
112
|
+
async findById(id: string) { /* ... */ }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class OrderNotifier {
|
|
116
|
+
constructor(private smtp: SmtpClient) {}
|
|
117
|
+
async notifyCustomer(order: Order) { /* ... */ }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class OrderAuditor {
|
|
121
|
+
constructor(private db: SupabaseClient) {}
|
|
122
|
+
async log(action: string, order: Order) { /* ... */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
class OrderService {
|
|
126
|
+
constructor(
|
|
127
|
+
private validator = new OrderValidator(),
|
|
128
|
+
private repo: OrderRepository,
|
|
129
|
+
private notifier: OrderNotifier,
|
|
130
|
+
private auditor: OrderAuditor,
|
|
131
|
+
) {}
|
|
132
|
+
|
|
133
|
+
async place(order: Order): Promise<Result> {
|
|
134
|
+
const v = this.validator.validate(order)
|
|
135
|
+
if (!v.ok) return { error: v.errors }
|
|
136
|
+
await this.repo.save(order)
|
|
137
|
+
await this.notifier.notifyCustomer(order)
|
|
138
|
+
await this.auditor.log('placed', order)
|
|
139
|
+
return { ok: true }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// OrderService final: ~25 linhas. Cada classe extraída testável isolada.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Pattern 4: Effort budget
|
|
146
|
+
|
|
147
|
+
| Tamanho classe | Hot spots típicos | Esforço extract class | Output |
|
|
148
|
+
|---|---|---|---|
|
|
149
|
+
| 300-500 linhas | 2-3 | 1-2 dias | classe principal + 2-3 extracted classes |
|
|
150
|
+
| 500-1000 linhas | 3-5 | 3-7 dias | classe principal + 3-5 extracted; tests acumulados |
|
|
151
|
+
| > 1000 linhas | 5+ | 1-3 semanas (pode requerer scratch refactoring antes) | considere extract interface se múltiplas implementações |
|
|
152
|
+
|
|
153
|
+
## Anti-patterns
|
|
154
|
+
|
|
155
|
+
### ANTI: extract class antes de characterization
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
ANTI: identificou hot spots e move tudo de uma vez.
|
|
159
|
+
|
|
160
|
+
PROBLEMA: extract class MOVE state e métodos. Sem characterization,
|
|
161
|
+
regressão silenciosa em ordem de chamadas, side effects,
|
|
162
|
+
state compartilhado.
|
|
163
|
+
|
|
164
|
+
CERTO: characterize primeiro (cap 13). Snapshots da classe ANTES.
|
|
165
|
+
Extract incremental. Snapshots devem continuar verdes a cada
|
|
166
|
+
commit (comportamento idêntico).
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### ANTI: extract class sem hot spot real
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
ANTI: "vou extrair Helper class só pra reduzir linhas".
|
|
173
|
+
|
|
174
|
+
PROBLEMA: classe nova sem coesão. Helper recebe métodos
|
|
175
|
+
arbitrários. Reviewer não consegue justificar a divisão.
|
|
176
|
+
Resultado: god class virou god class + helper class.
|
|
177
|
+
|
|
178
|
+
CERTO: hot spot REAL = cluster de fields + métodos com forte coupling
|
|
179
|
+
interno. Sem cluster, não extract — refactor outro
|
|
180
|
+
(extract method, eliminar dead code).
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Verificação
|
|
184
|
+
|
|
185
|
+
1. Hot spots identificados via matriz de coupling
|
|
186
|
+
2. Compilação verde a cada commit
|
|
187
|
+
3. Extracted classes têm coesão alta (fields + métodos relacionam-se)
|
|
188
|
+
4. Classe original encolheu significativamente (≥ 30%)
|
|
189
|
+
5. Tests acumulados nas classes extraídas (≥ 60% coverage)
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Ver também
|
|
194
|
+
|
|
195
|
+
- [`_shared-legacy/glossary.md`](../_shared-legacy/glossary.md) — vocabulário canônico
|
|
196
|
+
- [`legacy-characterization-tests`](../legacy-characterization-tests/SKILL.md) — characterize ANTES de extract class
|
|
197
|
+
- [`legacy-monster-methods`](../legacy-monster-methods/SKILL.md) — extract method é precursor; extract class lida com classe inteira
|
|
198
|
+
- [`legacy-effect-analysis`](../legacy-effect-analysis/SKILL.md) — sketch identifica hot spots antes do extract
|
|
199
|
+
- [`legacy-shotgun-surgery`](../legacy-shotgun-surgery/SKILL.md) — duplicação cross-class detecta candidatos a extract class
|
|
200
|
+
- [`supabase-architect`](../../agents/supabase-architect.md) (v1.8) — referencia extract class quando design domain inicial cresce além do esperado
|
|
201
|
+
|
|
202
|
+
*Material-fonte: Working Effectively with Legacy Code — Feathers, 2004 — Cap 20: "This Class Is Too Big and I Don't Want It to Get Any Bigger".*
|
|
203
|
+
*Modernização (2026):* Aplicação canônica em domain classes Supabase com SRP retroativo.
|
|
@@ -0,0 +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".*
|