CodeGym /Blog Java /Random-ES /¿Qué son los antipatrones? Veamos algunos ejemplos (Parte...
John Squirrels
Nivel 41
San Francisco

¿Qué son los antipatrones? Veamos algunos ejemplos (Parte 2)

Publicado en el grupo Random-ES
¿Qué son los antipatrones? Veamos algunos ejemplos (Parte 1) Hoy continuamos nuestro repaso por los antipatrones más populares. Si te perdiste la primera parte, aquí la tienes. ¿Qué son los antipatrones?  Veamos algunos ejemplos (Parte 2) - 1Por lo tanto, los patrones de diseño son las mejores prácticas. En otras palabras, son ejemplos de formas buenas y comprobadas de resolver problemas específicos. A su vez, los antipatrones son exactamente lo contrario, en el sentido de que son patrones de trampas o errores al resolver varios problemas (patrones malvados). Pasemos al siguiente antipatrón de desarrollo de software.

8. Martillo de oro

Un martillo dorado es un antipatrón definido por la confianza de que una solución particular es universalmente aplicable. Ejemplos:
  1. Después de encontrar un problema y encontrar un patrón para la solución perfecta, un programador intenta pegar este patrón en todas partes, aplicándolo a proyectos actuales y futuros, en lugar de buscar las soluciones adecuadas para casos específicos.

  2. Algunos desarrolladores alguna vez crearon su propia variante de un caché para una situación específica (porque nada más era adecuado). Más tarde, en el siguiente proyecto que no involucraba una lógica de caché especial, usaron su variante nuevamente en lugar de usar bibliotecas preparadas (por ejemplo, Ehcache). El resultado fue un montón de errores e incompatibilidades, así como una gran cantidad de tiempo perdido y nervios fritos.

    Cualquiera puede caer en este antipatrón. Si es un principiante, es posible que no tenga conocimientos sobre patrones de diseño. Esto puede llevarte a tratar de resolver todos los problemas de la única manera que dominas. Si hablamos de profesionales, a esto lo llamamos deformación profesional o visión nerd. Tiene sus propios patrones de diseño preferidos y, en lugar de usar el correcto, usa su favorito, asumiendo que un buen ajuste en el pasado garantiza el mismo resultado en el futuro.

    Este escollo puede producir resultados muy tristes: desde una implementación mala, inestable y difícil de mantener hasta un fracaso total del proyecto. Así como no existe una pastilla para todas las enfermedades, tampoco existe un patrón de diseño para todas las ocasiones.

9. Optimización prematura

La optimización prematura es un antipatrón cuyo nombre habla por sí solo.
"Los programadores pasan una gran cantidad de tiempo pensando y preocupándose por los lugares no críticos del código y tratando de optimizarlos, lo que solo afecta negativamente la depuración y el soporte posteriores. Por lo general, deberíamos olvidarnos de la optimización en, digamos, el 97 % de los casos. Además , la optimización prematura es la raíz de todos los males. Dicho esto, debemos prestar toda la atención al 3% restante". —Donald Knuth
Por ejemplo, agregar prematuramente índices a una base de datos. ¿Por qué es eso malo? Bueno, es malo porque los índices se almacenan como un árbol binario. Como resultado, cada vez que se agrega y elimina un nuevo valor, el árbol se recalcula y esto consume recursos y tiempo. Por lo tanto, los índices deben agregarse solo cuando existe una necesidad urgente (si tiene una gran cantidad de datos y las consultas demoran demasiado) y solo para los campos más importantes (los campos que se consultan con mayor frecuencia).

10. Código espagueti

El código espagueti es un antipatrón definido por un código mal estructurado, confuso y difícil de entender, que contiene todo tipo de bifurcaciones, como excepciones envolventes, condiciones y bucles. Anteriormente, el operador goto era el principal aliado de este antipatrón. Las sentencias Goto ya no se utilizan realmente, lo que felizmente elimina una serie de dificultades y problemas asociados.

public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
Se ve horrible, ¿no? Desafortunadamente, este es el antipatrón más común :( Incluso la persona que escribe dicho código no podrá entenderlo en el futuro. Otros desarrolladores que vean el código pensarán: "Bueno, si funciona, está bien. es mejor no tocarlo". A menudo, un método es inicialmente simple y muy transparente, pero a medida que se agregan nuevos requisitos, el método se carga gradualmente con más y más declaraciones condicionales, convirtiéndolo en una monstruosidad como esta. Si tal método aparece, debe refactorizarlo por completo o al menos las partes más confusas. Por lo general, al programar un proyecto, se asigna tiempo para la refactorización, por ejemplo, el 30% del tiempo del sprint es para la refactorización y las pruebas. Por supuesto, esto supone que no hay prisa (pero cuándo sucede eso).aquí _

11. Números mágicos

Los números mágicos son un antipatrón en el que se utilizan todo tipo de constantes en un programa sin ninguna explicación de su propósito o significado. Es decir, generalmente están mal nombrados o en casos extremos, no hay ningún comentario que explique cuáles son los comentarios o por qué. Al igual que el código espagueti, este es uno de los antipatrones más comunes. Alguien que no haya escrito el código puede o no tener idea de los números mágicos o de cómo funcionan (y con el tiempo, el propio autor no podrá explicarlos). Como resultado, cambiar o eliminar un número hace que el código deje de funcionar por arte de magia. Por ejemplo, 36 y 73. Para combatir este antipatrón, recomiendo una revisión de código. Su código debe ser revisado por desarrolladores que no estén involucrados en las secciones relevantes del código. Sus ojos estarán frescos y tendrán preguntas: ¿qué es esto y por qué hiciste eso? Y por supuesto, necesitas usar nombres explicativos o dejar comentarios.

12. Programación de copiar y pegar

La programación de copiar y pegar es un antipatrón en el que el código de otra persona se copia y pega sin pensar, lo que posiblemente produzca efectos secundarios inesperados. Por ejemplo, copiar y pegar métodos con cálculos matemáticos o algoritmos complejos que no comprendemos del todo. Puede funcionar para nuestro caso particular, pero en algunas otras circunstancias podría generar problemas. Supongamos que necesito un método para determinar el número máximo en una matriz. Rebuscando por Internet encontré esta solución:

public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
Obtenemos una matriz con los números 3, 6, 1, 4 y 2, y el método devuelve 6. Genial, ¡conservémoslo! Pero luego obtenemos una matriz que consta de 2.5, -7, 2 y 3, y luego nuestro resultado es -7. Y este resultado no es bueno. El problema aquí es que Math.abs() devuelve el valor absoluto. La ignorancia de esto conduce al desastre, pero solo en ciertas situaciones. Sin una comprensión profunda de la solución, hay muchos casos que no podrá verificar. El código copiado también puede ir más allá de la estructura interna de la aplicación, tanto en el estilo como en un nivel arquitectónico más fundamental. Tal código será más difícil de leer y mantener. Y, por supuesto, no debemos olvidar que copiar directamente el código de otra persona es un tipo especial de plagio.

13. Reinventar la rueda

Reinventar la rueda es un antipatrón, también conocido como reinventar la rueda cuadrada.. En esencia, esta plantilla es lo opuesto al antipatrón de copiar y pegar considerado anteriormente. En este antipatrón, el desarrollador implementa su propia solución para un problema para el que ya existen soluciones. A veces, estas soluciones existentes son mejores que las que inventa el programador. La mayoría de las veces, esto solo conduce a una pérdida de tiempo y una menor productividad: es posible que el programador no encuentre una solución en absoluto o que encuentre una solución que esté lejos de ser la mejor. Dicho esto, no podemos descartar la posibilidad de crear una solución independiente, porque hacerlo es un camino directo a la programación de copiar y pegar. El programador debe guiarse por las tareas de programación específicas que surgen para resolverlas de manera competente, ya sea utilizando soluciones listas para usar o creando soluciones personalizadas. Muy a menudo, la razón para usar este antipatrón es simplemente prisa. El resultado es un análisis superficial de (búsqueda de) soluciones preparadas. Reinventar la rueda cuadrada es un caso en el que el antipatrón en cuestión tiene un resultado negativo. Es decir, el proyecto requiere una solución personalizada y el desarrollador la crea, pero mal. Al mismo tiempo, ya existe una buena opción y otros la están utilizando con éxito. En pocas palabras: se pierde una gran cantidad de tiempo. Primero, creamos algo que no funciona. Luego tratamos de refactorizarlo y finalmente lo reemplazamos con algo que ya existía. Un ejemplo es implementar su propio caché personalizado cuando ya existen muchas implementaciones. No importa cuán talentoso sea como programador, debe recordar que reinventar una rueda cuadrada es, como mínimo, una pérdida de tiempo. Y, como saben, el tiempo es el recurso más valioso.

14. Problema de yo-yo

El problema del yo-yo es un antipatrón en el que la estructura de la aplicación es demasiado complicada debido a una excesiva fragmentación (por ejemplo, una cadena de herencia demasiado subdividida). El "problema del yo-yo" surge cuando necesita comprender un programa cuya jerarquía de herencia es larga y compleja, lo que crea llamadas de método profundamente anidadas. Como resultado, los programadores necesitan navegar entre muchas clases y métodos diferentes para inspeccionar el comportamiento del programa. El nombre de este antipatrón proviene del nombre del juguete. Como ejemplo, veamos la siguiente cadena de herencia: Tenemos una interfaz de tecnología:

public interface Technology {
   void turnOn();
}
La interfaz de transporte lo hereda:

public interface Transport extends Technology {
   boolean fillUp();
}
Y luego tenemos otra interfaz, GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
Y a partir de ahí, derivamos una clase Car abstracta:

public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
La siguiente es la clase Volkswagen abstracta:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
Y por último, un modelo específico:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
Esta cadena nos obliga a buscar respuestas a preguntas como:
  1. ¿ Cuántos métodos tiene VolkswagenAmarok?

  2. Qué tipo debe insertarse en lugar del signo de interrogación para lograr la máxima abstracción:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
Es difícil responder rápidamente a tales preguntas: requiere que miremos e investiguemos, y es fácil confundirse. ¿Y si la jerarquía es mucho más grande, más larga y más complicada, con todo tipo de sobrecargas y anulaciones? La estructura que tendríamos quedaría oscurecida por la excesiva fragmentación. La mejor solución sería reducir las divisiones innecesarias. En nuestro caso saldríamos de Tecnología → Automóvil → VolkswagenAmarok.

15. Complejidad accidental

La complejidad innecesaria es un antipatrón en el que se introducen complicaciones innecesarias a una solución.
"Cualquier tonto puede escribir código que una computadora pueda entender. Los buenos programadores escriben código que los humanos pueden entender". — Martín Fowler
Entonces, ¿qué es la complejidad? Se puede definir como el grado de dificultad con el que se realiza cada operación en el programa. Como regla general, la complejidad se puede dividir en dos tipos. El primer tipo de complejidad es el número de funciones que tiene un sistema. Puede reducirse de una sola manera: eliminando alguna función. Los métodos existentes necesitan ser monitoreados. Un método debe eliminarse si ya no se usa o todavía se usa pero sin aportar ningún valor. Además, debe evaluar cómo se utilizan todos los métodos de la aplicación para comprender dónde valdrían la pena las inversiones (mucha reutilización de código) y a qué puede decir que no. El segundo tipo de complejidad es la complejidad innecesaria. Se puede curar sólo a través de un enfoque profesional. En lugar de hacer algo "cool" (los desarrolladores jóvenes no son los únicos susceptibles a esta enfermedad), debe pensar en cómo hacerlo de la manera más simple posible, porque la mejor solución siempre es simple. Por ejemplo, supongamos que tenemos pequeñas tablas relacionadas con descripciones de algunas entidades, como un usuario: ¿Qué son los antipatrones?  Veamos algunos ejemplos (Parte 2) - 3Entonces, tenemos la identificación del usuario, la identificación del idioma en el que se realiza la descripción y la descripción en sí. De igual manera, contamos con descriptores auxiliares para las tablas de autos, archivos, planos y clientes. Entonces, ¿cómo sería insertar nuevos valores en dichas tablas?

public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
Y en consecuencia, tenemos esta enumeración:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Todo parece ser simple y bueno... Pero, ¿qué pasa con los otros métodos? De hecho, también tendrán un montón de switchdeclaraciones y un montón de consultas de base de datos casi idénticas, lo que a su vez complicará e inflará nuestra clase. ¿Cómo se podría hacer todo esto más fácil? Actualicemos un poco nuestra enumeración:

@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
Ahora cada tipo tiene los nombres de los campos originales de su tabla. Como resultado, el método para crear una descripción se convierte en:

private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
Conveniente, simple y compacto, ¿no crees? La indicación de un buen desarrollador no es ni siquiera la frecuencia con la que usa patrones, sino la frecuencia con la que evita los antipatrones. La ignorancia es el peor enemigo, porque necesitas conocer a tus enemigos de vista. Bueno, eso es todo lo que tengo por hoy. ¡Gracias a todos! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION