- Extract Function (134)
- Inline Function (144)
- Extract Variable (147)
- Inline Variable (152)
- Change Function Declaration (153)
- Encapsulate Variable (160)
- Rename Variable (165)
- Introduce Parameter Object (168)
- Combine Functions into Class (172)
- Combine Functions into Transform (177)
- Split Phase (183)
- Encapsulate Record (190)
- Encapsulate Collection (198)
- Replace Primitive with Object (203)
- Replace Temp with Query (207)
- Extract Class (211)
- Inline Class (215)
- Hide Delegate (218)
- Remove Middle Man (220)
- Substitute Algorithm (223)
- Move Function (225)
- Move Field (235)
- Move Statements into Function (241)
- Move Statements to Callers (245)
- Replace Inline Code with Function Call (251)
- Slide Statements (252)
- Split Loop (257)
- Replace Loop with Pipeline(261)
- Remove Dead Code(267)
- Split Variable(269)
- Rename Field(273)
- Replace Derived Variable with Query(277)
- Change Reference to Value(281)
- Change Value to Reference(284)
- Decompose Conditional(288)
- Consolidate Conditional Expression(291)
- Replace Nested Conditional with Guard Clauses(294)
- Replace Conditional with Polymorphism(299)
- Introduce Special Case(318)
- Introduce Assertion(333)
- Separate Query from Modifier(337)
- Parameterize Function(340)
- Remove Flag Argument(344)
- Preserve Whole Object(349)
- Replace Parameter with Query(354)
- Replace Query with Parameter(357)
- Remove Setting Method(361)
- Replace Constructor with Factory Function(363)
- Replace Function with Command(366)
- Replace Command with Function(373)
- Pull Up Method(378)
- Pull Up Field(381)
- Pull Up Constructor Body(383)
- Push Down Method(387)
- Push Down Field(388)
- Replace Type Code with Subclasses(389)
- Remove Subclass(397)
- Extract Superclass(402)
- Collapse Hierarchy(408)
- Replace Subclass with Delegate(409)
- Replace Superclass with Delegate(429)
Use quando funções são longas. Código usado mais do que uma vez merece a sua própria função. Se voce investe tempo para tentar entender o que um bloco de código faz, transforme-o numa função com um nome que descreva o seu comportamento.
Oposto do Extract Function. Utilizar quando o corpo da função consegue descrever o seu próprio comportamento. Excelente quando existem muitas chamadas indiretas de função(uma função que chama outra, onde o corpo da segunda já descreve o comportamento). Em alguns casos, é interessante utilizar o Inline Function para se ter uma visão melhor do que acontece, para posteriormente aplicar Extract Function com mais cuidado, evitando tantas delegações indiretas e desnecessárias.
Existem expressões que são complexas e muito difíceis de entender. Para esses casos, é interessante dividir em pequenas as expressões que sejam mais fácil de ler. Dessa forma, é possível dar nomes descritivos para essas pequenas expressões. Caso o sentido seja apenas no contexto de uma função, então utilizar Extract Variable é uma boa solução. Porém, em casos onde o contexto é mais amplo(além da função que a expressão se encontra), é mais interessante disponibilizar em forma de função Extract Function.
Utilizar quando o nome da variável não diz nada além do que a própria expressão.
Também conhecido como Rename Function, Rename Method, Add Parameter, Remove Parameter, Change Signature.
Bons nomes são essenciais para entendimento do sistema de forma fácil. Se possuo um nome descritivo, não tenho a necessidade de olhar o corpo da função. Porém, o nome de uma Função ou Método, na grande maioria das vezes, não é o melhor na primeira declaração.
Muito útil para diminuir o acoplamento entre módulos, onde uma função ou método muitas vezes não é necessário ter conhecimento da interface de um objeto. Por exemplo, se tiver uma função para formatar o número de telefone de uma pessoa, onde tal função aceita uma pessoa como parâmetro, não poderei usá-la para formatar o número de telefone de uma empresa.
Dados, diferentemente de funções, são difíceis de alterar ou refatorar caso o seu escopo seja amplo.
No caso de uma função antiga, é possível migra-la mantendo-a intacta como uma função de encaminhamento (o cliente continua a chamar a função antiga, que chama a função nova).
No caso de dados(variáveis), o ideal é ter funções que servem como encapsulamento, tanto de acesso como de atualização para manipulação.
O encapsulamento garante um controle sobre os dados, como eles podem ou não serem alterados. Ademais, transforma a difícil tarefa de reorganizar dados na tarefa mais simples de reorganizar funções, utilizando Change Function Declaration, por exemplo.
Bons nomes são essenciais para o entendimento do software sem gerar muito esforço. Utilizar para que o código se autodescreva. Não irei conseguir criar bons nomes de primeira tentativa. Ademais, a variável acaba a receber outro nome como consequência das mudanças nas necessidades dos meus usuários.
O nome de uma variável deve refletir o tamanho do seu escopo. Se ela está inserida numa função de 2 linhas, então posso ter uma letra como nome. Caso o seu escopo seja mais amplo, devo utilizar um nome mais descritivo.
Existem dados que aparecerem em grupo, onde ao chamar um é necessário chamar outro, por exemplo, startDate e endDate. Ao aplicar Introduce Parameter Object, eles farão parte do mesmo objeto ou classe, tornando esse relacionamento explicito, e assim diminuindo a quantidade de parâmetros das funções que os chamam.
O real benefício é que isso me permite criar comportamentos comuns nesses dados, por exemplo, isBetween, criando uma abstração que simplifica a compreensão do meu domínio.
Utilizar quando funções compartilham o mesmo corpo comum de dados (em geral, passados como argumentos da chamada de função).
Usar uma classe deixa o ambiente comum compartilhado por essas funções mais explicito, permite simplificar as chamadas de função dentro do objeto por meio da remoção de vários argumentos, já que usarão variáveis de instância.
Utilizar quando funções compartilham o mesmo corpo comum de dados (em geral, passados como argumentos da chamada de função).
A partir disso, criar uma função pura, que irá pegar o corpo comum, fazer uma deep copy, alterar as propriedades dessa cópia (transformar ou enriquecer com dados calculados) e devolver para o client com os valores atualizados, sem alterar o corpo original.
Dessa forma todos os lugares que transformam um objeto de entrada para gerar novos valores estão num único lugar.
Utilizar quando uma função faz mais de uma coisa.
Por exemplo, uma função que faz parse e calcula os valores de um pedido. Dividir em duas fases, onde a primeira fase irá fazer o parse da estrutura, e a segunda fase irá fazer os cálculos. A segunda fase irá receber os parâmetros necessários, seguido de uma estrutura de dados intermediária que fará a troca de dados entre a primeira e a segunda fase.
Estrutura de dados exemplo:
const range = { start: 1, end: 5 };
//ou
const range = { start: 1, length: 5 };
//ou
const range = { end:5, length: 5 };
Fowler prefere objetos a estrutura de dados quando trabalha com dados mutáveis, pois assim é possível expor apenas os comportamentos, um para cada valor acima, por exemplo. Além disso, o client de um objeto não saberá a estrutura interna nem como a lógica de cálculo está implementada, possibilitando assim fazer alterações mais facilmente.
Caso especial do Encapsulate Record.
Segundo Fowler, é mais fácil manter dados mutáveis encapsulados, pois, o rastreio das alterações na estrutura se torna explícito.
Muitas vezes, os getters de uma coleção subjacente de uma Classe(Encapsulated Record) devolve diretamente o seu estado, sendo possível modificar e manipular sem que o Encapsulated Record possa intervir, quebrando o encapsulamento e dificultando a depuração caso ocorra bugs (ou se os desenvolvedores não tiverem conhecimento sobre a estrutura).
O ideal, é devolver uma cópia da coleção e oferecer ao client métodos para manipulação da coleção a partir do Encapsulated Record(métodos add, remove e access).
Nas etapas iniciais do desenvolvimento de software, tomamos a decisão de representar valores como tipo primitivo, exemplo: string para representar CPF. À medida que o software evoluí, esses valores não serão mais simples. Um CPF pode ser representado sem ou com formatação, ter necessidade de validar e afins. E isso pode acabar com duplicação na lógica. (O mesmo vale para telefone, date range entre outros).
O ideal é encapsular esses valores para que, futuramente, possam ser adicionados comportamentos.
Variáveis temporárias são usadas para capturar o valor de um código de modo a referenciá-lo mais tarde numa função, além de explicar o significado da expressão.
Utilizar uma função ao invés de variável temporária, dando mais sentido num contexto mais amplo, além de permitir quebrar uma função longa em pequenas.
Caso a variável seja atribuída várias vezes, todas as atribuições devem ser extraídas e colocadas na consulta.
Essa refatoração funciona melhor em classe, pois existe um contexto compartilhado para os métodos extraídos. Fora de uma classe, pode correr o risco de ter muitos parâmetros numa função de nível mais alto, perdendo boa parte das vantagens de usar uma função.
Uma Classe deve ser uma abstração nítida, deve lidar apenas com algumas responsabilidades claras. Uma Classe cresce e acaba ficando cheias de responsabilidades, pois você julga não ser necessária outra classe.
Um bom sinal de que uma Classe deve ser dividida em outra(s) é quando um subconjunto dos dados e um subconjunto dos métodos parecem formar um conjunto. Outro sinal é quando um subconjunto de dados mudam juntos.
Às vezes, uma Classe extraída não vale mais a pena, ela não deveria existir. Isso é resultado de mover responsabilidade de uma Classe original, tornando-a vazia.
Outro motivo para utilizar Inline Class é quando tenho que refatorar duas classes em outro par de Classes com uma alocação diferente de recursos. O mais fácil é utilizar Inline Class para juntar ambas, e então usar Extract Class para fazer a nova separação.
Esta é a abordagem genérica para reorganizar o código: às vezes, é mais fácil mover elementos, um de cada vez, de um contexto para outro; outras vezes, porém, é melhor usar Inline Class para reunir os contextos e depois usar Extract Class para separar em elementos diferentes.
O segredo para um bom design modular é o encapsulamento. Um encapsulamento significa que módulos precisam saber menos sobre outras partes do sistema. Logo, quando houver mudanças num módulo, menos módulos precisarão ter conhecimento sobre elas.
Exemplo:
Se temos 4 clientes A, B, C e D, onde todos consomem o método getManager da Classe Department que está na Classe Person. Se a interface de Department mudar, todos os clientes saberão e deverão mudar junto a ele. Person pode esconder a delegação simplesmente chamando o método que se faz necessário em todos os clientes de Department. Em outras palavras, alguns métodos de Person irão encapsular as chamadas para os métodos de Department.
Dessa forma, caso Department mude a sua interface, o impacto será apenas em Person.
Certamente existem vantagens em utilizar o Hide Delegate, porém, a sua desvantagem é que, sempre que um cliente precisar de um método do objeto delegado, o middle man precisará implementar um método simples.
Existem muitas formas de fazer uma mesma tarefa. Algumas são mais fáceis que outras. Se eu achar uma maneira mais simples de fazer algo, substituirei o modo mais complicado pelo modo mais claro.
Exemplo: utilizar uma library ao invés de utilizar uma implementação complexa.
Um bom motivo para mover funções é quando ela referencia mais elementos de outros contextos do que do contexto em que se encontra no momento. Dessa forma, outras partes do software serão menos independentes dos detalhes desse módulo.
Sempre crie estruturas de dados com um bom design orientado ao domínio(DDD), pois geralmente isso ajuda a criar boas estruturas de dados.
Use essa refatoração sempre que possuir estruturas de dados com defeitos. Ou quando perceber que tem sempre que passar um campo de um registro quando passa outro registro para uma função. É melhor que porções de dados que são sempre passados em conjunto estejam num único registro para que o seu relacionamento esteja claro.
Remover duplicação é uma das melhores regras gerais de um código saudável. Se você ver o mesmo código executado sempre que chamar uma função, mova o código executado para dentro da função. Assim, quaisquer modificações futuras serão feitas num só lugar e usadas em todos os lugares que o chamam.
As funções devem ter apenas uma responsabilidade, mas com o tempo acabam crescendo e ficando com duas ou mais responsabilidades.
Use quando uma função for chamada em vários lugares e variar em algumas delas. O código que varia deve ser extraído da função e ser utilizado diretamente em quem chama.
Similar o Move Statements into Function, mas sem uma função preexistente.
Use para substituir um código inline por uma chamada de função. Dessa forma, é possível dar um nome para a função que explique o seu propósito e não o seu funcionamento.
Um código torna-se mais fácil de entender se todos os elementos relacionados aparecem juntos. Logo, é ideal para observar estruturas de dados e onde elas são manipuladas, facilitando o meu entendimento.
Geralmente utilizado como passo preparatório para outra refatoração como, por exemplo, Extract Function.
Tome cuidado ao mover códigos que possam ter efeitos colaterais. Para evitar isso, é interessante aplicar o Command-Query Separation, dessa forma qualquer função que devolva um valor está livre de efeitos colaterais. Caso não tenha tanto controle sobre o código, criar testes robustos que possibilitem capturar falhas.
Muitas vezes fazemos mais de uma tarefa num loop. Dessa forma, sempre que for necessário modificar uma dessas tarefas teremos que entender ambas.
Utilize para separar responsabilidades. Também pode ser usado como forma preparatória para outras refatorações, por exemplo, Extract Function.
Utilize Collection Pipelines(map
, filter
e afins) para maior legibilidade na manipulação de coleção de objetos.
Remover o código que não é utilizado. Não deixar o código como comentário. Dessa forma, evita outros programadores a lerem um código que não possuí nenhum efeito.
Variável que recebe valor mais de uma vez é sinal que possuí mais que uma responsabilidade no método. Qualquer variável com mais de uma responsabilidade deve ser substituída por diversas variáveis, uma para cada responsabilidade.
Variável acumuladora tem apenas uma responsabilidade, então não se encaixa nessa refatoração.
Nomes são importantes, principalmente se são nomes de campos de um objeto(record) amplamente utilizado.
Utilize sempre que precisar de um bom nome para refletir o real significado de um campo.
Utilize quando o uso da variável está distante dos locais em que ela pode sofrer mutação. Ao invés de ter uma variável que seja acumuladora, prefira fazer o cálculo sob demanda. Dessa forma, torna-se mais fácil rastrear os valores da variável.
Fowler defende que, de modo geral, é mais fácil trabalhar com dados imutáveis, pois é possível passar o objeto para diferentes contextos sem se preocupar que o seu valor seja alterado.
Caso precise que o objeto seja manipulado e observado por diferentes contextos, é melhor trata-lo como referência, ou seja, aplicar Change Value to Reference.
Inversa de Change Reference to Value.
Utilize quando precisar manipular algum objeto que é compartilhado entre diferentes contextos. Assim, haverá apenas uma referência de uma entidade sendo tratada por todos os contextos necessários, ao invés de ter vários Value Object espalhados por contextos do qual iriam gerar inconsistência.
Caso especial de Extract Function.
Utilize quando existirem condicionais complexas. Transforme o parâmetro do if
numa chamada de função que revela a sua
intenção. Faça isso para cada ramificação, e para o corpo de cada uma delas. No final de tudo, saberá o que acontece e o por que.
Utilize quando houver condicionais em que cada verificação difere, porém, a ação resultante é a mesma.
Ao aplicar essa refatoração, é possível usar Extract Function. Dessa forma, saberá o por que acontece ao invés de como acontece.
Não utilizar caso as verificações tiverem uma ação resultante diferente.
Segundo Fowler, as expressões condicionais se apresentam em dois estilos. No primeiro, os dois ramos da condicional fazem parte do happy path. No segundo, um ramo é o happy path enquanto os demais são incomuns(exceções).
Utilize quando houver condicionais aninhadas e complexas. Dessa forma, será possível diminuir a complexidade ciclomática, facilitando observar o happy path da função.
É possível aplicar Guard Clause com a simples inversão das condicionais. Em alguns casos, basta ter um retorno imediato, sem a necessidade de inversão da condicional. Em todos os casos, começar pela condicional mais externa.
Utilizar quando tiver condicionais complexas, que se repetem em diversas partes do código para gerar algum valor com base num tipo.
Existem casos onde a implementação base ficará na superclasse, por ser o caso mais comum ou o mais simples, então a variação ficará por conta da subclasse. Em outros casos, todas as condicionais variam e não existe um caso base, logo, toda a implementação ficará na subclasse.
Utilizar quando tiver condicional que busca por um valor específico, que acaba sendo repetida em diversos clientes. Por exemplo, verificar por null em alguma propriedade de um objeto. Ao invés de retornar null, deve ser retornado um objeto literal que represente o null, onde possuí os seus próprios métodos para valores default.
Special Case Object são sempre Value Object e, dessa forma, devem ser sempre imutáveis, mesmo que os objetos que eles estejam substituindo não sejam. Caso seja um objeto literal(objeto javascript, por exemplo), devo utilizar o Object.freeze para lhe tornar imutável.
Essa refatoração também é chamada de Null Object Pattern, porém, para Fowler, esse pattern é um caso especial do Special Case.
Utilize como último recurso para debug.
Uma falha numa asserção sinaliza um erro do programador, além disso, comunica-o sobre o estado em que se supõe que esteja o programa no respectivo ponto de execução. Asserções não devem afetar a execução de um sistema, ou seja, o programa deve funcionar da mesma maneira com ou sem elas.
Um código autotestável reduz a importância das asserções na depuração.
Segundo Fowler, funções que não possuem efeitos colaterais podem ser chamadas diversas vezes e serem inseridas em outras funções facilmente. Uma regra mencionada por ele é que: a função que devolve um valor não deve ter efeitos colaterais observáveis(command-query separation).
Em casos de cache, onde é alterado o estado do objeto, a mudança não é observável, por isso essa refatoração não deve ser aplicada.
Utilize quando existirem funções que possuem uma lógica muita parecida que difere apenas nos seus valores literais. Dessa forma, a função irá atender a diversos casos, pois poderá ser usada com diferentes valores.
Utilizar uma flag como argumento para decidir qual fluxo a função deve seguir sinaliza que a função faz mais de uma coisa.
Aplique juntamente Decompose Conditional para criar funções explícitas, que revelem a quem chama a sua verdadeira intenção.
Segundo Fowler, ao trabalhar com parâmetros de funções que recebem valores derivados, é melhor ter o registro completo e deixar o corpo da função tratar da derivação. Dessa forma, será mais fácil caso queira pegar outros valores do mesmo registro no futuro.
Além disso, Fowler defende que o principal motivo para não fazer essa refatoração é não querer depender do objeto completo, porém, isso é sinal de que a lógica deve ser movida para o próprio objeto completo.
Inversa de Replace Query with Parameter.
"Se uma chamada de função passar um valor que a função possa facilmente determinar por conta própria, essa é uma forma de duplicação.". Isso faz com que quem chama a função tenha que determinar o parâmetro, quando poderia estar livre dessa tarefa, sendo essa a responsabilidade do corpo da função.
O motivo mais comum para evitar essa refatoração é se com a remoção do parâmetro a função tenha uma dependência indesejada no seu corpo.
Inversa de Replace Parameter with Query.
Utilize quando tiver a necessidade de remover do corpo da função uma dependência indesejada. Dessa forma, quem chama a função recebe a responsabilidade de fornecer o valor que antes era interno.
Aplicando essa refatoração tenho a possibilidade de criar funções puras(transparência referencial), ou seja, sempre que eu chamar essa função passando os mesmos valores de parâmetros, terei o mesmo resultado. E com isso facilito os testes e a compreensão do módulo.
Utilizar quando não faz sentido alterar o valor de um campo após a instanciação. Caso algum cliente precise "alterar" o valor de um campo que antes era feito com um setter(e agora é imutável), ele deverá instanciar um novo objeto.
O método constructor
em muitas linguagens orientadas a objeto possuem limitações onde, por exemplo, não é possível retornar uma subclasse. A função de factory serve para tirar algumas dessas limitações.
Utilize quando precisar criar diferentes objetos.
Inversa de Replace Command with Function.
Utilizar quando necessitar quebrar uma função grande e complexa em pequena e simples.
Com isso, será criado um objeto de comando
(como Fowler chama), onde o seu propósito será requisição e execução. Um objeto de comando
contém estado e realiza operações como execute
e undo
,
além de ganhar mais flexibilidade, pois permitirá uso de inheritance(herança) e hooks.
Inversa de Replace Function with Command.
Utilizar quando o Command executar algo pequeno e simples. Uma função já é o suficiente para realizar tal tarefa.
Inversa de Push Down Method.
Utilizar quando subclasses possuem duplicação no código. O ideal em casos de herança é que comportamento em comum fique na superclass, enquanto os detalhes fiquem na subclass.
Existem casos onde esta refatoração é antecedida por outras, como Parameterize Function quando duas funções são muitos parecidas e ao serem alteradas serão essencialmente a mesma função, ou Pull Up Field quando o corpo da função duplicada referenciar um campo da subclass.
Inversa de Push Down Field.
Utilizar quando subclasses possuem duplicação nos campos.
Ao aplicar esta refatoração as duplicações são reduzidas de duas formas, tanto no campo quanto nos métodos que utilizam esse campo, pois serão movidos para a superclass.
Semelhante a Pull Up Method.
Utilizar quando possuir comportamento em comum no construtor das subclasses. Chamar o super()
(JavaScript) e jogar a duplicação na superclass.
Inversa de Pull Up Method.
Utilizar quando um método de uma superclass for relevante apenas para uma subclass.
Inversa de Pull Up Field.
Utilizar quando um campo de uma superclass for relevante apenas para uma subclass.
Inversa de Remove Subclass.
Utilizar quando tiver uma propriedade que classifica o "tipo" de um item, como código de tipo de employee(engineer, manager, salesman).
Existe um caso especial onde não posso utilizar subclasses(herança direta) se o código de tipo for mutável ou se precisar do código de tipo para outras tarefas. Nesse caso, devo utilizar herança indireta.
Inversa de Replace Type Code with Subclasses.
Subclasses são úteis quando precisar de diferentes objetos, por exemplo, employee(engineer, manager, salesman), e/ou comportamento polimórfico. Entretanto, em alguns casos uma subclass não faz muito e não valerá a pena tê-la.
Utilize para remover subclass, e substitui-la com um campo na superclass.
Uma dica valiosa dada por Fowler é: sempre que quero mudar o modo de representar algo, procuro primeiro encapsular a representação atual a fim de minimizar o impacto em qualquer código de cliente.
Utilizar quando classes fizerem tarefas parecidas, aplicar herança para extrair as semelhanças e inseri-las em uma superclass.
Utilizar quando uma subclass não for mais tão diferente da superclass, então combine-as numa única classe.
Segundo Fowler, uma das desvantagens de utilizar herança é que ela pode só ser usada uma vez, num único eixo de variação. Exemplo: posso variar comportamento das pessoas de acordo a categoria idade e o nível de renda, poderei ter subclasses para jovens e idosos, ou para ricos e pobres, mas não para ambos. Outra desvantagem de herança é que existirá um relacionamento íntimo entre as classes, então qualquer alteração na superclass poderá causar erros nas subclasses. É possível resolver ambos os problemas com delegação.
Fowler também diz que essa refatoração é basicamente Composition over inheritance
(gang of four) onde delegação é o mesmo que composition
.
E finaliza com a frase de efeito: Prefira uma mistura criteriosa de composição e herança a usar somente uma delas
.
Além de usar todas as funções da superclass, também deve ser verdade que toda instância da subclass é uma instância da superclass, e ela deve ser um objeto válido em todos os casos em que estivermos usando a superclass.
Se as funções da superclass não fazem sentido na subclass, é sinal de que não deveria utilizar herança para ter funcionalidades da superclass. Nesse caso, o mais interessante seria instanciar a superclass dentro da subclass e criar métodos de delegação, dessa forma, será possível utilizar alguns dos métodos da superclass que se fazem necessários na subclass.
Inspirado por: https://gist.github.com/cs-cordero/3799f26699bdecdb286fd719f08122af