Code quality is one of those terms that every team claims to care about and few can actually measure. Ask five engineers what "good quality" means and you'll get five different answers — clean formatting, good test coverage, meaningful variable names, low cyclomatic complexity. All true, all incomplete. Quality is the aggregate of structural decisions made (or avoided) over months. The problem is that structural degradation is invisible until it isn't: the codebase that felt fine six months ago now takes three engineers to understand a single change.Qualidade de código é um daqueles termos que todo time diz que se importa, mas poucos conseguem medir de verdade. Pergunte a cinco engenheiros o que significa "boa qualidade" e você terá cinco respostas diferentes — formatação limpa, boa cobertura de testes, nomes de variáveis significativos, baixa complexidade ciclomática. Tudo verdade, tudo incompleto. Qualidade é o agregado de decisões estruturais tomadas (ou evitadas) ao longo de meses. O problema é que a degradação estrutural é invisível até que não seja mais: o codebase que parecia bem há seis meses agora exige três engenheiros para entender uma única mudança.
This article covers the six anti-patterns that cause most of that degradation, why dependency cycles are a separate category of dangerous, how to score module health, and how to automate all of this so you catch problems early rather than explaining them in a post-mortem.Este artigo aborda os seis anti-patterns que causam a maior parte dessa degradação, por que ciclos de dependência são uma categoria separada de perigo, como pontuar a saúde de módulos e como automatizar tudo isso para que você detecte problemas cedo, e não os explique em um post-mortem.
Why Quality Degrades SilentlyPor Que a Qualidade Degrada Silenciosamente
Structural problems don't announce themselves. They accumulate through decisions that each looked reasonable at the time: adding one more method to the class that already does too much, importing a module that creates a subtle cycle, duplicating a constant "just this once". Each individual decision has a plausible justification. The compound effect — measured across months — is a codebase where changes are unpredictable, refactoring feels risky, and onboarding takes weeks instead of days.Problemas estruturais não se anunciam. Eles se acumulam por meio de decisões que, individualmente, pareciam razoáveis na época: adicionar mais um método à classe que já faz coisas demais, importar um módulo que cria um ciclo sutil, duplicar uma constante "só dessa vez". Cada decisão individual tem uma justificativa plausível. O efeito composto — medido ao longo de meses — é um codebase onde mudanças são imprevisíveis, refatorar parece arriscado e o onboarding leva semanas em vez de dias.
The silent accumulation problem: No single commit breaks a codebase. Anti-patterns emerge from a pattern of small, locally reasonable decisions that are globally destructive. This is why code reviews alone are insufficient — a reviewer approving a single commit can't see the structural trend that commit participates in.O problema da acumulação silenciosa: Nenhum commit único quebra um codebase. Anti-patterns emergem de um padrão de decisões pequenas e localmente razoáveis que são globalmente destrutivas. É por isso que code reviews sozinhos são insuficientes — um revisor aprovando um único commit não consegue ver a tendência estrutural da qual aquele commit participa.
The Six Most Destructive Anti-PatternsOs Seis Anti-Patterns Mais Destrutivos
1. God Class1. God Class (Classe Deus)
A God Class is a class (or module, or service) that knows too much and does too much. It accumulates responsibilities over time because it already exists, already has the database connection, already imports the right utilities — so new behavior lands there instead of in a proper home. The result is a class with hundreds of methods, thousands of lines, and intimate knowledge of every other domain in the system.Uma God Class é uma classe (ou módulo, ou serviço) que sabe coisas demais e faz coisas demais. Ela acumula responsabilidades ao longo do tempo porque já existe, já tem a conexão com o banco, já importa os utilitários certos — então novo comportamento aterra lá em vez de encontrar um lugar adequado. O resultado é uma classe com centenas de métodos, milhares de linhas e conhecimento íntimo de todos os outros domínios do sistema.
// God Class signals:
// • More than ~300 lines and growing
// • Methods that span unrelated domains (payments AND emails AND reports)
// • Every other module imports it
// • Changing it requires understanding the whole system// Sinais de God Class:
// • Mais de ~300 linhas e crescendo
// • Métodos que abrangem domínios não relacionados (pagamentos E emails E relatórios)
// • Todo outro módulo a importa
// • Alterá-la exige entender todo o sistema
class OrderService {
createOrder() { ... }
sendConfirmationEmail() { ... } // deveria ser EmailService
generateInvoicePDF() { ... } // deveria ser InvoiceService
updateInventory() { ... } // deveria ser InventoryService
applyDiscountCode() { ... } // deveria ser PromotionService
calculateShipping() { ... } // deveria ser ShippingService
notifyWarehouse() { ... } // deveria ser NotificationService
// ... 40 more methods
}The fix is decomposition by Single Responsibility: each class should have exactly one reason to change. An OrderService should handle the order lifecycle. Emails, invoices, inventory, and shipping belong in their own services with their own boundaries.A correção é a decomposição pelo Princípio da Responsabilidade Única: cada classe deve ter exatamente um motivo para mudar. Um OrderService deve lidar com o ciclo de vida do pedido. Emails, faturas, estoque e frete pertencem a seus próprios serviços com seus próprios limites.
2. Feature Envy2. Feature Envy (Inveja de Feature)
Feature Envy describes a method that is more interested in the data of another class than its own. The giveaway is a method that repeatedly calls getters on an external object to compute something. The method belongs with the data it's consuming — it just ended up in the wrong place.Feature Envy descreve um método que está mais interessado nos dados de outra classe do que nos seus próprios. O sinal é um método que chama repetidamente getters de um objeto externo para calcular algo. O método pertence aos dados que está consumindo — ele apenas acabou no lugar errado.
// Feature Envy — this method belongs in Order, not OrderReportService// Feature Envy — esse método pertence a Order, não a OrderReportService
class OrderReportService {
calculateTotal(order: Order): number {
const subtotal = order.getItems()
.reduce((sum, item) => sum + item.getPrice() * item.getQuantity(), 0);
const discount = order.getCoupon()?.getDiscountRate() ?? 0;
const tax = order.getAddress().getState().getTaxRate();
return subtotal * (1 - discount) * (1 + tax);
}
}Feature Envy is often a symptom of an anemic domain model: all the data lives in POJOs (plain objects with only getters/setters) while all the behavior ends up scattered in service classes. Moving behavior closer to the data it operates on reduces coupling and improves cohesion simultaneously.Feature Envy é frequentemente um sintoma de modelo de domínio anêmico: todos os dados vivem em POJOs (objetos simples com apenas getters/setters) enquanto todo o comportamento acaba espalhado em classes de serviço. Mover o comportamento para perto dos dados em que opera reduz o acoplamento e melhora a coesão simultaneamente.
3. Shotgun Surgery3. Shotgun Surgery (Cirurgia de Escopeta)
Shotgun Surgery is the inverse of God Class: a single logical change requires modifications to many unrelated files. Adding a new user field forces changes in the model, the DTO, the mapper, the validator, the API schema, the frontend form, the email template, and three test fixtures. No single file is too large — but every change is expensive because the concern is scattered everywhere.Shotgun Surgery é o inverso da God Class: uma única mudança lógica exige modificações em muitos arquivos não relacionados. Adicionar um novo campo de usuário força mudanças no modelo, no DTO, no mapper, no validador, no schema da API, no formulário do frontend, no template de email e em três fixtures de teste. Nenhum arquivo individual é muito grande — mas cada mudança é cara porque a responsabilidade está espalhada por toda parte.
// Shotgun Surgery — adding "phone" to User requires changing 7 files// Shotgun Surgery — adicionar "phone" ao User requer mudar 7 arquivos
// user.model.ts, user.dto.ts, user.mapper.ts, user.validator.ts,
// user.schema.ts (OpenAPI), user-form.component.ts, user-email.template.html// user.model.ts, user.dto.ts, user.mapper.ts, user.validator.ts,
// user.schema.ts (OpenAPI), user-form.component.ts, user-email.template.html
// ✓ After consolidation: one Zod schema is the canonical source of truth// ✓ Após consolidação: um schema Zod é a fonte única de verdade
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(2),
email: z.string().email(),
phone: z.string().optional(), // added once — derived everywhere// adicionado uma vez — derivado em todo lugar
});
type User = z.infer<typeof UserSchema>; // model derived// modelo derivado
type UserDTO = z.infer<typeof UserSchema>; // DTO is the same// DTO é o mesmo
// Validation: UserSchema.parse(input) — no separate validator// Validação: UserSchema.parse(input) — sem validador separado
// OpenAPI: generated from schema via @asteasolutions/zod-to-openapi// OpenAPI: gerado do schema via @asteasolutions/zod-to-openapiThe root cause is usually missing abstraction: the same concept is represented multiple times in different forms, and there's no single authoritative source that the others derive from. The fix is consolidation — identifying the canonical representation and generating or deriving the others from it.A causa raiz geralmente é abstração ausente: o mesmo conceito é representado múltiplas vezes em formas diferentes, e não há uma fonte autoritativa única da qual as outras derivam. A correção é consolidação — identificar a representação canônica e gerar ou derivar as outras a partir dela.
4. Data Clump4. Data Clump (Grupo de Dados)
Data Clumps are groups of data items that always appear together but haven't been named as a concept. If three or more fields always travel together — street, city, postalCode; or startDate, endDate, timezone — that's an object waiting to be born. Leaving them as loose parameters means every function that uses them must accept all three, every validation must repeat itself, and refactoring one means touching every call site.Data Clumps são grupos de itens de dados que sempre aparecem juntos, mas não foram nomeados como um conceito. Se três ou mais campos sempre viajam juntos — street, city, postalCode; ou startDate, endDate, timezone — é um objeto esperando para nascer. Deixá-los como parâmetros soltos significa que cada função que os usa deve aceitar todos os três, cada validação deve se repetir e refatorar um significa tocar todos os call sites.
// Data Clump — these three always appear together// Data Clump — esses três sempre aparecem juntos
function scheduleReport(startDate: Date, endDate: Date, timezone: string) { ... }
function fetchMetrics(startDate: Date, endDate: Date, timezone: string) { ... }
function exportData(startDate: Date, endDate: Date, timezone: string) { ... }
// After extracting the concept:// Após extrair o conceito:
class DateRange { constructor(start: Date, end: Date, timezone: string) { ... } }
function scheduleReport(range: DateRange) { ... }5. Dead Code5. Dead Code (Código Morto)
Dead code is code that is never executed: unreachable branches, unused exports, variables that are assigned but never read, functions that are defined but never called. Beyond the obvious problem of wasted space, dead code creates a maintenance trap — future developers can't tell if it's intentionally unused (a toggle, a migration path) or accidentally orphaned. Every hour spent reading dead code is an hour not spent understanding live code.Código morto é código que nunca é executado: branches inacessíveis, exports não utilizados, variáveis que são atribuídas mas nunca lidas, funções que são definidas mas nunca chamadas. Além do problema óbvio de espaço desperdiçado, o código morto cria uma armadilha de manutenção — futuros desenvolvedores não conseguem dizer se está intencionalmente sem uso (um toggle, um caminho de migração) ou acidentalmente órfão. Cada hora gasta lendo código morto é uma hora não gasta entendendo código vivo.
The aggressive fix is deletion. If it's truly dead and there's no planned use, remove it — that's what version control is for. The conservative fix is a clear comment marking the intent, but this approach compounds: every "will be used later" comment that ages becomes noise.A correção agressiva é a exclusão. Se está verdadeiramente morto e não há uso planejado, remova — é para isso que existe o controle de versão. A correção conservadora é um comentário claro marcando a intenção, mas essa abordagem se agrava: cada comentário "será usado depois" que envelhece se torna ruído.
6. Primitive Obsession6. Primitive Obsession (Obsessão por Primitivos)
Primitive Obsession is the habit of representing domain concepts with raw primitives instead of typed objects. User IDs as plain strings, monetary amounts as floating-point numbers, email addresses as unvalidated strings. The code compiles and runs — until someone passes a product ID where a user ID was expected, or adds two monetary amounts in different currencies, or stores an email without validation.Primitive Obsession é o hábito de representar conceitos de domínio com primitivos brutos em vez de objetos tipados. IDs de usuário como strings puras, valores monetários como números de ponto flutuante, endereços de email como strings não validadas. O código compila e roda — até que alguém passe um ID de produto onde se esperava um ID de usuário, ou some dois valores monetários em moedas diferentes, ou armazene um email sem validação.
// Primitive Obsession — easy to misuse, no domain protection// Primitive Obsession — fácil de usar errado, sem proteção de domínio
function chargeUser(userId: string, amount: number, currency: string) { ... }
chargeUser(productId, 9.99, "USD"); // compiles fine, wrong at runtime// compila bem, errado em runtime
// With value objects — the type system catches the mistake at compile time// Com value objects — o sistema de tipos captura o erro em tempo de compilação
function chargeUser(userId: UserId, amount: Money) { ... }
chargeUser(productId, Money.usd(9.99)); // type error — ProductId ≠ UserId// erro de tipo — ProductId ≠ UserIdValue objects — thin wrappers around primitives that enforce invariants — are the standard fix. They make invalid states unrepresentable and push validation to the boundary where data enters the system.Value objects — invólucros finos em torno de primitivos que impõem invariantes — são a correção padrão. Eles tornam estados inválidos não representáveis e empurram a validação para o limite onde os dados entram no sistema.
Dependency Cycles: A Separate Category of DangerousCiclos de Dependência: Uma Categoria Separada de Perigo
Circular dependencies deserve their own section because they're qualitatively different from the anti-patterns above. A cycle doesn't degrade performance or readability directly — it destroys your ability to reason about the system independently. When module A depends on B, and B depends on A, you cannot understand, test, or deploy either one without the other. Both must change together, be tested together, and be reasoned about together. The modules are coupled in the most fundamental sense.Dependências circulares merecem sua própria seção porque são qualitativamente diferentes dos anti-patterns acima. Um ciclo não degrada o desempenho ou a legibilidade diretamente — ele destrói sua capacidade de raciocinar sobre o sistema independentemente. Quando o módulo A depende de B e B depende de A, você não pode entender, testar ou implantar nenhum dos dois sem o outro. Ambos devem mudar juntos, ser testados juntos e raciocinar sobre eles juntos. Os módulos estão acoplados no sentido mais fundamental.
Cycles are insidious because they're hard to spot in code review. A single import statement looks harmless. The cycle only becomes visible when you draw the dependency graph — which almost no one does manually. Automated detection is the only practical approach at scale.Ciclos são insidiosos porque são difíceis de detectar em code reviews. Uma única instrução import parece inofensiva. O ciclo só fica visível quando você desenha o grafo de dependências — o que quase ninguém faz manualmente. A detecção automatizada é a única abordagem prática em escala.
The standard fix is extraction: identify the subset of behavior that both modules need, extract it into a third module with no dependencies on either, and have both import from there. Or introduce an abstraction (interface, event) that breaks the direct import relationship.A correção padrão é a extração: identificar o subconjunto de comportamento que ambos os módulos precisam, extraí-lo em um terceiro módulo sem dependências em nenhum dos dois, e fazer com que ambos importem de lá. Ou introduzir uma abstração (interface, evento) que quebra o relacionamento de importação direto.
Scoring Module Health: Cohesion, Coupling, and SizePontuando a Saúde de Módulos: Coesão, Acoplamento e Tamanho
Individual anti-patterns are useful to name. But a single number — a health score — is useful for ranking. When you have 15 modules and limited refactoring budget, you need to know which three to fix first. A 0–100 score per module, broken down by three dimensions, gives you that ranking:Anti-patterns individuais são úteis para nomear. Mas um único número — um health score — é útil para ranking. Quando você tem 15 módulos e orçamento limitado de refatoração, precisa saber quais três corrigir primeiro. Uma pontuação de 0–100 por módulo, dividida em três dimensões, fornece esse ranking:
A module scoring 30/100 on coupling with 80/100 on cohesion tells a different story than a module scoring 30/100 on cohesion with 80/100 on coupling. The first is tightly integrated with the system but internally well-organized — the fix is decoupling. The second is loosely connected but internally a mess — the fix is decomposition. Knowing which dimension is failing tells you what kind of refactoring to apply.Um módulo com pontuação 30/100 em acoplamento e 80/100 em coesão conta uma história diferente de um módulo com 30/100 em coesão e 80/100 em acoplamento. O primeiro está fortemente integrado ao sistema, mas bem organizado internamente — a correção é o desacoplamento. O segundo está fracamente conectado, mas internamente é uma bagunça — a correção é a decomposição. Saber qual dimensão está falhando indica que tipo de refatoração aplicar.
Cyclomatic Complexity: The Metric With a NumberComplexidade Ciclomática: A Métrica com um Número
Cyclomatic complexity measures the number of linearly independent paths through a function. Each if, else, for, while, case, &&, and || adds one to the count. A function with complexity 1 has a single path; a function with complexity 15 has fifteen. The number tells you directly how many test cases you need to achieve full branch coverage — and indirectly how hard the function is to understand, modify, and debug.A complexidade ciclomática mede o número de caminhos linearmente independentes por uma função. Cada if, else, for, while, case, && e || adiciona um ao contador. Uma função com complexidade 1 tem um único caminho; uma função com complexidade 15 tem quinze. O número diz diretamente quantos casos de teste você precisa para atingir cobertura total de branches — e indiretamente o quão difícil é entender, modificar e depurar a função.
// Cyclomatic complexity = 9 — one path per branch combination// Complexidade ciclomática = 9 — um caminho por combinação de branch
function applyDiscount(order: Order, user: User): number {
let discount = 0;
if (user.isPremium) { // +1
if (order.total > 100) { // +1
discount = 0.20;
} else {
discount = 0.10;
}
} else if (user.isLoyalty) { // +1
discount = 0.05;
}
if (order.hasPromoCode) { // +1
discount += order.promoCode.value;
}
if (discount > 0.30) { // +1 — cap at 30%// +1 — limite em 30%
discount = 0.30;
}
return order.total * (1 - discount);
}
// Refactored: each piece is complexity 1–2, composable and individually testable// Refatorado: cada peça é complexidade 1–2, combinável e individualmente testável
function getUserDiscount(user: User, orderTotal: number): number {
if (user.isPremium) return orderTotal > 100 ? 0.20 : 0.10;
if (user.isLoyalty) return 0.05;
return 0;
}
function applyDiscount(order: Order, user: User): number {
const discount = Math.min(getUserDiscount(user, order.total) + getPromoDiscount(order), 0.30);
return order.total * (1 - discount);
}Enforce it in CI: eslint --rule 'complexity: [error, 10]'. Functions exceeding the limit fail the build, which prevents new complexity from being merged without explicit justification. Start lenient (15–20) if your codebase is currently high, then tighten over sprints.Aplique no CI: eslint --rule 'complexity: [error, 10]'. Funções que excedem o limite falham o build, o que impede que nova complexidade seja mergeada sem justificativa explícita. Comece com limites mais altos (15–20) se seu codebase já está alto, e aperte ao longo das sprints.
Technical Debt Has Four Types — and Only Two Are Worth PayingDívida Técnica Tem Quatro Tipos — e Apenas Dois Vale a Pena Pagar
Martin Fowler's Technical Debt Quadrant is the most useful framework for deciding which debt to tackle. It maps debt along two axes: deliberate vs. inadvertent, and reckless vs. prudent. Teams that treat all debt as equivalent waste effort paying down debt that was intentional and managed, while leaving genuinely destructive debt untouched.O Quadrante de Dívida Técnica de Martin Fowler é o framework mais útil para decidir qual dívida atacar. Ele mapeia a dívida em dois eixos: deliberada vs. inadvertida, e imprudente vs. prudente. Times que tratam toda dívida como equivalente desperdiçam esforço pagando dívida que foi intencional e gerenciada, enquanto deixam dívida genuinamente destrutiva intocada.
| RecklessImprudente | PrudentPrudente | |
|---|---|---|
| DeliberateDeliberada |
"We don't have time for design""Não temos tempo para design"
Avoid — this creates debt with no plan to repayEvite — isso cria dívida sem plano de pagamento
|
"We must ship now, refactor next sprint""Precisamos entregar agora, refatorar na próxima sprint"
Acceptable — conscious decision, tracked, has a repayment planAceitável — decisão consciente, rastreada, com plano de pagamento
|
| InadvertentInadvertida |
"What's layering?""O que é layering?"
Most dangerous — team doesn't know they're creating debtMais perigosa — o time não sabe que está criando dívida
|
"Now we know how we should have done it""Agora sabemos como deveríamos ter feito"
Natural — discovered through learning; track and address in future sprintsNatural — descoberta pelo aprendizado; rastreie e enderece em sprints futuras
|
The practical implication: when you find debt, ask which quadrant it came from before deciding how urgently to fix it. Reckless debt (both types) needs systematic remediation — it's symptomatic of a team culture problem, not just a code quality problem. Prudent deliberate debt needs a tracked repayment plan. Prudent inadvertent debt is a learning outcome — fix it when the refactoring cost is low.A implicação prática: quando você encontrar dívida, pergunte de qual quadrante ela veio antes de decidir a urgência da correção. Dívida imprudente (ambos os tipos) precisa de remediação sistemática — é sintomática de um problema de cultura do time, não apenas um problema de qualidade de código. Dívida deliberada prudente precisa de um plano de pagamento rastreado. Dívida inadvertida prudente é um resultado de aprendizado — corrija quando o custo de refatoração for baixo.
Static Analysis: The First Line of DefenseAnálise Estática: A Primeira Linha de Defesa
Anti-pattern detection tools like archlens-ai operate at the architectural level. But the first line of defense is simpler: static analysis that runs on every file save and every commit. These tools catch the low-hanging fruit — unused variables, unreachable code, overly complex functions, missing null checks — before a human reviewer ever sees the code.Ferramentas de detecção de anti-patterns como o archlens-ai operam no nível arquitetural. Mas a primeira linha de defesa é mais simples: análise estática que roda em cada save de arquivo e cada commit. Essas ferramentas capturam os problemas mais fáceis — variáveis não usadas, código inacessível, funções excessivamente complexas, verificações de null ausentes — antes que um revisor humano veja o código.
# Recommended static analysis stack (works across most languages)# Stack recomendado de análise estática (funciona na maioria das linguagens)
# TypeScript / JavaScript# TypeScript / JavaScript
ESLint # style, complexity, dead code, import order# estilo, complexidade, código morto, ordem de imports
TypeScript # strict mode: noImplicitAny, strictNullChecks, noUnusedLocals# strict mode: noImplicitAny, strictNullChecks, noUnusedLocals
Knip # unused exports, dead files, unused dependencies# exports não usados, arquivos mortos, dependências não usadas
# Python# Python
Ruff # fast linter + formatter, replaces flake8+isort+pyupgrade# linter + formatter rápido, substitui flake8+isort+pyupgrade
mypy # static type checking# verificação estática de tipos
# Java / Kotlin# Java / Kotlin
Checkstyle # style and complexity rules# regras de estilo e complexidade
SpotBugs # bug pattern detection# detecção de padrões de bug
SonarQube # cross-language: complexity, coverage, smell density# multi-linguagem: complexidade, cobertura, densidade de smells
# Pre-commit hook — catches issues before they reach CI# Pre-commit hook — captura problemas antes de chegarem ao CI
npx lint-staged # runs linters only on staged files — fast # roda linters apenas em arquivos staged — rápidoThe Boy Scout Rule in practice: "Leave the code cleaner than you found it." When a PR touches a file, the author is responsible for leaving it better — not necessarily for a full refactor, but for removing obvious dead code, extracting a clearly misnamed variable, or breaking a function that grew past its complexity limit. Applied consistently, this turns every PR into an incremental quality improvement rather than a neutral or negative event.A Regra do Escoteiro na prática: "Deixe o código mais limpo do que você encontrou." Quando um PR toca um arquivo, o autor é responsável por deixá-lo melhor — não necessariamente para uma refatoração completa, mas para remover código morto óbvio, extrair uma variável claramente mal nomeada, ou dividir uma função que cresceu além do limite de complexidade. Aplicada consistentemente, isso transforma cada PR em uma melhoria incremental de qualidade em vez de um evento neutro ou negativo.
Automating Architecture Quality AnalysisAutomatizando a Análise de Qualidade Arquitetural
All of the above — anti-pattern detection, cycle detection, module health scoring — is tractable to automate. You don't need to audit your architecture manually. Tools exist that can scan your codebase, build the dependency graph, identify the patterns, and score each module in seconds.Tudo o que foi mencionado acima — detecção de anti-patterns, detecção de ciclos, pontuação de saúde de módulos — é viável de automatizar. Você não precisa auditar sua arquitetura manualmente. Existem ferramentas que podem escanear seu codebase, construir o grafo de dependências, identificar os padrões e pontuar cada módulo em segundos.
One such tool is archlens-ai — a CLI that points at any codebase and generates a self-contained HTML report with an interactive architecture graph, per-module health scores, detected anti-patterns with severity ratings, dependency cycle paths, and concrete refactoring suggestions. It runs on top of Claude Code and supports TypeScript, JavaScript, Python, Go, Rust, and Java/Kotlin.Uma dessas ferramentas é o archlens-ai — uma CLI que aponta para qualquer codebase e gera um relatório HTML autocontido com grafo interativo de arquitetura, health scores por módulo, anti-patterns detectados com níveis de severidade, caminhos de ciclos de dependência e sugestões concretas de refatoração. Funciona em cima do Claude Code e suporta TypeScript, JavaScript, Python, Go, Rust e Java/Kotlin.
# Install once# Instale uma vez
npm install -g archlens-ai
# Analyze any project — report opens in your browser automatically# Analise qualquer projeto — o relatório abre no navegador automaticamente
archlens-ai analyze ./my-project
# Deep mode reads key file contents for more accurate analysis# Modo deep lê o conteúdo de arquivos-chave para análise mais precisa
archlens-ai analyze ./my-project --depth deep
# Security-focused analysis (OWASP Top 10)# Análise focada em segurança (OWASP Top 10)
archlens-ai analyze ./my-project --profile securityThe report surfaces exactly the patterns covered in this article: God Classes with responsibility breakdowns, Feature Envy signals, Shotgun Surgery indicators, Data Clumps, Dead Code candidates, and Primitive Obsession instances — each with the module it lives in, its severity, and a concrete suggestion for addressing it.O relatório expõe exatamente os padrões abordados neste artigo: God Classes com análise de responsabilidades, sinais de Feature Envy, indicadores de Shotgun Surgery, Data Clumps, candidatos a Dead Code e instâncias de Primitive Obsession — cada um com o módulo onde vive, sua severidade e uma sugestão concreta para resolvê-lo.
What a real report looks like: Running archlens-ai on a mid-size TypeScript monorepo (130 files) produced a health score of 44/100 with 12 identified modules, 7 architectural suggestions, and 3 circular dependency paths — all in under 30 seconds, with an interactive graph showing which modules were critical, at-risk, or healthy. The worst-scoring module had cohesion 28/100 and coupling 71/100, immediately pointing to decomposition as the right intervention.Como é um relatório real: Executar archlens-ai em um monorepo TypeScript de médio porte (130 arquivos) produziu um health score de 44/100 com 12 módulos identificados, 7 sugestões arquiteturais e 3 caminhos de dependência circular — tudo em menos de 30 segundos, com um grafo interativo mostrando quais módulos eram críticos, em risco ou saudáveis. O módulo com pior pontuação tinha coesão 28/100 e acoplamento 71/100, apontando imediatamente para a decomposição como a intervenção correta.
Quality Gates in CIQuality Gates em CI
Detecting anti-patterns is valuable. Blocking merges when quality drops below a threshold is how you prevent them from accumulating in the first place. A CI quality gate turns the health score from a diagnostic into a policy.Detectar anti-patterns é valioso. Bloquear merges quando a qualidade cai abaixo de um limite é como você evita que eles se acumulem desde o início. Um quality gate em CI transforma o health score de um diagnóstico em uma política.
# .github/workflows/architecture.yml
# Blocks merge if health score drops below 70# .github/workflows/architecture.yml
# Bloqueia merge se o health score cair abaixo de 70
- name: Architecture quality gateQuality gate de arquitetura
run: npx archlens-ai analyze . --min-score 70 --no-open --output /tmp/archlens.html
- name: Upload report as artifactUpload do relatório como artefato
uses: actions/upload-artifact@v3
with:
name: architecture-report
path: /tmp/archlens.htmlThe --min-score flag exits with code 1 when the health score falls below the threshold, making it a standard blocking CI check. The report is uploaded as an artifact so the team can review exactly which modules and anti-patterns triggered the failure — not just that the build failed.O flag --min-score sai com código 1 quando o health score cai abaixo do limite, tornando-o um check de CI bloqueante padrão. O relatório é carregado como artefato para que o time possa revisar exatamente quais módulos e anti-patterns desencadearam a falha — não apenas que o build falhou.
A few practical notes on thresholds: don't set the initial threshold at your current score minus one. Set it at a defensible target (70 is a reasonable baseline for most teams) and accept that you'll need to refactor to reach it. The gate only creates value if it occasionally blocks — a gate that never triggers is not a gate, it's decoration.Algumas notas práticas sobre limites: não defina o limite inicial como sua pontuação atual menos um. Defina-o em um alvo defensável (70 é uma linha de base razoável para a maioria dos times) e aceite que você precisará refatorar para alcançá-lo. O gate só cria valor se ocasionalmente bloquear — um gate que nunca aciona não é um gate, é decoração.
Building the Habit: Architectural Review CadenceConstruindo o Hábito: Cadência de Revisão Arquitetural
A CI gate catches regressions. But catching a problem the day it's introduced is better than catching it in the CI gate. The teams that maintain quality over years tend to have a lightweight review habit: a short architectural review in sprint retrospectives, or a monthly "architecture health" meeting where the score trend is reviewed alongside new anti-pattern findings.Um gate de CI captura regressões. Mas detectar um problema no dia em que é introduzido é melhor do que capturá-lo no gate de CI. Os times que mantêm qualidade ao longo dos anos tendem a ter um hábito leve de revisão: uma revisão arquitetural curta nas retrospectivas da sprint, ou uma reunião mensal de "saúde da arquitetura" onde a tendência de pontuação é revisada junto com novos achados de anti-patterns.
The key insight is that quality is a trend, not a snapshot. A module that scores 60/100 today but has been improving from 40 for three months is in a better position than a module that scores 75/100 but has been declining from 90. The score at any point in time is less informative than the direction it's moving.O insight chave é que qualidade é uma tendência, não um snapshot. Um módulo que pontua 60/100 hoje, mas tem melhorado de 40 por três meses, está em melhor posição do que um módulo que pontua 75/100, mas tem declinado de 90. A pontuação em qualquer momento no tempo é menos informativa do que a direção em que está se movendo.
Key TakeawaysPontos Principais
- Anti-patterns accumulate through locally reasonable decisions. No single commit is the problem — the problem is the pattern across commits. This is why automated detection at scale is necessary.Anti-patterns se acumulam por meio de decisões localmente razoáveis. Nenhum commit único é o problema — o problema é o padrão ao longo dos commits. Por isso a detecção automatizada em escala é necessária.
- God Class, Feature Envy, Shotgun Surgery, Data Clump, Dead Code, Primitive Obsession are the six patterns responsible for most structural debt. Name them on your team so engineers can identify them in code review before they compound.God Class, Feature Envy, Shotgun Surgery, Data Clump, Dead Code, Primitive Obsession são os seis padrões responsáveis pela maior parte da dívida estrutural. Nomeie-os no seu time para que os engenheiros possam identificá-los em code reviews antes que se agravem.
- Circular dependencies are qualitatively different — they destroy the modularity boundary entirely. Detect them with tooling; don't rely on code review to catch them.Dependências circulares são qualitativamente diferentes — elas destroem completamente o limite de modularidade. Detecte-as com ferramentas; não confie no code review para encontrá-las.
- Score modules on cohesion, coupling, and size to know where to invest refactoring effort. The worst-first ranking is more actionable than a full audit.Pontue módulos em coesão, acoplamento e tamanho para saber onde investir esforço de refatoração. O ranking do pior para o melhor é mais acionável do que uma auditoria completa.
- Automate the analysis. Tools like archlens-ai can surface all of the above in one command — interactive graph, module scores, anti-patterns, and cycle paths — without manual effort.Automatize a análise. Ferramentas como o archlens-ai podem expor tudo isso em um único comando — grafo interativo, pontuações de módulos, anti-patterns e caminhos de ciclos — sem esforço manual.
- Add a CI quality gate with
--min-scoreto block merges that regress architecture health. A gate that never triggers provides no value; set a real threshold and invest in meeting it.Adicione um quality gate no CI com--min-scorepara bloquear merges que regridem a saúde da arquitetura. Um gate que nunca aciona não tem valor; defina um limite real e invista em atingi-lo.