JavaRush /Blog Java /Random-ES /Pausa para el café #85. Tres lecciones de Java que aprend...

Pausa para el café #85. Tres lecciones de Java que aprendí por las malas. Cómo utilizar los principios SOLID en el código

Publicado en el grupo Random-ES

Tres lecciones de Java que aprendí de la manera más difícil

Fuente: Medio Aprender Java es difícil. Aprendí de mis errores. Ahora tú también puedes aprender de mis errores y amargas experiencias, que no necesariamente es necesario que tengas. Pausa para el café #85.  Tres lecciones de Java que aprendí por las malas.  Cómo utilizar los principios SOLID en el código - 1

1. Lambdas puede causar problemas.

Las lambdas suelen superar las 4 líneas de código y son más grandes de lo esperado. Esto sobrecarga la memoria de trabajo. ¿Necesitas cambiar una variable de lambda? No puedes hacer eso. ¿Por qué? Si lambda puede acceder a las variables del sitio de llamadas, pueden surgir problemas de subprocesamiento. Por lo tanto, no puede cambiar las variables de lambda. Pero Happy path en lambda funciona bien. Después de un error en tiempo de ejecución, recibirá esta respuesta:
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
Es difícil seguir el seguimiento de la pila lambda. Los nombres son confusos y difíciles de localizar y depurar. Más lambdas = más seguimientos de pila. ¿Cuál es la mejor manera de depurar lambdas? Utilice resultados intermedios.
map(elem -> {
 int result = elem.getResult();
 return result;
});
Otra buena forma es utilizar técnicas avanzadas de depuración de IntelliJ. Utilice TAB para seleccionar el código que desea depurar y combinarlo con los resultados intermedios. “Cuando nos detenemos en una línea que contiene una lambda, si presionamos F7 (entrar), IntelliJ resalta el fragmento que necesita ser depurado. Podemos cambiar el bloque para depurar usando Tab y, una vez que lo decidamos, presionar F7 nuevamente”. ¿Cómo acceder a las variables del sitio de llamadas desde lambda? Sólo puede acceder a variables finales o realmente finales. Debe crear un contenedor alrededor de las variables de ubicación de la llamada. Ya sea usando AtomicType o usando su propio tipo. Puede cambiar el contenedor de variables creado con lambda. ¿Cómo resolver problemas de seguimiento de pila? Utilice funciones con nombre. De esta manera, puede encontrar rápidamente código responsable, verificar la lógica y resolver problemas. Utilice funciones con nombre para eliminar el rastro críptico de la pila. ¿Se repite la misma lambda? Colóquelo en una función con nombre. Tendrás un único punto de referencia. Cada lambda obtiene una función generada, lo que dificulta su seguimiento.
lambda$yourNamedFunction
lambda$0
Las funciones con nombre resuelven un problema diferente. Grandes lambdas. Las funciones con nombre dividen lambdas grandes, crean fragmentos de código más pequeños y crean funciones conectables.
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. Problemas con las listas

Necesitas trabajar con listas ( Lists ). Necesita un HashMap para los datos. Para los roles necesitará un TreeMap . La lista continua. Y no hay forma de evitar trabajar con colecciones. ¿Cómo hacer una lista? ¿Qué tipo de lista necesitas? ¿Debería ser inmutable o mutable? Todas estas respuestas afectan el futuro de su código. Elija la lista correcta con anticipación para no arrepentirse más tarde. Arrays::asList crea una lista "de un extremo a otro". ¿Qué no puedes hacer con esta lista? No puedes cambiar su tamaño. Él es inmutable. ¿Qué puedes hacer aquí? Especifique elementos, clasificación u otras operaciones que no afecten el tamaño. Utilice Arrays::asList con cuidado porque su tamaño es inmutable pero su contenido no. new ArrayList() crea una nueva lista "mutable". ¿Qué operaciones admite la lista creada? Eso es todo, y ésta es una razón para tener cuidado. Cree listas mutables a partir de inmutables usando new ArrayList() . List::of crea una colección "inmutable". Su tamaño y contenido no cambian bajo ciertas condiciones. Si el contenido son datos primitivos, como int , la lista es inmutable. Eche un vistazo al siguiente ejemplo.
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### PRUEBA lista DE con contenido mutable ### a123
Necesita crear objetos inmutables e insertarlos en List::of . Sin embargo, List::of no ofrece ninguna garantía de inmutabilidad. List::of proporciona inmutabilidad, confiabilidad y legibilidad. Sepa cuándo usar estructuras mutables y cuándo usar estructuras inmutables. La lista de argumentos que no deberían cambiar debería estar en la lista inmutable. Una lista mutable puede ser una lista mutable. Comprenda qué colección necesita para crear código confiable.

3. Las anotaciones te ralentizan

¿Utilizas anotaciones? ¿Los entiendes? ¿Sabes lo que hacen? Si cree que la anotación registrada es adecuada para todos los métodos, está equivocado. Utilicé Logged para registrar los argumentos del método. Para mi sorpresa, no funcionó.
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
¿Qué hay de malo en este código? Hay mucho resumen de configuración aquí. Te encontrarás con esto muchas veces. La configuración se mezcla con código normal. No está mal en sí mismo, pero llama la atención. Se necesitan anotaciones para reducir el código repetitivo. No es necesario escribir lógica de registro para cada punto final. No es necesario configurar transacciones, use @Transactional . Las anotaciones reducen el patrón extrayendo código. Aquí no hay un ganador claro ya que ambos están en el juego. Todavía uso XML y anotaciones. Cuando encuentre un patrón repetido, es mejor mover la lógica a la anotación. Por ejemplo, el registro es una buena opción de anotación. Moraleja: no abuses de las anotaciones y no olvides XML.

Bonificación: es posible que tengas problemas con Opcional

Usarás orElse de Opcional . Se produce un comportamiento indeseable cuando no se pasa la constante orElse . Debes tener esto en cuenta para evitar problemas en el futuro. Veamos algunos ejemplos. Cuando getValue(x) devuelve un valor, se ejecuta getValue(y) . El método en orElse se ejecuta si getValue(x) devuelve un valor opcional no vacío .
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
Utilice o de lo contrario obtenga . No ejecutará código para Opcionales que no estén vacíos .
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

Conclusión

Aprender Java es difícil. No puedes aprender Java en 24 horas. Perfecciona tus habilidades. Tómese el tiempo, aprenda y sobresalga en su trabajo.

Cómo utilizar los principios SOLID en el código

Fuente: Cleanthecode Escribir código confiable requiere principios SÓLIDOS. En algún momento todos tuvimos que aprender a programar. Y seamos honestos. Éramos ESTÚPIDOS. Y nuestro código era el mismo. Gracias a Dios tenemos SÓLIDO. Pausa para el café #85.  Tres lecciones de Java que aprendí por las malas.  Cómo utilizar los principios SOLID en el código - 2

Principios SÓLIDOS

Entonces, ¿cómo se escribe código SÓLIDO? En realidad es simple. Sólo necesitas seguir estas cinco reglas:
  • Principio de responsabilidad única
  • Principio abierto-cerrado
  • Principio de reemplazo de Liskov
  • Principio de separación de interfaces
  • Principio de inversión de dependencia
¡No te preocupes! ¡Estos principios son mucho más simples de lo que parecen!

Principio de responsabilidad única

En su libro, Robert C. Martin describe este principio de la siguiente manera: “Una clase debe tener sólo una razón para cambiar”. Veamos dos ejemplos juntos.

1. Qué no hacer

Tenemos una clase llamada Usuario que le permite al usuario hacer las siguientes cosas:
  • Registrar una cuenta
  • Acceso
  • Recibe una notificación la primera vez que inicies sesión
Esta clase ahora tiene varias responsabilidades. Si el proceso de registro cambia, la clase de Usuario cambiará. Lo mismo sucederá si cambia el proceso de inicio de sesión o el proceso de notificación. Esto significa que la clase está sobrecargada. Tiene demasiadas responsabilidades. La forma más sencilla de solucionar este problema es trasladar la responsabilidad a sus clases para que la clase Usuario solo sea responsable de combinar clases. Si luego el proceso cambia, tiene una clase clara y separada que necesita cambiar.

2. Qué hacer

Imagine una clase que debería mostrar una notificación a un nuevo usuario, FirstUseNotification . Constará de tres funciones:
  • Compruebe si ya se ha mostrado una notificación
  • Muestra la notificación
  • Marcar notificación como ya se muestra
¿Esta clase tiene múltiples razones para cambiar? No. Esta clase tiene una función clara: mostrar una notificación para un nuevo usuario. Esto significa que la clase tiene una razón para cambiar. Es decir, si este objetivo cambia. Por tanto, esta clase no viola el principio de responsabilidad única. Por supuesto, hay algunas cosas que pueden cambiar: la forma en que las notificaciones se marcan como leídas puede cambiar o la forma en que aparece la notificación. Sin embargo, dado que el propósito de la clase es claro y básico, está bien.

Principio abierto-cerrado

El principio abierto-cerrado fue acuñado por Bertrand Meyer: "Los objetos de software (clases, módulos, funciones, etc.) deben estar abiertos a la extensión, pero cerrados a la modificación". Este principio es realmente muy simple. Debe escribir su código para que se le puedan agregar nuevas funciones sin cambiar el código fuente. Esto ayuda a evitar una situación en la que necesite cambiar clases que dependan de su clase modificada. Sin embargo, este principio es mucho más difícil de implementar. Meyer sugirió utilizar la herencia. Pero conduce a una fuerte conexión. Discutiremos esto en los Principios de separación de interfaces y los Principios de inversión de dependencia. Entonces a Martin se le ocurrió un enfoque mejor: utilizar polimorfismo. En lugar de la herencia convencional, este enfoque utiliza clases base abstractas. De esta manera, las especificaciones heredadas se pueden reutilizar sin necesidad de implementación. La interfaz se puede escribir una vez y luego cerrar para realizar cambios. Luego, las nuevas funciones deben implementar esta interfaz y ampliarla.

Principio de reemplazo de Liskov

Este principio fue inventado por Barbara Liskov, ganadora del Premio Turing por sus contribuciones a los lenguajes de programación y la metodología del software. En su artículo, definió su principio de la siguiente manera: "Los objetos en un programa deben ser reemplazables con instancias de sus subtipos sin afectar la correcta ejecución del programa". Veamos este principio como programador. Imaginemos que tenemos un cuadrado. Podría ser un rectángulo, lo que suena lógico ya que un cuadrado es una forma especial de rectángulo. Aquí es donde el principio de sustitución de Liskov viene al rescate. Dondequiera que espere ver un rectángulo en su código, también es posible que aparezca un cuadrado. Ahora imagina que tu rectángulo tiene los métodos SetWidth y SetHeight . Esto significa que el cuadrado también necesita estos métodos. Desafortunadamente, esto no tiene ningún sentido. Esto significa que aquí se viola el principio de sustitución de Liskov.

Principio de separación de interfaces

Como todos los principios, el principio de separación de interfaces es mucho más simple de lo que parece: "Muchas interfaces específicas de cliente son mejores que una interfaz de propósito general". Al igual que con el principio de responsabilidad única, el objetivo es reducir los efectos secundarios y la cantidad de cambios necesarios. Por supuesto, nadie escribe ese código a propósito. Pero es fácil de encontrar. ¿Recuerdas el cuadrado del principio anterior? Ahora imaginemos que decidimos implementar nuestro plan: hacemos un cuadrado a partir de un rectángulo. Ahora estamos forzando al cuadrado a implementar setWidth y setHeight , que probablemente no hagan nada. Si hicieran eso, probablemente romperíamos algo porque el ancho y el alto no serían los que esperábamos. Afortunadamente para nosotros, esto significa que ya no estamos violando el principio de sustitución de Liskov, ya que ahora permitimos el uso de un cuadrado dondequiera que usemos un rectángulo. Sin embargo, esto crea un nuevo problema: ahora estamos violando el principio de separación de interfaces. Obligamos a la clase derivada a implementar una funcionalidad que decide no utilizar.

Principio de inversión de dependencia

El último principio es simple: los módulos de alto nivel deben ser reutilizables y no deben verse afectados por cambios en los módulos de bajo nivel.
  • R. Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deben depender de abstracciones (como las interfaces).
  • B. Las abstracciones no deben depender de los detalles. Los detalles (implementaciones concretas) deben depender de las abstracciones.
Esto se puede lograr implementando una abstracción que separe los módulos de alto y bajo nivel. El nombre del principio sugiere que la dirección de la dependencia cambia, pero no es así. Solo separa la dependencia introduciendo una abstracción entre ellas. Como resultado, obtendrás dos dependencias:
  • Módulo de alto nivel, dependiendo de la abstracción.
  • Módulo de bajo nivel dependiendo de la misma abstracción.
Esto puede parecer difícil, pero en realidad sucede automáticamente si se aplica correctamente el principio abierto/cerrado y el principio de sustitución de Liskov. ¡Eso es todo! Ahora ha aprendido los cinco principios básicos que sustentan SOLID. ¡Con estos cinco principios, puedes hacer que tu código sea increíble!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION