🪣 Sintomas de ball of mud
"Big ball of mud" é o termo arquitetural para código onde nada tem lugar: responsabilidades misturadas, dependências circulares, módulos que crescem por acreção. Você reconhece pelo cheiro: cada mudança simples cascateia em três arquivos não relacionados.
✓ Código saudável
- ✓Cada módulo tem uma responsabilidade clara e nomeável
- ✓Dependências fluem em uma direção (alto → baixo nível)
- ✓Vocabulário consistente entre código, testes e docs
- ✓Duplicação é exceção e justificada
- ✓Você prevê onde uma feature nova vai morar
- ✓Trocar uma implementação não vaza para 10 lugares
✗ Ball of mud
- ✗"Helper" / "utils" / "core" gigantes sem foco
- ✗A importa B que importa A (ciclos)
- ✗O mesmo conceito tem 3 nomes (user / customer / account)
- ✗Copy-paste com pequenas mutações em vários lugares
- ✗Toda feature nova exige "tour" pra saber onde por
- ✗Mudar uma regra requer caçar 8 arquivos
Cheiros típicos (code smells arquiteturais)
- • God modules: um arquivo com 2.000 linhas que "todo mundo importa"
- • Shotgun surgery: uma mudança simples toca 15 arquivos
- • Feature envy: métodos que mexem mais nos dados de outra classe do que nos próprios
- • Magic strings / numbers espalhados sem constante central
- • Conditionals por tipo: if/elif gigante checando "type === 'X'"
- • Wrappers que só repassam: camadas que não decidem nada
🔍 /improve-codebase-architecture
O comando /improve-codebase-architecture não pede pra "refatorar tudo".
Ele faz a coisa mais útil: lista deepening opportunities — pontos onde
o código pode ficar significativamente mais saudável, ordenados por impacto. A análise é informada pelo
CONTEXT.md e pelos ADRs do projeto, então as sugestões respeitam
o que já foi decidido.
⚙️ O que o comando faz
- •Lê o CONTEXT.md para entender o domínio e o vocabulário do projeto
- •Lê os ADRs para saber quais decisões arquiteturais estão vigentes
- •Varre a estrutura do código procurando cheiros que contradizem o domínio
- •Retorna uma lista priorizada de oportunidades, não um plano de rewrite
- •Cada item tem: descrição, impacto estimado, esforço, e ponto de partida
Saída de exemplo
terminal$ /improve-codebase-architecture # 5 deepening opportunities identified (sorted by impact) [1] HIGH src/billing/ — "materialization cascade" lives in 4 modules Impact: unifica lógica fragmentada, fecha conceito do CONTEXT.md Effort: 2-3 dias | Risk: medium Start: src/billing/cascade.py (extract; ADR-009 references this) [2] HIGH src/utils/helpers.py — 1,847 lines, 23 unrelated functions Impact: quebra god module; melhora descoberta Effort: 1-2 dias | Risk: low (mostly mechanical) Start: agrupar por consumer; mover; testar [3] MED "customer" vs "user" vs "account" usados intercambiavelmente Impact: alinha código ao glossário do CONTEXT.md (Sec. 2.3) Effort: 1 dia | Risk: low Start: renomear customer.* (canonical) e atualizar callsites [4] MED ciclo: api/orders → services/inventory → api/orders Impact: remove ciclo; clarifica direção de dependência Effort: meio dia | Risk: low Start: extract InventoryPort interface [5] LOW tests/ duplica fixtures de billing em 6 arquivos Impact: reduz manutenção de testes Effort: 2h | Risk: very low Start: conftest.py compartilhado por subdir
💡 Dica
Rode esse comando antes de planejar o próximo ciclo. Ele te dá uma lista honesta de débito arquitetural pra escolher 1 item por sprint — não pra atacar tudo de uma vez.
🔭 /zoom-out — perspectiva ampla
Existe um padrão de falha comum: o agente entra em um arquivo, começa a otimizar uma função, e depois de
20 minutos está re-escrevendo lógica isolada — sem perceber que aquela função
nem deveria existir nesse módulo. /zoom-out serve pra
isso: forçar o agente a parar e pedir contexto mais alto antes de continuar.
Zoom-in inicial (detalhe)
O agente entra no arquivo X e começa a editar
Foco estreito é necessário pra fazer a mudança imediata, mas vira armadilha quando a função, o arquivo, ou até o módulo inteiro estão no lugar errado. O agente "afunda" sem perguntar se o problema é maior.
Zoom-out (panorama)
/zoom-out — pede contexto arquitetural
O comando sobe um nível: qual é a responsabilidade desse módulo? O que o CONTEXT.md diz? Onde essa lógica deveria morar? A pergunta certa muda de "como melhorar essa função" para "essa função pertence aqui?". Frequentemente a melhor edição é mover ou remover, não otimizar.
Zoom-in informado (detalhe com mapa)
Volta ao código, mas com a foto grande
Agora a edição local é guiada pela arquitetura. Você sabe o que NÃO fazer (criar acoplamento novo, duplicar lógica de outro módulo) e o que SIM fazer (extrair, mover, alinhar nomes ao glossário). O detalhe importa, mas serve à estrutura.
💡 Use /zoom-out quando…
- • Você está editando o mesmo arquivo há >15 minutos sem progresso claro
- • A mudança simples está virando 4 arquivos diferentes
- • O agente está "consertando" algo que, na verdade, é sintoma de outro problema
- • Antes de aceitar uma sugestão grande que toca múltiplos módulos
🌱 Deepening opportunities, não rewrites
Deepening é um termo emprestado de Eric Evans (DDD): aprofundar a modelagem onde o domínio é mais importante, sem jogar fora o que já funciona. É o oposto do impulso "vamos re-escrever do zero" — que custa caro, raramente entrega o prometido, e descarta todo o aprendizado embutido no código atual.
✗ Rewrite destrutivo
- ✗"Vamos jogar fora e refazer com a arquitetura nova"
- ✗Branch paralelo de 3-6 meses sem deliver
- ✗Perde edge cases que o código atual já trata
- ✗Big bang merge → regressões silenciosas em massa
- ✗Time fica preso no novo, suporte do velho atrasa
- ✗Lições aprendidas (CONTEXT.md, ADRs) viram lixo
✓ Deepening incremental
- ✓"Vamos melhorar 1 oportunidade por sprint, sem parar deliver"
- ✓Cada PR é pequeno, revisável, mergeable
- ✓Mantém edge cases enquanto refina a forma
- ✓Regressões aparecem cedo, em mudanças pequenas
- ✓Time continua entregando feature ao lado
- ✓CONTEXT.md e ADRs evoluem junto, viram referência viva
📖 Termo: deepening
Deepening = aprofundar a modelagem do domínio incrementalmente. Em vez de re-escrever, você identifica conceitos que estão "tortos" no código (escondidos em utils, espalhados, mal nomeados) e os eleva a primeiro-classe — uma extração por vez.
Origem: Domain-Driven Design (Eric Evans, 2003) — "deepening the model".
🧭 CONTEXT.md como guia
O CONTEXT.md não é só onboarding — ele é
a régua arquitetural do projeto. A linguagem do domínio que ele
descreve é o que orienta onde refatorar: módulos que implementam conceitos centrais merecem mais atenção
arquitetural do que infraestrutura genérica.
🎯 Princípio: conceito de domínio = atenção arquitetural
Exemplo: se o CONTEXT.md descreve "materialization cascade" como conceito chave do sistema de billing —
a cadeia pela qual mudanças de preço se propagam para contratos ativos — então o módulo que implementa
essa cascade não pode estar escondido em utils/helpers.py
dividido em 4 funções soltas.
O deepening óbvio aqui é extrair billing/cascade.py com a cascade como entidade nomeada,
alinhada ao vocabulário do CONTEXT.md. O comando /improve-codebase-architecture vê esse
desalinhamento porque ele lê o CONTEXT.md.
Perguntas que CONTEXT.md responde
- • Quais são os conceitos centrais? → eles merecem módulos próprios e nomes consistentes
- • Qual é o glossário canônico? → "customer" vs "user" se resolve aqui, não em PR review
- • Quais são os boundaries? → onde uma feature pertence é função do bounded context
- • Quais invariantes existem? → regras que NÃO podem ser violadas em refator
💡 Dica
Se o CONTEXT.md está vago ou desatualizado, essa é a primeira deepening opportunity. Sem ele, qualquer melhoria arquitetural vai ser local e perderá coerência.
📜 ADRs como memória arquitetural
ADR = Architecture Decision Record. Um documento curto por decisão arquitetural relevante. Sem ADRs, o próximo refator desfaz silenciosamente uma escolha consciente — porque ninguém lembra por que X foi separado de Y.
ADR-009 — exemplo
docs/adr/0009-separate-cascade.md# ADR-009: Separar materialization cascade de pricing ## Status Accepted — 2025-08-14 ## Contexto A "materialization cascade" propaga mudanças de preço para contratos ativos. Originalmente vivia dentro de pricing/calculator.py porque parecia "cálculo de preço". Na prática, são duas responsabilidades: - pricing/ → calcula o preço de UM item, sem estado - cascade/ → reage a mudanças, atualiza N contratos, com estado Misturadas, qualquer mudança em pricing arriscava efeito colateral em milhares de contratos ativos. ## Decisão Extrair cascade para billing/cascade.py como entidade nomeada e independente. pricing/ vira pure function. cascade/ orquestra. ## Consequências + pricing testável sem mock de DB + cascade tem ownership claro de invariantes (idempotência, ordem) + vocabulário do CONTEXT.md (Sec. 4) finalmente reflete o código - duas pastas onde antes havia uma (custo de descoberta) - imports adicionais entre os módulos (acoplamento explícito) ## Alternativas consideradas - Manter junto: rejeitado, acopla cálculo a propagação - Event bus: prematuro, sem necessidade de async hoje - Inline em cada caller: rejeitado, duplicação garantida
🧠 Por que ADRs importam em deepening
- • Sem ADR, daqui 6 meses alguém vai "consolidar" cascade de volta em pricing por parecer redundante
- • O ADR é prova de que a separação já foi pensada e tem motivo
- •
/improve-codebase-architecturelê ADRs antes de sugerir — não vai propor algo já rejeitado - • ADRs descontinuados também valem: registram aprendizado ("tentamos X, não funcionou porque Y")
💡 Dica: ADR curto > ADR perfeito
ADR de 1 página vence ADR de 10 páginas que ninguém escreve. Status, Contexto, Decisão, Consequências —
quatro headings, parágrafos curtos. Numere sequencialmente (0001, 0002…) e não delete: marque como
Superseded by ADR-XXX quando substituído.
📈 Exemplo de melhoria progressiva
Como uma equipe sai de ball of mud sem parar o produto. Narrativa de 3 sprints, baseada em padrão real: uma oportunidade por sprint, CONTEXT.md e ADRs atualizados a cada ciclo.
Sprint 0 — Diagnóstico
Rodamos /improve-codebase-architecture pela primeira vez
Saída lista 5 oportunidades. Time discute em 30 min, escolhe atacar 3 nas próximas 3 sprints, na ordem de impacto. As outras 2 ficam no backlog explicitamente — não esquecidas, não urgentes.
Sprint 1 — Extrair cascade
Oportunidade #1: materialization cascade espalhada
Time extrai billing/cascade.py como módulo nomeado. PR pequeno, ~400 linhas movidas, sem
mudar comportamento. Escreve ADR-009 documentando a decisão. Atualiza CONTEXT.md (Sec. 4) com o
vocabulário canônico. Features novas seguem acontecendo ao lado, sem branch paralelo.
Sprint 2 — Quebrar god module
Oportunidade #2: utils/helpers.py com 1.847 linhas
Maioria mecânico: agrupa funções por consumer, move para módulos próximos. 3 funções "órfãs" levantam perguntas — o que são? Resposta vem do CONTEXT.md (uma é cascade, deveria ter ido na S1; outras duas expõem um conceito novo que vira ADR-010). Bug evitado.
Sprint 3 — Alinhar vocabulário
Oportunidade #3: customer / user / account intercambiáveis
Renomeação guiada pelo glossário do CONTEXT.md. Mecânico, mas mexe em muitos arquivos — feature flag não ajuda aqui, então: PRs pequenos por subdir, com codeowner do subdir aprovando. Ao final, código fala a mesma língua que docs e times de produto.
Sprint 4 — Re-diagnóstico
Roda /improve-codebase-architecture de novo
Lista nova: as 3 oportunidades originais sumiram (foram resolvidas). As 2 que ficaram no backlog continuam. Mas surgiram 2 novas, porque o domínio evoluiu nos 3 sprints. Arquitetura melhorou visivelmente, sem big bang, sem parar deliver.
📊 Resultado em 4 sprints
- •3 oportunidades de alto impacto resolvidas, 2 ADRs novos
- •CONTEXT.md atualizado 3 vezes, virou referência viva
- •Zero pause em features de produto
- •Bug evitado (cascade órfã em helpers) encontrado por coincidência
- •Time confia no processo: próxima rodada já planejada
🎯 Exercício prático
Você não aprende deepening lendo. Aprende rodando o comando no seu projeto e defendendo a escolha da primeira oportunidade a atacar.
🛠️ Passos
-
1.
Confirme as bases: seu projeto tem CONTEXT.md? Tem pelo menos 1-2 ADRs? Se não, comece por aí (esse é o pré-deepening).
-
2.
Rode /improve-codebase-architecture no projeto real (não em sandbox).
-
3.
Anote as 3 primeiras sugestões com: descrição, impacto, esforço. Não as 5 — só as 3 primeiras.
-
4.
Qual você ataca primeiro? Pode ser a #1 — mas pode não ser. Defenda a escolha em 3 frases: por que essa, por que agora, qual o risco se deixar.
-
5.
Bonus: rascunhe o ADR antes de mexer no código. Se você não consegue escrever 1 página justificando, talvez a sugestão não esteja madura ainda.
💡 Critério de escolha
Boa primeira escolha: alto impacto, esforço baixo-médio, risco baixo, toca conceito do CONTEXT.md. Má primeira escolha: alto impacto mas alto risco — guarde pra quando o time já confia no processo.
⚠️ Não faça
- ✗Atacar as 5 oportunidades de uma vez — você volta ao rewrite
- ✗Pular o ADR — daqui 6 meses ninguém lembra o motivo
- ✗Refatorar sem teste pré-existente cobrindo o caminho — escreva primeiro
✅ Resumo do Módulo
Próximo Módulo:
1.6 — continuando a jornada de skills.