Deriva de Convenções.
Código gerado por IA que ignora silenciosamente as convenções estabelecidas de um repositório — reinventando helpers, escolhendo a biblioteca errada e usando nomenclatura e tratamento de erros fora do padrão da casa — fazendo a base de código derivar em direção a um estilo genérico, médio da internet.
##Signs and Symptoms
Um revisor reconhece a Deriva de Convenções quando uma mudança da IA funciona, mas não parece pertencer à base de código. Ela recorre ao padrão da média global em vez do padrão local:
- Um novo helper é escrito inline mesmo que já exista um utilitário testado em batalha (
utils/date.ts,lib/apiClient, um tipoResultcompartilhado). - Aparece uma biblioteca ou API diferente do padrão do repositório (
axiosem um app que padronizou em um wrapper defetch;momentonde o repositório usadate-fns). - Nomenclatura, layout de arquivos e estilo de tratamento de erros divergem —
camelCaseonde o módulo ésnake_case,throw new Error("...")bruto onde todo o resto retorna um erro tipado,try/catchsob medida com logging em vez do logger compartilhado. - Cada novo requisito ganha seu próprio bloco recém-criado e estreitamente ajustado em vez de reutilizar uma abstração ("Sobre-especificação" da Ox Security, vista em 80–90% do código de IA).
- Assinatura sintomática: blocos duplicados se multiplicam. A GitClear mediu um salto de 8× em blocos de clones de 5+ linhas em 2024, com linhas copiadas/coladas ultrapassando linhas movidas (refatoradas) pela primeira vez.
// Estilo da casa (já no repositório)
import { apiClient } from "@/lib/apiClient"; // encapsula auth, retries, URL base
import { formatDate } from "@/utils/date"; // de todo o app, ciente de locale
// Mudança gerada por IA — deriva para longe de ambos
import axios from "axios"; // não é um padrão de dependência do projeto
async function getUser(id: string) {
try {
const res = await axios.get(`https://api.example.com/users/${id}`); // URL base codificada
return { ...res.data, joined: new Date(res.data.joined).toLocaleDateString() }; // reinventa formatDate
} catch (e) {
console.log("error", e); // não é o logger compartilhado; engole o erro
}
}
O indício é a consistência, não a correção: três revisores cada um encontra uma escolha diferente "errada-mas-funcional", e nenhuma delas corresponde ao arquivo ao lado.
##Reasons for the Problem
Por que os modelos o produzem
- Regressão à média do treinamento. Os LLMs são treinados em um corpus vasto de código médio da internet, não no seu repositório. Quando não recebem instruções, eles emitem a expressão idiomática estatisticamente mais comum (
axios,moment,console.log), não a expressão da sua casa — suas convenções são um sinal minúsculo e fora da distribuição perto da média global. - Autoconsistência do próximo token em vez de alcance no repositório. É localmente mais fácil completar um bloco autocontido (embutir um formatador de datas) do que "saber" que
@/utils/dateexiste e importar um identificador que o modelo nunca viu. A reutilização exige contexto do repositório que o modelo não detém; a reinvenção só exige o buffer atual. - Contexto de repositório limitado / com perdas. O helper canônico, a configuração de lint e o ADR que diz "use o wrapper do fetch" geralmente estão fora do prompt. O modelo não consegue seguir uma convenção que nunca lhe foi mostrada. Como Eno Reyes, da Factory, colocou, boa parte de uma convenção é tácita — padrões que os humanos absorvem lendo a base de código e que os agentes simplesmente nunca veem.
- Bajulação / foco literal na tarefa. Pedido para "adicionar
getUser", o modelo faz exatamente isso e não se voluntaria a dizer "na verdade, já temos um cliente para isso". Ele otimiza para concluir a tarefa declarada, não para se encaixar no sistema. A "Fixação no Manual" (By-The-Book Fixation) da Ox Security (80–90% das amostras) é a mesma força: ele segue uma convenção genérica de livro-texto em vez de avaliar a do próprio projeto. - Defasagem do corte de treinamento. Se o repositório migrou para uma biblioteca ou padrão mais novo depois do corte do modelo, o modelo reintroduz com confiança a versão na qual foi treinado.
Por que isso prejudica
- Manutenibilidade & carga cognitiva. Cada escolha derivada é mais uma maneira de fazer a mesma coisa. Os leitores precisam manter N variantes de "como formatamos datas" na cabeça; a base de código perde sua fonte única de verdade.
- Correção na mudança. Lógica duplicada/reinventada diverge silenciosamente — um bug corrigido no helper compartilhado não é corrigido na cópia da IA. A GitClear liga a explosão de clones diretamente aos assistentes de IA, que tornam o tab-para-inserir mais barato do que a reutilização, enquanto a participação da refatoração nas mudanças caiu de 25% (2021) para menos de 10% (2024).
- Segurança. Validação, autenticação ou construção de queries reinventadas contornam o caminho compartilhado e endurecido (URLs base codificadas, erros engolidos, SQL ad-hoc por string). A Ox chama o efeito agregado de "Exército de Juniores": rápido, funcional, sem julgamento arquitetural.
- Carga de revisão & acúmulo de dívida técnica. A deriva não é capturada por testes (o código funciona), então ela aterrissa na revisão ou não é capturada de forma alguma, acumulando-se no smell arquitetural de "Funcionalidade Espalhada / Miragem Modular", onde comportamentos relacionados ficam fragmentados em arquivos sem coesão real.
##Treatment
Táticas de prompting / fluxo de trabalho
- Mostre as convenções. Coloque as regras onde o agente as lê — um arquivo
CLAUDE.md/AGENTS.md/regras listando o cliente sancionado, o logger, o tipo de erro, a nomenclatura e "use X, não Y". Convenções que o modelo não consegue ver, ele não consegue seguir. - Aponte para o código canônico. "Use
@/lib/apiCliente@/utils/date; não adicione novas bibliotecas HTTP. Combine com o tratamento de erros emservices/orders.ts." Faça few-shot da expressão idiomática do repositório colando um arquivo exemplar. - Pergunte antes de ele escrever. "Quais helpers/abstrações existentes cobrem isto? Reutilize-os; só adicione código novo se nenhum servir." Isso converte reinvenção em reutilização logo de início.
- Faça do linter o portão. Exija que o agente execute o formatador, o linter e o verificador de tipos e corrija todas as constatações antes de retornar. O conselho da Factory é sistematizar os sinais de qualidade (lint/format/type-check/test) para que o agente otimize em direção a eles em vez de derivar.
- Revise pelo encaixe, não só pela função. Faça diff dos imports em busca de bibliotecas não sancionadas; faça grep por lógica que deveria ter chamado o helper compartilhado; rode um detector de copy-paste no PR.
A refatoração — nomeie os movimentos clássicos: Remover Duplicação / Substituir Código Inline por Chamada de Função, Substituir Algoritmo e Extrair Função se a abstração certa ainda não existir.
// ANTES — derivado, reinventa cliente + lógica de data, URL codificada, erro engolido
import axios from "axios";
async function getUser(id: string) {
try {
const res = await axios.get(`https://api.example.com/users/${id}`);
return { ...res.data, joined: new Date(res.data.joined).toLocaleDateString() };
} catch (e) {
console.log("error", e);
}
}
// DEPOIS — reutiliza as convenções estabelecidas
import { apiClient } from "@/lib/apiClient";
import { formatDate } from "@/utils/date";
async function getUser(id: string) {
const user = await apiClient.get<User>(`/users/${id}`); // URL base, auth, retries, erros tipados tratados aqui
return { ...user, joined: formatDate(user.joined) };
}
Se o mesmo bloco derivado aparece em vários PRs, esse é um sinal de que a convenção é indescobrível — corrija a causa-raiz documentando-a no arquivo de regras e/ou expondo-a por meio de um ponto de entrada único e óbvio, não revisando cada instância de novo.
##Detected by
- jscpd duplication threshold (min-lines / min-tokens) — Detecção de copy/paste
- PMD CPD CPD duplicated code blocks — Detector de Copy-Paste
- ESLint no-restricted-imports — no-restricted-imports
- typescript-eslint @typescript-eslint/naming-convention — naming-convention
- SonarQube Source files should not have any duplicated blocks — Blocos duplicados