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?
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
¿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 olor | Descripción | Señal de alerta |
|---|---|---|
| Código duplicado | El mismo fragmento aparece en varios lugares | Si cambias algo, tienes que cambiarlo en varios sitios |
| Método largo | Un método hace demasiadas cosas a la vez | Más de 20-30 líneas, múltiples niveles de indentación |
| Clase grande | Una clase acumula demasiadas responsabilidades | Más de 200-300 líneas, muchos atributos no relacionados |
| Lista larga de parámetros | Un método recibe 5, 6 o más parámetros | Difícil de llamar y de recordar el orden |
| Comentario explicativo excesivo | Comentarios 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 descriptivo | Variables como a, tmp, x2, dato1 | Imposible entender qué contiene sin rastrear su uso |
| Número mágico | Literales numéricos sin significado aparente (0.21, 1440, 86400...) | No se entiende qué representa ese valor |
| Clase de datos | Una clase solo tiene getters y setters, sin comportamiento | Puede 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.
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}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
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.
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
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.
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.
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.
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
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
Operaciones esenciales
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 últimoRamas: 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.
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-googleBuenas 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:
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✗ wipRepositorios 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.
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 -vControl de versiones integrado en el IDE
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
| Etiqueta | Uso | Ejemplo |
|---|---|---|
| @param nombre | Documenta un parámetro del método | @param cantidad El número de unidades a comprar |
| @return | Documenta el valor de retorno | @return El precio total con IVA incluido |
| @throws / @exception | Documenta las excepciones que puede lanzar | @throws IllegalArgumentException si cantidad ≤ 0 |
| @author | Autor de la clase o método | @author María García |
| @version | Versión del componente | @version 2.1.0 |
| @since | Desde qué versión existe este elemento | @since 1.0 |
| @see | Referencia a otro elemento relacionado | @see Pedido#calcularTotal() |
| @deprecated | Marca un elemento como obsoleto | @deprecated Usar calcularTotalConIVA() en su lugar |
| {@code texto} | Código en línea dentro del comentario | Usa {@code new ArrayList<>()} para crear la lista |
| {@link Clase#metodo} | Enlace a otro elemento en la documentación | Ver {@link GestorPedidos#procesar(Pedido)} |
Javadoc bien escrito: ejemplo completo
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.
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 navegadorIntegració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
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.
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-progressEl pipeline como guardián de la calidad
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