Los 10 errores más comunes al trabajar con la plataforma Spring. Parte 1

Hola a todos Hoy compartimos la primera parte del artículo, cuya traducción fue preparada específicamente para estudiantes del curso "Desarrollador en el Marco de Primavera" . ¡Empecemos!





Spring es quizás una de las plataformas de desarrollo de Java más populares. Esta es una herramienta poderosa, pero bastante difícil de aprender. Sus conceptos básicos son bastante fáciles de entender y aprender, pero lleva tiempo y esfuerzo convertirse en un desarrollador experimentado de Spring.

En este artículo, veremos algunos de los errores más comunes cometidos al trabajar en Spring y, en particular, relacionados con el desarrollo de aplicaciones web y el uso de la plataforma Spring Boot. Como se señaló en el sitio web de Spring Boot , Spring Boot adopta un enfoque estandarizado para crear aplicaciones listas para usar, y este artículo seguirá este enfoque. Dará una serie de recomendaciones que se pueden utilizar de manera efectiva en el desarrollo de aplicaciones web estándar basadas en Spring Boot.

En caso de que no esté muy familiarizado con la plataforma Spring Boot, pero quiera experimentar con los ejemplos de este artículo, creé un repositorio de GitHub con materiales adicionales para este artículo . Si en algún momento está un poco confundido mientras lee este artículo, le aconsejaría que cree un clon de este repositorio y experimente con el código en su computadora.

Error común n. ° 1: ir a una programación de nivel demasiado bajo


Sucumbimos fácilmente a este error común, porque el "síndrome de rechazo del desarrollo de otro" es bastante típico para el entorno de programación. Uno de sus síntomas es la reescritura constante de piezas de código de uso común, y este es un síntoma visto por muchos programadores.
Estudiar la estructura interna y las características de implementación de una biblioteca en particular a menudo es un proceso útil, necesario e interesante, pero si constantemente escribe el mismo tipo de código mientras realiza una implementación de funciones de bajo nivel, esto puede resultar perjudicial para usted como desarrollador de software. Es por eso que existen y se utilizan abstracciones y plataformas como Spring, que te salvan del trabajo manual repetitivo y te permiten concentrarte en objetos de tu área temática y programar la lógica del código en un nivel superior.

Por lo tanto, no se deben evitar las abstracciones. La próxima vez que se enfrente a la necesidad de resolver un problema específico, primero realice una búsqueda rápida y descubra si la biblioteca que resuelve este problema ya está integrada en Spring; es probable que encuentre una solución llave en mano adecuada. Una de estas bibliotecas útiles es el Proyecto Lombok , las anotaciones de las que usaré en los ejemplos de este artículo. Lombok se utiliza como un generador de código de plantilla, por lo que el desarrollador perezoso que vive en cada uno de nosotros estará muy feliz de conocer esta biblioteca. Vea, por ejemplo, cómo se ve el componente estándar JavaBean en Lombok:

@Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; } 

Como ya habrá entendido, el código anterior se convierte al siguiente formulario:

 public class Bean implements Serializable { private int firstBeanProperty; private String secondBeanProperty; public int getFirstBeanProperty() { return this.firstBeanProperty; } public String getSecondBeanProperty() { return this.secondBeanProperty; } public void setFirstBeanProperty(int firstBeanProperty) { this.firstBeanProperty = firstBeanProperty; } public void setSecondBeanProperty(String secondBeanProperty) { this.secondBeanProperty = secondBeanProperty; } public Bean() { } } 

Tenga en cuenta que lo más probable es que tenga que instalar el complemento apropiado si desea utilizar Lombok en su entorno de desarrollo integrado. La versión de este complemento para IntelliJ IDEA se puede encontrar aquí .

Error común número 2. "Fuga" de estructuras internas


Dar acceso a estructuras internas nunca ha sido una buena idea, ya que perjudica la flexibilidad del modelo de servicio y, como resultado, contribuye a la formación de un mal estilo de programación. La "fuga" de las estructuras internas se manifiesta en el hecho de que la estructura de la base de datos se vuelve accesible desde ciertos puntos finales API. Por ejemplo, suponga que el siguiente "buen objeto Java antiguo" (POJO) representa una tabla en su base de datos:

 @Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } } 

Suponga que hay un punto final que necesita acceder a los datos de un objeto TopTalentEntity . TopTalentEntity instancias de TopTalentEntity parece una idea tentadora, pero una solución más flexible sería crear una nueva clase que represente los datos de TopTalentEntity para el punto final de la API:

 @AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; } 

Por lo tanto, realizar cambios en los componentes internos de la base de datos no requerirá cambios adicionales en el nivel de servicio. Veamos qué sucede si el campo de contraseña se agrega a la clase TopTalentEntity para almacenar hashes de contraseña de usuario en la base de datos: si no hay un conector, como TopTalentData , y el desarrollador olvida cambiar la parte de interfaz del servicio, ¡esto puede conducir a una divulgación no deseada de información secreta!

Error común número 3. Combinar funciones que sería mejor distribuir en el código


Organizar el código de la aplicación a medida que crece se convierte en una tarea cada vez más importante. Por extraño que parezca, la mayoría de los principios de programación efectiva dejan de funcionar cuando se alcanza una cierta escala de desarrollo, especialmente si la arquitectura de la aplicación no ha sido bien pensada. Y uno de los errores más frecuentes es la combinación de funciones en el código que son más razonables de implementar por separado.

La razón de la violación del principio de separación de responsabilidades suele ser la adición de nuevas funcionalidades a las clases existentes. Quizás esta sea una buena solución momentánea (en particular, requiere escribir una cantidad menor de código), pero en el futuro inevitablemente se convertirá en un problema, incluso en las etapas de prueba y mantenimiento del código y entre ellos. Considere el siguiente controlador que devuelve TopTalentData desde el repositorio:

 @RestController public class TopTalentController { private final TopTalentRepository topTalentRepository; @RequestMapping("/toptal/get") public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(this::entityToData) .collect(Collectors.toList()); } private TopTalentData entityToData(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } } 

A primera vista, parece que no hay errores obvios en este fragmento de código. Proporciona una lista de objetos TopTalentData , que se toma de instancias de la clase TopTalentEntity . Pero si observa el código más de cerca, veremos que en realidad TopTalentController realiza varias acciones aquí, a saber, correlaciona las solicitudes con un punto final específico, recupera datos del repositorio y convierte los objetos recibidos de TopTalentRepository en un formato diferente. Una solución más limpia debería separar estas funciones en clases separadas. Por ejemplo, podría verse así:

 @RestController @RequestMapping("/toptal") @AllArgsConstructor public class TopTalentController { private final TopTalentService topTalentService; @RequestMapping("/get") public List<TopTalentData> getTopTalent() { return topTalentService.getTopTalent(); } } @AllArgsConstructor @Service public class TopTalentService { private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter; public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); } } @Component public class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } } 

Una ventaja adicional de esta jerarquía es que nos permite comprender dónde se encuentra una función simplemente mirando el nombre de la clase. Posteriormente, durante las pruebas, podemos reemplazar fácilmente el código de cualquiera de estas clases con código sustituto, si surge la necesidad.

Número de error común 4. Código uniforme y manejo de errores deficiente


El tema de la uniformidad del código no es exclusivo de Spring (o de Java en general), pero, sin embargo, es un aspecto importante que debe considerarse al trabajar con proyectos en Spring. Elegir un estilo de programación específico puede ser un tema de discusión (y generalmente es consistente dentro del equipo de desarrollo o en toda la empresa), pero en cualquier caso, la presencia de un estándar común para escribir código contribuye a un aumento en la eficiencia del trabajo. Esto es especialmente cierto si varias personas trabajan en el código. El código uniforme se puede pasar de un desarrollador a otro sin gastar mucho esfuerzo para mantenerlo o largas explicaciones sobre por qué se necesitan estas o esas clases.

Imagine que hay un proyecto Spring en el que hay varios archivos de configuración, servicios y controladores. Después de la uniformidad semántica al nombrarlos, creamos una estructura mediante la cual puede buscar fácilmente y en la que cualquier desarrollador puede comprender fácilmente el código. Por ejemplo, puede agregar el sufijo de configuración a los nombres de las clases de configuración, el sufijo de servicio a los nombres de servicio y el sufijo de controlador a los nombres de controlador.

El manejo de errores del lado del servidor está estrechamente relacionado con la uniformidad del código y merece especial atención. Si alguna vez ha manejado excepciones provenientes de una API mal escrita, probablemente sepa lo difícil que es entender el significado de estas excepciones correctamente, y aún más difícil determinar por qué realmente ocurrieron.
Como desarrollador de API, idealmente le gustaría cubrir todos los puntos finales con los que el usuario está trabajando y llevarlos a usar un único formato de mensaje de error. Por lo general, esto significa que debe usar códigos de error estándar y su descripción y abandonar decisiones dudosas como dar al usuario un mensaje de "500 Error interno del servidor" o los resultados de un seguimiento de la pila (la última opción, por cierto, debe evitarse por todos los medios, ya que está revelando datos internos, y además, estos resultados son difíciles de procesar en el lado del cliente).
Aquí, por ejemplo, cómo se vería el formato general del mensaje de error:

 @Value public class ErrorResponse { private Integer errorCode; private String errorMessage; } 

Un formato similar a este se encuentra a menudo en las API más populares y, como regla, funciona muy bien porque puede documentarse de manera fácil y sistemática. Puede convertir una excepción a este formato agregando la anotación @ExceptionHandler al método (para ver un ejemplo de la anotación, consulte la sección "Error común n. ° 6").

Error común número 5. Trabajo incorrecto con subprocesos múltiples


La implementación de subprocesos múltiples puede ser una tarea difícil, independientemente de si se usa en aplicaciones de escritorio o en aplicaciones web, en proyectos Spring o proyectos para otras plataformas. Los problemas causados ​​por la ejecución paralela de programas son difíciles de rastrear, y tratarlos con un depurador a menudo es muy difícil. Si comprende que está lidiando con un error relacionado con la ejecución paralela, lo más probable es que tenga que abandonar el depurador y examinar el código manualmente hasta que se encuentre la causa raíz del error. Desafortunadamente, no hay una forma estándar de resolver tales problemas. En cada caso, es necesario evaluar la situación y "atacar" el problema con el método que parece mejor en las condiciones dadas.

Idealmente, por supuesto, le gustaría evitar por completo los errores asociados con el subprocesamiento múltiple. Y aunque no hay una receta universal aquí, todavía puedo ofrecer algunas recomendaciones prácticas.

Evita usar estados globales


Primero, recuerde siempre el problema de los "estados globales". Si está desarrollando una aplicación multiproceso, debe controlar cuidadosamente todas las variables modificables globalmente y, si es posible, eliminarlas por completo. Si todavía tiene una razón por la cual la variable global debería ser modificable, sincronice y monitoree adecuadamente el rendimiento de su aplicación; debe asegurarse de que no se ralentice debido a los períodos de espera adicionales.

Evitar objetos mutables


Esta idea proviene directamente de los principios de la programación funcional y, al estar adaptada a los principios de OOP, establece que las clases mutables y los estados mutables deben evitarse. En resumen, esto significa que debe abstenerse de establecer métodos (establecedores) y tener campos privados con el modificador final en todas las clases de modelos. La única vez que cambian sus valores es cuando se crea el objeto. En este caso, puede estar seguro de que no hay problemas asociados con la competencia por los recursos, y al acceder a las propiedades del objeto, siempre se obtendrán los valores correctos.

Registrar datos importantes


Evalúe dónde pueden surgir problemas en la aplicación y configure de antemano un registro de todos los datos importantes. Si se produce un error, le complacerá recibir información sobre las solicitudes recibidas y podrá comprender mejor los motivos del mal funcionamiento de su aplicación. Sin embargo, tenga en cuenta que el registro implica E / S de archivos adicionales y no se debe abusar, ya que esto puede afectar negativamente el rendimiento de la aplicación.

Use implementaciones de subprocesos listas para usar


Cuando necesite generar sus hilos (por ejemplo, para crear solicitudes asincrónicas a varios servicios), use implementaciones de hilos seguras ya preparadas en lugar de crear las suyas propias. En la mayoría de los casos, puede utilizar las abstracciones de ExecutorService y las espectaculares abstracciones funcionales CompletableFuture para Java 8. Para crear subprocesos. La plataforma Spring también le permite manejar solicitudes asincrónicas utilizando la clase DeferredResult .

El final de la primera parte.
Lee la segunda parte.

Source: https://habr.com/ru/post/464429/


All Articles