Las cuatro lecciones anteriores de este módulo dieron a BiblioTech un estilo consistente, un vocabulario de patrones de diseño, dependencias desacopladas mediante interfaces, y una batería de pruebas unitarias que verifican su comportamiento sin necesitar una base de datos real. Esta última lección del Módulo 8 cierra el círculo: enseña qué busca una buena revisión de código, cataloga los code smells (señales de un diseño que puede mejorar) más comunes, y refactoriza un método de Biblioteca que mezcla varias responsabilidades —apoyándose, precisamente, en las pruebas unitarias de la lección anterior como red de seguridad. Con esto, BiblioTech llega al Módulo 9 (Proyecto Final) con un código no solo funcional, sino revisado, probado y depurado según todo lo visto en este módulo.

Contenido

  1. Qué busca una buena revisión de código
  2. Checklist de revisión
  3. Code smells comunes
  4. Refactorización: extraer método y extraer clase
  5. Las pruebas unitarias como red de seguridad antes de refactorizar
  6. Ejemplo: refactorizando un método largo de Biblioteca
  7. Cierre del Módulo 8 y enlace con el Proyecto Final

  1. Qué busca una buena revisión de código

Una revisión de código (code review) es el proceso de que otra persona (o, en un proyecto individual, uno mismo con perspectiva fresca) lea un cambio antes de incorporarlo definitivamente al proyecto, buscando problemas que el autor original, inmerso en el detalle, puede no haber visto. Una buena revisión no busca imponer gustos personales de estilo —eso ya lo automatiza EditorConfig (Lección 1)—, sino verificar cuatro aspectos concretos:

Aspecto Pregunta que se hace el revisor
Legibilidad ¿Se entiende qué hace este código sin tener que ejecutarlo mentalmente paso a paso?
Pruebas ¿Existen pruebas unitarias (Lección 4) que cubran el comportamiento nuevo o modificado?
Adherencia a estándares ¿Sigue las convenciones de nomenclatura, documentación y nulabilidad ya establecidas (Lección 1)?
Diseño ¿Alguna clase o método ha crecido hasta mezclar responsabilidades que deberían separarse (Lección 2, responsabilidad única)?

Una revisión centrada en estos cuatro aspectos, y no en preferencias personales de estilo, es más rápida, más objetiva, y genera menos fricción entre quien escribe el código y quien lo revisa.

  1. Checklist de revisión

Una checklist concreta ayuda a que una revisión no dependa solo de la intuición del revisor. Una checklist razonable para un cambio en BiblioTech:

  • [ ] ¿El nombre de cada método y variable nuevos sigue las convenciones de la Lección 1 (PascalCase/camelCase, prefijos I/_, sufijo Async)?
  • [ ] ¿Hay algún método que haga más de una cosa claramente separable (validar, persistir, notificar...)?
  • [ ] ¿Las dependencias externas (persistencia, servicios) se reciben inyectadas, o se crean directamente con new dentro de la clase (Lección 3)?
  • [ ] ¿El cambio incluye pruebas unitarias nuevas, o modifica pruebas existentes que ya no reflejan el comportamiento nuevo (Lección 4)?
  • [ ] ¿La nulabilidad (?) refleja fielmente qué puede devolver null y qué no?
  • [ ] ¿Los comentarios explican decisiones no evidentes, o solo repiten lo que el código ya dice (Lección 1)?

Esta lista no es exhaustiva ni universal —cada equipo ajusta la suya con el tiempo—, pero sirve como punto de partida concreto, más útil que una revisión sin ningún criterio explícito.

  1. Code smells comunes

Un code smell (literalmente, "olor de código") es una señal superficial en el código que sugiere, sin ser en sí misma un error, que el diseño subyacente podría mejorarse. Cuatro code smells especialmente comunes:

Code smell Cómo se reconoce Por qué es un problema
Método largo Un método de decenas de líneas, con varios bloques claramente separables Difícil de leer de una sola vez, difícil de probar de forma aislada (cada bloque necesitaría su propia prueba)
Duplicación El mismo fragmento de lógica, copiado (quizás con pequeñas variaciones) en varios lugares Un cambio de esa lógica obliga a recordar actualizar cada copia; es fácil olvidar alguna
Clase con demasiadas responsabilidades Una clase que mezcla, por ejemplo, lógica de dominio, persistencia y presentación a la vez Viola el principio de responsabilidad única (Lección 1); cambiar una responsabilidad arriesga romper las demás sin relación
Parámetros excesivos Un método con seis o más parámetros, varios de ellos relacionados entre sí Sugiere que esos parámetros deberían agruparse en un objeto propio (por ejemplo, un record, Módulo 3)

Ninguno de estos smells es, por sí solo, un error que impida compilar o ejecutar el programa —de ahí que se llamen "olores" y no "errores"—; son señales que conviene investigar, no reglas absolutas que obliguen siempre a refactorizar de inmediato.

  1. Refactorización: extraer método y extraer clase

Refactorizar significa cambiar la estructura interna del código sin cambiar su comportamiento observable: el programa sigue haciendo exactamente lo mismo desde fuera, pero su código interno queda mejor organizado. Dos técnicas de refactorización cubren la mayoría de los smells del apartado anterior:

  • Extraer método: tomar un fragmento de un método largo y convertirlo en un método propio, con un nombre que explique qué hace ese fragmento. Resuelve directamente el smell de "método largo".
  • Extraer clase: cuando una clase mezcla varias responsabilidades (el smell de "demasiadas responsabilidades"), mover parte de sus miembros a una clase nueva dedicada a esa responsabilidad —exactamente lo que ya hizo la Lección 3 al extraer IRepositorioBiblioteca y sus implementaciones fuera de Biblioteca.
// Antes de "extraer metodo": un fragmento de logica de formato mezclado con otra logica
Console.WriteLine($"Prestamo #{prestamo.Libro.Titulo}: prestado el {prestamo.FechaPrestamo:dd/MM/yyyy}" +
    (prestamo.FechaDevolucion is not null ? $", devuelto el {prestamo.FechaDevolucion:dd/MM/yyyy}" : ", en curso"));

// Despues de "extraer metodo": el fragmento tiene ahora un nombre propio
string DescribirPrestamo(Prestamo prestamo)
{
    string estado = prestamo.FechaDevolucion is not null
        ? $"devuelto el {prestamo.FechaDevolucion:dd/MM/yyyy}"
        : "en curso";

    return $"Prestamo #{prestamo.Libro.Titulo}: prestado el {prestamo.FechaPrestamo:dd/MM/yyyy}, {estado}";
}

Console.WriteLine(DescribirPrestamo(prestamo));

DescribirPrestamo no cambia el mensaje mostrado por consola —el comportamiento observable es idéntico—, pero ahora tiene un nombre que explica su propósito, y puede reutilizarse en cualquier otro punto del programa que necesite el mismo formato, sin duplicar la lógica.

  1. Las pruebas unitarias como red de seguridad antes de refactorizar

La pregunta que debería surgir siempre antes de refactorizar es: ¿cómo sé que no he roto nada? Sin pruebas, la única respuesta es "ejecutando la aplicación entera a mano y confiando en no haber pasado nada por alto" —lento, y poco fiable. Con las pruebas unitarias de la Lección 4 ya escritas sobre Prestamo.RegistrarDevolucion() y Biblioteca.PrestarLibroAsync, refactorizar deja de ser un salto de fe:

flowchart LR
    A["Pruebas existentes en verde"] --> B["Refactorizar el codigo interno"]
    B --> C{"Pruebas siguen en verde?"}
    C -->|"Si"| D["El comportamiento no cambio: refactorizacion segura"]
    C -->|"No"| E["Algo cambio sin querer: revisar antes de continuar"]

Este es el papel exacto de las pruebas unitarias como red de seguridad: no impiden cometer un error al refactorizar, pero lo detectan de inmediato —en segundos, al volver a ejecutar el mismo conjunto de pruebas— en vez de descubrirlo mucho más tarde, quizás ya en producción. Refactorizar código sin ninguna prueba que lo respalde no es imposible, pero es bastante más arriesgado: cada cambio depende únicamente de la atención del programador en ese momento.

  1. Ejemplo: refactorizando un método largo de Biblioteca

Imagina que, con las prisas de ir añadiendo funcionalidad módulo a módulo, Biblioteca terminó con un método que mezcla validación, registro del préstamo y notificación, todo junto:

// Antes: un metodo largo que valida, registra y notifica, todo mezclado
public async Task GestionarPrestamoAsync(Libro libro, Socio socio)
{
    // Validacion
    if (libro is null)
    {
        throw new ArgumentNullException(nameof(libro));
    }
    if (socio is null)
    {
        throw new ArgumentNullException(nameof(socio));
    }
    if (!libro.Disponible)
    {
        throw new InvalidOperationException($"'{libro.Titulo}' no esta disponible para prestamo.");
    }

    // Espera simulada
    await Task.Delay(1000);

    // Registro
    libro.Prestar();
    Prestamo prestamo = new Prestamo(libro, socio);
    Prestamos.Add(prestamo);
    PrestamoRegistrado?.Invoke(prestamo);

    // Persistencia
    _repositorio.GuardarCatalogo(Catalogo);

    // Notificacion por consola
    Console.WriteLine($"'{libro.Titulo}' prestado correctamente a {socio.Nombre}.");
}

Este método funciona, y las pruebas de la lección anterior probablemente ya lo cubrirían con algún ajuste menor —pero mezcla, en un único bloque de código, cuatro responsabilidades distintas (validar, esperar, registrar+persistir, mostrar un mensaje), lo que lo hace difícil de leer de un vistazo y difícil de probar de forma aislada. Aplicando "extraer método" sobre cada bloque:

// Despues: cada responsabilidad tiene su propio metodo, con un nombre que la explica
public async Task GestionarPrestamoAsync(Libro libro, Socio socio)
{
    ValidarPrestamo(libro, socio);

    await Task.Delay(1000); // simulacion de una verificacion lenta, Modulo 4

    Prestamo prestamo = RegistrarYPersistirPrestamo(libro, socio);

    Console.WriteLine($"'{libro.Titulo}' prestado correctamente a {socio.Nombre}.");
}

private void ValidarPrestamo(Libro libro, Socio socio)
{
    ArgumentNullException.ThrowIfNull(libro);
    ArgumentNullException.ThrowIfNull(socio);

    if (!libro.Disponible)
    {
        throw new InvalidOperationException($"'{libro.Titulo}' no esta disponible para prestamo.");
    }
}

private Prestamo RegistrarYPersistirPrestamo(Libro libro, Socio socio)
{
    libro.Prestar();
    Prestamo prestamo = new Prestamo(libro, socio);

    Prestamos.Add(prestamo);
    PrestamoRegistrado?.Invoke(prestamo);
    _repositorio.GuardarCatalogo(Catalogo);

    return prestamo;
}

El comportamiento observable no ha cambiado en absoluto: las mismas excepciones se lanzan en los mismos casos, el mismo mensaje se muestra por consola, el mismo evento se dispara y el mismo repositorio se invoca. Lo que ha cambiado es que ahora GestionarPrestamoAsync se lee casi como una lista de pasos con nombre propio (ValidarPrestamo, RegistrarYPersistirPrestamo), y cada uno de esos pasos podría probarse por separado si hiciera falta más granularidad en el futuro. Al ejecutar de nuevo las pruebas de la Lección 4 (PrestarLibroAsync_ConLibroDisponible_GuardaElCatalogo y PrestarLibroAsync_ConLibroNoDisponible_LanzaExcepcionYNoGuarda, adaptadas al nuevo nombre del método) sobre esta versión refactorizada, deberían seguir pasando exactamente igual que antes: esa es la confirmación concreta de que la refactorización fue segura.

  1. Cierre del Módulo 8 y enlace con el Proyecto Final

Con esta lección se cierra el Módulo 8 (Mejores Prácticas y Patrones de Diseño). A lo largo de sus cinco lecciones, BiblioTech no ha ganado ni una sola funcionalidad de dominio nueva —el objetivo explícito de este módulo era otro: dar un paso atrás y consolidar todo lo construido en los módulos anteriores. El recorrido completo:

Lección Qué aportó
1. Estándares de codificación Nomenclatura consistente, EditorConfig, responsabilidad única, comentarios útiles, documentación XML, nulabilidad consistente
2. Patrones de diseño Vocabulario común (Singleton, Factory Method, Adapter, Decorator, Strategy, Observer) y reconocimiento de que PrestamoRegistrado ya era un Observer
3. Inyección de dependencias IRepositorioBiblioteca desacoplando Biblioteca de la persistencia concreta, y el contenedor de DI de ASP.NET Core en profundidad
4. Pruebas unitarias xUnit, Arrange-Act-Assert, y mocks de Moq sustituyendo IRepositorioBiblioteca en las pruebas
5. Revisión y refactorización Checklist de revisión, code smells, y refactorización de un método largo apoyada en las pruebas ya existentes

El Módulo 9 (Proyecto Final) retoma ahora todo lo construido en el curso —el dominio completo desde el Módulo 2, la persistencia del Módulo 5, las cinco interfaces del Módulo 7, y las prácticas de este Módulo 8— para construir la versión final y completa de BiblioTech: se definirán sus requisitos con precisión, se planificará su implementación, se construirá siguiendo los estándares y patrones ya aprendidos, se probará con la misma disciplina de pruebas unitarias vista aquí, y finalmente se desplegará. Nada de lo aprendido en este módulo queda aparte: es, precisamente, el conjunto de prácticas con las que se construirá esa versión final.

Errores Comunes y Consejos

  • Refactorizar sin ninguna prueba que respalde el cambio: sin pruebas previas, no hay forma objetiva de confirmar que el comportamiento no cambió; en ese caso, escribir primero al menos las pruebas más importantes sobre el comportamiento actual, y refactorizar después.
  • Cambiar comportamiento "de paso" mientras se refactoriza: refactorizar y corregir un error real son dos actividades distintas; mezclarlas en el mismo cambio dificulta saber, si algo falla después, si fue la refactorización o la corrección la causa.
  • Revisar código fijándose solo en el estilo: una revisión centrada únicamente en espacios, nombres o formato (automatizable con EditorConfig, Lección 1) desaprovecha la oportunidad de detectar problemas de diseño, ausencia de pruebas, o riesgos reales.
  • Extraer métodos hasta el extremo: dividir un método en fragmentos tan pequeños que hace falta saltar entre diez métodos distintos para entender un flujo simple también dificulta la lectura; el objetivo es claridad, no fragmentación por sí misma.
  • Consejo: si dudas si un método necesita refactorizarse, pregúntate si podrías explicarlo en una frase corta; si la respuesta requiere un "y también..." varias veces, probablemente mezcla más de una responsabilidad.

Ejercicios

  1. Identifica, en el método GestionarPrestamoAsync "antes" del apartado 6, qué code smell* del apartado 3 describe mejor su problema principal, y explica en una frase por qué.

  2. El siguiente método de un Socio mezcla registrar una sanción con mostrar un mensaje por consola. Refactorízalo con "extraer método", separando ambas responsabilidades:

    public void AplicarSancion(decimal importe)
    {
        SaldoPendiente += importe;
        Console.WriteLine($"Se ha aplicado una sancion de {importe:C} a {Nombre}. Saldo pendiente: {SaldoPendiente:C}");
    }
    

Soluciones

El code smell principal es método largo (con una responsabilidad mezclada adicional, "demasiadas responsabilidades" a nivel de método): un único método valida, espera, registra, persiste y notifica, todo en el mismo bloque de código, lo que dificulta leerlo y probarlo por partes.

public void AplicarSancion(decimal importe)
{
    RegistrarSancion(importe);
    NotificarSancion(importe);
}

private void RegistrarSancion(decimal importe)
{
    SaldoPendiente += importe;
}

private void NotificarSancion(decimal importe)
{
    Console.WriteLine($"Se ha aplicado una sancion de {importe:C} a {Nombre}. Saldo pendiente: {SaldoPendiente:C}");
}

Conclusión

En esta lección has visto qué busca una buena revisión de código (legibilidad, pruebas, adherencia a estándares, diseño), una checklist concreta para aplicarla, los code smells más comunes (método largo, duplicación, clases con demasiadas responsabilidades, parámetros excesivos), dos técnicas de refactorización (extraer método y extraer clase), y cómo las pruebas unitarias de la lección anterior actúan como red de seguridad al refactorizar un método largo de Biblioteca sin cambiar su comportamiento observable.

Con esto se cierra el Módulo 8 al completo. BiblioTech llega al Módulo 9 —el Proyecto Final del curso— con un dominio sólido, persistencia desacoplada, una batería de pruebas unitarias, y un código revisado según los estándares y patrones aprendidos en estas cinco lecciones: todo lo necesario para construir, planificar, probar y desplegar la versión completa y definitiva de BiblioTech.

Curso de Programación en C#

Módulo 1: Introducción a C#

Módulo 2: Estructuras de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Conceptos Avanzados de C#

Módulo 5: Trabajando con Datos

Módulo 6: Temas Avanzados

Módulo 7: Construcción de Aplicaciones

Módulo 8: Mejores Prácticas y Patrones de Diseño

Módulo 9: Proyecto Final

© Copyright 2026. Todos los derechos reservados