ConstructiCat Logo
CodeBust.
Browse section ▾

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 tipo Result compartilhado).
  • Aparece uma biblioteca ou API diferente do padrão do repositório (axios em um app que padronizou em um wrapper de fetch; moment onde o repositório usa date-fns).
  • Nomenclatura, layout de arquivos e estilo de tratamento de erros divergem — camelCase onde o módulo é snake_case, throw new Error("...") bruto onde todo o resto retorna um erro tipado, try/catch sob 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/date existe 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/apiClient e @/utils/date; não adicione novas bibliotecas HTTP. Combine com o tratamento de erros em services/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