Uno de los desafios mas comunes en el desarrollo de aplicaciones con interfaz grafica es la aparicion de God Classes — clases que acumulan demasiada responsabilidad y se vuelven dificiles de mantener. Llama CPP Launcher no fue la excepcion, y este post documenta como se resolvio este problema.
El Problema: MainWindow como God Class
Despues de la migracion a Qt6, la clase MainWindow habia crecido hasta tener mas de 500 lineas de codigo, concentrando multiples responsabilidades:
- Construccion de comandos — Generar la linea de comandos para el servidor llama.cpp
- Escaneo de carpetas — Buscar modelos GGUF en directorios configurados
- Monitorizacion de GPU — Leer y mostrar estadisticas de uso de GPU
- Gestion de servidores — Iniciar, detener y monitorear procesos del servidor
- Interfaz de usuario — Gestionar la interfaz grafica y sus eventos
Esta concentracion de responsabilidades violaba el Principio de Responsabilidad Unica (SRP) de SOLID, haciendo que el codigo fuera:
- Dificil de mantener — Cambios en una funcionalidad podrian afectar otras
- Dificil de probar — No era posible probar componentes de forma aislada
- Dificil de reutilizar — Los componentes estaban acoplados al MainWindow
- Dificil de entender — El codigo era denso y con multiples niveles de abstraccion
La Solucion: Descomposicion en Componentes
Se decidio refactorizar MainWindow en cuatro componentes especializados, cada uno con una responsabilidad clara y bien definida:
1. CommandBuilder
Responsable exclusivamente de construir la linea de comandos para el servidor llama.cpp:
- Entrada — Configuracion del modelo, parametros del servidor, opciones de GPU
- Salida — String con el comando completo listo para ejecutar
- Logica — Validacion de parametros, construccion de flags, manejo de valores por defecto
2. FolderScanner
Responsable de escanear directorios en busca de modelos GGUF:
- Entrada — Lista de directorios a escanear
- Salida — Lista de modelos encontrados con sus metadatos
- Logica — Recursividad, filtrado por extension, ordenamiento
3. GpuMonitor
Responsable de monitorizar el rendimiento de la GPU:
- Entrada — Datos raw de nvidia-smi o herramientas similares
- Salida — Estadisticas procesadas (uso de memoria, carga, temperatura)
- Logica — Parsing de datos, calculo de promedios, deteccion de anomalias
4. ServerController
Responsable de gestionar los procesos del servidor:
- Entrada — Comandos de inicio/parada, configuracion del servidor
- Salida — Estado del proceso, logs, metricas de rendimiento
- Logica — Gestion de QProcess, monitoreo de estado, manejo de errores
Implementacion con SDD
La refactorizacion se realizo utilizando Spec-Driven Development (SDD), una metodologia que garantiza que los cambios esten bien documentados y sean verificables:
Fase 1: Propuesta
Se creo una propuesta detallada que definia:
- Objetivo — Reducir la complejidad de MainWindow de 500+ lineas a menos de 100
- Alcance — Cuatro componentes nuevos, MainWindow como orquestador
- Restricciones — Mantener funcionalidad existente, no cambiar la interfaz externa
Fase 2: Especificaciones
Se escribieron especificaciones detalladas para cada componente:
- Arquitectura de componentes — Estructura, dependencias, interfaces
- Ciclo de vida de procesos — Como se gestionan los servidores
- Estructura de archivos — Organizacion del codigo fuente
Fase 3: Diseno
Se definio la arquitectura tecnica:
- Diagramas de dependencias — Como se relacionan los componentes
- Flujo de datos — Como se mueve la informacion entre componentes
- Patrones de diseño — Observer, Strategy, Factory segun corresponda
Fase 4: Tareas
Se descompuso el trabajo en tareas atomicas:
- Crear CommandBuilder — Implementar la construccion de comandos
- Crear FolderScanner — Implementar el escaneo de directorios
- Crear GpuMonitor — Implementar la monitorizacion de GPU
- Crear ServerController — Implementar la gestion de procesos
- Refactorizar MainWindow — Convertir en orquestador
Resultados de la Refactorizacion
Reduccion de Complejidad
- MainWindow original — 500+ lineas con multiples responsabilidades
- MainWindow refactorizado — ~100 lineas como orquestador
- Componentes nuevos — 50-150 lineas cada uno con responsabilidad unica
Mejoras en Mantenibilidad
- Separacion de intereses — Cada componente tiene una razon para cambiar
- Codigo mas legible — Funciones mas cortas y con nombres descriptivos
- Depuracion mas facil — Los problemas se aislaron a componentes especificos
Mejoras en Testabilidad
- Componentes independientes — Cada uno puede probarse en aislamiento
- Dependencias inyectadas — Facilita el uso de mocks en tests
- Interfaces claras — Los contratos entre componentes son explicitos
Mejoras en Reutilizacion
- Componentes modulares — Pueden usarse en otros contextos
- Acoplamiento reducido — Los componentes no conocen detalles internos de otros
- Extension facilitada — Agregar nuevas funcionalidades es mas sencillo
Lecciones Aprendidas
Cuando Refactorizar
- Clase con mas de 200-300 lineas — Considerar descomposicion
- Multiples razones para cambiar — Indica violacion del SRP
- Dificultad para probar — Señal de acoplamiento excesivo
- Nombre generico — Si la clase tiene un nombre vasio, probablemente hace demasiado
Como Refactorizar
- Identificar responsabilidades — Listar todo lo que hace la clase
- Agrupar por cohesion — Funcionalidades relacionadas van juntas
- Crear interfaces — Definir contratos claros entre componentes
- Mover gradualmente — No intentar refactorizar todo a la vez
- Probar continuamente — Verificar que la funcionalidad se mantiene
Conclus
La refactorizacion del MainWindow de Llama CPP Launcher demuestra como aplicar principios SOLID en un proyecto real. El resultado es un codigo mas limpio, mantenible y extensible, que facilita el desarrollo futuro y reduce el riesgo de errores.
Este proceso tambien demuestra el valor de utilizar metodologias estructuradas como SDD para guiar refactorizaciones complejas, asegurando que cada paso este bien documentado y verificado.