CodeGym/Blog Java/Random-ES/Cómo funciona la refactorización en Java
Autor
Artem Divertitto
Senior Android Developer at United Tech

Cómo funciona la refactorización en Java

Publicado en el grupo Random-ES
A medida que aprende a programar, pasa mucho tiempo escribiendo código. La mayoría de los desarrolladores principiantes creen que esto es lo que harán en el futuro. Esto es parcialmente cierto, pero el trabajo de un programador también incluye el mantenimiento y la refactorización del código. Hoy vamos a hablar sobre la refactorización. Cómo funciona la refactorización en Java - 1

Refactorización en CodeGym

La refactorización se trata dos veces en el curso de CodeGym: La gran tarea brinda la oportunidad de familiarizarse con la refactorización real a través de la práctica, y la lección sobre la refactorización en IDEA lo ayuda a sumergirse en las herramientas automatizadas que le harán la vida increíblemente más fácil.

¿Qué es la refactorización?

Está cambiando la estructura del código sin cambiar su funcionalidad. Por ejemplo, supongamos que tenemos un método que compara 2 números y devuelve verdadero si el primero es mayor y falso en caso contrario:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if (a == b) {
        return false;
    } else {
        return false;
    }
}
Este es un código bastante difícil de manejar. Incluso los principiantes rara vez escribirían algo como esto, pero existe la posibilidad. ¿Por qué usar un if-elsebloque si puede escribir el método de 6 líneas de manera más concisa?
public boolean max(int a, int b) {
     return a > b;
}
Ahora tenemos un método simple y elegante que realiza la misma operación que el ejemplo anterior. Así es como funciona la refactorización: cambias la estructura del código sin afectar su esencia. Hay muchos métodos y técnicas de refactorización que veremos más de cerca.

¿Por qué necesita una refactorización?

Hay varias razones. Por ejemplo, para lograr simplicidad y brevedad en el código. Los defensores de esta teoría creen que el código debe ser lo más conciso posible, incluso si se necesitan varias docenas de líneas de comentarios para entenderlo. Otros desarrolladores están convencidos de que el código debe refactorizarse para hacerlo comprensible con la mínima cantidad de comentarios. Cada equipo adopta su propia posición, pero recuerda que refactorizar no significa reducir . Su objetivo principal es mejorar la estructura del código. Varias tareas pueden incluirse en este propósito general:
  1. La refactorización mejora la comprensión del código escrito por otros desarrolladores.
  2. Ayuda a encontrar y corregir errores.
  3. Puede acelerar la velocidad del desarrollo de software.
  4. En general, mejora el diseño del software.
Si la refactorización no se realiza durante mucho tiempo, el desarrollo puede encontrar dificultades, incluida la interrupción completa del trabajo.

"El código huele mal"

Cuando el código requiere refactorización, se dice que tiene un "olor". Por supuesto, no literalmente, pero dicho código realmente no parece muy atractivo. A continuación, exploraremos las técnicas básicas de refactorización para la etapa inicial.

Clases y métodos irrazonablemente grandes

Las clases y los métodos pueden ser engorrosos, imposibles de trabajar de manera efectiva precisamente por su enorme tamaño.

Clase grande

Tal clase tiene una gran cantidad de líneas de código y muchos métodos diferentes. Por lo general, es más fácil para un desarrollador agregar una función a una clase existente que crear una nueva, razón por la cual la clase crece. Como regla, demasiada funcionalidad está abarrotada en tal clase. En este caso, ayuda mover parte de la funcionalidad a una clase separada. Hablaremos de esto con más detalle en la sección sobre técnicas de refactorización.

método largo

Este "olor" surge cuando un desarrollador agrega una nueva funcionalidad a un método: "¿Por qué debo poner una verificación de parámetros en un método separado si puedo escribir el código aquí?", "¿Por qué necesito un método de búsqueda separado para encontrar el máximo elemento en una matriz? Dejémoslo aquí. El código será más claro de esta manera", y otros conceptos erróneos similares.

Hay dos reglas para refactorizar un método largo:

  1. Si tiene ganas de agregar un comentario al escribir un método, debe poner la funcionalidad en un método separado.
  2. Si un método requiere más de 10 a 15 líneas de código, debe identificar las tareas y subtareas que realiza e intentar colocar las subtareas en un método separado.

Hay algunas formas de eliminar un método largo:

  • Mover parte de la funcionalidad del método a un método separado
  • Si las variables locales le impiden mover parte de la funcionalidad, puede mover todo el objeto a otro método.

Usar muchos tipos de datos primitivos

Este problema suele ocurrir cuando el número de campos de una clase crece con el tiempo. Por ejemplo, si almacena todo (moneda, fecha, números de teléfono, etc.) en tipos primitivos o constantes en lugar de objetos pequeños. En este caso, una buena práctica sería mover una agrupación lógica de campos a una clase separada (clase de extracción). También puede agregar métodos a la clase para procesar los datos.

Demasiados parámetros

Este es un error bastante común, especialmente en combinación con un método largo. Por lo general, ocurre si un método tiene demasiada funcionalidad o si un método implementa varios algoritmos. Las listas largas de parámetros son muy difíciles de entender y el uso de métodos con tales listas es inconveniente. Como resultado, es mejor pasar un objeto completo. Si un objeto no tiene suficientes datos, debe usar un objeto más general o dividir la funcionalidad del método para que cada método procese datos relacionados lógicamente.

Grupos de datos

Los grupos de datos lógicamente relacionados a menudo aparecen en el código. Por ejemplo, parámetros de conexión a la base de datos (URL, nombre de usuario, contraseña, nombre de esquema, etc.). Si no se puede eliminar un solo campo de una lista de campos, estos campos deben moverse a una clase separada (clase de extracto).

Soluciones que violan los principios de programación orientada a objetos

Estos "olores" ocurren cuando un desarrollador viola el diseño de programación orientada a objetos adecuado. Esto sucede cuando él o ella no entiende completamente las capacidades de la programación orientada a objetos y no las usa por completo o correctamente.

Falta de uso de la herencia.

Si una subclase usa solo un pequeño subconjunto de las funciones de la clase principal, entonces huele a la jerarquía incorrecta. Cuando esto sucede, por lo general, los métodos superfluos simplemente no se anulan o generan excepciones. Una clase que hereda otra implica que la clase secundaria usa casi toda la funcionalidad de la clase principal. Ejemplo de una jerarquía correcta: Cómo funciona la refactorización en Java - 2Ejemplo de una jerarquía incorrecta: Cómo funciona la refactorización en Java - 3

Declaración de cambio

¿Qué podría estar mal con una switchdeclaración? Es malo cuando se vuelve muy complejo. Un problema relacionado es una gran cantidad de ifdeclaraciones anidadas.

Clases alternativas con diferentes interfaces

Varias clases hacen lo mismo, pero sus métodos tienen nombres diferentes.

campo temporal

Si una clase tiene un campo temporal que un objeto necesita solo ocasionalmente cuando se establece su valor, y está vacío o, Dios no lo quiera, nullel resto del tiempo, entonces el código huele mal. Esta es una decisión de diseño cuestionable.

Olores que dificultan la modificación

Estos olores son más serios. Otros olores principalmente dificultan la comprensión del código, pero impiden que lo modifiques. Cuando intentas introducir nuevas funciones, la mitad de los desarrolladores se dan por vencidos y la otra mitad se vuelven locos.

Jerarquías de herencia paralelas

Este problema se manifiesta cuando la subclasificación de una clase requiere que cree otra subclase para una clase diferente.

Dependencias distribuidas uniformemente

Cualquier modificación requiere que busque todos los usos de una clase (dependencias) y realice muchos cambios pequeños. Un cambio: ediciones en muchas clases.

Árbol complejo de modificaciones

Este olor es lo opuesto al anterior: los cambios afectan a una gran cantidad de métodos en una clase. Como regla, dicho código tiene una dependencia en cascada: cambiar un método requiere que arregles algo en otro, y luego en el tercero y así sucesivamente. Una clase, muchos cambios.

"Huele a basura"

Una categoría bastante desagradable de olores que causa dolores de cabeza. Código antiguo, inútil e innecesario. Afortunadamente, los IDE y los linters modernos han aprendido a advertir sobre tales olores.

Un gran número de comentarios en un método.

Un método tiene muchos comentarios explicativos en casi todas las líneas. Esto generalmente se debe a un algoritmo complejo, por lo que es mejor dividir el código en varios métodos más pequeños y darles nombres explicativos.

Código duplicado

Diferentes clases o métodos usan los mismos bloques de código.

clase perezosa

Una clase adquiere muy poca funcionalidad, aunque se planeó que fuera grande.

Código sin usar

Una clase, método o variable no se usa en el código y es peso muerto.

Conectividad excesiva

Esta categoría de olores se caracteriza por una gran cantidad de relaciones no justificadas en el código.

Métodos externos

Un método utiliza datos de otro objeto con mucha más frecuencia que sus propios datos.

intimidad inapropiada

Una clase depende de los detalles de implementación de otra clase.

Largas llamadas de clase

Una clase llama a otra, que solicita datos de una tercera, que obtiene datos de una cuarta, y así sucesivamente. Una cadena tan larga de llamadas significa una alta dependencia de la estructura de clases actual.

Clase de distribuidor de tareas

Una clase es necesaria solo para enviar una tarea a otra clase. ¿Quizás debería ser eliminado?

Técnicas de refactorización

A continuación, analizaremos las técnicas básicas de refactorización que pueden ayudar a eliminar los olores de código descritos.

Extraer una clase

Una clase realiza demasiadas funciones. Algunos de ellos deben ser trasladados a otra clase. Por ejemplo, supongamos que tenemos una Humanclase que también almacena una dirección de casa y tiene un método que devuelve la dirección completa:
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();
    }
 }
Es una buena práctica poner la información de la dirección y el método asociado (comportamiento de procesamiento de datos) en una clase 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();
   }
}

Extraer un método

Si un método tiene alguna funcionalidad que se puede aislar, debe colocarlo en un método separado. Por ejemplo, un método que calcula las raíces de una ecuación cuadrá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 una de las tres opciones posibles en 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");
}
El código de cada método se ha vuelto mucho más corto y más fácil de entender.

Pasar un objeto completo

Cuando se llama a un método con parámetros, a veces puede ver un 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;
}
El employeeMethodtiene 2 líneas completas dedicadas a recibir valores y almacenarlos en variables primitivas. A veces, tales construcciones pueden tomar hasta 10 líneas. Es mucho más fácil pasar el objeto en sí y usarlo para extraer los datos necesarios:
public void employeeMethod(Employee employee) {
    // Some actions
    double monthlySalary = getMonthlySalary(employee);
    // Continue processing
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}

Sencillo, breve y conciso.

Agrupando campos lógicamente y moviéndolos a un lugar separado, classDespiteel hecho de que los ejemplos anteriores son muy simples, y cuando los mira, muchos de ustedes pueden preguntarse: "¿Quién hace esto?", Muchos desarrolladores cometen tales errores estructurales por descuido, falta de voluntad para refactorizar el código, o simplemente una actitud de "eso es lo suficientemente bueno".

Por qué la refactorización es efectiva

Como resultado de una buena refactorización, un programa tiene un código fácil de leer, la perspectiva de alterar su lógica no es aterradora, y la introducción de nuevas funciones no se convierte en un infierno de análisis de código, sino que es una experiencia agradable durante un par de días. . No debería refactorizar si fuera más fácil escribir un programa desde cero. Por ejemplo, suponga que su equipo estima que el trabajo requerido para comprender, analizar y refactorizar el código será mayor que implementar la misma funcionalidad desde cero. O si el código que se va a refactorizar tiene muchos problemas que son difíciles de depurar. Saber mejorar la estructura del código es fundamental en el trabajo de un programador. Y aprender a programar en Java se hace mejor en CodeGym, el curso en línea que enfatiza la práctica. Más de 1200 tareas con verificación instantánea, unos 20 miniproyectos, tareas del juego: todo esto te ayudará a sentirte seguro en la codificación. El mejor momento para empezar es ahora :)

Recursos para sumergirse aún más en la refactorización

El libro más famoso sobre refactorización es "Refactoring. Improving the Design of Existing Code" de Martin Fowler. También hay una publicación interesante sobre refactorización, basada en un libro anterior: "Refactoring Using Patterns" de Joshua Kerievsky. Hablando de patrones... Al refactorizar, siempre es muy útil conocer patrones de diseño básicos. Estos excelentes libros ayudarán con esto: Hablando de patrones... Al refactorizar, siempre es muy útil conocer los patrones básicos de diseño. Estos excelentes libros ayudarán con esto:
  1. "Patrones de diseño" de Eric Freeman, Elizabeth Robson, Kathy Sierra y Bert Bates, de la serie Head First
  2. "El arte del código legible" por Dustin Boswell y Trevor Foucher
  3. "Code Complete" de Steve McConnell, que establece los principios de un código hermoso y elegante.
Comentarios
  • Populares
  • Nuevas
  • Antiguas
Debes iniciar sesión para dejar un comentario
Esta página aún no tiene comentarios