Cada entrada nos quatro volumes — técnicas de refatoração, padrões de projeto, maus cheiros de código e padrões arquiteturais — agrupados por família.
Reestruturação controlada de código existente — preserva o comportamento, melhora o design.
Problema: Um método não tem dados suficientes para realizar certas ações. Solução: Crie um novo parâmetro para passar os dados necessários.
Problema: Um método não é usado por outras classes ou é usado apenas dentro de sua própria hierarquia de classes. Solução: Torne o método privado ou protegido.
Problema: Seus métodos contêm um grupo de parâmetros que se repete. Solução: Substitua esses parâmetros por um objeto.
Problema: Múltiplos métodos realizam ações semelhantes que diferem apenas em seus valores internos, números ou operações. Solução: Combine esses métodos usando um parâmetro que passará o valor especial necessário.
Problema: Você obtém vários valores de um objeto e depois os passa como parâmetros para um método. Solução: Em vez disso, tente passar o objeto inteiro.
Problema: Um parâmetro não é usado no corpo de um método. Solução: Remova o parâmetro não utilizado.
Problema: O valor de um campo deve ser definido apenas no momento de sua criação e não deve mudar em nenhum momento depois disso. Solução: Portanto, remova os métodos que definem o valor do campo.
Problema: O nome de um método não explica o que o método faz. Solução: Renomeie o método.
Problema: você tem um construtor complexo que faz algo mais do que apenas atribuir valores de parâmetros a campos do objeto. Solução: crie um método de fábrica e use-o para substituir as chamadas ao construtor.
Problema: Um método retorna um valor especial que indica um erro? Solução: Em vez disso, lance uma exceção.
Problema: Você lança uma exceção em um lugar onde um teste simples faria o trabalho? Solução: Substitua a exceção por um teste de condição.
Problema: Um método é dividido em partes, cada uma das quais é executada dependendo do valor de um parâmetro. Solução: Extraia as partes individuais do método para seus próprios métodos e chame-os em vez do método original.
Problema: chamar um método de consulta e passar seus resultados como parâmetros de outro método, quando esse método poderia chamar a consulta diretamente. Solução: em vez de passar o valor por meio de um parâmetro, tente colocar a chamada de consulta dentro do corpo do método.
Problema: Você tem um método que retorna um valor mas também altera algo dentro de um objeto? Solução: Divida o método em dois métodos separados. Como você esperaria, um deles deve retornar o valor e o outro modifica o objeto.
Problema: Você tem uma associação bidirecional entre classes, mas uma das classes não usa os recursos da outra. Solução: Remova a associação não utilizada.
Problema: Você tem um objeto de referência pequeno demais e alterado com pouca frequência para justificar o gerenciamento de seu ciclo de vida. Solução: Transforme-o em um objeto de valor.
Problema: Você tem duas classes que precisam, cada uma, usar os recursos da outra, mas a associação entre elas é apenas unidirecional. Solução: Adicione a associação que falta à classe que precisa dela.
Problema: Você tem muitas instâncias idênticas de uma mesma classe que precisa substituir por um único objeto. Solução: Converta os objetos idênticos em um único objeto de referência.
Problema: Os dados de domínio estão armazenados em classes responsáveis pela GUI? Solução: Então é uma boa ideia separar os dados em classes próprias, garantindo a conexão e a sincronização entre a classe de domínio e a GUI.
Problema: Uma classe contém um campo de coleção e um getter e setter simples para trabalhar com a coleção. Solução: Torne o valor retornado pelo getter somente leitura e crie métodos para adicionar/remover elementos da coleção.
Problema: Você tem um campo público. Solução: Torne o campo privado e crie métodos de acesso para ele.
Problema: Você tem um array que contém vários tipos de dados. Solução: Substitua o array por um objeto que terá campos separados para cada elemento.
Problema: uma classe (ou grupo de classes) contém um campo de dados. O campo tem seu próprio comportamento e dados associados. Solução: crie uma nova classe, coloque o campo antigo e seu comportamento na classe e armazene o objeto dessa classe na classe original.
Problema: seu código usa um número que tem um certo significado. Solução: substitua esse número por uma constante com um nome legível por humanos que explique o significado do número.
Problema: Você tem subclasses que diferem apenas em seus métodos (que retornam constantes). Solução: Substitua os métodos por campos na classe pai e exclua as subclasses.
Problema: Uma classe tem um campo que contém um código de tipo. Os valores desse tipo não são usados em condições de operadores e não afetam o comportamento do programa. Solução: Crie uma nova classe e use seus objetos no lugar dos valores do código de tipo.
Problema: Você tem um tipo codificado que afeta o comportamento, mas não pode usar subclasses para se livrar dele. Solução: Substitua o código de tipo por um objeto de estado. Se for necessário substituir o valor de um campo por código de tipo, outro objeto de estado é “plugado”.
Problema: Você tem um tipo codificado que afeta diretamente o comportamento do programa (os valores desse campo disparam diversos trechos de código em condicionais). Solução: Crie subclasses para cada valor do tipo codificado. Em seguida, extraia os comportamentos relevantes da classe original para essas subclasses. Substitua o código de controle de fluxo por polimorfismo.
Problema: Você usa acesso direto a campos privados dentro de uma classe. Solução: Crie um getter e um setter para o campo e use apenas eles para acessar o campo.
Problema: Você tem uma hierarquia de classes na qual uma subclasse é praticamente igual à sua superclasse. Solução: Mescle a subclasse e a superclasse.
Problema: Vários clientes estão usando a mesma parte da interface de uma classe. Outro caso: parte da interface em duas classes é idêntica. Solução: Mova essa porção idêntica para sua própria interface.
Problema: uma classe tem funcionalidades que são usadas apenas em certos casos. Solução: crie uma subclasse e use-a nesses casos.
Problema: Você tem duas classes com campos e métodos em comum. Solução: Crie uma superclasse compartilhada para elas e mova todos os campos e métodos idênticos para ela.
Problema: Suas subclasses implementam algoritmos que contêm etapas semelhantes na mesma ordem. Solução: Mova a estrutura do algoritmo e as etapas idênticas para uma superclasse, e deixe a implementação das etapas diferentes nas subclasses.
Problema: suas subclasses têm construtores com código quase idêntico. Solução: crie um construtor na superclasse e mova para ele o código que é igual nas subclasses. Chame o construtor da superclasse nos construtores das subclasses.
Problema: Duas classes têm o mesmo campo. Solução: Remova o campo das subclasses e mova-o para a superclasse.
Problema: Suas subclasses têm métodos que realizam um trabalho semelhante. Solução: Torne os métodos idênticos e, em seguida, mova-os para a superclasse relevante.
Problema: Um campo é usado apenas em algumas subclasses? Solução: Mova o campo para essas subclasses.
Problema: Um comportamento implementado em uma superclasse é usado por apenas uma (ou algumas) subclasses? Solução: Mova esse comportamento para as subclasses.
Problema: Uma classe contém muitos métodos simples que delegam a todos os métodos de outra classe. Solução: Faça a classe herdar do delegado, o que torna os métodos delegadores desnecessários.
Problema: Você tem uma subclasse que usa apenas uma parte dos métodos de sua superclasse (ou não é possível herdar os dados da superclasse). Solução: Crie um campo e coloque nele um objeto da superclasse, delegue os métodos ao objeto da superclasse e elimine a herança.
Problema: você tem vários condicionais que levam ao mesmo resultado ou ação. Solução: consolide todos esses condicionais em uma única expressão.
Problema: Código idêntico pode ser encontrado em todas as ramificações de um condicional. Solução: Mova o código para fora do condicional.
Problema: Você tem uma condicional complexa (if-then/else ou switch). Solução: Decomponha as partes complicadas da condicional em métodos separados: a condição, o then e o else.
Problema: para que um trecho de código funcione corretamente, certas condições ou valores precisam ser verdadeiros. Solução: substitua essas suposições por verificações de asserção específicas.
Problema: Como alguns métodos retornam null em vez de objetos reais, você tem muitas verificações de null no seu código. Solução: Em vez de null, retorne um objeto nulo que apresenta o comportamento padrão.
Problema: Você tem uma variável booleana que atua como um sinalizador de controle para várias expressões booleanas. Solução: Em vez da variável, use break, continue e return.
Problema: Você tem um condicional que executa várias ações dependendo do tipo ou das propriedades de um objeto. Solução: Crie subclasses correspondentes aos ramos do condicional. Nelas, crie um método compartilhado e mova para ele o código do ramo correspondente do condicional. Em seguida, substitua o condicional pela chamada do método relevante. O resultado é que a implementação adequada será obtida por meio de polimorfismo, dependendo da classe do objeto.
Problema: Você tem um grupo de condicionais aninhadas e é difícil determinar o fluxo normal de execução do código. Solução: Isole todas as verificações especiais e casos de borda em cláusulas separadas e coloque-as antes das verificações principais. Idealmente, você deve ter uma lista “plana” de condicionais, uma após a outra.
Problema: Quando uma classe faz o trabalho de duas, surge um desconforto. Solução: Em vez disso, crie uma nova classe e coloque nela os campos e métodos responsáveis pela funcionalidade relevante.
Problema: O cliente obtém o objeto B a partir de um campo ou método do objeto A. Em seguida, o cliente chama um método do objeto B. Solução: Crie um novo método na classe A que delega a chamada ao objeto B. Agora o cliente não conhece a classe B nem depende dela.
Problema: Uma classe quase não faz nada e não é responsável por nada, e nenhuma responsabilidade adicional está planejada para ela. Solução: Mova todas as funcionalidades da classe para outra.
Problema: Uma classe utilitária não contém o método de que você precisa e você não pode adicionar o método à classe. Solução: Adicione o método a uma classe cliente e passe a ela um objeto da classe utilitária como argumento.
Problema: uma classe utilitária não contém alguns métodos de que você precisa. Mas você não pode adicionar esses métodos à classe. Solução: crie uma nova classe contendo os métodos e torne-a filha ou wrapper da classe utilitária.
Problema: Um campo é usado mais em outra classe do que na sua própria classe. Solução: Crie um campo em uma nova classe e redirecione todos os usuários do campo antigo para ele.
Problema: um método é usado mais em outra classe do que na sua própria classe. Solução: crie um novo método na classe que mais usa o método e, em seguida, mova o código do método antigo para lá. Transforme o código do método original em uma referência ao novo método na outra classe ou então remova-o por completo.
Problema: Uma classe tem métodos demais que simplesmente delegam para outros objetos. Solução: Exclua esses métodos e force o cliente a chamar os métodos finais diretamente.
Problema: Você tem um fragmento de código que pode ser agrupado. Solução: Mova esse código para um novo método (ou função) separado e substitua o código antigo por uma chamada ao método.
Problema: Você tem uma expressão difícil de entender. Solução: Coloque o resultado da expressão ou de suas partes em variáveis separadas que sejam autoexplicativas.
Problema: Quando o corpo de um método é mais óbvio que o próprio método, use esta técnica. Solução: Substitua as chamadas ao método pelo conteúdo do método e exclua o próprio método.
Problema: Você tem uma variável temporária à qual é atribuído o resultado de uma expressão simples e nada mais. Solução: Substitua as referências à variável pela própria expressão.
Problema: Algum valor é atribuído a um parâmetro dentro do corpo do método. Solução: Use uma variável local em vez de um parâmetro.
Problema: Você tem um método longo cujas variáveis locais estão tão entrelaçadas que não é possível aplicar Extrair Método. Solução: Transforme o método em uma classe separada para que as variáveis locais se tornem campos da classe. Então você pode dividir o método em vários métodos dentro da mesma classe.
Problema: Você coloca o resultado de uma expressão em uma variável local para uso posterior no seu código. Solução: Mova a expressão inteira para um método separado e retorne o resultado dele. Consulte o método em vez de usar uma variável. Incorpore o novo método em outros métodos, se necessário.
Problema: você tem uma variável local usada para armazenar diversos valores intermediários dentro de um método (exceto variáveis de laço). Solução: use variáveis diferentes para valores diferentes. Cada variável deve ser responsável por apenas uma coisa em particular.
Problema: Então você quer substituir um algoritmo existente por um novo? Solução: Substitua o corpo do método que implementa o algoritmo por um novo algoritmo.
Soluções típicas para problemas comuns no design de software, organizadas por intenção.
O Abstract Factory é um padrão de projeto criacional que permite que você produza famílias de objetos relacionados sem ter que especificar suas classes concretas.
O Builder é um padrão de projeto criacional que permite a você construir objetos complexos passo a passo. O padrão permite que você produza diferentes tipos e representações de um objeto usando o mesmo código de construção.
O Factory Method é um padrão criacional de projeto que fornece uma interface para criar objetos em uma superclasse, mas permite que as subclasses alterem o tipo de objetos que serão criados.
O Prototype é um padrão de projeto criacional que permite copiar objetos existentes sem fazer seu código ficar dependente de suas classes.
O Singleton é um padrão de projeto criacional que permite a você garantir que uma classe tenha apenas uma instância, enquanto provê um ponto de acesso global para essa instância.
O Adapter é um padrão de projeto estrutural que permite objetos com interfaces incompatíveis colaborarem entre si.
O Bridge é um padrão de projeto estrutural que permite que você divida uma classe grande ou um conjunto de classes intimamente ligadas em duas hierarquias separadas—abstração e implementação—que podem ser desenvolvidas independentemente umas das outras.
O Composite é um padrão de projeto estrutural que permite que você componha objetos em estruturas de árvores e então trabalhe com essas estruturas como se elas fossem objetos individuais.
O Decorator é um padrão de projeto estrutural que permite que você acople novos comportamentos para objetos ao colocá-los dentro de invólucros de objetos que contém os comportamentos.
O Facade é um padrão de projeto estrutural que fornece uma interface simplificada para uma biblioteca, um framework, ou qualquer conjunto complexo de classes.
O Flyweight é um padrão de projeto estrutural que permite a você colocar mais objetos na quantidade de RAM disponível ao compartilhar partes comuns de estado entre os múltiplos objetos ao invés de manter todos os dados em cada objeto.
O Proxy é um padrão de projeto estrutural que permite que você forneça um substituto ou um espaço reservado para outro objeto. Um proxy controla o acesso ao objeto original, permitindo que você faça algo ou antes ou depois do pedido chegar ao objeto original.
O Chain of Responsibility é um padrão de projeto comportamental que permite que você passe pedidos por uma corrente de handlers. Ao receber um pedido, cada handler decide se processa o pedido ou o passa adiante para o próximo handler na corrente.
O Command é um padrão de projeto comportamental que transforma um pedido em um objeto independente que contém toda a informação sobre o pedido. Essa transformação permite que você parameterize métodos com diferentes pedidos, atrase ou coloque a execução do pedido em uma fila, e suporte operações que não podem ser feitas.
O Iterator é um padrão de projeto comportamental que permite a você percorrer elementos de uma coleção sem expor as representações dele (lista, pilha, árvore, etc.)
O Mediator é um padrão de projeto comportamental que permite que você reduza as dependências caóticas entre objetos. O padrão restringe comunicações diretas entre objetos e os força a colaborar apenas através do objeto mediador.
O Memento é um padrão de projeto comportamental que permite que você salve e restaure o estado anterior de um objeto sem revelar os detalhes de sua implementação.
O Observer é um padrão de projeto comportamental que permite que você defina um mecanismo de assinatura para notificar múltiplos objetos sobre quaisquer eventos que aconteçam com o objeto que eles estão observando.
O State é um padrão de projeto comportamental que permite que um objeto altere seu comportamento quando seu estado interno muda. Parece como se o objeto mudasse de classe.
O Strategy é um padrão de projeto comportamental que permite que você defina uma família de algoritmos, coloque-os em classes separadas, e faça os objetos deles intercambiáveis.
O Template Method é um padrão de projeto comportamental que define o esqueleto de um algoritmo na superclasse mas deixa as subclasses sobrescreverem etapas específicas do algoritmo sem modificar sua estrutura.
O Visitor é um padrão de projeto comportamental que permite que você separe algoritmos dos objetos nos quais eles operam.
Indicadores de problemas mais profundos — e as refactorizações que os tratam.
Duas classes executam funções idênticas, mas têm nomes de métodos diferentes.
Se uma subclasse usa apenas alguns dos métodos e propriedades herdados de suas classes pai, a hierarquia está desequilibrada. Os métodos desnecessários podem simplesmente ficar sem uso ou serem redefinidos e lançar exceções.
Você tem um operador switch complexo ou uma sequência de instruções if.
Campos temporários recebem seus valores (e, portanto, são necessários aos objetos) apenas sob certas circunstâncias. Fora dessas circunstâncias, eles ficam vazios.
Um método está repleto de comentários explicativos.
Uma classe de dados se refere a uma classe que contém apenas campos e métodos rudimentares para acessá-los (getters e setters). Elas são simplesmente contêineres de dados usados por outras classes. Essas classes não contêm nenhuma funcionalidade adicional e não conseguem operar de forma independente sobre os dados que possuem.
Uma variável, parâmetro, campo, método ou classe não é mais usado (geralmente porque está obsoleto).
Dois fragmentos de código parecem quase idênticos.
Entender e manter classes sempre custa tempo e dinheiro. Portanto, se uma classe não faz o suficiente para merecer sua atenção, ela deve ser excluída.
Há uma classe, método, campo ou parâmetro sem uso.
Às vezes, diferentes partes do código contêm grupos idênticos de variáveis (como parâmetros para conexão com um banco de dados). Esses aglomerados devem ser transformados em suas próprias classes.
Uma classe contém muitos campos/métodos/linhas de código.
Um método contém linhas de código demais. Em geral, qualquer método com mais de dez linhas deve começar a levantar suspeitas.
Mais de três ou quatro parâmetros para um método.
Uso de primitivos em vez de pequenos objetos para tarefas simples (como moeda, intervalos, strings especiais para números de telefone etc.)Uso de constantes para codificar informação (como uma constante USER_ADMIN_ROLE = 1 para se referir a usuários com direitos de administrador.)Uso de constantes de string como nomes de campos para uso em arrays de dados.
Você se vê tendo que alterar muitos métodos não relacionados ao fazer mudanças em uma classe. Por exemplo, ao adicionar um novo tipo de produto, você precisa alterar os métodos para encontrar, exibir e fazer pedidos de produtos.
Sempre que você cria uma subclasse para uma classe, percebe que precisa criar uma subclasse para outra classe.
Fazer qualquer modificação exige que você faça muitas pequenas alterações em muitas classes diferentes.
Um método acessa os dados de outro objeto mais do que os seus próprios dados.
Uma classe usa os campos e métodos internos de outra classe.
No código você vê uma série de chamadas parecidas com $a->b()->c()->d()
Se uma classe realiza apenas uma ação, delegando o trabalho a outra classe, por que ela existe?
Padrões e metodologias a nível de sistema — DDD, CQRS, TDD e Spec-Driven Development.
Uma abordagem de desenvolvimento de software que centra o design em um modelo rico do domínio de negócio, expresso em uma linguagem compartilhada por engenheiros e especialistas do domínio.
Separe o modelo que altera o estado (comandos) do modelo que lê o estado (consultas), de modo que cada lado possa ser modelado, escalado e otimizado de forma independente.
Uma disciplina de desenvolvimento na qual você escreve um teste que falha antes do código que o faz passar e depois refatora — deixando os testes guiarem o design em ciclos curtos e enxutos.
Escreva primeiro uma especificação explícita e autoritativa e, então, derive dela a implementação, os testes e o código gerado — mantendo a especificação como a única fonte da verdade.