← Calidad, observabilidad y documentación

Sesión 46 · Semana 23

Documentación y revisión de calidad

Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado verificar archivos y efectos externos. En Servidor continúas la implementación del mismo producto.

Se explica

25 minutos · explicación y demostración

Ya has revisado pruebas y diagnóstico. Hoy comprobarás que la documentación representa la API real. Una revisión de código contrasta implementación y criterios de calidad; necesita peticiones reproducibles y observaciones concretas, no solo preferencias de estilo.

La documentación viva frente a los documentos muertos

Un documento Word o PDF con la descripción de una API queda obsoleto en el mismo instante en que un programador cambia el nombre de un atributo en un DTO.

La solución de la industria es la documentación viva y autogenerada a partir del código:

  • Con la librería springdoc-openapi-starter-webmvc-ui, Spring Boot inspecciona los controladores, las anotaciones de validación (@NotNull, @Size) y las reglas de seguridad.
  • Genera la especificación OpenAPI 3 (JSON) y una interfaz web interactiva en /swagger-ui.html.

Para que esa documentación sea profesional y no una cáscara vacía, debemos enriquecerla con semántica, ejemplos realistas y respuestas de error documentadas:

@Operation(
    summary = "Crear un nuevo proyecto",
    description = "Registra un nuevo proyecto en el sistema. Requiere rol JEFE_PROYECTO o ADMINISTRADOR."
)
@ApiResponses({
    @ApiResponse(responseCode = "201", description = "Proyecto creado con éxito"),
    @ApiResponse(responseCode = "400", description = "Datos de entrada inválidos (RFC 7807)"),
    @ApiResponse(responseCode = "401", description = "No autenticado (falta token Bearer)"),
    @ApiResponse(responseCode = "403", description = "Permisos insuficientes para esta acción"),
    @ApiResponse(responseCode = "409", description = "Ya existe un proyecto con ese nombre")
})

Metodología de Revisión de Código por Pares (Peer Code Review)

El software no se evalúa únicamente por si compila y pasa los tests: se evalúa por su mantenibilidad a largo plazo.

Durante un Code Review, los desarrolladores revisan el código de sus compañeros siguiendo una rúbrica técnica estructurada en 5 dimensiones:

Las 5 dimensiones de la revisión técnica de código
  1. 1. Arquitectura: Separación estricta de capas sin fugas
  2. 2. Seguridad: Autenticación, RBAC y sanitización
  3. 3. Resiliencia: Timeouts en red y degradación
  4. 4. Rendimiento: JPA sin N+1 y transacciones acotadas
  5. 5. Calidad: Tests de casos límite y logs con MDC

Rúbrica de Auditoría Técnica de Código Backend

Utiliza esta lista de comprobación para auditar la aplicación:

Dimensión Pregunta de auditoría Señal de alarma (Red Flag) Criterio de excelencia
Arquitectura ¿Están los DTOs desacoplados de las entidades JPA? Un controlador recibe o devuelve una entidad @Entity de JPA directamente. DTOs inmutables (record) para peticiones y respuestas; mapeo en servicios.
Seguridad ¿Están protegidos todos los endpoints destructivos? Un DELETE o POST sin @PreAuthorize ni regla en SecurityFilterChain. Matriz RBAC verificada; contraseñas con BCrypt factor 12; sin secretos en código.
Integración ¿Tienen las llamadas externas timeouts y degradación? Llamadas con RestClient sin timeout o reenvío del JSON externo crudo. Timeouts de 2s/3s; Capa Anticorrupción; degradación elegante sin lanzar 500.
Rendimiento ¿Existen consultas N+1 en relaciones JPA? Relaciones @OneToMany con FetchType.EAGER o bucles for llamando a repositorios. Consultas con JOIN FETCH, paginación en listados y transacciones de solo lectura (readOnly = true).
Observabilidad ¿Están las trazas estructuradas con Correlation ID? Uso de System.out.println o logs que imprimen contraseñas / tokens. SLF4J en niveles adecuados, logback-spring.xml rotativo y X-Correlation-ID en MDC.

Se trabaja

140 minutos · implementación guiada sobre el proyecto propio

Paso 1 · Retomar el proyecto y preparar la comprobación

  1. Abre Swagger UI, la colección, el README y los DTO actuales. Selecciona una ruta pública y otra protegida.
  2. Anota qué necesita un compañero para arrancar, autenticarse y ejecutar ambas. Comprueba si esos pasos están escritos y si las variables tienen ejemplos sin secretos.
  3. Selecciona una operación con validación y conflicto para revisar también los casos que no terminan con éxito.

Paso 2 · Enriquecimiento de OpenAPI y Swagger UI

Actualiza el único OpenApiConfig de la sesión 31. Añade el esquema HTTP bearer y referencia exactamente el mismo nombre en SecurityRequirement. En los controladores añade las anotaciones sobre los métodos existentes, conservando sus argumentos y cuerpo. El ejemplo de subida abreviado ilustra esas anotaciones: no se copia ... como firma Java. Arranca y verifica primero /v3/api-docs, después el botón Authorize y finalmente una petición protegida.

package com.ejemplo.gestor.config;

import io.swagger.v3.oas.models.Components;
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.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        final String securitySchemeName = "bearerAuth";

        return new OpenAPI()
            .info(new Info()
                .title("API REST del Gestor de Proyectos e Incidencias")
                .description("Servicio backend modular para gestión de proyectos, tareas, meteorología y adjuntos.")
                .version("1.0.0")
                .contact(new Contact().name("Equipo de Desarrollo Backend").email("soporte@empresa.com")))
            .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
            .components(new Components()
                .addSecuritySchemes(securitySchemeName, new SecurityScheme()
                    .name(securitySchemeName)
                    .type(SecurityScheme.Type.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")));
    }
}
    @Operation(summary = "Subir un archivo adjunto a una tarea",
               description = "Sube un archivo (PDF, PNG, JPG) de hasta 5 MB vinculado a una tarea específica.")
    @ApiResponses({
        @ApiResponse(responseCode = "201", description = "Fichero subido y registrado con éxito"),
        @ApiResponse(responseCode = "400", description = "Tipo de archivo no permitido o fichero vacío"),
        @ApiResponse(responseCode = "401", description = "No autenticado"),
        @ApiResponse(responseCode = "403", description = "Permisos insuficientes"),
        @ApiResponse(responseCode = "404", description = "Tarea no encontrada")
    })
    @PostMapping(value = "/tareas/{id}/adjuntos", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @PreAuthorize("hasAnyRole('DESARROLLADOR', 'JEFE_PROYECTO', 'ADMINISTRADOR')")
    // Coloca estas anotaciones sobre tu método subirAdjunto existente.
    // Conserva su firma, sus parámetros multipart y todo su cuerpo.

Paso 3 · Inspección de Swagger UI y sesión de Code Review

  1. Abre Swagger UI en tu navegador: http://localhost:8080/swagger-ui.html
  2. Verifica la documentación interactiva:
    • Pulsa el botón Authorize en la esquina superior derecha e introduce tu token JWT.
    • Lanza peticiones directamente desde la interfaz y comprueba que las respuestas documentadas coinciden exactamente con los códigos y cuerpos JSON devueltos por tu backend.
  3. Simulación de revisión por pares:
    • Intercambia el repositorio de código con un compañero de clase (o revisa una rama secundaria de tu propio proyecto).
    • Aplica la Rúbrica de Auditoría Técnica de las 5 dimensiones.
    • Identifica y redacta los 3 hallazgos principales con sugerencias constructivas de mejora.

Cómo se revisa el código de otra persona sin que la revisión se estropee

Una revisión existe para mejorar el código, no para puntuar a quien lo escribió. Tres reglas que la mantienen útil:

Se comenta el código, no a la persona. «Este método hace tres cosas» se puede discutir; «no has separado responsabilidades» se defiende. La primera abre una conversación técnica, la segunda la cierra.

Cada hallazgo lleva una razón y una consecuencia. «Cambia esto» no es revisable. «Este listado carga las tareas dentro del bucle: con 200 proyectos son 201 consultas» sí lo es, porque quien lo lee puede comprobarlo y decidir.

Se separa lo que bloquea de lo que es opinión. Marca cada comentario como bloqueante (un fallo de seguridad, una pérdida de datos), recomendado o sugerencia. Sin esa etiqueta, quien recibe la revisión no sabe qué es urgente y acaba ignorándola entera o rehaciéndolo todo.

Paso 4 · Si algo no sale como dice el guion

Síntoma Causa casi segura Qué mirar
Swagger sale vacío tras añadir seguridad Las rutas de documentación no están permitidas /v3/api-docs/** y /swagger-ui/** con permitAll() en tu SecurityFilterChain
Try it out devuelve 401 en todo Falta declarar el esquema de seguridad Añade el SecurityScheme bearer/JWT a OpenApiConfig para que aparezca el botón Authorize
La revisión del compañero no encuentra nada El proyecto no se puede arrancar Si no arranca en la máquina del revisor, ese ya es el primer hallazgo, y de los graves
Corriges un hallazgo y se rompen tres tests Estabas cambiando comportamiento, no forma Es información valiosa: significa que el comportamiento estaba probado. Decide cuál de los dos es correcto
SonarLint devuelve cientos de avisos Estás mirando todas las severidades Filtra por Blocker y Critical: el resto es ruido para lo que toca hoy

Paso 5 · Auditar y ser auditado

Utiliza un clon y una configuración de desarrollo separados. Para cada dimensión de la rúbrica registra la comprobación realizada y su resultado; si no encuentras un defecto, indica la evidencia favorable en lugar de inventar un hallazgo. Cuando haya fallo, incluye petición, esperado, observado y ubicación. La persona autora corrige el caso, repite la comprobación y deja ambos resultados enlazados en el registro de sesión.

  1. Recibe: intercambia repositorios con otro equipo. Clona el suyo desde cero y arráncalo siguiendo solo su documentación, sin preguntarles nada. Cronometra cuánto tardas.
  2. Audita: recorre las cinco dimensiones de la rúbrica y anota al menos un hallazgo en cada una, con la etiqueta de gravedad y la razón. Un informe con quince comentarios de estilo y ninguno de seguridad es un informe que no ha hecho su trabajo.
  3. Busca específicamente estas cinco cosas, que son las que más se repiten a estas alturas del curso:
    • Un endpoint de escritura sin @PreAuthorize ni regla en el filterChain.
    • Un listado que carga una relación dentro del bucle (el N+1 de la UD5).
    • Un catch (Exception e) vacío o que solo hace printStackTrace().
    • Un DTO de respuesta que publica un campo que no debería salir (una contraseña, un campo interno).
    • Un endpoint sin ningún test.
  4. Entrega: pásales el informe ordenado por gravedad, no por el orden en que fuiste encontrando las cosas.
  5. Recibe el tuyo y respóndelo entero, punto por punto. Por cada hallazgo, una de tres respuestas: lo corrijo, no lo corrijo y este es el motivo, o lo anoto como deuda técnica para la UD12. Ninguna de las tres es peor que las otras; lo que no vale es dejar un hallazgo sin respuesta.
  6. Corrige los bloqueantes y vuelve a ejecutar ./mvnw verify para comprobar que ninguna corrección rompió nada.
  7. Guarda el informe recibido: el apartado de deuda técnica de la memoria de la UD12 sale casi entero de aquí.
Cómo saber que lo has terminado
Has arrancado el proyecto de otro equipo sin ayuda; tu informe tiene hallazgos en las cinco dimensiones, etiquetados por gravedad y con su razón; has respondido a todos los que te hicieron; los bloqueantes están corregidos y la suite sigue verde.

Paso 6 · Comprobar y registrar el resultado del proyecto

  1. Ejecuta desde la documentación los casos válidos y rechazados y contrasta estados, campos, ejemplos y requisitos de acceso.
  2. Intercambia la revisión con otra persona, corrige discrepancias reproducibles y registra qué cambió en código o documentación y cómo se verificó.

Ampliación si has completado el trabajo

Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.

Reto · Detección estática de deuda técnica con SonarLint

Instala la extensión SonarLint en tu entorno de desarrollo (IntelliJ o VS Code):

  1. Analiza todos los archivos Java de tu proyecto.
  2. Revisa la pestaña de problemas de SonarLint y clasifica los hallazgos según su tipología:
    • Code Smells (mantenibilidad).
    • Bugs potenciales (valores que pueden ser nulos, recursos no cerrados).
    • Vulnerabilidades de seguridad.
  3. Resuelve las incidencias detectadas hasta dejar el código con cero advertencias de severidad alta.

Formato de entrega

Incluye esta explicación en el registro de la sesión dentro del repositorio de GitHub, junto al código y las comprobaciones. La entrega es el enlace al repositorio y al commit de la sesión.

Objetivo mínimoDocumentación OpenAPI 3 enriquecida con seguridad JWT visible en Swagger UI.
Si lo tienesRúbrica de 5 dimensiones aplicada y corrección de hallazgos de arquitectura y rendimiento.
RetoInspección estática de código con SonarLint y resolución completa de advertencias de deuda técnica.
Ver respuestas

1 · Garantiza que la documentación nunca quede desactualizada respecto a la implementación real, ya que se autogenera directamente a partir del código y sus anotaciones.

2 · Definiendo un SecurityScheme de tipo HTTP con esquema "bearer" y formato "JWT" en el bean de configuración de OpenAPI.

3 · Porque el formateo estético debe delegarse a herramientas automáticas (linters/formatters); el criterio humano del revisor debe concentrarse en la lógica de negocio, la seguridad, la concurrencia y la mantenibilidad.

4 · Ocurre cuando al consultar una lista de N entidades se dispara una consulta adicional individual por cada elemento para cargar sus relaciones perezosas (1 + N consultas SQL); se detecta revisando logs de SQL o buscando relaciones sin JOIN FETCH.

Cierre

15 minutos · resultado comprobable y explicación individual

Al terminar la sesión:

Otra persona puede arrancar la API, comprender sus permisos y reproducir un recorrido documentado.

Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.