Muestra las diferencias entre dos versiones de la página.
Próxima revisión | Revisión previa | ||
ada:howto:sicoferp:factory:cleancode [2023/11/07 13:01] 192.168.175.10 creado |
ada:howto:sicoferp:factory:cleancode [2023/11/07 13:23] (actual) 192.168.175.10 |
||
---|---|---|---|
Línea 1: | Línea 1: | ||
====== Buenas prácticas de desarrollo de software: Principios Clean Code (Código Limpio) ====== | ====== Buenas prácticas de desarrollo de software: Principios Clean Code (Código Limpio) ====== | ||
- | Esta sección presenta las 7 reglas principales del Clean Code (Código Limpio). La información ha sido adaptada tomando como referencia el portal https://www.hostgator.mx/blog/clean-code-codigo-limpio/ | + | Esta sección presenta las 7 reglas principales del Clean Code (Código Limpio). |
+ | El concepto de código limpio se le atribuye a **Robert Cecil Martin**, un destacado ingeniero de software que popularizó este término en su libro "//**Clean Code: Refactoring, Patterns, Testing and Techniques for Clean Code**//". No obstante, los principios subyacentes a la idea de código limpio tienen raíces mucho más profundas y no se limitan exclusivamente al ámbito de la programación. A continuación, te ofrecemos una explicación sobre lo que implica el código limpio, sus beneficios y cómo llevarlo a la práctica((https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/clean-code-que-es-el-codigo-limpio/)). | ||
- | ===== Tipos de Documentación ===== | + | ===== ¿Que es Clean Code? ===== |
+ | El código limpio no se basa en reglas rígidas, sino en una serie de principios que promueven la creación de un código intuitivo y fácil de modificar. En este contexto, la intuición implica que cualquier profesional de desarrollo pueda comprenderlo de inmediato. Un código que es fácilmente adaptable presenta las siguientes características: | ||
- | * **Pública**: Documentación que esta disponible publicamente, es decir puede ser accedida directamente por cualquiera Como: Manuales públicos, Páginas Web o especificaciones de APIs Rest como Swagger u OpenAPI. | + | * El flujo de ejecución del programa sigue una lógica clara y tiene una estructura sencilla. |
- | * **Privada**: Aquella que se utiliza internamente dentro la empresa que desarrolla el producto de software como: Documentos de arquitectura, Javadoc, Fichas técnicas de integraciones. | + | * Las relaciones entre las distintas partes del código son transparentes. |
+ | * La función de cada clase, función, método y variable es evidente a simple vista. | ||
- | ==== Consideraciones ==== | + | Un código se considera fácil de modificar cuando es flexible y extensible, lo que facilita la corrección de posibles errores. Por estas razones, el código limpio resulta sencillo de mantener y exhibe las siguientes cualidades: |
- | En esta sección nos centraremos en las especificaciones APIs Rest las cuales son las que más se utilizan en las tecnologías actuales. | + | |
- | ===== Documentación de APIs Rest: OpenAPI ===== | + | * Las clases y los métodos son concisos y, siempre que sea posible, tienen una única función bien definida. |
+ | * Las clases y los métodos son predecibles, operan de acuerdo a las expectativas y se acceden a través de API (interfaces) claramente documentadas. | ||
+ | * El código ha sido sometido a pruebas unitarias. | ||
- | ==== ¿Que es OpenAPI? ==== | + | Las ventajas de este enfoque de programación son evidentes: el código limpio se vuelve independiente del desarrollador que lo creó. En esencia, cualquier programador puede trabajar con él, lo que evita los problemas asociados con el código heredado. Además, el mantenimiento del software se simplifica, ya que los errores son más fáciles de identificar y corregir((https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/clean-code-que-es-el-codigo-limpio/)). |
- | OpenAPI es el estándar global para escribir APIs RESTful. Es como una especificación que permite a los desarrolladores de todo el planeta estandarizar el diseño de sus APIs. | + | ===== 7 Reglas principales del Clean Code ===== |
- | Además, cumple con toda la seguridad, el versionado, el manejo de errores y otras mejores prácticas al escribir APIs REST desde cero. Y no sólo desde el principio, sino que incluso las APIs existentes pueden ajustarse para cumplir con un estándar global((https://codigoencasa.com/openapi/)). | + | ==== 1. La importancia de los nombres ==== |
+ | La elección de nombres desempeña un papel fundamental en la comprensión de un código, ya sea para variables, funciones, parámetros, clases o métodos. Al seleccionar un nombre, es esencial considerar dos aspectos clave: | ||
- | La especificación OpenAPI es un lenguaje de especificación para API HTTP que proporciona un medio estandarizado para definir su API ante otros. Podemos descubrir rápidamente cómo funciona una API, configurar la infraestructura, generar código de cliente y crear casos de prueba. [[https://www.openapis.org/|Para mas información -> Ir al sitio web oficial.]] | + | Debe ser preciso y expresar la idea central de manera directa. |
+ | No temas usar nombres largos si ello es necesario para representar con claridad la función o propósito. | ||
- | ===== Modelo Propuesto Fabrica de Software ===== | + | ==== 2. Regla del boy scout ==== |
- | A continuación se comparte el proceso de documentación propuesto para la documentación de Servicios Web. Favor tener presente que este proceso dependerá de la versión java y el framework utilizado. | + | Esta regla se basa en la idea de que, al salir de un área de acampada, debes dejarla más ordenada de lo que la encontraste. En el ámbito de la programación, esto se traduce en dejar el código más limpio de lo que estaba antes de editarlo. |
- | ==== Dependencias ==== | + | ==== 3. Sé el autor auténtico del código ==== |
- | Para java 11 -> 17 puede utilizar la siguiente configuración. | + | El código es una narrativa, y los programadores son los autores de esta historia. Para estructurar un código limpio, es fundamental crear funciones simples, claras y concisas. Dos reglas orientadoras son: |
- | <code xml> | + | * Mantén las funciones pequeñas. |
- | <!-- versión Springboot --> | + | * Y, si es posible, aún más pequeñas. |
- | <parent> | + | |
- | <groupId>org.springframework.boot</groupId> | + | |
- | <artifactId>spring-boot-starter-parent</artifactId> | + | |
- | <version>2.7.17</version> | + | |
- | <relativePath/> <!-- lookup parent from repository --> | + | |
- | </parent> | + | |
- | <!-- versión Java --> | + | |
- | <properties> | + | |
- | <java.version>11</java.version> | + | |
- | </properties> | + | |
- | <!-- versión OpenAPI --> | + | |
- | <dependency> | + | |
- | <groupId>org.springdoc</groupId> | + | |
- | <artifactId>springdoc-openapi-ui</artifactId> | + | |
- | <version>1.6.12</version> | + | |
- | </dependency> | + | |
- | </code> | + | |
- | ==== Configuración de opciones en el archivo de configuración (.properties/.yaml) ==== | + | Es importante no confundir "**nombre**" con "**función**". Como se mencionó en el primer principio, los nombres largos no son un problema, pero las funciones deben mantenerse breves. |
- | Se deben adicionar las siguientes propiedades para activar la generación y visualización de la documentación. | + | |
- | <code properties> | + | ==== 4. DRY (No te repitas) ==== |
- | #***************************************************************************** | + | Este principio, acuñado en el libro "//**The Pragmatic Programmer**//," se aplica a diversas áreas de desarrollo, como: |
- | #OpenAPI Configuration | + | |
- | #***************************************************************************** | + | |
- | # Specify the path of the OpenAPI documentation | + | |
- | springdoc.api-docs.path=/api-docs | + | |
- | # Specify the path of the Swagger UI | + | * Bases de datos |
- | springdoc.swagger-ui.path=/swagger-ui.html | + | * Pruebas |
+ | * Documentación | ||
+ | * Codificación | ||
+ | |||
+ | **DRY** defiende que cada elemento del conocimiento del sistema debe ser único y exento de ambigüedades, evitando así la duplicación de funcionalidades. | ||
- | # Enable or disable Swagger UI | + | ==== 5. Comentar con moderación ==== |
- | springdoc.swagger-ui.enabled=true | + | Los comentarios en el código deben ser utilizados con moderación y solo cuando sean realmente necesarios. Según la perspectiva de **Uncle Bob**, los comentarios pueden inducir a error, ya que suelen quedar obsoletos al modificarse el código. Por lo tanto, si se opta por comentar, debe ser de manera esencial y revisada conjuntamente con la versión del código. |
- | </code> | + | |
- | ==== Bean de configuración ==== | + | ==== 6. Manejo de errores ==== |
- | Defina una clase que implemente la anotación @Config y adicione un Bean OpenAPI como se ilustra a continuación. | + | El autor **Michael Feathers** destacó la importancia de tratar adecuadamente las excepciones en el desarrollo web. Los programadores son responsables de garantizar que el código siga funcionando incluso cuando surgen problemas. Tratar las excepciones de manera correcta es un aspecto clave en este proceso. |
- | <code java> | + | ==== 7. Pruebas limpias ==== |
- | /** | + | La realización de pruebas es una etapa crucial en la programación, y solo un código que ha pasado pruebas limpias puede considerarse verdaderamente limpio. Para ello, se deben cumplir ciertas reglas: |
- | * Copyright © 2023 ADA Corporation. All rights reserved. | + | |
- | * | + | |
- | * This component is protected by copyright. | + | |
- | * | + | |
- | * Use of this component is subject to the terms of the license agreement. | + | |
- | */ | + | |
- | package com.ada.viatico.proceso.config; | + | |
- | import java.util.List; | + | * **Rápido**: Las pruebas deben ejecutarse rápidamente y en cualquier momento. |
- | import java.util.TimeZone; | + | * **Independiente**: Las pruebas deben ser independientes para evitar efectos en cascada en caso de fallo. |
+ | * **Repetible**: Deben ser repetibles en diferentes entornos. | ||
+ | * **Autovalidación**: Las pruebas bien escritas devuelven respuestas claras (verdadero o falso) para evitar subjetividades en los errores. | ||
+ | * **Puntual**: Las pruebas deben ser escritas antes del código mismo, siguiendo estrictamente el criterio de puntualidad. | ||
- | import javax.annotation.PostConstruct; | + | El **Clean Code** es un concepto arraigado que resuelve de manera eficiente uno de los principales desafíos que enfrentan muchos proyectos de desarrollo de sistemas: el mantenimiento((https://www.hostgator.mx/blog/clean-code-codigo-limpio/)). |
- | + | ||
- | import org.slf4j.Logger; | + | |
- | import org.slf4j.LoggerFactory; | + | |
- | import org.springframework.beans.factory.annotation.Value; | + | |
- | import org.springframework.context.annotation.Bean; | + | |
- | import org.springframework.context.annotation.Configuration; | + | |
- | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; | + | |
- | + | ||
- | import io.swagger.v3.oas.models.OpenAPI; | + | |
- | import io.swagger.v3.oas.models.info.Contact; | + | |
- | import io.swagger.v3.oas.models.info.Info; | + | |
- | import io.swagger.v3.oas.models.info.License; | + | |
- | import io.swagger.v3.oas.models.servers.Server; | + | |
- | + | ||
- | @Configuration | + | |
- | public class OpenApiConfig { | + | |
- | + | ||
- | /** The log. */ | + | |
- | private static Logger log = LoggerFactory.getLogger(OpenApiConfig .class); | + | |
- | + | ||
- | /** The url. */ | + | |
- | @Value("${openapi.url}") | + | |
- | private String url; | + | |
- | + | ||
- | /** The application title. */ | + | |
- | @Value("${application.title}") | + | |
- | private String applicationTitle; | + | |
- | + | ||
- | /** The application summary. */ | + | |
- | @Value("${application.version}") | + | |
- | private String applicationVersion; | + | |
- | + | ||
- | /** | + | |
- | * My open API. | + | |
- | * | + | |
- | * @return the open API | + | |
- | */ | + | |
- | @Bean | + | |
- | public OpenAPI myOpenAPI() { | + | |
- | Server server = new Server(); | + | |
- | server.setUrl(url); | + | |
- | server.setDescription("Server URL environment"); | + | |
- | + | ||
- | Contact contact = new Contact(); | + | |
- | contact.setEmail("developer1@ada.co; developer2@ada.co"); | + | |
- | contact.setName("Developer 1 | Developer 2"); | + | |
- | contact.setUrl("www.ada.co"); | + | |
- | + | ||
- | License mitLicense = new License().name("ADA License").url("www.ada.co/licenses/"); | + | |
- | + | ||
- | Info info = new Info() | + | |
- | .title(applicationTitle) | + | |
- | .version("v" + applicationVersion) | + | |
- | .summary("Resumen dle microservicio.") | + | |
- | .description("Descripción del microservicio.") | + | |
- | .contact(contact) | + | |
- | .termsOfService("https://www.ada.co/terms") | + | |
- | .license(mitLicense); | + | |
- | + | ||
- | return new OpenAPI().info(info).servers(List.of(server)); | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | ==== Exposición de EndPoints ==== | + | |
- | Se deben documentar los métodos del API Rest que van a ser expuestos. | + | |
- | + | ||
- | <code java> | + | |
- | /** | + | |
- | * Copyright © 2023 ADA Corporation. All rights reserved. | + | |
- | * | + | |
- | * This component is protected by copyright. | + | |
- | * | + | |
- | * Use of this component is subject to the terms of the license agreement. | + | |
- | */ | + | |
- | package com.ada.viatico.proceso.controller.v2; | + | |
- | + | ||
- | import org.slf4j.Logger; | + | |
- | import org.slf4j.LoggerFactory; | + | |
- | import org.springframework.beans.factory.annotation.Autowired; | + | |
- | import org.springframework.http.HttpStatus; | + | |
- | import org.springframework.http.ResponseEntity; | + | |
- | import org.springframework.web.bind.annotation.CrossOrigin; | + | |
- | import org.springframework.web.bind.annotation.PostMapping; | + | |
- | import org.springframework.web.bind.annotation.RequestBody; | + | |
- | import org.springframework.web.bind.annotation.RequestMapping; | + | |
- | import org.springframework.web.bind.annotation.RequestMethod; | + | |
- | import org.springframework.web.bind.annotation.RestController; | + | |
- | + | ||
- | import com.ada.viatico.proceso.dto.DatosGenerarAprobacion; | + | |
- | import com.ada.viatico.proceso.dto.GenericResponseDto; | + | |
- | import com.ada.viatico.proceso.repository.IMestroCompromisoRepo; | + | |
- | import com.ada.viatico.proceso.service.Impl.TicketsImp; | + | |
- | import com.ada.viatico.proceso.service.Impl.ViaticoSolicitudImpl; | + | |
- | import com.ada.viatico.proceso.service.v2.ViaticosServiceV2; | + | |
- | import com.ada.viatico.proceso.sfcomercial.dto.AprobacionLegalizacionDto; | + | |
- | import com.ada.viatico.proceso.utiliti.AdelantoSolicitud; | + | |
- | import com.ada.viatico.proceso.utiliti.GuardarLegalizacion; | + | |
- | import com.ada.viatico.proceso.utiliti.ProcesoGuardar; | + | |
- | import com.ada.viatico.proceso.utiliti.ProcesoViaticoAprobacion; | + | |
- | import com.ada.viatico.proceso.utiliti.ViaticosException; | + | |
- | + | ||
- | import io.swagger.v3.oas.annotations.Operation; | + | |
- | import io.swagger.v3.oas.annotations.Parameter; | + | |
- | import io.swagger.v3.oas.annotations.enums.ParameterIn; | + | |
- | import io.swagger.v3.oas.annotations.media.Content; | + | |
- | import io.swagger.v3.oas.annotations.media.Schema; | + | |
- | import io.swagger.v3.oas.annotations.responses.ApiResponse; | + | |
- | + | ||
- | /** | + | |
- | * The Class ViaticosController. | + | |
- | * @version 2.0 | + | |
- | * @author carlos.torres@ada.co | + | |
- | */ | + | |
- | @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE}) | + | |
- | @RestController | + | |
- | @RequestMapping("v2") | + | |
- | public class ViaticosControllerV2 { | + | |
- | + | ||
- | /** The log. */ | + | |
- | private Logger log = LoggerFactory.getLogger(ViaticosControllerV2.class); | + | |
- | + | ||
- | /** The ticketsimpl. */ | + | |
- | @Autowired | + | |
- | TicketsImp ticketsimpl; | + | |
- | + | ||
- | /** The guardar. */ | + | |
- | @Autowired | + | |
- | ProcesoGuardar guardar; | + | |
- | + | ||
- | /** The aprobacion. */ | + | |
- | @Autowired | + | |
- | ProcesoViaticoAprobacion aprobacion; | + | |
- | + | ||
- | /** The solicitud repo. */ | + | |
- | @Autowired | + | |
- | ViaticoSolicitudImpl solicitudRepo; | + | |
- | + | ||
- | /** The savelega. */ | + | |
- | @Autowired | + | |
- | GuardarLegalizacion savelega; | + | |
- | + | ||
- | /** The repo compromiso. */ | + | |
- | @Autowired | + | |
- | IMestroCompromisoRepo repoCompromiso; | + | |
- | + | ||
- | /** The solicitud adelanto. */ | + | |
- | @Autowired | + | |
- | AdelantoSolicitud solicitudAdelanto; | + | |
- | + | ||
- | /** The viaticos service. */ | + | |
- | @Autowired | + | |
- | ViaticosServiceV2 viaticosServiceV2; | + | |
- | + | ||
- | /** | + | |
- | * Aprobar legalizacion. | + | |
- | * | + | |
- | * @param aprobacionLegalizacionDto the aprobacion legalizacion dto | + | |
- | * @return the response entity | + | |
- | * @throws ViaticosException the viaticos exception | + | |
- | */ | + | |
- | @Operation(summary = "Aprobar Legalización.", description = "Aprueba la Legalización de un Viático.") | + | |
- | @ApiResponse(responseCode = "201", content = { @Content(schema = @Schema(implementation = GenericResponseDto.class)) }) | + | |
- | @ApiResponse(responseCode = "500", content = { @Content(schema = @Schema(implementation = ViaticosException.class)) }) | + | |
- | @Parameter(name = "aprobacionLegalizacionDto", description = "Componente que contiene los parametros necesarios para el proceso de legalización.", in = ParameterIn.QUERY, required = true) | + | |
- | @PostMapping("/aprobarlegalizacion") | + | |
- | public ResponseEntity<GenericResponseDto> aprobarLegalizacion(@RequestBody AprobacionLegalizacionDto aprobacionLegalizacionDto) throws ViaticosException { | + | |
- | var result = viaticosServiceV2.aprobarLegalizacion(aprobacionLegalizacionDto); | + | |
- | if(result.getCode() == -1L) { | + | |
- | log.info("approve legalization executed with errors."); | + | |
- | return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR); | + | |
- | } | + | |
- | log.info("approve legalization executed correctly."); | + | |
- | return new ResponseEntity<>(result, HttpStatus.CREATED); | + | |
- | } | + | |
- | + | ||
- | /** | + | |
- | * Guardar viatico. | + | |
- | * | + | |
- | * @param datos the datos | + | |
- | * @return the response entity | + | |
- | * @throws ViaticosException the viaticos exception | + | |
- | */ | + | |
- | @Operation(summary = "Aprobar Solicitud.", description = "Aprueba la solicitud de un Viático.") | + | |
- | @ApiResponse(responseCode = "201", content = { @Content(schema = @Schema(implementation = GenericResponseDto.class)) }) | + | |
- | @ApiResponse(responseCode = "500", content = { @Content(schema = @Schema(implementation = ViaticosException.class)) }) | + | |
- | @Parameter(name = "datos", description = "Componente que contiene los parametros necesarios para el proceso de solicitud.", in = ParameterIn.QUERY, required = true) | + | |
- | @PostMapping("/generaraprobacion") | + | |
- | public ResponseEntity<GenericResponseDto> guardarViatico(@RequestBody DatosGenerarAprobacion datos) throws ViaticosException { | + | |
- | var result = viaticosServiceV2.aprobarSolicitud(datos); | + | |
- | if(result.getCode() == -1L) return new ResponseEntity<>(result, HttpStatus.SERVICE_UNAVAILABLE); | + | |
- | return new ResponseEntity<>(result, HttpStatus.CREATED); | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | ==== Documentación de Dtos expuestos en los EndPoints (Controller) ==== | + | |
- | Cada servicio expuesto debe describir la definición del request (consumo) a continuación se comparte ejemplo de documentación de un dto. | + | |
- | + | ||
- | <code java> | + | |
- | /** | + | |
- | * Copyright © 2023 ADA Corporation. All rights reserved. | + | |
- | * | + | |
- | * This component is protected by copyright. | + | |
- | * | + | |
- | * Use of this component is subject to the terms of the license agreement. | + | |
- | */ | + | |
- | package com.ada.viatico.proceso.sfcomercial.dto; | + | |
- | + | ||
- | import java.io.Serializable; | + | |
- | + | ||
- | import io.swagger.v3.oas.annotations.media.Schema; | + | |
- | import lombok.Data; | + | |
- | + | ||
- | /** | + | |
- | * Instantiates a new afectacion presupuestal dto. | + | |
- | */ | + | |
- | @Schema(description = "Dto que contiene la configuración de afectación presupuestal de un rubro de una disponibilidad..") | + | |
- | @Data | + | |
- | public class AfectacionPresupuestalDto implements Serializable { | + | |
- | + | ||
- | /** The Constant serialVersionUID. */ | + | |
- | private static final long serialVersionUID = -7167570415608375973L; | + | |
- | + | ||
- | /** The cod disponibilidad. */ | + | |
- | @Schema(description = "Código de la disponibilidad.") | + | |
- | Long codDisponibilidad; | + | |
- | + | ||
- | /** The codigo rubro. */ | + | |
- | @Schema(description = "Código del rubro.") | + | |
- | Long codigoRubro; | + | |
- | + | ||
- | /** The valor. */ | + | |
- | @Schema(description = "Valor de afectación del rubro prespuestal.") | + | |
- | Long valor; | + | |
- | + | ||
- | /** The cod C costos. */ | + | |
- | @Schema(description = "Código del centro de costos.") | + | |
- | Long codCCostos; | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | + | ||
- | ===== Notas ===== | + | |
- | * Toda actualización debe estar soportada por un ticket o control de cambios. | + | |
- | * Toda actualización debe estar validada exitosamente por el rol de QA. | + | |
- | * El versionamiento aplica para la liberación de release. | + | |
- | * Cada versión debe ser almacenada en su repositorio especifico. | + | |
[[ada:howto:sicoferp:factory:goodsoftwaredevelopmentpractices|←Volver atras]] | [[ada:howto:sicoferp:factory:goodsoftwaredevelopmentpractices|←Volver atras]] | ||