Eu construo SaaS sozinho e programo com IA o dia inteiro, ela é rápida, entrega feature, resolve bug às 2 da manhã, mas ela tem um defeito que ninguém te conta no começo: ela não respeita a sua arquitetura. Ela lê o padrão, concorda com você, e cinco prompts depois copia o jeito errado que já existe no código, porque o objetivo dela é "fazer funcionar", não "manter o padrão".
Eu descobri isso do jeito mais concreto possível: mandei fazer uma auditoria no meu próprio SaaS e o resultado foi que 89 pontos do código chamavam uma classe do jeito que o meu próprio manual de arquitetura proíbe, contra só 6 no jeito certo, o errado tinha virado a maioria, e cada vez que eu pedia uma feature nova, a IA olhava, via que "todo mundo faz assim", e fazia igual.
A lição não é "pare de usar IA", é que o que importa de verdade você não protege com documentação, protege com um teste que quebra o build, esse post é sobre a técnica que está segurando a minha barra, e sobre o furo dela que me ensinou mais que o acerto.
Documentação não segura a IA
Eu tenho um CLAUDE.md e ADRs (registros de decisão de arquitetura) caprichados. A IA lê, e ignora quando dá, não por má vontade: é que um arquivo de texto é um pedido, não uma barreira, e no modo "só entrega logo", pedido perde pra imitação toda vez.
O que a IA não ignora é um teste vermelho no CI, ela não consegue passar por cima de um build quebrado, então a regra que eu adotei é simples:
Se uma decisão de arquitetura importa de verdade, ela vira um teste automatizado. Se ela não vale um teste, ela não vale um parágrafo no
CLAUDE.md, é só preferência.
Regra que importa vira teste que quebra o build
No Laravel isso é barato com o arch testing do Pest. Você descreve a regra em linguagem quase natural:
// tests/Arch/ArchitectureTest.php
// Controllers não tocam no banco direto: quem faz isso é a camada de dados
arch('controllers não usam a facade DB')
->expect('App\Http\Controllers')
->not->toUse('Illuminate\Support\Facades\DB');
// Toda Action tem que morar no lugar certo e ter o sufixo certo
arch('actions seguem a convenção')
->expect('App\Actions')
->toBeClasses()
->toHaveSuffix('Action');
Isso já mata uma classe inteira de "criatividade" da IA: ela não consegue enfiar uma query no controller sem o build reclamar. Escreveu, ficou vermelho, ela mesma corrige.
O pulo do gato: a baseline que só encolhe
O problema real não é começar limpo. É que você já tem código bagunçado (seu ou gerado por IA) e não dá pra consertar tudo hoje. Se você liga a regra agora, o build fica vermelho em 200 lugares e você desliga a regra, aí não vale nada.
A solução que eu uso (e que um revisor externo elogiou como rara) é uma baseline que só pode encolher. Você congela a lista de violações que já existem, mas escreve o teste de um jeito que essa lista nunca cresce e ainda te obriga a limpar:
// Dívida herdada. A regra de ouro: essa lista SÓ diminui.
const LEGACY = [
'App\Http\Controllers\Tickets\TicketsController',
// ... o que já infringe hoje
];
test('sem violação nova, e a dívida velha só encolhe', function () {
$violacoes = encontrarViolacoes(); // varre o código e devolve os infratores
// 1) Código NOVO não pode infringir: nada fora da baseline
$novas = array_diff($violacoes, LEGACY);
expect($novas)->toBeEmpty(
'Código novo quebrando o padrão: ' . implode(', ', $novas)
);
// 2) O truque: se você consertou um arquivo, ele TEM que sair da baseline.
// Esqueceu de tirar? Teste vermelho. Isso força a lista a encolher.
$jaCorrigidas = array_diff(LEGACY, $violacoes);
expect($jaCorrigidas)->toBeEmpty(
'Remova da baseline (já corrigido): ' . implode(', ', $jaCorrigidas)
);
});
A segunda asserção é o coração de tudo. Sem ela, uma baseline vira um tapete pra empurrar sujeira pra baixo. Com ela, cada limpeza é obrigatória de registrar, e o número só cai. É a diferença entre "dívida técnica documentada" e "dívida técnica que se paga sozinha com o tempo".
O invariante mais importante ganha um guarda em produção
Tem uma regra que eu não confio nem no teste: isolamento entre clientes (multi-tenancy). Um where esquecido não dá erro: vaza dado de um cliente pro outro, calado. Pra esse caso, além do escopo automático, eu tenho um guard que estoura uma exceção em produção se o escopo de tenant não foi aplicado numa query que deveria ter. Cinto e suspensório. É o tipo de coisa onde "provavelmente está certo" não é aceitável.
O teste verde que me enganou
Agora a parte que interessa mais que os acertos.
Lembra dos 89 call-sites errados? Eu tinha um arch test pra Actions. Ele estava verde o tempo todo, enquanto 89 lugares faziam o proibido.
Como? Porque o teste checava a coisa errada. Ele garantia que certos métodos estavam na baseline, e não o estilo de invocação, que era justamente onde a IA driftava. O guardrail existia, passava no CI, me dava uma falsa sensação de segurança, e guardava a porta errada. E como o estilo errado era o mais visível no código, a IA copiava ele por imitação a cada feature nova. O teste verde estava, na prática, abençoando o drift.
A lição doeu e é a melhor que eu tenho pra te dar: verde não quer dizer certo, quer dizer que aquilo que eu de fato testei está ok. Se você nunca viu o teste ficar vermelho, você não sabe o que ele guarda.
As 3 regras que eu tiro disso
-
Se importa, vira teste que quebra o build.
CLAUDE.mde ADR a IA lê e contorna sob pressão. Vermelho no CI ela não contorna. - Baseline que só encolhe. Você não precisa consertar tudo hoje, mas o débito não pode crescer, e limpar tem que ser obrigatório de registrar.
- Escreva o caso que falha primeiro. Antes de confiar num guardrail, quebre o padrão de propósito e veja o teste ficar vermelho. Um guardrail que nunca viu vermelho provavelmente está guardando a porta errada. Foi exatamente o meu caso.
Pra fechar, com honestidade
Meu SaaS está no meio dessa migração agora, enquanto escrevo isso. Não é código perfeito, é código real, com guardrails reais, um deles furado, sendo consertado à luz do dia. Nos próximos posts eu vou mostrando cada etapa da limpeza: como eu deduplico dois controllers que a IA clonou, como converto o padrão de Actions domínio a domínio, e como eu fecho o furo daquele teste pra ele parar de mentir pra mim.
Se você também constrói com IA e sente que o código foge do controle, esse é o caminho que está funcionando pra mim. Me conta o seu.
Top comments (0)