Software · Design Patterns

Princípios de Design de Software: KISS, DRY, YAGNI e o Equilíbrio de Coesão e Acoplamento

Da sobre-engenharia à simplicidade intencional: como aplicar os axiomas KISS, DRY, YAGNI e dominar a métrica fundamental de coesão versus acoplamento na sustentabilidade de sistemas corporativos.

Diagrama de forças e tensões entre acoplamento, coesão, complexidade essencial e complexidade acidental

No ciclo de vida de qualquer sistema de software, a entropia é inexorável. Conforme novos requisitos de negócio chegam e prazos apertam, bases de código tendem a se degradar caso a equipe não compartilhe princípios de engenharia claros. Frequentemente, a deterioração do código ocorre não pela ausência de padrões, mas pelo excesso de abstrações especulativas — a armadilha clássica da sobre-engenharia (overengineering).

Para construir sistemas sustentáveis, a engenharia de software consolidou um conjunto de axiomas pragmáticos: KISS (Keep It Simple, Stupid), DRY (Don't Repeat Yourself) e YAGNI (You Ain't Gonna Need It), balanceados pela constante busca por alta coesão e baixo acoplamento.


O Equilíbrio Fundamental: Coesão vs. Acoplamento

A métrica primária que determina a manutenibilidade de qualquer módulo, classe ou microsserviço repousa sobre a relação entre coesão e acoplamento:

flowchart TD
    subgraph Ideal["Design Ideal: Módulos Autônomos"]
        direction LR
        subgraph M1["Módulo A (Alta Coesão)"]
            A1["Função 1"] <--> A2["Função 2"]
            A2 <--> A3["Função 3"]
        end
        subgraph M2["Módulo B (Alta Coesão)"]
            B1["Serviço 1"] <--> B2["Serviço 2"]
        end
        M1 <--"Contrato Estável (Baixo Acoplamento)"--> M2
    end

Coesão: Responsabilidades Focadas

A coesão mede o grau em que as responsabilidades internas de um módulo pertencem logicamente juntas.

  • Baixa Coesão (Anti-pattern): Uma classe utilitária "God Class" (CommonUtils ou Manager) que valida CPF, envia e-mails, calcula impostos e gera planilhas Excel. Ela muda constantemente por razões não relacionadas.
  • Alta Coesão: Um componente TaxCalculator que contém unicamente algoritmos tributários e suas regras fiscais. Todas as suas funções e variáveis operam sobre o mesmo domínio restrito.

Acoplamento: O Grau de Interdependência

O acoplamento mede a dependência direta entre módulos distintos.

  • Alto Acoplamento (Fragilidade): Módulo A acessa diretamente variáveis de instância privadas de Módulo B, ou conhece detalhes de implementação do banco de dados utilizado por B. Qualquer alteração em B quebra A em cascata (Shotgun Surgery).
  • Baixo Acoplamento: Módulo A comunica-se com Módulo B estritamente através de contratos de interface pública ou mensagens assíncronas. B pode reescrever sua camada de persistência inteira sem que A sofra impacto.

Separação de Preocupações (SoC) e Preocupações Transversais

O princípio de Separação de Preocupações (Separation of Concerns - SoC) postula que um programa deve ser dividido em seções distintas, onde cada seção aborda uma preocupação específica de computação:

  1. Preocupações de Negócio / Domínio: Regras de cálculo de juros, validação de limites de crédito, workflow de pedidos.
  2. Preocupações de Infraestrutura: Conexão com banco de dados, chamadas HTTP para parceiros externos, serialização JSON.
  3. Preocupações Transversais (Cross-Cutting Concerns): Funcionalidades que cortam horizontalmente múltiplos módulos da aplicação, tais como:
    • Autenticação e autorização de segurança
    • Registro de logs e rastreamento distribuído (tracing)
    • Auditoria e conformidade (LGPD/GDPR)
    • Cache e tratamento global de exceções
flowchart TB
    subgraph CrossCutting["Preocupações Transversais (Cross-Cutting Concerns)"]
        Auth["Segurança / Autenticação"]
        Log["Logging / Rastreamento"]
        Cache["Resiliência / Caching"]
    end

    subgraph Camadas["Camadas de Domínio e Negócio"]
        Ped["Módulo de Pedidos"]
        Fin["Módulo Financeiro"]
        Est["Módulo de Estoque"]
    end

    Auth -.-> Camadas
    Log -.-> Camadas
    Cache -.-> Camadas

Tais preocupações transversais não devem ser duplicadas no interior dos métodos de negócio. Em vez disso, devem ser abstraídas usando Aspect-Oriented Programming (AOP), Middlewares, Decorators ou Service Meshes.


Os Três Axiomas do Pragmatismo: KISS, DRY e YAGNI

Princípio Definição Central Armadilha de Interpretação Aplicação Pragmática
KISS (Keep It Simple, Stupid) A simplicidade deve ser o objetivo primário de design; sistemas funcionam melhor se mantidos simples em vez de tornados complexos. Confundir código simples com código ingênuo ou desestruturado. Escrever código que um desenvolvedor júnior consiga ler em 5 minutos sem diagramas auxiliares.
DRY (Don't Repeat Yourself) Todo conhecimento ou regra de negócio deve ter uma representação única, não ambígua e definitiva no sistema. Tratar linhas de código idênticas por coincidência como duplicidade de conhecimento. Unificar a regra de validação de CPF em um Value Object; aceitar repetição de código sintático se os domínios forem diferentes.
YAGNI (You Ain't Gonna Need It) Nunca adicione funcionalidade até que ela seja necessária no presente momento. Negligenciar flexibilidade arquitetural mínima (como interfaces para I/O). Não criar motores de plugins genéricos quando o cliente solicitou apenas suporte a um formato fixo de arquivo.

O Perigo do DRY Mal Aplicado (Acoplamento Acidental)

A falácia mais comum em times de software é aplicar DRY puramente em nível de sintaxe. Se dois microsserviços (ex.: Faturamento e Gestão de Clientes) possuem um método com 5 linhas similares para formatar um endereço, agrupá-los em uma biblioteca compartilhada acopla os dois times ao ciclo de vida daquela dependência.

Regra de Ouro: A duplicação de código é muito mais barata do que a abstração errada. DRY aplica-se à duplicação de intenção de negócio, não a semelhanças estruturais acidentais.


Requisitos Funcionais vs. Não Funcionais na Arquitetura

O design de software é o mecanismo de transição entre o levantamento de requisitos de produto e o sistema em operação:

flowchart LR
    subgraph Requisitos["Engenharia de Requisitos"]
        RF["Requisitos Funcionais<br/>(O que o sistema faz: Emitir Nota, Cancelar Pedido)"]
        RNF["Requisitos Não Funcionais<br/>(Como o sistema opera: Latência < 50ms, 99.99% Uptime)"]
    end

    subgraph Decisoes["Decisões de Design de Software"]
        Pat["Padrões de Projeto & Estilos de Arquitetura"]
        Infra["Dimensionamento e Escolha de Bancos de Dados"]
    end

    RF --> Pat
    RNF --> Infra
    RNF --> Pat
  1. Requisitos Funcionais (RF): Definem o comportamento do sistema diante de entradas específicas. São guiados diretamente pelas histórias de usuário e casos de uso do negócio.
  2. Requisitos Não Funcionais (RNF): Também conhecidos como atributos de qualidade (desempenho, escalabilidade, segurança, portabilidade, testabilidade e disponibilidade). Um sistema pode atender 100% dos seus requisitos funcionais e ainda assim falhar miseravelmente se não atender aos atributos de latência ou segurança.

O Ciclo de Mudança Arquitetural (Architecture Change Cycle)

A arquitetura de software não é estática; ela evolui em ciclos contínuos de inspeção e adaptação:

flowchart TD
    A["1. Identificação da Tensão<br/>(Novo requisito, gargalo de escala ou débito técnico)"] --> B["2. Análise de Trade-offs<br/>(Simplicidade vs. Flexibilidade, Custo vs. Performance)"]
    B --> C["3. Refatoração Cirúrgica<br/>(Preservação de testes unitários e contratos públicos)"]
    C --> D["4. Verificação por Testes de Caracterização<br/>(Garantia de não-regressão de comportamento)"]
    D --> E["5. Documentação da Decisão<br/>(Architecture Decision Record - ADR)"]
    E --> A

Ao documentar decisões técnicas via ADRs (registros de decisões arquiteturais), a equipe impede a regressão para decisões ingênuas e comunica explicitamente as premissas que justificaram a escolha de design.


Conclusão

Projetar software de alto nível não significa demonstrar erudição através de designs complexos com dezenas de camadas indiretas. A verdadeira maturidade da engenharia consiste em entregar soluções robustas que resolvem o problema essencial do usuário com o mínimo de código indispensável. Ao calibrar rigorosamente o acoplamento, buscar alta coesão e subordinar todas as decisões de código ao pragmatismo de KISS, DRY e YAGNI, sua equipe constrói sistemas que permanecem fáceis de alterar, testar e operar por décadas.