La comparación honesta, criterio a criterio
Las comparaciones entre MVP, MVVM y MVI suelen fallar por dos motivos: eligen criterios que favorecen a la conclusión ya decidida, y presentan como propiedades objetivas lo que en realidad depende del tamaño del equipo y de la app. Esta lección construye una tabla con criterios definidos de antemano y medibles —comprobabilidad, ceremonia, curva de aprendizaje, escalabilidad, depuración, coste de cambio y coste de un fallo— y a continuación enseña a leerla sin trampas, señalando dónde cada columna esconde un supuesto y por qué ningún patrón gana todas las filas. El resultado no es un ganador sino un mapa de en qué condiciones gana cada uno.
Toda comparación de arquitecturas es una trampa potencial, y la trampa casi nunca está en los datos sino en la elección de criterios. Si comparo MVVM y MVI midiendo líneas por pantalla, gana MVVM y el asunto queda cerrado. Si los comparo midiendo estados incoherentes representables, gana MVI por goleada y el asunto queda igual de cerrado. Ninguna de las dos comparaciones miente en sus números y las dos son inútiles, porque cada una eligió el eje que ya contenía su conclusión. La única forma de hacer esto con honestidad es fijar los criterios antes de mirar los patrones, incluir deliberadamente aquellos en los que sabes que tu favorito va a perder, y explicitar el supuesto que hay debajo de cada fila. Esta lección hace ese ejercicio con siete criterios, construye la tabla completa y después dedica el mismo esfuerzo a enseñar a desconfiar de ella, porque una tabla que se lee sin sus supuestos es una herramienta de confirmación, no de decisión.
- Fijar criterios de comparación definidos y medibles antes de evaluar cualquier patrón.
- Construir la tabla comparativa completa entre
MVP,MVVMdisperso,MVVMcon estado unificado yMVI. - Detectar el supuesto oculto que cada fila de la tabla arrastra consigo.
- Reconocer que el tamaño de la app y del equipo son variables de la comparación, no contexto externo a ella.
Los criterios, definidos antes de mirar
Un criterio sirve si se puede describir de forma que dos personas midan lo mismo. Estos son los siete que se usan aquí, con su definición operativa.
Comprobabilidad es cuánta infraestructura hace falta para verificar una transición: si basta con construir valores y llamar a una función pura, es alta; si exige dobles de prueba, planificadores y esperas, es baja. Ceremonia es el número de líneas de andamiaje antes de la primera regla de negocio. Curva de aprendizaje es el tiempo hasta que alguien nuevo en el equipo puede añadir una funcionalidad sin supervisión. Escalabilidad es cómo crece la dificultad de mantener la pantalla cuando el estado pasa de tres a diez piezas. Depuración es qué hace falta para reproducir un fallo reportado. Coste de cambio es el número de puntos de edición coordinados que exige añadir un dato. Y coste de un fallo es lo que se pierde cuando la pantalla queda incoherente, que en un buscador es una molestia y en un pago es dinero.
Conviene notar que estos siete criterios no son independientes entre sí, y que las correlaciones importan tanto como los valores. Ceremonia y curva de aprendizaje se mueven juntas casi siempre, porque más piezas que escribir suelen ser más conceptos que aprender. Comprobabilidad y depuración también, porque ambas dependen de si existe una fotografía del estado que se pueda construir y comparar. Presentar siete filas como si fueran siete mediciones independientes infla la aparente robustez del resultado: en realidad hay tres o cuatro ejes latentes, y el patrón que gana en uno tiende a ganar en sus correlacionados.
Falta además un criterio que casi ninguna comparación incluye y que en la práctica pesa mucho: la resistencia a la erosión. Es decir, cuánto se degrada la arquitectura cuando la aplican personas con prisa y sin contexto. Un patrón que se degrada con gracia —donde una desviación puntual estropea una pantalla— es preferible a uno que se degrada de golpe, donde una sola puerta trasera anula la garantía completa. Este criterio se retoma en la lección siguiente porque no se puede evaluar sin conocer al equipo.
Meter todo el MVVM en una sola columna es la distorsión más común de estas comparaciones. El MVVM disperso —varios observables mutables— y el MVVM con estado unificado —un solo StateFlow de un tipo compuesto, pero con varios métodos de entrada— se comportan de forma muy distinta en cinco de los siete criterios. Separarlos es lo que revela el hallazgo más útil de toda la tabla: buena parte de lo que se atribuye a MVI se consigue en realidad con el primer paso, unificar el estado, sin pagar el segundo, unificar la entrada.
La tabla
| Criterio | MVP | MVVM disperso | MVVM unificado | MVI |
|---|---|---|---|---|
| Comprobabilidad | Media: exige dobles de la vista | Baja: hay que instrumentar observables | Alta: se afirma sobre un valor | Muy alta: reduce es puro |
| Ceremonia | Alta: interfaz de vista enorme | Muy baja: una línea por dato | Baja: un tipo de estado | Alta: estado, intents, reductor y efectos |
| Curva de aprendizaje | Media: familiar pero verboso | Muy baja: es el estilo por defecto | Baja: solo añade una data class |
Alta: vocabulario y flujo nuevos |
| Escalabilidad del estado | Mala: el estado es implícito | Mala: crecimiento multiplicativo | Buena: un tipo que se puede apretar | Muy buena: además la entrada acota |
| Depuración | Difícil: no hay fotografía | Difícil: varias fuentes que mirar | Media: hay fotografía del estado | Fácil: fotografía más lista de intents |
| Coste de cambio | Tres puntos: contrato, vista, presentador | Uno: una línea | Dos: tipo y escritura | Cuatro: estado, intent, reductor, vista |
| Estados imposibles | Representables e invisibles | Representables: Cargando | Error a la vez |
Evitables con tipos suma | Evitables y además atómicos |
| Coste tolerable de un fallo | Alto | Alto | Medio | Bajo |
Lo primero que salta al leerla en horizontal es que ningún patrón gana todas las filas, y lo segundo es que los dos que ganan más filas son los dos extremos en ceremonia. MVVM disperso gana ceremonia, curva y coste de cambio; MVI gana comprobabilidad, escalabilidad, depuración y estados imposibles. La columna intermedia no gana casi ninguna fila y sin embargo es la que queda segunda en casi todas, que en una decisión real es una posición muy fuerte.
Las tres columnas centrales se distinguen mejor viendo su fachada pública una al lado de la otra, porque la tabla mide consecuencias de esas tres formas y no otra cosa.
// MVVM disperso: superficie proporcional al tamano de la pantalla
class A : ViewModel() {
val cargando = MutableStateFlow(false)
val items = MutableStateFlow<List<Item>>(emptyList())
fun onBuscar() {}
fun onReintentar() {}
}
// MVVM unificado: una salida, varias entradas
class B : ViewModel() {
val state: StateFlow<BusquedaState> = TODO()
fun onBuscar() {}
fun onReintentar() {}
}
// MVI: una salida y una entrada
class C : ViewModel() {
val state: StateFlow<BusquedaState> = TODO()
fun onIntent(intent: BusquedaIntent) {}
}
La lectura de esas tres fachadas explica por qué la columna intermedia queda segunda en casi todo. El salto de la primera a la segunda cierra la salida, que es de donde vienen la atomicidad, la comparabilidad y la posibilidad de apretar tipos: cuatro de las siete filas. El salto de la segunda a la tercera cierra la entrada, que aporta trazabilidad y políticas transversales: dos filas más, pero las más caras de conseguir y las menos universales en su utilidad.
Merece la pena detenerse en la columna de MVP, que suele descartarse como pieza de museo y que sin embargo enseña algo. Pierde en casi todo, pero no pierde por las razones que se le atribuyen: su problema no era la verbosidad, sino que su comprobabilidad era falsa. Verificaba conversaciones entre objetos en lugar de resultados, de modo que sus tests pasaban mientras la pantalla se rompía. Es un recordatorio útil de que un criterio mal definido puede dar una puntuación alta a una propiedad que no se tiene.
Leer la tabla sin trampas
Cada fila esconde un supuesto que, si no se explicita, convierte la tabla en propaganda. Conviene desenterrar los tres más importantes.
Un supuesto, en este contexto, es una condición que debe cumplirse para que la puntuación de esa fila signifique lo que parece significar. Cuando no se cumple, la celda no se vuelve falsa: se vuelve irrelevante, que es peor, porque sigue sumando en la comparación como si midiera algo.
Ceremonia supone que las líneas cuestan
La fila de ceremonia mide líneas escritas una vez. La de escalabilidad mide decisiones repetidas muchas veces. Compararlas como si fueran la misma moneda es el error más frecuente al usar la tabla.
Curva supone rotación
La curva de aprendizaje solo importa en proporción a cuánta gente nueva entra al código. En un equipo estable de tres personas es un coste único; en uno con rotación alta es recurrente.
El coste del fallo no es del patrón
La última fila no describe una propiedad del patrón sino del dominio. Es la fila que decide de verdad, y es la única que no se puede leer sin saber qué hace la pantalla.
Hay un cuarto supuesto, más sutil, en la fila de comprobabilidad. Decir que MVI puntúa muy alto ahí presupone que la parte difícil de la pantalla vive en el reductor, y no siempre es así. Si la complejidad real está en coordinar tres llamadas de red con reintentos y cancelaciones, el reductor será trivial y la lógica difícil seguirá siendo asíncrona, con lo que la comprobabilidad ganada es menor de lo que la fila sugiere. MVI hace fácil probar las transiciones de estado; no hace fácil probar la orquestación.
Hay además una trampa de agregación que conviene nombrar. Sumar las filas y declarar ganador a quien tenga más victorias supone implícitamente que todos los criterios pesan igual, y no pesan igual en ninguna app real. En un prototipo que se descarta en dos meses, ceremonia y curva pesan casi todo y el resto casi nada. En un flujo de pago mantenido cinco años por un equipo rotatorio, escalabilidad, depuración y coste del fallo pesan casi todo. La tabla no decide; la ponderación decide, y la ponderación la pone el contexto.
// La misma transicion, en las dos formulaciones extremas.
// MVVM disperso: la comprobacion exige observar y coordinar.
@Test fun mvvm_dispersa() {
vm.onBuscar()
// hay que esperar corrutinas y leer tres flujos por separado
}
// MVI: la comprobacion es una funcion pura sobre valores.
@Test fun mvi_pura() {
val siguiente = reduce(BusquedaState(), BusquedaIntent.Buscar)
assertEquals(Contenido.Cargando, siguiente.contenido)
}
flowchart LR A[MVVM disperso] -->|unificar el estado| B[MVVM unificado] B -->|unificar la entrada| C[MVI] A -.->|gana ceremonia y curva| A C -.->|gana pruebas y depuracion| C style B fill:#f9e2af,color:#11111b style C fill:#a6e3a1,color:#11111b
El hallazgo que la tabla revela
Conviene aplicar la misma sospecha a la fila de ceremonia, que es donde MVI pierde con más claridad. Esa fila cuenta líneas de andamiaje por pantalla y las cuenta bien, pero cuenta la primera pantalla. En una app con treinta pantallas que comparten un contenedor común, la mayor parte de ese andamiaje se escribe una vez y se hereda, de modo que el coste marginal de la pantalla número treinta es una fracción del de la primera. Una fila que mide el caso peor y se lee como si midiera el caso medio distorsiona la conclusión en la dirección contraria a las anteriores, lo cual al menos confirma que el sesgo no es de una sola parte.
Si se mira la tabla en vertical y se compara la columna intermedia con las de los extremos, aparece el resultado más útil de todo el ejercicio: los dos pasos que separan MVVM disperso de MVI no cuestan lo mismo ni rinden lo mismo. Unificar el estado es barato —una data class y disciplina para escribirla entera— y se lleva la mayor parte de las ganancias: fotografía completa, comprobabilidad por valor, estados imposibles eliminables con tipos suma, atomicidad de la publicación. Unificar la entrada es más caro —tipo sellado, despachador, indirección— y lo que aporta es más específico: trazabilidad, reproducibilidad y políticas transversales.
Eso significa que la decisión no es binaria y que hay un punto intermedio legítimo, no un compromiso vergonzante. Un equipo puede dar el primer paso en toda la app y reservar el segundo para las pantallas donde la trazabilidad y la reproducibilidad valgan lo que cuestan. Presentar la elección como MVVM contra MVI oculta precisamente la opción que más equipos deberían tomar.
Este hallazgo tiene además una consecuencia sobre cómo discutir el asunto en un equipo. Cuando alguien defiende MVI citando la comprobabilidad y la fotografía del estado, está defendiendo en realidad el paso uno, que es barato y que casi nadie discute. Cuando alguien lo ataca citando la ceremonia y la indirección, está atacando el paso dos, que es el caro. Las dos partes tienen razón y hablan de cosas distintas, y separar los dos pasos es lo que convierte una discusión circular en un acuerdo en veinte minutos.
Ese acuerdo rápido tiene además un efecto secundario valioso: convierte una decisión política en una decisión técnica reversible. Nadie tiene que declararse a favor o en contra de un patrón delante de su equipo, que es la forma más segura de bloquear una discusión durante meses; solo hay que decidir si esta pantalla concreta da el paso uno ahora y deja el paso dos abierto. Las decisiones que no exigen identidad se toman mucho más deprisa que las que sí.
Queda una última advertencia sobre la tabla. Todas sus celdas evalúan la arquitectura tal como está escrita en el manual, no tal como acabará estando en la base de código dentro de dos años. Un MVI con excepciones no puntúa como MVI; puntúa peor que un MVVM unificado bien aplicado, porque ha pagado la ceremonia entera y ha perdido la garantía que la justificaba. Ninguna tabla comparativa recoge eso, y es lo primero que hay que corregir al usarla.
Lo verdaderamente formativo de construir esta tabla no es la tabla, sino descubrir que la conclusión estaba decidida antes de rellenar una sola celda, en el momento de elegir las filas. Si hubiera incluido velocidad de prototipado y omitido depuración, la tabla diría con la misma apariencia de rigor que MVVM disperso es superior. Si hubiera incluido reproducibilidad de fallos y omitido curva de aprendizaje, diría que MVI no tiene rival. Los números no habrían cambiado; habría cambiado qué se cuenta, que es donde vive el sesgo en cualquier comparación técnica. Esto no es un defecto corregible de esta tabla en particular: es la naturaleza de toda comparación multicriterio, y afecta por igual a las evaluaciones de bases de datos, de lenguajes, de proveedores de nube y de candidatos en una entrevista. De ahí se sigue una obligación profesional concreta y bastante exigente: quien presenta una comparación debe publicar sus criterios antes de los resultados, incluir deliberadamente aquellos en los que su opción preferida pierde, y decir en voz alta qué ponderación está asumiendo. Y quien la recibe debe hacer la pregunta que desarma cualquier tabla sesgada: qué fila falta aquí, y a quién beneficia que falte. La madurez técnica no consiste en tener la tabla correcta, porque no existe: consiste en saber que la tabla es un modelo del problema y no el problema, en pedir siempre la ponderación junto con los datos, y en aceptar que dos equipos honestos con la misma tabla pueden decidir cosas opuestas sin que ninguno se equivoque, porque están pesando futuros distintos. Quien entiende esto deja de buscar el patrón ganador y empieza a preguntar por el contexto, que es exactamente el sitio donde la respuesta ha estado siempre.
- Rehaz la tabla con los criterios que tu equipo pesa de verdad, e incluye al menos uno en el que tu opción preferida pierda claramente.
- Asigna a cada fila un peso numérico justificado por tu contexto: horizonte de la app, rotación del equipo, criticidad del dominio.
- Calcula el resultado ponderado y comprueba si cambia respecto a contar victorias por fila; explica por qué cambia o por qué no.
- Identifica una fila que hayas omitido inconscientemente y evalúa a quién beneficiaba esa omisión.
- Aplica la tabla a dos pantallas reales de la misma app con pesos distintos y justifica que la conclusión correcta sea diferente en cada una.