Design de software

Por que padrões de design importam — e não devem ser idolatrados

A pergunta “Ainda precisamos estudar padrões de design?” aparece com frequência nas comunidades de desenvolvimento.

6 min de leitura
Imagem de capa de Por que padrões de design importam — e não devem ser idolatrados

A pergunta “Ainda precisamos estudar padrões de design?” aparece com frequência nas comunidades de desenvolvimento.

De um lado, dizem: “Aprenda obrigatoriamente; é fundamento”. Do outro: “Nas linguagens atuais, metade já não é necessária”. Os dois têm razão em parte, o que torna tudo mais confuso.

Indo direto ao ponto: vale muito a pena aprender padrões de design. Mas, quando “aplicar um padrão” vira o objetivo em si, isso se torna um atalho para estragar o código.

Neste artigo, vamos analisar em sequência por que padrões de design são necessários e por que não devemos confiar neles cegamente.


O que são padrões de design

Em uma frase, um padrão de design é um catálogo de soluções comprovadas para problemas recorrentes de design.

O ponto de partida foi 1994, quando a GoF (Gang of Four) organizou 23 padrões no livro 『Design Patterns』. Nomes como Singleton, Factory, Observer e Strategy vieram todos dali.

O importante é que esses padrões não foram inventados por alguém sentado à mesa. Eles surgiram da observação de desenvolvedores resolvendo problemas semelhantes de maneiras parecidas em inúmeros projetos, até dar nomes a essas soluções e organizá-las em um catálogo.

Por isso, a essência dos padrões de design está mais próxima de “experiência organizada” do que de “nova tecnologia”.


Por que são necessários 1 — Um vocabulário comum da equipe

O maior valor prático dos padrões de design não está no código, mas na comunicação.

Em vez de explicar “vamos manter uma única instância desta classe, permitir acesso global e adiar a inicialização até o primeiro acesso”, basta dizer: “Vamos usar um Singleton”.

Pense em quanta informação cabe em um comentário de code review como “Não seria melhor usar o padrão Observer aqui?”. Nomes de padrões são uma linguagem com uma taxa de compressão altíssima.

Sem esse vocabulário, você lê mais devagar as conversas da equipe, a documentação técnica e os comentários de código open source. Esse também é o motivo de padrões de design continuarem aparecendo em entrevistas técnicas.


Por que são necessários 2 — A chave para ler frameworks

Os frameworks que usamos todos os dias já são conjuntos de padrões de design.

  • O UITableViewDelegate do iOS usa o padrão Delegate
  • NotificationCentere os publishers do Combine usam o padrão Observer
  • A composição de View protocolos do SwiftUI se aproxima do padrão Composite
  • URLSession.shared é um Singleton

Conhecendo os padrões, mesmo ao ver uma API de framework pela primeira vez você consegue pensar: “Ah, então esta deve ser a estrutura” e deduzir a intenção do design. Você não apenas lê a documentação mais rápido; também prevê o que não está documentado.

Ilustração de uma tela de app iOS montada com blocos dos padrões de design Delegate, Observer, Singleton, Factory e Strategy
Delegate, Observer, Singleton… os frameworks que usamos todos os dias já são montados com padrões

Por que são necessários 3 — Reutilizar soluções comprovadas

Resolver o mesmo problema do zero e partir de soluções aprimoradas por décadas são pontos de partida completamente diferentes.

Por exemplo, o padrão Factory já tratou do problema de “a lógica de criação de objetos ficar espalhada e ser esquecida a cada alteração”, enquanto o padrão Observer já tratou da necessidade de “atualizar várias telas sempre que o estado mudar”.

Os padrões organizam não apenas soluções, mas também os trade-offs dessas soluções. Efeitos colaterais como “Singleton dificulta testes por ser estado global” também fazem parte do padrão. É como ter um mapa com as minas pisadas por desenvolvedores anteriores.


Mas por que não devemos idolatrá-los?

Até aqui, padrões de design podem parecer universais. O problema costuma começar “logo depois” que você os aprende.

Quando você tem um martelo, tudo parece prego

Depois de aprender padrões, dá vontade de aplicá-los em todos os lugares. Essa é a conhecida armadilha do martelo de ouro (golden hammer).

Você aplica uma Abstract Factory ao código que lê uma configuração, introduz Strategy em uma lógica com duas ramificações e divide em três interfaces e quatro implementações algo que caberia em uma classe.

Padrões são ferramentas para reduzir complexidade, mas, quando usados onde o problema não é complexo o suficiente, o próprio padrão vira uma nova complexidade. Se alguém que vê o código pela primeira vez precisa percorrer seis arquivos para encontrar a lógica real, isso não é design: é um labirinto.

Quando a linguagem evolui, os padrões desaparecem

Os 23 padrões da GoF foram organizados tendo como base o C++ e o Smalltalk de 1994. Muitos foram absorvidos pelos recursos das linguagens.

// 1994da época: padrão Strategy — classes de  + implementação de protocolos
protocol SortStrategy {
    func sort(_ numbers: [Int]) -> [Int]
}
final class AscendingSort: SortStrategy {
    func sort(_ numbers: [Int]) -> [Int] { numbers.sorted(by: <) }
}

// Hoje Swift: uma única closure atinge o mesmo objetivo
let sorted = numbers.sorted(by: >)

Em linguagens que permitem passar funções como valores, a maioria dos padrões Strategy e Command cabe em uma closure de uma linha. O enum do Swift, tipos por valor e implementações padrão de protocolos também resolvem, no nível da sintaxe, problemas que antes exigiam padrões.

Outra forma de entender padrões é: “algo que as pessoas estruturaram para compensar o que a linguagem ainda não conseguia fazer”. Portanto, memorizar a lista como uma verdade atemporal leva a código que resolve, com técnicas de décadas atrás, problemas que a linguagem já resolveu.

Quando o padrão vira o objetivo

O sinal mais perigoso é a discussão de design começar com “Qual padrão devemos usar?” em vez de “Como vamos resolver este problema?”.

Um padrão se aproxima mais de um destino ao qual você chega ao resolver um problema do que de um ponto de partida. A literatura de refatoração recomenda não introduzir padrões desde o início, mas refatorar nessa direção quando o código começar a sofrer essa pressão.

Fluxograma para decidir se um padrão de design deve ser aplicado: avalie a recorrência e os trade-offs do problema e escolha entre manter o código simples ou refatorá-lo para um padrão
Um padrão não é o ponto de partida, mas o destino ao qual você chega quando surge pressão

Então, como devemos usá-los?

Em resumo, o equilíbrio é este.

Ao aprender, observe primeiro o problema, não a solução. É preciso lembrar “em que situações” cada padrão surgiu para poder evitá-lo quando elas não se aplicarem. Metade do estudo de padrões é saber “quando não usá-los”.

Ao aplicar, comece pelo código mais simples. Quando a duplicação aparecer três vezes, uma mudança real for solicitada e a estrutura atual ficar pesada — aí será o momento de refatorar para um padrão. Comprar complexidade hoje em troca de flexibilidade futura quase sempre é um mau negócio.

Ao ler, use-os ativamente. Ao ler código de terceiros, frameworks ou open source, conhecimento de padrões traz apenas benefícios. O risco da idolatria aparece “ao usar”, não “ao ler” padrões.


Resumo

  • Padrões de design são um catálogo de soluções comprovadas para problemas recorrentes de design: resultado de observação, não de invenção.
  • Por que são necessários: o vocabulário comum da equipe, a chave para ler o design de frameworks e soluções comprovadas com seus trade-offs organizados.
  • Por que não idolatrá-los: aplicá-los a problemas simples transforma o padrão em complexidade (o martelo de ouro); muitos desaparecem com a evolução das linguagens; e, quando o padrão vira o objetivo, o design fica invertido.
  • Regra prática: comece pelo problema e pelo código simples; refatore para um padrão quando surgir pressão. Para leitura, use-os sem economizar.

Leituras recomendadas