La perspectiva de un desarrollador iOS sobre codebases infladas, trampas del legacy y la prueba de los 20 MB.
He sido desarrollador iOS desde 2008, enfocado en streaming y podcasting. Empecé a trabajar con HLS en iPhone en 2009, y a lo largo de los años he construido, mantenido, depurado y, a veces, heredado aplicaciones de streaming de todos los tamaños.
Y he desarrollado una opinión bastante firme sobre lo que realmente significa "clean code" en esta industria. No la versión de los libros de texto. La real. La que descubres cuando abres el proyecto Xcode de otra persona y tu primer instinto es cerrar la tapa.
He revisado cientos de ofertas de trabajo y contratos freelance para desarrolladores iOS a lo largo de los años. Casi todas orgullosamente enumeran "clean code" y "buenas prácticas" como requisitos. Principios SOLID. MVVM. Cobertura de tests unitarios. Design patterns. Todo el manual.
Y luego abres el proyecto.
La brecha entre las ofertas de empleo y la realidad
Esto es lo que realmente he encontrado al abrir esos proyectos de streaming supuestamente "clean code": archivos XIB de 2014 que todavía controlan flujos de interfaz críticos. Bridging headers de Objective-C más largos que algunas aplicaciones enteras. Tres capas de red diferentes coexistiendo porque nadie se atrevió a eliminar la antigua. Un patrón Coordinator apilado sobre un patrón Navigator apilado sobre un patrón Router, porque cada nuevo lead arquitecto añadió su favorito sin eliminar el anterior.
El "clean code" en la mayoría de las ofertas de empleo para apps de streaming es aspiracional, no descriptivo. Describe lo que el responsable de contratación desearía que la codebase pareciera, no lo que te espera en el repositorio.
Aquí está la verificación de la realidad que nadie escribe en la descripción del puesto: ¿ese patrón Coordinator del que están tan orgullosos? Coordina otros tres patrones de navegación que nadie tuvo tiempo de eliminar. ¿Esos principios SOLID? Se disolvieron en el momento en que el CEO quiso una funcionalidad entregada para el viernes. ¿Los tests unitarios? Cubren la pantalla de inicio de sesión y absolutamente nada más.
No estoy tirando piedras desde una casa de cristal. Yo mismo he contribuido a este tipo de entropía. Todos los desarrolladores móviles lo han hecho. Pero creo que la industria del streaming tiene una versión particularmente grave de este problema, y tengo una teoría sobre cómo detectarlo instantáneamente.
La regla de los 20 MB: una prueba de fuego para la calidad del código de apps de streaming
Piensa en lo que realmente hace una aplicación de streaming. Obtiene un manifiesto (una playlist HLS, un MPD DASH), envía segmentos a un reproductor de vídeo, renderiza algo de interfaz alrededor (canales, EPG, ajustes), y eso es básicamente todo. El contenido se transmite por streaming. Lo pesado — el vídeo, el audio, las miniaturas — vive en un CDN, no en el binario de tu app.
Así que aquí está mi teoría: si una aplicación de streaming pesa más de 20 MB en la App Store, cada megabyte por encima de ese umbral es probablemente código "sucio". No malicioso, no necesariamente con bugs, pero código que no debería estar ahí. Código que refleja malas decisiones arquitectónicas acumuladas durante años de compromisos.
Veamos algunos números. Mi última aplicación, My TV Channel, pesa 9 MB en la App Store. Nueve. Funciona nativamente en iPhone, iPad, Mac (Apple Silicon), Apple TV y Apple Vision Pro. Soporta 9 idiomas. Los usuarios pueden crear y ver canales de TV lineales 24/7, con programación VOD-to-Live, notificaciones push, descargas offline, picture-in-picture, multiview, búsqueda avanzada con entrada de voz, modo solo audio, canales privados y un sistema completo de gestión de contenido para creadores. Eso es más funcionalidades que muchas aplicaciones de streaming convencionales.
Ve a comprobarlo tú mismo en la App Store. La mayoría de las grandes aplicaciones de streaming pesan entre 80 y 200 MB. Algunas van incluso más alto. Eso es de 10 a 20 veces más pesado que My TV Channel. Estas aplicaciones tienen el mismo trabajo principal: transmitir vídeo. Sin embargo, llevan el equivalente de docenas de My TV Channels en sus binarios.
Claro, los grandes servicios de streaming necesitan algo de ese peso extra. Soportan amplias matrices de dispositivos, funcionalidades de accesibilidad, DRM offline, a veces juegos. Pero ¿más de 150 MB para lo que es, en esencia, una aplicación de streaming de vídeo? Esa diferencia cuenta una historia de código legacy, compromisos cross-platform y años de deuda arquitectónica acumulada.
De dónde viene la hinchazón
Después de años de consultoría en proyectos de streaming, sigo viendo los mismos patrones que inflan los binarios de las aplicaciones mucho más allá de lo necesario.
Tecnologías de interfaz legacy. Los archivos XIB y Storyboard son un clásico. Fueron la forma estándar de construir interfaces iOS durante años, e incorporan grafos de objetos serializados, restricciones de layout, a veces incluso referencias a imágenes directamente en el binario. Muchas aplicaciones de streaming aún llevan XIBs de su lanzamiento inicial, parcheados y mantenidos pero nunca migrados a SwiftUI o incluso a patrones UIKit modernos. Cada archivo XIB es deuda técnica congelada con una extensión .xib.
La sobrecarga de frameworks cross-platform. React Native, Flutter, Kotlin Multiplatform: cada uno añade su propio runtime, su propia capa de puente, sus propias abstracciones. Para una aplicación de streaming donde el componente más crítico en rendimiento es de todos modos el reproductor de vídeo nativo, el compromiso entre conveniencia cross-platform y tamaño del binario rara vez tiene sentido. Terminas distribuyendo un motor JavaScript (o una VM Dart, o un runtime KMP) solo para renderizar una cuadrícula de miniaturas que SwiftUI o UIKit manejan en una fracción del tamaño.
Es la misma lógica que llevó a los equipos de backend por la madriguera de los microservicios. ¿Recuerdas cuando cada startup necesitaba Kubernetes y 47 contenedores Docker para servir una API REST que una sola aplicación Django podía manejar? El equivalente móvil es distribuir tres runtimes de frameworks para mostrar una lista de vídeos. El patrón es idéntico: adoptar complejidad arquitectónica que resuelve problemas que no tienes, a un coste que pagarás durante años.
Acumulación de design patterns. Este es más sutil. No se traduce directamente en megabytes, pero multiplica archivos, clases y abstracciones, lo que aumenta las dependencias en tiempo de compilación, los metadatos embebidos y el tamaño del binario. He visto proyectos de streaming con un ViewModel, Coordinator, UseCase, Repository, DataSource, Mapper y Entity separados para cada pantalla. Son siete archivos donde dos serían suficientes. Multiplica por 30 pantallas y tienes 210 archivos en lugar de 60, cada uno añadiendo sus metadatos de clase al binario.
VIPER es el peor infractor aquí. Fue diseñado para un mundo UIKit donde los view controllers masivos eran un problema real. Portar VIPER a un proyecto SwiftUI es como llevar un camión de bomberos para apagar una vela. Las vistas SwiftUI ya son ligeras, sin estado y componibles por diseño. Envolver cada una en una pila VIPER (View, Interactor, Presenter, Entity, Router) añade cinco archivos y tres capas de indirección para lo que SwiftUI maneja con una struct y una clase @Observable. He visto equipos hacerlo de todos modos porque "esa es nuestra arquitectura", y el resultado es siempre el mismo: más boilerplate que lógica de negocio.
Assets embebidos que deberían ser remotos. Fuentes embebidas, animaciones embebidas (los archivos JSON de Lottie son notorios por esto), imágenes placeholder embebidas en resolución 3x para cada clase de dispositivo. En una aplicación de streaming, casi cada recurso visual podría cargarse bajo demanda desde un CDN. Embeberlos es el camino fácil, y se nota en la balanza.
Código muerto de funcionalidades abandonadas. Los frameworks de A/B testing dejan atrás condicionales que nunca se limpian. Los experimentos de funcionalidades fallidos permanecen en la codebase porque "quizás lo reactivemos". El SDK de analytics del proveedor anterior al anterior aún se compila porque alguien olvidó eliminarlo del Podfile.
El verdadero coste: cuando el código sucio decide tu roadmap
La hinchazón del binario es una cosa. Pero la peor consecuencia de una codebase sucia no es el tamaño de la descarga. Es lo que ocurre en cada reunión de planificación de sprint.
Conoces la frase. Un product manager pide una nueva funcionalidad — digamos picture-in-picture, modo solo audio, un simple rediseño de la interfaz — y las primeras palabras que salen de la boca del lead developer son: "Va a ser complicado."
Esa frase es la prueba de olfato del código sucio. No "llevará tiempo", no "necesitamos pensar en los casos límite", sino complicado. La codebase se ha vuelto tan enmarañada que nadie puede predecir qué se romperá en otros tres módulos al tocar uno.
En las codebases limpias, añadir funcionalidades se siente natural. Encuentras la capa correcta, la extiendes, la entregas. En las codebases sucias, se siente como una cirugía a un paciente sin historial médico. Cada cambio requiere primero una expedición arqueológica. Cada estimación viene con un multiplicador de riesgo que nadie se atreve a poner por escrito. Cada sprint lleva la advertencia tácita: "suponiendo que nada inesperado se rompa."
He visto proyectos de streaming donde implementar una simple funcionalidad de "continuar viendo" requería modificaciones a través de siete capas arquitectónicas, dos bridging headers y un bus de eventos custom que nadie entendía completamente. Eso no es ingeniería de software. Eso es una negociación de rehenes con tu propia codebase.
Y la parte que los equipos de producto rara vez comprenden: el código sucio no solo te ralentiza hoy. Decide lo que puedes construir mañana. Las funcionalidades no se rechazan porque sean malas ideas. Se rechazan porque "nuestra arquitectura no lo soporta", que es una forma educada de decir "nos acorralamos hace cinco años y nadie quiere admitirlo". La codebase se convierte en el product manager de facto, vetando funcionalidades solo por fricción.
Verificación de la realidad: tus competidores con codebases más limpias entregarán la misma funcionalidad en dos semanas mientras tu equipo aún está mapeando grafos de dependencias en Miro. No son más listos. Simplemente no tienen que luchar contra su propio código antes de luchar contra el mercado.
La prueba LLM: una nueva forma de medir la limpieza del código
Más allá del tamaño del binario, hay otra prueba de fuego que he estado usando últimamente, una que no existía hace cinco años.
Apunta un LLM a tu codebase. Si no puede entenderla, tu próxima contratación tampoco podrá.
Herramientas como Claude Code, OpenAI Codex y otros entornos de desarrollo asistidos por IA son muy buenos navegando codebases bien estructuradas. Pueden leer un proyecto Swift limpio, entender su arquitectura, identificar bugs, sugerir correcciones e implementar nuevas funcionalidades con una guía mínima.
Pero intenta apuntarlos a una aplicación de streaming legacy con 15 años de patrones acumulados, Objective-C y Swift mezclados, tres enfoques diferentes de inyección de dependencias y un sistema de build sostenido por scripts shell custom. El LLM se perderá. Alucinará relaciones entre clases que no existen. Sugerirá correcciones que rompen otras partes del sistema. Producirá más crashes que un desarrollador senior.
Eso no es una limitación de la IA. Es un espejo. Si un LLM entrenado con millones de repositorios no puede parsear la arquitectura de tu proyecto, significa que tus abstracciones tienen fugas, que tu nomenclatura es inconsistente, que tus dependencias están enredadas y que la estructura de tu proyecto no sigue ninguna convención reconocible.
Piénsalo como la prueba definitiva de "incorporación de un nuevo desarrollador". Un LLM aborda tu codebase con cero conocimiento institucional, solo reconocimiento de patrones y una comprensión amplia de las convenciones de programación. Si se pierde, tu próximo desarrollador junior se perderá también. Y tu próximo desarrollador senior pasará sus tres primeros meses desenredando los mismos nudos, excepto que te lo facturará.
Ahora dale la vuelta. Cuando tu codebase está limpia, algo interesante ocurre: herramientas como Claude Code no solo revisan tu código, sino que construyen contigo. He estado usando Claude Code en My TV Channel, y en una codebase limpia, puede implementar una nueva funcionalidad, escribir los tests y abrir una PR en el tiempo que antes me llevaba escribir un ticket de Jira. Lee la arquitectura, entiende las convenciones y produce código que encaja.
Aquí es donde las cosas se ponen interesantes para equipos que aún funcionan con sprints de dos semanas. Los sprints fueron diseñados para gestionar la incertidumbre humana: cuánto tiempo llevará esto, a qué nos comprometemos, cuándo hacemos la demo. Pero cuando un agente de IA puede navegar de forma fiable por tu codebase y entregar código funcional en horas, el sprint en sí se convierte en el cuello de botella. La reunión de planificación tarda más que la implementación. El ceremonial alrededor de estimar una funcionalidad cuesta más tiempo que construirla.
Por supuesto, esto solo funciona si la codebase está limpia. Apunta Claude Code a un monstruo infestado de VIPER, plagado de XIBs y multi-framework, y vuelve a alucinar. La herramienta no crea la velocidad. La arquitectura limpia la crea. Claude Code solo la revela. ¿Y las codebases sucias? Ya no solo ralentizan a tus desarrolladores humanos. También impiden que la IA te ayude, lo que va a ser un handicap cada vez más costoso.
Cómo se ve realmente el clean code en una app de streaming
Construí My TV Channel desde cero con estos principios en mente. Esto es lo que creo que significa clean code para aplicaciones de streaming específicamente.
Seguir las convenciones de la plataforma. Las Human Interface Guidelines de Apple existen por una razón. Los componentes estándar de UIKit o SwiftUI te dan accesibilidad, Dynamic Type, Dark Mode y localización gratis. Cada componente custom que construyes en su lugar es uno que tienes que mantener, probar en distintos dispositivos y depurar cuando Apple cambia algo en una nueva versión de iOS. My TV Channel funciona en cinco plataformas Apple con una codebase compartida porque se apoya en la interfaz nativa de la plataforma, no en alguna capa de abstracción ingeniosa.
Mantener el grafo de dependencias superficial. Una aplicación de streaming necesita un reproductor de vídeo, una capa de red y una capa de persistencia. Más allá de eso, cada dependencia de terceros debería ser cuestionada seriamente. No todas las dependencias son malas. Uso Kingfisher para el caché de imágenes en My TV Channel porque hace una cosa bien, está activamente mantenida y su huella binaria es razonable para el valor que aporta. Ese es el listón. ¿La dependencia resuelve un problema real mejor de lo que podrías con las APIs de la plataforma? ¿Está mantenida? ¿El tamaño es proporcional al valor? Si la respuesta a cualquiera de esas preguntas es no, no debería estar en tu Podfile. El problema no es usar librerías. El problema es usar cinco cuando una bastaría, o usar un framework reactivo de 5 MB cuando el async/await nativo de Swift y Combine hacen el mismo trabajo sin código externo.
Tratar el tamaño del binario como una funcionalidad, no como una métrica. Usuarios en redes móviles, usuarios con iPhones de 64 GB, usuarios en mercados donde el almacenamiento es limitado: todos se benefician de una app más ligera. Apple muestra el tamaño de la app de forma prominente en la App Store por una razón. Una descarga de 9 MB se instala en segundos con cualquier conexión. Una descarga de 150 MB necesita Wi-Fi para muchos usuarios y compite por espacio con fotos, mensajes y otras apps que les importan más.
Eliminar código. La parte más difícil de mantener una codebase limpia no es escribir código nuevo. Es eliminar el viejo. Feature flags que no se han activado en seis meses, eventos de analytics que nadie revisa en el dashboard, rutas de migración para formatos de datos de hace tres versiones. Todo eso tiene que irse. El mejor código es el código que no existe.
Un desafío a la industria
El streaming es una de las categorías más competitivas de la App Store. Sin embargo, la mayoría de las apps OTT distribuyen binarios de 5 a 20 veces más grandes de lo necesario, llevan años de código legacy y siguen patrones arquitectónicos que ni la IA puede desenredar.
La próxima vez que veas una oferta de empleo para un desarrollador iOS en una empresa de streaming que exija "prácticas de clean code" y "arquitectura moderna", hazles una sola pregunta: ¿cuánto pesa vuestro binario?
Si la respuesta supera los 50 MB para lo que es fundamentalmente un reproductor de vídeo con un catálogo de contenidos, sabes exactamente lo que te espera en ese repositorio.
Y si quieres ver cómo se ve una aplicación de streaming construida desde cero con principios de clean code, sin legacy, sin compromisos cross-platform, sin deuda acumulada: descarga My TV Channel y comprueba el tamaño tú mismo.
9 MB. Cinco plataformas. Todas las funcionalidades. Así se ve el clean code.