Violação de Abstração (Abstractness Violation)
ID: abstractness | Gravidade: Média (padrão)
O que esta regra detecta (TL;DR)
Esta regra marca módulos que são:
- Muito concretos e muito estáveis — muitos arquivos dependem de uma classe concreta (difícil de mudar com segurança).
- Muito abstratos e muito instáveis — abstrações das quais ninguém depende (sobreengenharia/YAGNI).
Em resumo: 👉 Módulos estáveis (a base) devem ser abstratos. 👉 Módulos instáveis (as folhas) devem ser concretos.
O que é um "Módulo"?
Em archlint, um módulo é um único arquivo fonte (.ts, .tsx, etc.) que exporta pelo menos um símbolo.
- Cada arquivo é analisado independentemente.
- Arquivos barrel (
index.ts) são tratados como módulos de agregação. - Reexportações e importações são contadas como dependências entre módulos.
Métricas e Intuição
1. Instabilidade (I)
Mede o quão propenso um módulo é a mudanças.
I = Acoplamento Eferente (Ce) / (Acoplamento Aferente (Ca) + Acoplamento Eferente (Ce))
Intuição:
- Estável (I ≈ 0): Muitos arquivos te importam, mas você quase não importa ninguém. Você é um componente fundamental.
- Instável (I ≈ 1): Você importa muitas coisas, mas ninguém te importa. Você está na borda do sistema.
2. Abstração (A)
Usamos um Cálculo Semântico baseado no uso real:
A = (Clientes que importam apenas Interfaces/Tipos) / (Total de Clientes)
Diferença importante do A clássico: NÃO apenas contamos palavras-chave ou interfaces dentro do arquivo. Em vez disso, medimos como o módulo é realmente usado:
- Importar uma
classconcreta → dependência concreta. - Importar uma
interfaceoutype→ dependência abstrata.
Isso reflete o acoplamento arquitetural real, não apenas a sintaxe. Usar import type é um sinal forte de intenção abstrata.
3. Distância (D)
A distância da linha ideal da "Sequência Principal" onde A + I = 1.
D = |A + I - 1|
Zonas de Risco (Interpretação)
Com base nos valores de A e I, os módulos caem em zonas específicas:
🧱 Zona de Dor
- Métricas: I ≈ 0–0.3 (estável), A ≈ 0–0.3 (concreto).
- Problema: Todos dependem de uma implementação concreta. Mudá-la é perigoso porque é tanto rígida quanto altamente acoplada.
Exemplo Ruim (Dependência concreta):
// database.service.ts
export class DatabaseService {
save(data: any) {
/* lógica concreta */
}
}
// client.ts (100+ arquivos fazendo isso)
import { DatabaseService } from './database.service'; // Importação direta da classe
const db = new DatabaseService();Por que é marcado:
Ca= 100+ (muito estável,I ≈ 0).A= 0 (clientes importam a classe diretamente).D≈ 1 → Distância máxima da sequência principal.
💨 Zona de Inutilidade
- Métricas: I ≈ 0.7–1.0 (instável), A ≈ 0.7–1.0 (abstrato).
- Problema: Abstrações sobreengenheiradas que ninguém usa.
Exemplo:
// complex-plugin.interface.ts
export interface IComplexPlugin {
execute(context: any): Promise<void>;
}
// 0 implementações e 0 clientes usando esta interface.Por que é marcado:
I≈ 1 (ninguém depende dele).A= 1 (é puramente abstrato).D≈ 1 → A abstração existe sem propósito.
Heurísticas para Reduzir Falsos Positivos
A análise estática pode ser ruidosa. Essas heurísticas focam a regra em decisões arquiteturais, não em código incidental:
- Limiar de Estabilidade (Fan-in): Apenas módulos com pelo menos
fan_in_threshold(padrão: 10) dependentes são analisados. Se apenas alguns arquivos usam um módulo, seu impacto arquitetural é baixo. - DTOs e Entidades: Classes sem métodos (apenas dados) são ignoradas. Elas são transportadores de dados, não componentes comportamentais.
- Erros: Classes que estendem
Errorsão ignoradas. Elas são sempre concretas por design. - Scripts de Infraestrutura: Migrações de banco de dados (
up/down) são ignoradas, pois são scripts procedimentais, não parte da arquitetura de longo prazo.
Como Corrigir (Guia de Decisão)
O módulo é estável (tem muitos dependentes)?
- Sim: Extraia uma
interface. Certifique-se de que os clientes usemimport type { ... }. Use Injeção de Dependências. - Não: As abstrações podem ser desnecessárias. Mantenha-o concreto até que a estabilidade aumente.
- Sim: Extraia uma
Há mais de uma implementação?
- Não: Se for instável, considere remover a interface (YAGNI).
- Sim: A interface é justificada, mas certifique-se de que os clientes dependam da interface, não das classes.
Configuração
rules:
abstractness:
severity: medium
distance_threshold: 0.85 # Limiar de ativação para a distância D
fan_in_threshold: 10 # Mínimo de dependências entrantes (Fan-in) para ativar a análise