Apuntes DAM
Tema 4 de 7ED · 1º DAM/DAW

Entornos de Desarrollo

Optimización y Documentación

Refactorización, analizadores de código (Checkstyle, PMD, SpotBugs), control de versiones con Git, documentación con Javadoc e integración continua.

60 min lecturaIntermedioRevisado el

Optimización y documentación del código

Escribir código que funcione es solo el primer paso. Un buen desarrollador va más allá: se asegura de que ese código sea legible, mantenible, eficiente y bien documentado. En proyectos reales, el código que escribes hoy lo leerá alguien dentro de seis meses — y ese alguien probablemente serás tú mismo. Por eso, el dominio de las técnicas de optimización y documentación es tan importante como conocer la sintaxis del lenguaje.

En esta unidad abordaremos cuatro grandes pilares que todo desarrollador profesional debe dominar. El primero es la refactorización, que consiste en mejorar la estructura interna del código sin cambiar su comportamiento externo. El segundo son los analizadores de código, herramientas automáticas que detectan problemas de calidad antes de que lleguen a producción. El tercero es el control de versiones integrado en el entorno de desarrollo, que registra la historia completa de cada cambio y permite colaborar en equipo sin caos. Y el cuarto es la documentación técnica mediante Javadoc, que genera automáticamente manuales en HTML a partir de comentarios en el código fuente.

Estos cuatro pilares no son independientes: se complementan y refuerzan mutuamente. Un código bien refactorizado es más fácil de documentar. Un analizador de código detecta ausencia de documentación. El control de versiones registra cuándo se añadió cada mejora. Y un código bien documentado facilita la revisión entre compañeros. Trabajarlos juntos es lo que separa al desarrollador novato del profesional.

¿Por qué es importante esta unidad?

Según estudios del sector, los desarrolladores dedican entre el 60% y el 80% de su tiempo a leer código, no a escribirlo. Un código mal estructurado, sin documentar y sin historial de cambios multiplica ese tiempo y genera errores. Las técnicas de esta unidad son la respuesta a ese problema.


Refactorización

La refactorización (del inglés refactoring) es el proceso de reestructurar el código fuente existente sin alterar su comportamiento externo. El programa hace exactamente lo mismo antes y después de refactorizar — lo que cambia es cómo está escrito internamente: más claro, más simple, más organizado.

La metáfora más usada para explicar la refactorización es la de limpiar la cocina mientras cocinas. No cambias el plato que estás preparando, pero vas ordenando los utensilios, tirando lo que no sirve y dejando el espacio listo para el siguiente paso. Sin esa limpieza continua, la cocina acaba siendo un caos donde encontrar algo se convierte en una tarea agotadora. Con el código ocurre exactamente lo mismo.

Martin Fowler, quien popularizó el concepto en su libro de 1999, definió la refactorización como una secuencia de pequeñas transformaciones que preservan el comportamiento. Cada transformación individual es tan pequeña que parece insignificante, pero el resultado acumulado de muchas de ellas puede transformar completamente la calidad de un sistema.

Condición fundamental de la refactorización

Para refactorizar con seguridad, es imprescindible contar con pruebas automáticas que verifiquen que el comportamiento no ha cambiado. Sin pruebas, refactorizar es peligroso: puedes romper algo sin darte cuenta. Por eso, el BOE vincula refactorización y pruebas en el mismo resultado de aprendizaje (RA4).

¿Cuándo refactorizar?

La regla práctica más extendida es la regla de las tres veces: la primera vez que escribes algo, simplemente hazlo funcionar. La segunda vez que necesitas algo similar, duplica si es necesario. La tercera vez que necesitas algo similar, refactoriza. Esta regla evita tanto la sobre-ingeniería prematura como el caos por duplicación excesiva.

También es el momento de refactorizar cuando añades una funcionalidad nueva y el código existente lo dificulta, cuando revisas código antiguo y te cuesta entenderlo, o cuando corriges un error y el propio proceso de localización fue complicado por la falta de claridad del código.

Malos olores del código (code smells)

Los code smells son indicios de que el código necesita refactorización. No son errores en sí mismos — el código puede funcionar perfectamente y aun así tener malos olores. Son síntomas de problemas de diseño que, si se ignoran, acaban generando errores o haciendo el sistema imposible de mantener.

Mal olorDescripciónSeñal de alerta
Código duplicadoEl mismo fragmento aparece en varios lugaresSi cambias algo, tienes que cambiarlo en varios sitios
Método largoUn método hace demasiadas cosas a la vezMás de 20-30 líneas, múltiples niveles de indentación
Clase grandeUna clase acumula demasiadas responsabilidadesMás de 200-300 líneas, muchos atributos no relacionados
Lista larga de parámetrosUn método recibe 5, 6 o más parámetrosDifícil de llamar y de recordar el orden
Comentario explicativo excesivoComentarios que explican qué hace el código en vez de por quéEl código debería ser tan claro que no necesite comentarios
Nombre poco descriptivoVariables como a, tmp, x2, dato1Imposible entender qué contiene sin rastrear su uso
Número mágicoLiterales numéricos sin significado aparente (0.21, 1440, 86400...)No se entiende qué representa ese valor
Clase de datosUna clase solo tiene getters y setters, sin comportamientoPuede ser síntoma de mala distribución de responsabilidades

Patrones de refactorización más comunes

Cada patrón de refactorización es una transformación concreta con nombre propio, condiciones de aplicación y pasos definidos. Conocer estos patrones te permite identificar el problema y aplicar la solución correcta sistemáticamente. Los IDEs modernos como IntelliJ IDEA y Eclipse tienen la mayoría automatizados — seleccionas el código, pulsas un atajo de teclado y el IDE realiza la transformación de forma segura.

Antes de refactorizar — Método largo con código duplicado
1// ✗ ANTES: Un método enorme que hace demasiadas cosas
2public double procesarPedido(String nombreCliente, String producto,
3                             int cantidad, double precioUnitario,
4                             boolean esClienteVIP, boolean hayStock) {
5    // Validar datos
6    if (nombreCliente == null || nombreCliente.isEmpty()) {
7        throw new IllegalArgumentException("Nombre requerido");
8    }
9    if (cantidad <= 0) {
10        throw new IllegalArgumentException("Cantidad debe ser positiva");
11    }
12    if (!hayStock) {
13        throw new IllegalStateException("Sin stock");
14    }
15
16    // Calcular precio
17    double subtotal = cantidad * precioUnitario;
18    double descuento = 0;
19    if (esClienteVIP) {
20        descuento = subtotal * 0.15;  // ← número mágico
21    }
22    double precioConDescuento = subtotal - descuento;
23    double iva = precioConDescuento * 0.21;  // ← número mágico
24    double total = precioConDescuento + iva;
25
26    // Registrar pedido (código repetido en otros métodos)
27    System.out.println("Pedido registrado: " + nombreCliente);
28    System.out.println("Producto: " + producto + " x" + cantidad);
29    System.out.println("Total: " + total + " €");
30
31    return total;
32}
Después de refactorizar — Extracción de métodos y constantes
1// ✓ DESPUÉS: Código organizado, legible y reutilizable
2
3public class GestorPedidos {
4
5    private static final double DESCUENTO_VIP    = 0.15;
6    private static final double PORCENTAJE_IVA   = 0.21;
7
8    // Método principal: ahora se lee como una narración
9    public double procesarPedido(Pedido pedido) {
10        validarPedido(pedido);
11        double total = calcularTotal(pedido);
12        registrarPedido(pedido, total);
13        return total;
14    }
15
16    // Cada método tiene una sola responsabilidad
17    private void validarPedido(Pedido pedido) {
18        if (pedido.getNombreCliente() == null || pedido.getNombreCliente().isEmpty()) {
19            throw new IllegalArgumentException("Nombre del cliente requerido");
20        }
21        if (pedido.getCantidad() <= 0) {
22            throw new IllegalArgumentException("La cantidad debe ser positiva");
23        }
24        if (!pedido.hayStock()) {
25            throw new IllegalStateException("Producto sin stock disponible");
26        }
27    }
28
29    private double calcularTotal(Pedido pedido) {
30        double subtotal       = pedido.getCantidad() * pedido.getPrecioUnitario();
31        double descuento      = calcularDescuento(subtotal, pedido.isClienteVIP());
32        double baseImponible  = subtotal - descuento;
33        double iva            = baseImponible * PORCENTAJE_IVA;
34        return baseImponible + iva;
35    }
36
37    private double calcularDescuento(double subtotal, boolean esVIP) {
38        return esVIP ? subtotal * DESCUENTO_VIP : 0.0;
39    }
40
41    private void registrarPedido(Pedido pedido, double total) {
42        System.out.println("Pedido registrado: " + pedido.getNombreCliente());
43        System.out.println("Producto: " + pedido.getProducto()
44                         + " x" + pedido.getCantidad());
45        System.out.printf("Total: %.2f €%n", total);
46    }
47}

Refactorización en IntelliJ IDEA

IntelliJ IDEA automatiza los patrones más comunes. Selecciona un fragmento de código y pulsa:
Refactor → Extract → Method (Ctrl+Alt+M) para extraer un método.
Refactor → Rename (Shift+F6) para renombrar con seguridad en todo el proyecto.
Refactor → Introduce Constant para eliminar números mágicos. El IDE actualiza automáticamente todas las referencias.

Refactorización y pruebas: el ciclo seguro

La única forma de refactorizar con confianza es tener una suite de pruebas que se ejecute rápidamente. El ciclo es: ejecutar las pruebas (todas verdes), aplicar una pequeña refactorización, volver a ejecutar las pruebas. Si alguna falla, has roto algo y puedes deshacer inmediatamente. Si todas siguen verdes, el comportamiento se ha preservado.

Paso 1
Ejecutar pruebas
Todas deben estar en verde antes de empezar
⟳
Paso 2
Refactorizar
Una transformación pequeña y concreta
Paso 3
Ejecutar pruebas
Verificar que siguen en verde
↑
Paso 4
Confirmar cambio
Commit en el control de versiones


Analizadores de código

Un analizador de código estático es una herramienta que examina el código fuente sin ejecutarlo, buscando problemas de calidad, posibles errores, incumplimientos de estilo y vulnerabilidades de seguridad. Es como tener un revisor experto que lee tu código de principio a fin antes de que salga a producción, y lo hace en cuestión de segundos.

El análisis estático se diferencia del análisis dinámico (las pruebas) en que no requiere ejecutar el programa. Esto tiene una ventaja importante: detecta problemas en cualquier camino del código, incluso en rutas que las pruebas no han ejercitado. Ambos enfoques son complementarios, no excluyentes.

En el ecosistema Java existen varios analizadores ampliamente usados en la industria. Checkstyle verifica que el código cumple una guía de estilo — sangría, longitud de líneas, nombres de variables, formato de comentarios. PMD detecta problemas más profundos: variables sin usar, bloques catch vacíos, código duplicado, clases demasiado complejas. SpotBugs (sucesor de FindBugs) busca patrones que casi siempre son errores reales, como comparaciones incorrectas de Strings o recursos no cerrados.

SonarQube: el estándar industrial

SonarQube es la plataforma de análisis de código más usada en empresas. Integra múltiples motores de análisis, clasifica los problemas por severidad (bloqueador, crítico, mayor, menor, informativo), calcula métricas de deuda técnica (cuántas horas llevaría arreglar todos los problemas) y muestra el historial de calidad del proyecto a lo largo del tiempo. Muchas empresas no aceptan fusionar código que no supere los umbrales de calidad de SonarQube.

Checkstyle: verificación de estilo

Checkstyle aplica reglas de formato definidas en un fichero XML. La configuración más usada es la de Google Java Style Guide. Integrado en el IDE, subraya en tiempo real las violaciones mientras escribes, de la misma forma que un corrector ortográfico. Esto elimina las discusiones de estilo en las revisiones de código: si compila y pasa Checkstyle, el formato es correcto.

checkstyle.xml — Configuración básica
1<?xml version="1.0"?>
2<!DOCTYPE module PUBLIC
3    "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4    "https://checkstyle.org/dtds/configuration_1_3.dtd">
5
6<module name="Checker">
7    <!-- Longitud máxima de línea: 120 caracteres -->
8    <module name="LineLength">
9        <property name="max" value="120"/>
10    </module>
11
12    <module name="TreeWalker">
13        <!-- Nombres de clases: UpperCamelCase -->
14        <module name="TypeName"/>
15
16        <!-- Nombres de métodos y variables: lowerCamelCase -->
17        <module name="MethodName"/>
18        <module name="LocalVariableName"/>
19
20        <!-- Constantes: UPPER_SNAKE_CASE -->
21        <module name="ConstantName"/>
22
23        <!-- Llaves siempre en la misma línea -->
24        <module name="LeftCurly"/>
25        <module name="RightCurly"/>
26
27        <!-- Espacios alrededor de operadores -->
28        <module name="WhitespaceAround"/>
29
30        <!-- Javadoc obligatorio en métodos públicos -->
31        <module name="JavadocMethod">
32            <property name="scope" value="public"/>
33        </module>
34
35        <!-- Detectar importaciones sin usar -->
36        <module name="UnusedImports"/>
37    </module>
38</module>

PMD: detección de problemas lógicos

PMD va más allá del estilo y analiza la lógica del código en busca de construcciones problemáticas. Detecta, entre otras cosas, variables declaradas pero nunca usadas, bloques catch que silencian excepciones, bucles que podrían simplificarse, o código que nunca se ejecutará porque está después de un return.

Problemas que detecta PMD
1// ✗ PMD: "Unused variable 'resultado'" — variable declarada pero no usada
2public void calcular() {
3    int resultado = operacion();  // nunca se usa
4    System.out.println("Hecho");
5}
6
7// ✗ PMD: "Empty catch block" — excepción silenciada
8try {
9    archivo.close();
10} catch (IOException e) {
11    // nada — el error desaparece sin dejar rastro
12}
13
14// ✗ PMD: "Unnecessary return" — el return es redundante
15public void imprimir(String texto) {
16    System.out.println(texto);
17    return;  // innecesario al final de un método void
18}
19
20// ✗ PMD: "Avoid using 'System.out.println'" en código de producción
21// usar un Logger en su lugar
22
23// ✓ Versiones corregidas:
24public void calcular() {
25    int resultado = operacion();
26    System.out.println("Resultado: " + resultado);
27}
28
29try {
30    archivo.close();
31} catch (IOException e) {
32    logger.error("Error al cerrar el archivo: " + e.getMessage());
33}
34
35public void imprimir(String texto) {
36    System.out.println(texto);
37    // sin return innecesario
38}

SpotBugs: detección de errores reales

SpotBugs analiza el bytecode (el .class compilado, no el .java) en busca de patrones que casi siempre indican un error real. Sus avisos tienen una tasa de falsos positivos muy baja, lo que significa que cuando SpotBugs marca algo, merece la pena leerlo con atención.

Errores que detecta SpotBugs
1// ✗ SpotBugs: "Equals method compares class names" — comparación incorrecta de Strings
2String nombre = obtenerNombre();
3if (nombre == "Juan") {      // == compara referencias, no contenido
4    System.out.println("Es Juan");
5}
6// ✓ Correcto:
7if ("Juan".equals(nombre)) { // equals() compara contenido
8    System.out.println("Es Juan");
9}
10
11// ✗ SpotBugs: "Resource not closed" — conexión que puede no cerrarse si hay excepción
12Connection conn = dataSource.getConnection();
13Statement stmt = conn.createStatement();
14ResultSet rs = stmt.executeQuery(sql);
15// si la siguiente línea lanza excepción, conn nunca se cierra → fuga de recursos
16procesarResultados(rs);
17conn.close();
18
19// ✓ Correcto: try-with-resources garantiza el cierre
20try (Connection conn    = dataSource.getConnection();
21     Statement stmt     = conn.createStatement();
22     ResultSet rs       = stmt.executeQuery(sql)) {
23    procesarResultados(rs);
24} // conn, stmt y rs se cierran automáticamente al salir del bloque
25
26// ✗ SpotBugs: "Null pointer dereference" — posible NullPointerException
27String resultado = obtenerResultado(); // puede devolver null
28int longitud = resultado.length();     // NullPointerException si resultado es null
29
30// ✓ Correcto:
31String resultado = obtenerResultado();
32if (resultado != null) {
33    int longitud = resultado.length();
34}

Integración en el flujo de trabajo

La forma más efectiva de usar estos analizadores es integrarlos en el IDE (subrayado en tiempo real) y también en el proceso de build con Maven o Gradle. Así, si alguien olvida revisar el IDE, el build del servidor fallará antes de que el código llegue al repositorio compartido.


Control de versiones

Un sistema de control de versiones (VCS, Version Control System) registra el historial completo de cambios de un proyecto a lo largo del tiempo. Permite saber quién cambió qué, cuándo y por qué. Permite recuperar cualquier versión anterior. Permite que varios desarrolladores trabajen en paralelo sin pisarse. Y permite identificar exactamente qué cambio introdujo un error.

Git es con diferencia el sistema de control de versiones más usado en el mundo, presente en prácticamente todos los proyectos profesionales. Fue creado por Linus Torvalds en 2005 para gestionar el desarrollo del kernel de Linux. A diferencia de los sistemas centralizados anteriores (como SVN), Git es distribuido: cada desarrollador tiene una copia completa del repositorio en su máquina, lo que permite trabajar sin conexión y hace que el sistema sea extremadamente resiliente.

Conceptos fundamentales de Git

Entender Git requiere interiorizar unos pocos conceptos clave. El repositorio es el almacén que guarda todo el historial del proyecto — es la base de datos de todos los cambios. El directorio de trabajo es la carpeta donde editas los archivos. El área de preparación (staging area o índice) es una zona intermedia donde seleccionas exactamente qué cambios incluirás en el próximo commit. Y el commit es una instantánea permanente del estado del proyecto en un momento dado.

Flujo de trabajo básico en Git

Directorio de trabajo
Archivos que editas
Área de preparación
Cambios seleccionados
Repositorio local
Historial permanente
Repositorio remoto
GitHub / GitLab
git add → git commit → git push

Operaciones esenciales

Comandos Git fundamentales
1# ── Configuración inicial (una sola vez) ─────────────────────────────
2git config --global user.name  "Tu Nombre"
3git config --global user.email "tu@email.com"
4
5# ── Inicializar un repositorio nuevo ─────────────────────────────────
6git init                    # crea un repositorio en el directorio actual
7git clone URL               # descarga un repositorio existente
8
9# ── Ver el estado actual ──────────────────────────────────────────────
10git status                  # qué archivos han cambiado
11git diff                    # qué cambios exactos hay en los archivos modificados
12git log --oneline           # historial de commits en una línea cada uno
13git log --oneline --graph   # historial con visualización de ramas
14
15# ── Registrar cambios ─────────────────────────────────────────────────
16git add NombreArchivo.java  # preparar un archivo concreto
17git add .                   # preparar TODOS los cambios del directorio
18git commit -m "feat: añadir validación de edad en formulario de registro"
19
20# ── Sincronizar con el repositorio remoto ────────────────────────────
21git push origin main        # subir commits locales al servidor
22git pull origin main        # descargar y fusionar cambios del servidor
23git fetch origin            # descargar cambios del servidor sin fusionar
24
25# ── Deshacer cambios ─────────────────────────────────────────────────
26git restore NombreArchivo.java  # descartar cambios del directorio de trabajo
27git restore --staged Nombre.java # quitar del área de preparación (sin perder cambios)
28git revert HEAD             # crear un nuevo commit que deshace el último

Ramas: trabajar en paralelo sin conflictos

Una rama (branch) en Git es una línea de desarrollo independiente. La rama principal se llama convencionalmente main (o master en proyectos más antiguos). Cuando vas a desarrollar una nueva funcionalidad o corregir un error, creas una rama nueva a partir de la principal, haces todos tus cambios en esa rama y, cuando está listo y revisado, lo fusionas de vuelta en main.

Esta forma de trabajar tiene varias ventajas cruciales. La rama main siempre está en un estado estable y funcional. Puedes empezar a trabajar en la funcionalidad B antes de haber terminado la A. Si una funcionalidad resulta ser un error de diseño, puedes descartarla sin afectar a nada más. Y las revisiones de código (pull requests) son posibles precisamente porque el trabajo se hace en ramas independientes.

Trabajo con ramas
1# Crear una nueva rama y situarse en ella
2git switch -c feature/login-con-google
3# equivalente al clásico: git checkout -b feature/login-con-google
4
5# Ver todas las ramas
6git branch          # locales
7git branch -a       # locales y remotas
8
9# Cambiar entre ramas
10git switch main
11git switch feature/login-con-google
12
13# Fusionar una rama en la actual (estando en main)
14git switch main
15git merge feature/login-con-google
16
17# Subir la rama al repositorio remoto
18git push origin feature/login-con-google
19
20# Eliminar la rama local una vez fusionada
21git branch -d feature/login-con-google

Buenas prácticas en los mensajes de commit

El mensaje de un commit es tan importante como el código que contiene. Un buen mensaje de commit explica por qué se hizo el cambio, no solo qué se cambió (eso ya lo muestra el diff). Los mensajes que dicen "arreglos", "cambios varios" o "wip" son inútiles para quien tenga que entender la historia del proyecto meses después.

La convención más extendida en la industria es Conventional Commits, que establece un formato estándar que facilita la generación automática de changelogs y la lectura del historial:

Formato Conventional Commits
1tipo(ámbito): descripción breve en imperativo
2
3[cuerpo opcional: explicación más detallada del cambio y la razón]
4
5[pie opcional: referencias a issues, breaking changes]
6
7─────────────────────────────────────────────────────────────────────
8Tipos más comunes:
9  feat:     nueva funcionalidad
10  fix:      corrección de error
11  docs:     cambios solo en documentación
12  style:    formato (sin cambio de lógica)
13  refactor: refactorización (sin nueva función ni corrección)
14  test:     añadir o corregir pruebas
15  chore:    tareas de mantenimiento (build, dependencias)
16
17─────────────────────────────────────────────────────────────────────
18Ejemplos de buenos mensajes:
19
20✓ feat(auth): añadir inicio de sesión con Google OAuth2
21✓ fix(pedidos): corregir cálculo de IVA en pedidos con descuento
22✓ refactor(cliente): extraer validación de email a método propio
23✓ test(inventario): añadir pruebas para el caso de stock agotado
24
25✗ arreglos
26✗ cambios
27✗ actualizaciones del código
28✗ cosas varias
29✗ wip

Repositorios remotos: GitHub y GitLab

Un repositorio remoto es una copia del repositorio alojada en un servidor accesible por todos los miembros del equipo. GitHub y GitLab son las plataformas más populares. Además de alojar el repositorio, ofrecen herramientas de colaboración: pull requests (propuestas de fusión con revisión de código), gestión de issues (seguimiento de tareas y errores), wikis de documentación y pipelines de integración continua.

Conectar un repositorio local con GitHub
1# 1. Crear el repositorio en GitHub (desde la web, sin inicializarlo)
2
3# 2. En tu terminal, desde el directorio del proyecto:
4git init
5git add .
6git commit -m "feat: commit inicial del proyecto"
7
8# 3. Conectar con el repositorio remoto
9git remote add origin https://github.com/usuario/mi-proyecto.git
10
11# 4. Subir el código
12git push -u origin main
13# El flag -u establece origin/main como la rama remota por defecto
14# En los push siguientes basta con: git push
15
16# 5. Verificar la conexión
17git remote -v

Control de versiones integrado en el IDE

IntelliJ IDEA y Eclipse integran Git de forma nativa. Puedes ver el historial de cambios de cada archivo, comparar versiones, resolver conflictos de fusión, hacer commits y push — todo sin salir del entorno de desarrollo. El panel Git (Alt+9 en IntelliJ) muestra el historial completo con gráfico de ramas.


Documentación técnica con Javadoc

Javadoc es el sistema estándar de documentación de Java. Permite escribir documentación directamente en el código fuente, en forma de comentarios con una sintaxis especial, y generar automáticamente a partir de esos comentarios un sitio web en HTML con el manual completo de la API. Es el mismo sistema que usa Oracle para documentar las clases de la propia Java Development Kit (JDK).

La gran ventaja de Javadoc sobre la documentación externa (un Word, un PDF, una wiki) es que vive junto al código. Cuando modificas un método, modificas su Javadoc al mismo tiempo — están en el mismo archivo. Esto reduce drásticamente el problema de la documentación desactualizada, que es uno de los males más comunes en el software empresarial.

Los comentarios Javadoc van justo antes del elemento que documentan (clase, interfaz, método o campo) y empiezan con /** en lugar del /* de los comentarios normales. Dentro del comentario se usan etiquetas especiales que Javadoc interpreta para estructurar la documentación generada.

Etiquetas Javadoc más importantes

EtiquetaUsoEjemplo
@param nombreDocumenta un parámetro del método@param cantidad El número de unidades a comprar
@returnDocumenta el valor de retorno@return El precio total con IVA incluido
@throws / @exceptionDocumenta las excepciones que puede lanzar@throws IllegalArgumentException si cantidad ≤ 0
@authorAutor de la clase o método@author María García
@versionVersión del componente@version 2.1.0
@sinceDesde qué versión existe este elemento@since 1.0
@seeReferencia a otro elemento relacionado@see Pedido#calcularTotal()
@deprecatedMarca un elemento como obsoleto@deprecated Usar calcularTotalConIVA() en su lugar
{@code texto}Código en línea dentro del comentarioUsa {@code new ArrayList<>()} para crear la lista
{@link Clase#metodo}Enlace a otro elemento en la documentaciónVer {@link GestorPedidos#procesar(Pedido)}

Javadoc bien escrito: ejemplo completo

GestorInventario.java — Documentación Javadoc completa
1/**
2 * Gestiona el inventario de productos de la tienda.
3 *
4 * <p>Esta clase proporciona operaciones de consulta, actualización y
5 * auditoría del stock disponible. Todas las operaciones son atómicas
6 * y seguras para uso en entornos concurrentes.</p>
7 *
8 * <p>Ejemplo de uso básico:</p>
9 * <pre>{@code
10 * GestorInventario gestor = new GestorInventario(dataSource);
11 * boolean disponible = gestor.hayStock("REF-001", 5);
12 * if (disponible) {
13 *     gestor.reducirStock("REF-001", 5);
14 * }
15 * }</pre>
16 *
17 * @author Rodrigo Cabello
18 * @version 1.3.0
19 * @since 1.0
20 * @see Producto
21 * @see PedidoService
22 */
23public class GestorInventario {
24
25    /**
26     * Comprueba si hay suficiente stock de un producto.
27     *
28     * <p>La comprobación incluye stock reservado por otros pedidos
29     * en proceso. Para conocer el stock bruto sin reservas,
30     * usar {@link #getStockTotal(String)}.</p>
31     *
32     * @param referencia La referencia única del producto a comprobar.
33     *                   No puede ser {@code null} ni estar vacía.
34     * @param cantidadNecesaria El número de unidades requeridas.
35     *                          Debe ser un valor positivo mayor que cero.
36     * @return {@code true} si hay stock disponible para la cantidad solicitada;
37     *         {@code false} en caso contrario.
38     * @throws IllegalArgumentException si {@code referencia} es {@code null}
39     *         o vacía, o si {@code cantidadNecesaria} es menor o igual a cero.
40     * @throws ProductoNotFoundException si la referencia no existe en el catálogo.
41     */
42    public boolean hayStock(String referencia, int cantidadNecesaria) {
43        if (referencia == null || referencia.isBlank()) {
44            throw new IllegalArgumentException("La referencia no puede ser nula ni vacía");
45        }
46        if (cantidadNecesaria <= 0) {
47            throw new IllegalArgumentException(
48                "La cantidad necesaria debe ser positiva, se recibió: " + cantidadNecesaria);
49        }
50        int stockDisponible = repositorio.getStockDisponible(referencia);
51        return stockDisponible >= cantidadNecesaria;
52    }
53
54    /**
55     * Reduce el stock de un producto tras confirmar un pedido.
56     *
57     * @param referencia      La referencia del producto.
58     * @param cantidadVendida El número de unidades vendidas.
59     * @throws IllegalArgumentException   si los parámetros no son válidos.
60     * @throws StockInsuficienteException si el stock disponible es menor
61     *                                   que la cantidad a reducir.
62     * @deprecated Usar {@link #reducirStockConAuditoria(String, int, String)}
63     *             para registrar el motivo de la reducción. Este método
64     *             se eliminará en la versión 2.0.
65     */
66    @Deprecated(since = "1.2", forRemoval = true)
67    public void reducirStock(String referencia, int cantidadVendida) {
68        reducirStockConAuditoria(referencia, cantidadVendida, "Sin motivo registrado");
69    }
70}

Generar la documentación HTML

Una vez escrito el Javadoc en el código, generar el sitio HTML es inmediato. En IntelliJ IDEA: Tools → Generate JavaDoc. En la línea de comandos con Maven:mvn javadoc:javadoc. El resultado es una web navegable con índice de clases, buscador y toda la documentación estructurada — equivalente a la documentación oficial de Java.

Generar Javadoc desde la línea de comandos
1# Con la herramienta javadoc directamente
2javadoc -d docs/api -sourcepath src/main/java -subpackages com.miempresa
3
4# Con Maven (genera en target/site/apidocs/)
5mvn javadoc:javadoc
6
7# Con Maven, incluyendo los tests
8mvn javadoc:test-javadoc
9
10# Ver la documentación generada
11# Abrir target/site/apidocs/index.html en el navegador


Integración continua

La integración continua (CI, Continuous Integration) es una práctica de desarrollo en la que los miembros del equipo integran su trabajo en el repositorio compartido con frecuencia — idealmente varias veces al día. Cada integración se verifica automáticamente mediante un proceso de build y pruebas, de forma que los errores de integración se detectan lo antes posible.

El problema que resuelve la CI es el llamado infierno de la integración: cuando varios desarrolladores trabajan durante días o semanas en paralelo y luego intentan unir su trabajo, los conflictos pueden ser enormes y muy difíciles de resolver. La CI elimina ese problema integrando de forma continua, cuando los cambios son todavía pequeños y manejables.

Un pipeline de CI es la secuencia automatizada de pasos que se ejecuta ante cada push al repositorio. Un pipeline típico en un proyecto Java incluye: descargar dependencias, compilar el código, ejecutar los tests unitarios, ejecutar los analizadores de código estático, generar el Javadoc y empaquetar la aplicación. Si cualquiera de estos pasos falla, el pipeline se detiene y notifica al equipo inmediatamente.

Pipeline de CI típico

↑
1. Trigger— git push → el servidor detecta el cambio y lanza el pipeline
↓
2. Checkout— El servidor descarga el código del repositorio
⚙
3. Compilar— mvn compile — verifica que el código compila correctamente
✓
4. Pruebas unitarias— mvn test — ejecuta JUnit y verifica que todas pasan
🔍
5. Análisis estático— Checkstyle, PMD, SpotBugs — verifica calidad del código
📦
6. Empaquetar— mvn package — genera el .jar o .war listo para desplegar
📣
7. Notificar— Resultado al equipo por email, Slack o en el panel de GitHub

GitHub Actions: CI gratuita en GitHub

GitHub Actions es la plataforma de automatización integrada en GitHub. Permite definir pipelines de CI mediante ficheros YAML que se guardan en el repositorio. Es gratuita para repositorios públicos y tiene un generoso plan gratuito para privados. Su integración nativa con GitHub significa que los resultados del pipeline aparecen directamente en los pull requests, mostrando claramente si el código propuesto pasa o falla las verificaciones.

.github/workflows/ci.yml — Pipeline de CI para un proyecto Java
1name: CI — Integración Continua
2
3# El pipeline se ejecuta en cada push y en cada pull request a main
4on:
5  push:
6    branches: [ main, develop ]
7  pull_request:
8    branches: [ main ]
9
10jobs:
11  build-y-pruebas:
12    runs-on: ubuntu-latest     # servidor con Ubuntu donde se ejecutará
13
14    steps:
15      # 1. Descargar el código
16      - name: Checkout del repositorio
17        uses: actions/checkout@v4
18
19      # 2. Configurar Java 17
20      - name: Configurar Java 17
21        uses: actions/setup-java@v4
22        with:
23          java-version: '17'
24          distribution: 'temurin'
25
26      # 3. Caché de dependencias Maven (acelera ejecuciones posteriores)
27      - name: Caché de dependencias Maven
28        uses: actions/cache@v4
29        with:
30          path: ~/.m2
31          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
32
33      # 4. Compilar el proyecto
34      - name: Compilar
35        run: mvn compile --no-transfer-progress
36
37      # 5. Ejecutar pruebas unitarias
38      - name: Pruebas unitarias
39        run: mvn test --no-transfer-progress
40
41      # 6. Análisis de código estático
42      - name: Checkstyle
43        run: mvn checkstyle:check --no-transfer-progress
44
45      # 7. Empaquetar (solo en la rama main)
46      - name: Empaquetar
47        if: github.ref == 'refs/heads/main'
48        run: mvn package -DskipTests --no-transfer-progress

El pipeline como guardián de la calidad

Cuando el equipo configura el repositorio para que no se puedan fusionar pull requests que fallen el pipeline, la integración continua se convierte en un guardián automático. Ningún código llega a main sin pasar la compilación, las pruebas y el análisis de calidad. Esto es la base de un proceso de desarrollo profesional.


Resumen de la unidad

Refactorización

  • ✓Mejorar código sin cambiar su comportamiento
  • ✓Detectar y eliminar code smells
  • ✓Aplicar patrones: extraer método, renombrar, eliminar duplicados
  • ✓Siempre con pruebas automáticas como red de seguridad

Analizadores de código

  • ✓Checkstyle: verificación de estilo y formato
  • ✓PMD: detección de problemas lógicos
  • ✓SpotBugs: bugs reales en el bytecode
  • ✓SonarQube: análisis integral en proyectos empresariales

Control de versiones con Git

  • ✓Historial completo de todos los cambios
  • ✓Ramas para trabajar en paralelo sin conflictos
  • ✓Repositorios remotos: GitHub, GitLab
  • ✓Mensajes de commit claros y estandarizados

Documentación e integración continua

  • ✓Javadoc: documentación técnica junto al código
  • ✓Etiquetas @param, @return, @throws, @author
  • ✓CI: verificación automática ante cada cambio
  • ✓GitHub Actions: pipelines de CI gratuitos en GitHub

¿Has encontrado un error, algo desactualizado o una explicación que no se entiende? Avísanos y lo corregimos.

¿Has terminado este tema?

Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

Guardar mi progreso