ConstructiCat Logo
CodeBust.
Browse section ▾

Abstração Rasa.

Um assistente de IA envolve o código em funções, classes ou interfaces extras que adicionam uma camada de indireção sem esconder nenhuma complexidade nem permitir reutilização — abstrações que apenas repassam para um único ponto de chamada.

##Signs and Symptoms

Um revisor reconhece a Abstração Rasa quando uma nova função/classe/interface adiciona um nome e uma camada mas nenhuma alavancagem: seu corpo apenas repete sua assinatura, repassa direto para uma única chamada subjacente, é invocada exatamente uma vez ou não esconde nenhuma decisão, invariante ou variação. O código parece ter camadas e um ar "corporativo", mas você precisa ler todas as camadas para entender qualquer coisa — a abstração não encapsula nada.

Sinais reveladores:

  • Funções/métodos de repasse cujo corpo é uma única chamada delegante sem nenhum comportamento adicional.
  • Classes wrapper (FooServiceFooRepositorydb) em que cada camada apenas chama a próxima.
  • Interfaces de implementação única (IClock apenas com SystemClock) introduzidas especulativamente "para testabilidade" sem uma segunda implementação ou fake.
  • Funções utils/helpers usadas em exatamente um lugar.
  • Aliasing/renomeação redundante que repassa um valor inalterado.

Esse é o clássico smell Middle Man / Lazy Class / Speculative Generality, e o outro lado da constatação de "superespecificação" da OX Security (soluções hiperespecíficas e de uso único em vez de componentes generalizáveis).

// A IA adicionou três "camadas" que apenas repassam para a próxima.
function getUserName(id: string) {
  return fetchUserName(id);            // não acrescenta nada
}
function fetchUserName(id: string) {
  return userRepository.getName(id);   // não acrescenta nada
}
class UserRepository {
  getName(id: string) {
    return db.query("SELECT name FROM users WHERE id = ?", [id]);
  }
}
// getUserName é o único ponto de chamada, usado uma vez.
// Duas das três camadas não adicionam nenhum comportamento — puro Middle Man.

##Reasons for the Problem

Por que os modelos a produzem

  • Imitar a forma de uma boa arquitetura. Os corpora de treinamento estão saturados de tutoriais e scaffolding corporativo — camadas de service/repository/factory, DI, interfaces com uma única implementação. O modelo reconhece o cerimonial da abstração sem a substância, porque um auxiliar nomeado é localmente plausível e "parece profissional". A geração do próximo token otimiza para parecer código bem estruturado, não para saber se uma camada justifica sua existência.
  • Sem contexto do repositório. Gerando localmente, o modelo não consegue ver que o auxiliar é chamado uma única vez, ou que já existe uma abstração canônica em outro lugar — então inventa novas camadas rasas em vez de reutilizá-las. Essa é exatamente a tendência medida pela GitClear: linhas refatoradas ("movidas") caíram de ~24% (2020) para menos de 10% (2024), enquanto as linhas copiadas/coladas aumentaram e os clones de código cresceram ~4x. Os modelos adicionam estrutura, mas nunca a consolidam.
  • Fixação no "manual" + superespecificação. A OX Security constatou que a IA superespecifica em 80–90% do código gerado, produzindo "soluções estreitas que não podem ser reutilizadas", e evita refatorações em 80–90% dos casos ("para no suficientemente bom"). Os wrappers rasos são o resíduo visível.
  • Literalismo no prompt e bajulação. Instruído a "adicionar uma camada de serviço" ou "deixar limpo/modular", o modelo adiciona camadas literalmente mesmo quando vale o YAGNI — a tendência de superengenharia / "god agent" que os profissionais descrevem na discussão code-smells-for-AI-agents.
  • Desatualização pelo corte do treinamento. Ele envolve uma API obsoleta em um adaptador fino em vez de migrar para fora dela.

Por que isso prejudica

  • Manutenibilidade e carga de revisão. Toda mudança precisa atravessar camadas mortas; quem lê precisa percorrer Middle Men inline. O argumento do "Exército de Juniores" da OX: os revisores não conseguem acompanhar código que parece estruturado mas não tem costuras reais.
  • Correção e segurança. O arXiv 2509.20491 e a SonarSource observam que o aliasing e os wrappers "obscurecem os pontos de chamada decisivos", degradando a análise de fluxo de dados/controle e de taint — um wrapper pode engolir silenciosamente um erro ou esconder um sink de um scanner.
  • Acúmulo de dívida técnica. Interfaces especulativas de uma única implementação se ossificam; e como cada abstração rasa é de uso único, a próxima variação é copiada e colada em vez de parametrizada — alimentando diretamente o crescimento de clones que a GitClear mediu. Você paga pela indireção sem nunca colher a reutilização que ela prometia.

##Treatment

Táticas de revisão e de prompting

  • Aplique a Regra de Três: "Não adicione uma função/classe/interface a menos que ela seja usada em ≥2 lugares ou esconda uma decisão/invariante real. Faça inline dos auxiliares de uso único (YAGNI)."
  • Force a reutilização: "Antes de adicionar um auxiliar/serviço/interface, procure no repositório por um já existente e reutilize-o." Aponte o modelo para o módulo canônico.
  • Faça-o justificar cada camada: "Liste cada nova função/classe e diga qual variação ou invariante ela esconde; remova as que apenas repassam."
  • Exija que ele execute o linter (no-useless-constructor, no-useless-rename) e uma varredura de duplicação (jscpd) e corrija o que for sinalizado.

Refatorações (nomeie-as): Inline Function, Inline Class, Remove Middle Man para repasses; Collapse Hierarchy / descarte a interface no caso de interfaces de implementação única. Quando a duplicação é o motivo real, faça Extract Function uma vez para um auxiliar genuinamente compartilhado e parametrizado — não um wrapper por ponto de chamada.

Antes — Middle Man:

function getUserName(id: string) { return fetchUserName(id); }
function fetchUserName(id: string) { return userRepository.getName(id); }

Depois — Inline Function / Remove Middle Man (um ponto de chamada, sem comportamento adicional):

const name = userRepository.getName(id);

Antes — interface especulativa de implementação única:

interface IClock { now(): number; }
class SystemClock implements IClock { now() { return Date.now(); } }

Depois — Collapse Hierarchy (use o tipo concreto; reintroduza a interface apenas quando uma segunda implementação, por exemplo um fake de teste, realmente existir):

class SystemClock { now() { return Date.now(); } }

##Detected by