CodeGym /Blogue Java /Random-PT /Como a refatoração funciona em Java
John Squirrels
Nível 41
San Francisco

Como a refatoração funciona em Java

Publicado no grupo Random-PT
Ao aprender a programar, você gasta muito tempo escrevendo código. A maioria dos desenvolvedores iniciantes acredita que é isso que farão no futuro. Isso é parcialmente verdade, mas o trabalho de um programador também inclui manter e refatorar o código. Hoje vamos falar sobre refatoração. Como funciona a refatoração em Java - 1

Refatorando no CodeGym

A refatoração é abordada duas vezes no curso CodeGym: A grande tarefa oferece uma oportunidade de se familiarizar com a refatoração real por meio da prática, e a lição sobre refatoração no IDEA ajuda você a mergulhar nas ferramentas automatizadas que tornarão sua vida incrivelmente mais fácil.

O que é refatoração?

Está mudando a estrutura do código sem alterar sua funcionalidade. Por exemplo, suponha que temos 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;
        }
    }
Este é um código bastante pesado. Mesmo iniciantes raramente escreveriam algo assim, mas há uma chance. Por que usar um if-elsebloco se você pode escrever o método de 6 linhas de forma mais concisa?

 public boolean max(int a, int b) {
      return a > b;
 }
Agora temos um método simples e elegante que realiza a mesma operação do exemplo acima. A refatoração funciona assim: você muda a estrutura do código sem afetar sua essência. Existem muitos métodos e técnicas de refatoração que examinaremos mais de perto.

Por que você precisa de refatoração?

Existem várias razões. Por exemplo, para obter simplicidade e brevidade no código. Os proponentes dessa teoria acreditam que o código deve ser o mais conciso possível, mesmo que várias dezenas de linhas de comentários sejam necessárias para entendê-lo. Outros desenvolvedores estão convencidos de que o código deve ser refatorado para torná-lo compreensível com o número mínimo de comentários. Cada equipe adota sua própria posição, mas lembre-se que refatoração não significa redução . Seu principal objetivo é melhorar a estrutura do código. Várias tarefas podem ser incluídas neste propósito geral:
  1. A refatoração melhora a compreensão do código escrito por outros desenvolvedores.
  2. Ajuda a encontrar e corrigir bugs.
  3. Pode acelerar a velocidade do desenvolvimento de software.
  4. No geral, melhora o design do software.
Se a refatoração não for realizada por muito tempo, o desenvolvimento pode encontrar dificuldades, incluindo uma parada total do trabalho.

"Código cheira"

Quando o código requer refatoração, diz-se que ele tem um "cheiro". Claro, não literalmente, mas esse código realmente não parece muito atraente. A seguir, exploraremos as técnicas básicas de refatoração para o estágio inicial.

Classes e métodos excessivamente grandes

Classes e métodos podem ser complicados, impossíveis de trabalhar de forma eficaz precisamente por causa de seu tamanho enorme.

classe grande

Essa classe tem 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 a classe cresce. Como regra, muitas funcionalidades são colocadas em tal classe. Nesse caso, ajuda mover parte da funcionalidade para uma classe separada. Falaremos sobre isso com mais detalhes na seção sobre técnicas de refatoração.

método longo

Esse "cheiro" surge quando um desenvolvedor adiciona uma nova funcionalidade a um método: "Por que devo colocar uma verificação de parâmetro em um método separado se posso escrever o código aqui?", "Por que preciso de um método de pesquisa separado para encontrar o máximo elemento em uma matriz? Vamos mantê-lo aqui. O código ficará mais claro desta forma", e outros equívocos.

Existem duas regras para refatorar um método longo:

  1. Se quiser adicionar um comentário ao escrever um método, coloque a funcionalidade em um método separado.
  2. Se um método ocupa mais de 10 a 15 linhas de código, você deve identificar as tarefas e subtarefas que ele executa e tentar colocar as subtarefas em um método separado.

Existem algumas maneiras de eliminar um método longo:

  • Mova parte da funcionalidade do método para um método separado
  • Se as variáveis ​​locais o impedirem de mover parte da funcionalidade, você poderá mover todo o objeto para outro método.

Usando muitos tipos de dados primitivos

Esse problema geralmente ocorre quando o número de campos em uma classe aumenta com o tempo. Por exemplo, se você armazenar tudo (moeda, data, números de telefone, etc.) em tipos primitivos ou constantes em vez de pequenos objetos. Nesse caso, uma boa prática seria mover um agrupamento lógico de campos para uma classe separada (classe de extração). Você também pode adicionar métodos à classe para processar os dados.

Muitos parâmetros

Este é um erro bastante comum, especialmente em combinação com um método longo. Geralmente, ocorre se um método tiver muitas funcionalidades ou se um método implementar vários algoritmos. Listas longas de parâmetros são muito difíceis de entender e usar métodos com essas listas é inconveniente. Como resultado, é melhor passar um objeto inteiro. Se um objeto não tiver dados suficientes, você deve usar um objeto mais geral ou dividir a funcionalidade do método para que cada método processe dados relacionados logicamente.

Grupos de dados

Grupos de dados logicamente relacionados geralmente aparecem no código. Por exemplo, parâmetros de conexão de banco de dados (URL, nome de usuário, senha, nome do esquema, etc.). Se nenhum campo puder ser removido de uma lista de campos, esses campos devem ser movidos para uma classe separada (classe de extração).

Soluções que violam os princípios OOP

Esses "cheiros" ocorrem quando um desenvolvedor viola o design OOP adequado. Isso acontece quando ele ou ela não entende totalmente os recursos OOP e falha em usá-los total ou adequadamente.

Falha ao usar herança

Se uma subclasse usa apenas um pequeno subconjunto das funções da classe pai, ela cheira a hierarquia errada. Quando isso acontece, geralmente os métodos supérfluos simplesmente não são substituídos ou lançam exceções. Uma classe herdando outra implica que a classe filha usa quase toda a funcionalidade da classe pai. Exemplo de hierarquia correta: Como funciona a refatoração em Java - 2Exemplo de hierarquia incorreta: Como funciona a refatoração em Java - 3

Instrução de troca

O que poderia estar errado com uma switchdeclaração? É ruim quando se torna muito complexo. Um problema relacionado é um grande número de ifinstruções aninhadas.

Aulas alternativas com diferentes interfaces

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

Campo temporário

Se uma classe tem um campo temporário que um objeto precisa apenas ocasionalmente quando seu valor é definido e está vazio ou, Deus me livre, nullo resto do tempo, então o código cheira mal. Esta é uma decisão de design questionável.

Odores que dificultam a modificação

Esses cheiros são mais sérios. Outros cheiros dificultam principalmente a compreensão do código, mas impedem que você o modifique. Quando você tenta introduzir novos recursos, metade dos desenvolvedores desiste e a outra metade enlouquece.

Hierarquias de herança paralelas

Esse problema se manifesta quando a subclasse de uma classe exige que você crie outra subclasse para uma classe diferente.

Dependências uniformemente distribuídas

Qualquer modificação exige que você procure todos os usos (dependências) de uma classe e faça várias pequenas alterações. Uma mudança — edições em muitas classes.

Árvore complexa de modificações

Esse cheiro é o oposto do anterior: mudanças afetam um grande número de métodos em uma classe. Como regra, esse código tem dependência em cascata: alterar um método exige que você conserte algo em outro e depois no terceiro e assim por diante. Uma aula — muitas mudanças.

"cheiro de lixo"

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

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

Um método tem muitos comentários explicativos em quase todas as linhas. Isso geralmente ocorre devido a um algoritmo complexo, portanto, é melhor dividir o código em vários métodos menores e dar a eles nomes explicativos.

Código duplicado

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

aula preguiçosa

Uma classe assume muito pouca funcionalidade, embora tenha sido planejada para ser grande.

código não utilizado

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

Conectividade excessiva

Esta categoria de odores é caracterizada por um grande número de relações injustificadas no código.

Métodos externos

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

Intimidade inadequada

Uma classe depende dos detalhes de implementação de outra classe.

Chamadas de classe longa

Uma classe chama outra, que solicita dados de uma terceira, que obtém dados de uma quarta e assim por diante. Uma cadeia tão longa de chamadas significa alta dependência da estrutura de classe atual.

Classe do negociante de tarefas

Uma classe é necessária apenas para enviar uma tarefa para outra classe. Talvez deva ser removido?

Técnicas de refatoração

A seguir, discutiremos as técnicas básicas de refatoração que podem ajudar a eliminar os code smells descritos.

Extrair uma classe

Uma classe executa muitas funções. Alguns deles devem ser movidos para outra classe. Por exemplo, suponha que temos uma Humanclasse que também armazena um endereço residencial e possui um método que retorna 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();
    }
 }
É uma boa prática colocar as informações de endereço e o método associado (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();
    }
 }

Extrair um método

Se um método tiver alguma funcionalidade que possa ser isolada, você deve colocá-lo 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");
        }
    }
Calculamos cada uma das três opções possíveis em 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 ficou muito mais curto e fácil de entender.

Passando um objeto inteiro

Quando um método é chamado com parâmetros, às vezes você pode ver um código como este:

 public void employeeMethod(Employee employee) {
     // Some actions
     double yearlySalary = employee.getYearlySalary();
     double awards = employee.getAwards();
     double monthlySalary = getMonthlySalary(yearlySalary, awards);
     // Continue processing
 }
 
 public double getMonthlySalary(double yearlySalary, double awards) {
      return (yearlySalary + awards)/12;
 }
O employeeMethodtem 2 linhas inteiras dedicadas a receber valores e armazená-los em variáveis ​​primitivas. Às vezes, essas construções podem levar até 10 linhas. É muito mais fácil passar o próprio objeto e utilizá-lo para extrair os dados necessários:

 public void employeeMethod(Employee employee) {
     // Some actions
     double monthlySalary = getMonthlySalary(employee);
     // Continue processing
 }
 
 public double getMonthlySalary(Employee employee) {
     return (employee.getYearlySalary() + employee.getAwards())/12;
 }

Simples, breve e conciso.

Agrupando campos logicamente e movendo-os em separado, classDespiteo fato de que os exemplos acima são muito simples, e quando você olha para eles, muitos de vocês podem perguntar: "Quem faz isso?", Muitos desenvolvedores cometem tais erros estruturais por causa de descuido, falta de vontade de refatorar o código ou simplesmente uma atitude de "isso é bom o suficiente".

Por que a refatoração é eficaz

Como resultado de uma boa refatoração, um programa tem um código fácil de ler, a perspectiva de alterar sua lógica não é assustadora e a introdução de novos recursos não se torna um inferno de análise de código, mas sim uma experiência agradável por alguns dias . Você não deve refatorar se for mais fácil escrever um programa do zero. Por exemplo, suponha que sua equipe estima que o trabalho necessário para entender, analisar e refatorar o código será maior do que implementar a mesma funcionalidade do zero. Ou se o código a ser refatorado tiver muitos problemas difíceis de depurar. Saber como melhorar a estrutura do código é essencial no trabalho de um programador. E aprender a programar em Java é melhor feito no CodeGym, o curso online que enfatiza a prática. Mais de 1200 tarefas com verificação instantânea, cerca de 20 miniprojetos, tarefas do jogo - tudo isso ajudará você a se sentir confiante na codificação. A melhor hora para começar é agora :)

Recursos para mergulhar ainda mais na refatoração

O livro mais famoso sobre refatoração é "Refactoring. Improving the Design of Existing Code", de Martin Fowler. Há também uma interessante publicação sobre refatoração, baseada em um livro anterior: "Refactoring Using Patterns" de Joshua Kerievsky. Falando em padrões... Ao refatorar, é sempre muito útil conhecer os padrões básicos de projeto. Esses excelentes livros ajudarão com isso: Falando em padrões... Ao refatorar, é sempre muito útil conhecer os padrões básicos de projeto. Esses excelentes livros ajudarão com isso:
  1. "Design Patterns" de Eric Freeman, Elizabeth Robson, Kathy Sierra e Bert Bates, da série Use a Cabeça
  2. "A arte do código legível" por Dustin Boswell e Trevor Foucher
  3. "Code Complete" de Steve McConnell, que estabelece os princípios para um código bonito e elegante.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION