Pular para conteúdo

Princípios de Design

Princípios ajudam a avaliar designs; não são regras que mecanicamente produzem uma estrutura de classe correta. Aplique-os para reduzir custo de alterações prováveis, não para maximizar interfaces ou camadas.

Coesão, acoplamento e ocultação de informação

  • Coesão: pergunta se responsabilidades de um módulo pertencem juntas.
  • Acoplamento: pergunta quanto um módulo sabe sobre e depende de outro.
  • Ocultação de informação: mantém decisões de design voláteis atrás de limites estáveis.

Alta coesão e acoplamento intencional e estreito geralmente tornam o raciocínio local mais fácil. Zero acoplamento nem é possível nem desejável: colaboradores devem compartilhar um contrato para trabalhar juntos.

SOLID

Princípio Responsabilidade Única (SRP)

Um módulo deve ter uma responsabilidade coesa ou uma razão primária de mudar sob a perspectiva das partes interessadas. “Um método por classe” não é o princípio. Agrupe o comportamento que muda pela mesma razão.

Princípio Aberto/Fechado (OCP)

Entidades de software devem permitir extensão apropriada sem exigir edições em política estável, testada. Estratégias e composição podem fornecer pontos de extensão, mas pontos especulativos adicionam custo. Primeiro identifique um eixo crível de mudança.

Princípio Substituição de Liskov (LSP)

Um subtipo deve honrar expectativas comportamentais de seu supertipo. Não deve fortalecer pré-condições de entrada aceitas, enfraquecer pós-condições prometidas ou violar invariantes e restrições de histórico relevantes.

Princípio Segregação de Interface (ISP)

Clientes não devem depender de métodos que não usam. Prefira interfaces focadas em um papel a uma grande interface servindo clientes não relacionados. Não dividir um protocolo coeso em fragmentos que não podem mais expressar seus invariantes.

Princípio Inversão de Dependência (DIP)

Política de alto nível e detalhes de baixo nível devem depender de abstrações moldadas pela política. Injeção de dependência é uma técnica construtiva que pode suportar esse princípio, mas os dois não são sinônimos.

interface BookCatalog {
    Optional<Book> find(BookId id);
}

final class FindBook {
    private final BookCatalog catalog;

    FindBook(BookCatalog catalog) {
        this.catalog = catalog;
    }
}

O caso de uso define a capacidade que precisa. Um adaptador de banco de dados pode implementar essa capacidade sem tornar conceitos de banco de dados parte da política de caso de uso.

Outros princípios úteis

  • DRY: evitar múltiplas representações autoritárias do mesmo conhecimento; código semelhante não é necessariamente o mesmo conhecimento.
  • YAGNI: não implementar capacidade especulativa sem necessidade presente.
  • KISS: preferir o design mais simples que satisfaça as restrições reais.
  • Tell, do not ask: colocar as decisões no objeto que mantém o invariante relevante, permitindo ainda modelos de leitura e código de relatório.
  • Least knowledge: limitar a navegação por colaboradores, mas não ocultar estrutura de domínio necessária atrás de métodos de encaminhamento sem sentido.

Pacotes e limites de arquitetura

Direção de dependência importa mais que nomes de pasta. Um limite ganha seu custo quando isola volatilidade, posse, política, implantação ou um ponto de teste. Adicionar camadas controller/service/repository a toda operação trivial não é prova de bom design.

Exercícios

  1. Identificar qual tipo de alteração afetaria cada classe em um pequeno fluxo de checkout.
  2. Dar exemplo onde remover código semelhante melhor preserva DRY.
  3. Distinguir inversão de dependência de injeção por construtor.