Uma introdução à refatoração com o IntelliJ IDEA
1. Visão geral
Manter o código organizado nem sempre é fácil. Felizmente para nós, nossos IDEs são muito inteligentes hoje em dia e podem nos ajudar a conseguir isso. Neste tutorial, vamos nos concentrar no IntelliJ IDEA, o editor de código Java JetBrains.
Veremos alguns recursos oferecidos pelo editor para refatorar o código, desde renomear variáveis até alterar a assinatura de um método.
2. Renomeando
2.1. Renomeação básica
Primeiro, vamos começar com o básico:renaming. IntelliJ offers us the possibility to rename different elements of our code: types, variables, methods, and even packages.
Para renomear um elemento, precisamos seguir estas etapas:
-
Clique com o botão direito do mouse no elemento
-
Acione a opçãoRefactor > Rename
-
Digite o novo nome do elemento
-
PressioneEnter
Já agora,we can replace the first two steps by selecting the element and pressing Shift + F6.
Quando acionado, a ação de renomeação serásearch across the code for every usage of the element and then change them with the provided value.
Vamos imaginar uma classeSimpleClass com um método de adição mal nomeado,someAdditionMethod, chamado no métodomain:
public class SimpleClass {
public static void main(String[] args) {
new SimpleClass().someAdditionMethod(1, 2);
}
public int someAdditionMethod(int a, int b) {
return a + b;
}
}
Assim, se escolhermos renomear este método emadd, o IntelliJ produzirá o seguinte código:
public class SimpleClass() {
public static void main(String[] args) {
new SimpleClass().add(1, 2);
}
public int add(int a, int b) {
return a + b;
}
}
2.2. Renomeação avançada
No entanto, o IntelliJ faz mais do que procurar usos de código de nossos elementos e renomeá-los. Por uma questão de fato, mais algumas opções estão disponíveis. IntelliJcan also search for occurrences in comments and strings, and even in files that don’t contain source code. Quanto aos parâmetros, ele pode renomeá-los na hierarquia de classes no caso de métodos substituídos.
Essas opções estão disponíveis pressionandoShift + F6 mais uma vez antes de renomear nosso elemento e um pop-up aparecerá:
A opçãoSearch in comments and strings está disponível para qualquer renomeação. Quanto à opçãoSearch for text occurrences, não está disponível para parâmetros de método e variáveis locais. Finalmente, a opçãoRename parameters in hierarchy está disponível apenas para parâmetros de método.
Então,if any match is found with one of those two options, IntelliJ will show them and offer us the possibility to opt out of some of the changes (digamos, no caso de corresponder a algo não relacionado com a nossa renomeação).
Vamos adicionar um pouco de Javadoc ao nosso método e, em seguida, renomear seu primeiro parâmetro,a:
/**
* Adds a and b
* @param a the first number
* @param b the second number
*/
public int add(int a, int b) {...}
Ao marcar a primeira opção no pop-up de confirmação, o IntelliJ corresponde a qualquer menção dos parâmetros no comentário Javadoc do método e oferece a renomeação deles:
/**
* Adds firstNumber and b
* @param firstNumber the first number
* @param b the second number
*/
public int add(int firstNumber, int b) {...}
Finalmente, devemos notar queIntelliJ is smart and searches mostly for usages in the scope of the renamed element. Em nosso caso, isso significaria que um comentário situado fora do método (exceto para o Javadoc) e contendo uma menção aa não teria sido considerado para renomeação.
3. Extraindo
Agora, vamos falar sobreextraction. Extraction enables us to grab a piece of code and put it into a variable, a method or even a class. IntelliJ lida com isso de maneira bastante inteligente, pois procura por partes de código semelhantes e se oferece para extraí-las da mesma maneira.
Portanto, nesta seção, aprenderemos como aproveitar o recurso de extração oferecido pelo IntelliJ.
3.1. Variáveis
Primeiro, vamos começar com a extração de variáveis. That means local variables, parameters, fields, and constants. Para extrair uma variável, devemos seguir estas etapas:
-
Selecione uma expressão que caiba em uma variável
-
Clique com o botão direito do mouse na área selecionada
-
Acione a opçãoRefactor > Extract > Variable/Parameter/Field/Constant
-
Escolha entre as opçõesReplace this occurrence only ouReplace all x occurrences, se propostas
-
Insira um nome para a expressão extraída (se a escolhida não for adequada para nós)
-
PressioneEnter
Quanto à renomeação, é possível usar atalhos de teclado em vez de usar o menu. The default shortcuts are, respectively, Ctrl + Alt + V, Ctrl + Alt + P, Ctrl + Alt + F and Ctrl + Alt + C.
O IntelliJ tentará adivinhar um nome para nossa expressão extraída, com base no que a expressão retorna. Se não corresponder às nossas necessidades, podemos alterá-lo antes de confirmar a extração.
Vamos ilustrar com um exemplo. Podemos imaginar a adição de um método à nossa classeSimpleClass nos dizendo se a data atual está entre duas datas fornecidas:
public static boolean isNowBetween(LocalDate startingDate, LocalDate endingDate) {
return LocalDate.now().isAfter(startingDate) && LocalDate.now().isBefore(endingDate);
}
Digamos que queremos mudar nossa implementação porque usamosLocalDate.now() duas vezes e gostaríamos de ter certeza de que tem exatamente o mesmo valor em ambas as avaliações. Vamos apenas selecionar a expressão e extraí-la em uma variável local,now:
Então, nossa chamadaLocalDate.now() é capturada em uma variável local:
public static boolean isNowBetween(LocalDate startingDate, LocalDate endingDate) {
LocalDate now = LocalDate.now();
return now.isAfter(startingDate) && now.isBefore(endingDate);
}
Ao marcar a opçãoReplace all, certificamo-nos de que ambas as expressões foram substituídas de uma vez.
3.2. Métodos
Vamos agora verificar como extrair métodos usando IntelliJ:
-
Selecione a expressão ou linhas de código que se ajustam ao método que queremos criar
-
Clique com o botão direito do mouse na área selecionada
-
Acione a opçãoRefactor > Extract > Method
-
Digite as informações do método: nome, visibilidade e parâmetros
-
PressioneEnter
BaterCtrl + Alt + M depois de selecionar o corpo do método também funciona.
Vamos reutilizar nosso exemplo anterior e dizer que queremos ter um método para verificar se alguma data está entre outras datas. Então, teríamos apenas que selecionar nossa última linha no métodoisNowBetween e acionar o recurso de extração do método.
Na janela aberta, podemos ver que o IntelliJ já identificou os três parâmetros necessários:startingDate, endingDateenow. Como queremos que este método seja o mais genérico possível, renomeamos o parâmetronow paradate. E, para fins de coesão, o colocamos como o primeiro parâmetro.
Finalmente, damos ao nosso método um nome,isDateBetween, e finalizamos o processo de extração:
Em seguida, obteríamos o seguinte código:
public static boolean isNowBetween(LocalDate startingDate, LocalDate endingDate) {
LocalDate now = LocalDate.now();
return isDateBetween(now, startingDate, endingDate);
}
private static boolean isDateBetween(LocalDate date, LocalDate startingDate, LocalDate endingDate) {
return date.isBefore(endingDate) && date.isAfter(startingDate);
}
Como podemos ver, a ação desencadeou a criação do novo métodoisDateBetween, que também é chamado no métodoisNowBetween. O método é privado, por padrão. Obviamente, isso poderia ter sido alterado usando a opção de visibilidade.
3.3. Aulas
Depois de tudo isso, podemos querer obter nossos métodos relacionados a datas em uma classe específica, com foco no gerenciamento de datas, digamos:DateUtils. Novamente, isso é bem fácil:
-
Clique com o botão direito do mouse na classe que possui nossos elementos que queremos mover
-
Acione a opçãoRefactor > Extract > Delegate
-
Digite as informações da classe: seu nome, seu pacote, os elementos a serem delegados, a visibilidade desses elementos
-
PressioneEnter
Por padrão, nenhum atalho de teclado está disponível para esse recurso.
Digamos que, antes de acionar o recurso, chamemos nossos métodos relacionados à data no métodomain:
isNowBetween(LocalDate.MIN, LocalDate.MAX);
isDateBetween(LocalDate.of(2019, 1, 1), LocalDate.MIN, LocalDate.MAX);
Em seguida, delegamos esses dois métodos a uma classeDateUtils usando a opção delegar:
Disparar o recurso produziria o seguinte código:
public class DateUtils {
public static boolean isNowBetween(LocalDate startingDate, LocalDate endingDate) {
LocalDate now = LocalDate.now();
return isDateBetween(now, startingDate, endingDate);
}
public static boolean isDateBetween(LocalDate date, LocalDate startingDate, LocalDate endingDate) {
return date.isBefore(endingDate) && date.isAfter(startingDate);
}
}
Podemos ver que o métodoisDateBetween foi feitopublic. Esse é o resultado da opção de visibilidade, que é definida comoescalate por padrão. Escalate means that visibility will be changed in order to ensure current calls to the delegated elements are still compiling.
Em nosso caso,isDateBetween é usado no métodomain deSimpleClass:
DateUtils.isNowBetween(LocalDate.MIN, LocalDate.MAX);
DateUtils.isDateBetween(LocalDate.of(2019, 1, 1), LocalDate.MIN, LocalDate.MAX);
Assim, ao mover o método, é necessário torná-lo não privado.
No entanto, é possível dar visibilidade específica aos nossos elementos selecionando as outras opções.
4. Inlining
Agora que cobrimos a extração, vamos falar sobre sua contraparte:inlining. Inlining is all about taking a code element and replace it with what it’s made of. Para uma variável, esta seria a expressão que lhe foi atribuída. Para um método, seria seu corpo.
Para embutir um elemento, devemos clicar com o botão direito do mouse neste elemento - seja sua definição ou uma referência a ele - e acionar a opçãoRefactor > Inline. Também podemos conseguir isso selecionando o elemento e pressionando as teclasCtrl + Alt + N.
Nesse momento, o IntelliJ nos oferecerá várias opções, se queremos alinhar uma variável ou um método, se selecionamos uma definição ou uma referência. Essas opções são:
-
Inline todas as referências e remova o elemento
-
Inline todas as referências, mas mantenha o elemento
-
Apenas inline a referência selecionada e mantenha o elemento
Vamos pegar nosso métodoisNowBetween e nos livrar da variávelnow, que agora parece um pouco exagerada:
Ao incluir essa variável, obteríamos o seguinte resultado:
public static boolean isNowBetween(LocalDate startingDate, LocalDate endingDate) {
return isDateBetween(LocalDate.now(), startingDate, endingDate);
}
No nosso caso, a única opção era remover todas as referências e remover o elemento. Mas vamos imaginar que também queremos nos livrar da chamadaisDateBetween e escolher embuti-la. Então, o IntelliJ nos ofereceria as três possibilidades sobre as quais falamos antes:
Choosing the first one would replace all calls with the method body and delete the method. As for the second one, it would replace all calls with the method body but keep the method. And finally, the last one would only replace the current call with the method body:
public class DateUtils {
public static boolean isNowBetween(LocalDate startingDate, LocalDate endingDate) {
LocalDate date = LocalDate.now();
return date.isBefore(endingDate) && date.isAfter(startingDate);
}
public static boolean isDateBetween(LocalDate date, LocalDate startingDate, LocalDate endingDate) {
return date.isBefore(endingDate) && date.isAfter(startingDate);
}
}
Nosso métodomain emSimpleClass também permanece intocado.
5. Movendo
Anteriormente, vimos como criar uma nova classe e delegar alguns de nossos elementos de código a ela. Mas há vezeswe might want to delegate a method to an existing class. É disso que trata esta seção.
Para mover um elemento, devemos seguir estas etapas:
-
Selecione o elemento para mover
-
Clique com o botão direito do mouse no elemento
-
Acione a opçãoRefactor > Move
-
Escolha a classe de destinatário e a visibilidade do método
-
PressioneEnter
Também podemos conseguir isso pressionandoF6 após selecionar o elemento.
Digamos que adicionemos um novo método ao nossoSimpleClass,isDateOutstide(), que nos dirá se uma data está situada fora de um intervalo de datas:
public static boolean isDateOutside(LocalDate date, LocalDate startingDate, LocalDate endingDate) {
return !DateUtils.isDateBetween(date, startingDate, endingDate);
}
Então percebemos que seu lugar deve ser em nossa classeDateUtils. Então decidimos movê-lo:
Nosso método está agora na classeDateUtils. Podemos ver que a referência aDateUtils dentro do método desapareceu, pois não é mais necessária:
public static boolean isDateOutside(LocalDate date, LocalDate startingDate, LocalDate endingDate) {
return !isDateBetween(date, startingDate, endingDate);
}
O exemplo que acabamos de executar funciona bem no que diz respeito a um método estático. No entanto, no caso de um método de instância, as coisas não são tão diretas.
Se quisermosmove an instance method,IntelliJ will search for classes referenced in the fields of the current class and offer to move the method to one of those classes (desde que sejam nossos para modificar).
Se nenhuma classe modificável for referenciada nos campos, o IntelliJ propõe fazer o métodostatic antes de movê-lo.
6. Alterando uma assinatura de método
Finalmente, vamos falar sobre um recurso que nos permitechange a method signature. The purpose of this feature is to manipulate every aspect of a method signature.
Como de costume, precisamos executar algumas etapas para ativar o recurso:
-
Selecione o método para alterar
-
Clique com o botão direito do mouse no método
-
Acione a opçãoRefactor > Change signature
-
Trazer alterações para a assinatura do método
-
PressioneEnter
Se preferirmos usar o atalho de teclado,it’s possible to use Ctrl + F6 as well.
Esse recurso abrirá uma janela muito semelhante ao recurso de extração do método. E entãowe have the same possibilities that when we extract a method: alterando seu nome, sua visibilidade e também adicionando / removendo parâmetros e fazendo o ajuste fino deles.
Vamos imaginar que queremos mudar nossa implementação deisDateBetween para considerar os limites de data como inclusivos ou exclusivos. Para fazer isso, queremos adicionar um parâmetroboolean ao nosso método:
Alterando a assinatura do método, podemos adicionar este parâmetro, nomear e atribuir um valor padrão:
public static boolean isDateBetween(LocalDate date, LocalDate startingDate,
LocalDate endingDate, boolean inclusive) {
return date.isBefore(endingDate) && date.isAfter(startingDate);
}
Depois disso, apenas precisamos adaptar o corpo do método de acordo com nossas necessidades.
Se quiséssemos, poderíamos ter marcado a opçãoDelegate via overloading method para criar outro método com o parâmetro em vez de modificar o atual.
7. Conclusão
Neste artigo, tivemos a chance de mergulhar profundamente em alguns dos recursos de refatoração oferecidos pelo IntelliJ. Claro, não cobrimos todas as possibilidades, pois o IntelliJ é uma ferramenta muito poderosa. Para aprender mais sobre este editor, podemos sempre nos referir aits documentation.
Vimos algumas coisas, como renomear nossos elementos de código e extrair alguns comportamentos em variáveis, métodos ou classes. Também aprendemos como embutir alguns elementos se não precisávamos deles sozinhos, mover algum código para outro lugar ou até mesmo alterar totalmente uma assinatura de método existente.