JavaRush /Blogue Java /Random-PT /Como funciona a refatoração em Java

Como funciona a refatoração em Java

Publicado no grupo Random-PT
Ao aprender a programar, gasta-se muito tempo escrevendo código. A maioria dos desenvolvedores iniciantes acredita que esta é sua atividade futura. Isso é parcialmente verdade, mas as tarefas de um programador também incluem manutenção e refatoração de código. Hoje falaremos sobre refatoração. Como funciona a refatoração em Java - 1

Refatoração no curso JavaRush

O curso JavaRush cobre o tópico de refatoração duas vezes: Graças à grande tarefa, é possível conhecer a refatoração real na prática, e uma palestra sobre refatoração no IDEA ajudará você a entender as ferramentas automáticas que tornam a vida incrivelmente mais fácil.

O que é refatoração?

Esta é uma mudança na estrutura do código sem alterar sua funcionalidade. Por exemplo, existe um método que compara 2 números e retorna verdadeiro se o primeiro for maior, e falso caso contrário:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
O resultado foi um código muito complicado. Mesmo os iniciantes raramente escrevem algo assim, mas existe um grande risco. Ao que parece, por que há um bloco aqui if-elsese você pode escrever um método 6 linhas mais curto:
public boolean max(int a, int b) {
     return a>b;
}
Agora, este método parece simples e elegante, embora faça a mesma coisa que o exemplo acima. É assim que funciona a refatoração: ela altera a estrutura do código sem afetar sua essência. Existem muitos métodos e técnicas de refatoração, que consideraremos com mais detalhes.

Por que a refatoração é necessária?

Existem vários motivos. Por exemplo, a busca pela simplicidade e concisão do código. Os defensores desta teoria acreditam que o código deve ser tão conciso quanto possível, mesmo que exija dezenas de linhas de comentários para ser compreendido. Outros desenvolvedores acreditam que o código deve ser refatorado para que seja compreensível com um número mínimo de comentários. Cada equipe escolhe sua posição, mas devemos lembrar que refatorar não é uma redução . Seu principal objetivo é melhorar a estrutura do código. Vários objetivos podem ser incluídos nesta meta global:
  1. A refatoração melhora a compreensão do código escrito por outro desenvolvedor;
  2. Ajuda a encontrar e corrigir erros;
  3. Permite aumentar a velocidade de desenvolvimento de software;
  4. No geral, melhora a composição do software.
Se a refatoração não for realizada por muito tempo, podem surgir dificuldades de desenvolvimento, até a parada total do trabalho.

“Cheiros de código”

Quando o código requer refatoração, eles dizem que “cheira”. Claro, não literalmente, mas esse código realmente não parece muito bonito. A seguir consideraremos as principais técnicas de refatoração para o estágio inicial.

Elementos desnecessariamente grandes

Existem classes e métodos complicados com os quais é impossível trabalhar de forma eficaz precisamente devido ao seu enorme tamanho.

Grande aula

Essa classe possui um grande número de linhas de código e muitos métodos diferentes. Geralmente é mais fácil para um desenvolvedor adicionar um recurso a uma classe existente em vez de criar uma nova, e é por isso que ela cresce. Via de regra, a funcionalidade desta classe está sobrecarregada. Nesse caso, separar parte da funcionalidade em uma classe separada ajuda. Falaremos sobre isso com mais detalhes na seção de técnicas de refatoração.

Grande método

Esse “cheiro” ocorre quando um desenvolvedor adiciona novas funcionalidades a um método. “Por que devo colocar a verificação de parâmetros em um método separado se posso escrevê-la aqui?”, “Por que é necessário separar o método para encontrar o elemento máximo no array, vamos deixar aqui. Assim o código fica mais claro” e outros equívocos. Existem duas regras para refatorar um método grande:
  1. Se, ao escrever um método, você quiser adicionar um comentário ao código, será necessário separar essa funcionalidade em um método separado;
  2. Se um método ocupar mais de 10 a 15 linhas de código, você deverá identificar as tarefas e subtarefas que ele executa e tentar separar as subtarefas em um método separado.
Várias maneiras de eliminar um método grande:
  • Separe parte da funcionalidade de um método em um método separado;
  • Se as variáveis ​​locais não permitirem extrair parte da funcionalidade, você poderá passar o objeto inteiro para outro método.

Usando muitos tipos de dados primitivos

Normalmente, esse problema ocorre quando o número de campos para armazenar dados em uma classe aumenta com o tempo. Por exemplo, se você usar tipos primitivos em vez de pequenos objetos para armazenar dados (moeda, data, números de telefone, etc.) ou constantes para codificar qualquer informação. Uma boa prática neste caso seria agrupar logicamente os campos e colocá-los em uma classe separada (selecionando uma classe). Você também pode incluir métodos para processar esses dados na classe.

Longa lista de opções

Um erro bastante comum, especialmente em combinação com um método grande. Geralmente ocorre se a funcionalidade do método estiver sobrecarregada ou se o método combinar vários algoritmos. Longas listas de parâmetros são muito difíceis de entender e tais métodos são inconvenientes de usar. Portanto, é melhor transferir o objeto inteiro. Caso o objeto não possua dados suficientes, vale a pena utilizar um objeto mais geral ou dividir a funcionalidade do método para que ele processe dados logicamente relacionados.

Grupos de dados

Grupos de dados logicamente relacionados geralmente aparecem no código. Por exemplo, parâmetros de conexão ao banco de dados (URL, nome de usuário, senha, nome do esquema, etc.). Se nenhum campo puder ser removido da lista de elementos, então a lista é um grupo de dados que deve ser colocado em uma classe separada (seleção de classe).

Soluções que estragam o conceito de OOP

Esse tipo de “cheiro” ocorre quando o desenvolvedor viola o design OOP. Isso acontece se ele não compreender totalmente as capacidades deste paradigma, utilizá-las de forma incompleta ou incorreta.

Recusa de herança

Se uma subclasse usa uma parte mínima das funções da classe pai, isso cheira a uma hierarquia incorreta. Normalmente, neste caso, métodos desnecessários simplesmente não são substituídos ou exceções são lançadas. Se uma classe é herdada de outra, isso implica no uso quase completo de sua funcionalidade. Exemplo de hierarquia correta: Como funciona a refatoração em Java - 2 Exemplo de hierarquia incorreta: Como funciona a refatoração em Java - 3

declaração de mudança

O que poderia estar errado com um operador switch? É ruim quando seu design é muito complexo. Isso também inclui muitos blocos aninhados if.

Classes alternativas com interfaces diferentes

Várias classes fazem essencialmente a mesma coisa, mas seus métodos recebem nomes diferentes.

Campo temporário

Se a classe contém um campo temporário que o objeto precisa apenas ocasionalmente, quando é preenchido com valores, e no resto do tempo está vazio ou, Deus me livre, null, então o código “cheira”, e tal design é duvidoso decisão.

Odores que dificultam a modificação

Esses “cheiros” são mais graves. O restante prejudica principalmente a compreensão do código, mas não permite modificá-lo. Ao introduzir qualquer recurso, metade dos desenvolvedores desistirá e a outra metade enlouquecerá.

Hierarquias de herança paralela

Ao criar uma subclasse de uma classe, você deve criar outra subclasse de outra classe.

Distribuição uniforme de dependências

Ao realizar qualquer modificação, você deve procurar todas as dependências (usos) desta classe e fazer muitas pequenas alterações. Uma mudança – edições em muitas classes.

Árvore de modificação complexa

Esse cheiro é o oposto do anterior: as mudanças afetam um grande número de métodos da mesma classe. Via de regra, a dependência nesse código é em cascata: depois de alterar um método, você precisa consertar algo em outro, depois em um terceiro e assim por diante. Uma aula – muitas mudanças.

“Cheiro de lixo”

Uma categoria de odores bastante desagradável que causa dores de cabeça. Código antigo, inútil e desnecessário. Felizmente, IDEs e linters modernos aprenderam a alertar sobre esses odores.

Um grande número de comentários no método

O método tem muitos comentários explicativos em quase todas as linhas. Isso geralmente está associado a um algoritmo complexo, por isso é melhor dividir o código em vários métodos menores e dar-lhes nomes significativos.

Duplicação de código

Diferentes classes ou métodos usam os mesmos blocos de código.

Aula preguiçosa

A classe assume muito pouca funcionalidade, embora muitas delas tenham sido planejadas.

Código não utilizado

Uma classe, método ou variável não é usada no código e é “peso morto”.

Acoplamento excessivo

Esta categoria de cheiros é caracterizada por um grande número de conexões desnecessárias no código.

Métodos de terceiros

Um método usa os dados de outro objeto com muito mais frequência do que seus próprios dados.

Intimidade inadequada

Uma classe usa campos de serviço e métodos de outra classe.

Chamadas de aula longas

Uma classe chama outra, que solicita dados da terceira, aquela da quarta e assim por diante. Uma cadeia de chamadas tão longa significa um alto nível de dependência da atual estrutura de classes.

Revendedor de tarefas de classe

Uma classe só é necessária para passar uma tarefa para outra classe. Talvez deva ser removido?

Técnicas de Refatoração

A seguir falaremos sobre técnicas iniciais de refatoração que ajudarão a eliminar os cheiros de código descritos.

Seleção de classe

A classe executa muitas funções; algumas delas precisam ser movidas para outra classe. Por exemplo, existe uma classe Humanque também contém um endereço residencial e um método que fornece o endereço completo:
class Human {
   private String name;
   private String age;
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}
Seria uma boa ideia colocar as informações de endereço e método (comportamento de processamento de dados) em uma classe separada:
class Human {
   private String name;
   private String age;
   private Address address;

   private String getFullAddress() {
       return address.getFullAddress();
   }
}
class Address {
   private String country;
   private String city;
   private String street;
   private String house;
   private String quarter;

   public String getFullAddress() {
       StringBuilder result = new StringBuilder();
       return result
                       .append(country)
                       .append(", ")
                       .append(city)
                       .append(", ")
                       .append(street)
                       .append(", ")
                       .append(house)
                       .append(" ")
                       .append(quarter).toString();
   }
}

Seleção de método

Se alguma funcionalidade puder ser agrupada em um método, ela deverá ser colocada em um método separado. Por exemplo, um método que calcula as raízes de uma equação quadrática:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
    else if (D == 0) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
    else {
        System.out.println("Equation has no roots");
    }
}
Vamos mover o cálculo de todas as três opções possíveis para métodos separados:
public void calcQuadraticEq(double a, double b, double c) {
    double D = b * b - 4 * a * c;
    if (D > 0) {
        dGreaterThanZero(a, b, D);
    }
    else if (D == 0) {
        dEqualsZero(a, b);
    }
    else {
        dLessThanZero();
    }
}

public void dGreaterThanZero(double a, double b, double D) {
    double x1, x2;
    x1 = (-b - Math.sqrt(D)) / (2 * a);
    x2 = (-b + Math.sqrt(D)) / (2 * a);
    System.out.println("x1 = " + x1 + ", x2 = " + x2);
}

public void dEqualsZero(double a, double b) {
    double x;
    x = -b / (2 * a);
    System.out.println("x = " + x);
}

public void dLessThanZero() {
    System.out.println("Equation has no roots");
}
O código de cada método tornou-se muito mais curto e claro.

Transferindo o objeto inteiro

Ao chamar um método com parâmetros, às vezes você pode ver um código como este:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double yearlySalary = employee.getYearlySalary();
    double awards = employee.getAwards();
    double monthlySalary = getMonthlySalary(yearlySalary, awards);
    // Продолжение обработки
}

public double getMonthlySalary(double yearlySalary, double awards) {
     return (yearlySalary + awards)/12;
}
No método, employeeMethodaté 2 linhas são alocadas para obter valores e armazená-los em variáveis ​​primitivas. Às vezes, esses designs ocupam até 10 linhas. É muito mais fácil passar o próprio objeto para o método, de onde você pode extrair os dados necessários:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}
Simples, curto e conciso.

Agrupamento lógico de campos e colocação deles em uma classe separada

Apesar de os exemplos acima serem muito simples e ao olhar para eles muitos podem fazer a pergunta “Quem realmente faz isso?”, muitos desenvolvedores, por desatenção, falta de vontade de refatorar o código, ou simplesmente “Vai servir”, fazem erros estruturais semelhantes.

Por que a refatoração é eficaz

O resultado de uma boa refatoração é um programa cujo código é fácil de ler, modificações na lógica do programa não se tornam uma ameaça e a introdução de novos recursos não se transforma em um inferno de análise de código, mas em uma atividade agradável por alguns dias. . A refatoração não deve ser usada se for mais fácil reescrever o programa do zero. Por exemplo, a equipe estima que os custos de mão de obra para analisar, analisar e refatorar o código sejam maiores do que para implementar a mesma funcionalidade do zero. Ou o código que precisa ser refatorado contém muitos erros que são difíceis de depurar. Saber melhorar a estrutura do código é obrigatório no trabalho de um programador. Bem, é melhor aprender programação Java no JavaRush - um curso online com ênfase na prática. Mais de 1.200 tarefas com verificação instantânea, cerca de 20 miniprojetos, tarefas de jogo - tudo isso ajudará você a se sentir confiante na codificação. A melhor hora para começar é agora :) Как устроен рефакторинг в Java - 4

Recursos para aprofundar ainda mais na refatoração

O livro mais famoso sobre refatoração é “Refactoring. Melhorando o Design do Código Existente” por Martin Fowler. Há também uma publicação interessante sobre refatoração, escrita com base em um livro anterior - “Refactoring with Patterns” de Joshua Kiriewski. Falando em modelos. Ao refatorar, é sempre muito útil conhecer os padrões básicos de design do aplicativo. Esses ótimos livros ajudarão com isso:
  1. “Design Patterns” - por Eric Freeman, Elizabeth Freeman, Kathy Sierra, Bert Bates da série Head First;
  2. “Código Legível ou Programação como Arte” - Dustin Boswell, Trevor Faucher.
  3. “Perfect Code” de Steve McConnell, que descreve os princípios de um código bonito e elegante.
Bem, alguns artigos sobre refatoração:
  1. Uma tarefa incrível: vamos começar a refatorar o código legado ;
  2. Reestruturação ;
  3. Refatoração para todos .
    Comentários
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION