wandres.dev
MÁS ALLÁ DEL MÓVIL · Wear, TV, Auto y XR

Compartir codigo entre superficies

La pregunta de cuánto código se reutiliza entre el teléfono, el reloj, el televisor, el coche y el visor tiene una respuesta precisa que casi nadie formula bien, porque la frontera no cae donde la intuición la sitúa. Esta lección establece el criterio que separa lo compartible de lo irreductiblemente propio, propone una topología de módulos de Gradle que hace esa frontera imposible de cruzar por accidente, desmonta la idea de que el coste de una superficie nueva se mide en líneas de código, y ofrece un método para planificar el soporte multi-dispositivo sin comprometerse con más de lo que se puede sostener.

⏱ 21 min

Al terminar de recorrer cuatro superficies aparece siempre la misma pregunta de gestión, y suele venir formulada de una manera que garantiza la respuesta equivocada: cuánto código podemos reutilizar. La pregunta es engañosa porque presupone que el trabajo se mide en código y que la reutilización es el objetivo. Ninguna de las dos cosas es cierta. La frontera real no separa código bueno de código duplicado, sino lo que responde a qué hace el producto de lo que responde a cómo se presenta en esta superficie, y esa frontera es nítida, es estable y no coincide con las capas que la mayoría de los proyectos ya tiene dibujadas. Por debajo de ella prácticamente todo se comparte y compartirlo es obligatorio; por encima prácticamente nada se comparte y compartirlo es un error caro que se paga en interfaces que no encajan en ninguna parte. Esta lección cierra el nivel convirtiendo esa frontera en una topología de módulos, y después convirtiendo la topología en un plan que un equipo real pueda sostener durante años.

🎯 Al terminar esta lección sabrás
  • Trazar la frontera entre lo compartible y lo propio de cada superficie con un criterio verificable.
  • Diseñar una topología de módulos que impida cruzar esa frontera por accidente.
  • Estimar el coste de una superficie nueva en términos de matrices y no de líneas de código.
  • Planificar un soporte multi-dispositivo por fases con criterios explícitos de entrada y de abandono.

Dónde cae realmente la frontera

El criterio que funciona es una pregunta única aplicada a cada pieza de código: ¿esta decisión cambiaría si el usuario tuviera otra pantalla delante? Si la respuesta es no, la pieza es compartible y debe vivir en un módulo común. Si la respuesta es sí, la pieza pertenece a la superficie y compartirla producirá un compromiso que no sirve bien a ninguna.

La razón por la que ese criterio funciona mejor que los habituales es que no habla de tecnología. Los criterios que fracasan suelen tener la forma de compartamos todo lo que no sea Compose, o compartamos hasta el modelo de vista, y fracasan porque trazan la línea sobre una frontera de biblioteca que no tiene por qué coincidir con la frontera del producto. El criterio de si la decisión cambiaría con otra pantalla delante, en cambio, se puede aplicar a una función concreta, se puede discutir con alguien que no programa y da la misma respuesta hoy que dentro de cinco años, cuando las bibliotecas sean otras.

Aplicado con honestidad, ese criterio deja del lado compartido más de lo que la mayoría espera. Los modelos de dominio no cambian: un pedido es el mismo pedido en el reloj y en el televisor. La capa de datos no cambia: los mismos clientes de red, los mismos esquemas de persistencia, las mismas políticas de caché y de reintento. Las reglas de negocio no cambian, y si cambian es que había reglas escondidas en la interfaz. La autenticación, la telemetría, la configuración remota y el grafo de inyección de dependencias no cambian. Y una parte sustancial de la lógica de presentación tampoco cambia, siempre que se entienda por tal la transformación de datos crudos en datos ya listos para mostrar: formatos, ordenaciones, agrupaciones, cálculos derivados.

Hay una pieza que cae exactamente sobre la línea y que conviene resolver con precisión porque genera discusiones interminables: el sistema de diseño. La respuesta es que se parte en dos. Los valores de marca —la paleta, las familias tipográficas, la escala de espaciado, la forma de los ángulos, los iconos— son los mismos en todas partes y deben vivir en un módulo común, porque su razón de cambio es la identidad de la empresa y esa razón no depende de la pantalla. Los componentes que aplican esos valores, en cambio, son propios de cada superficie por construcción, porque la biblioteca base es distinta y las métricas ergonómicas son distintas. Compartir los valores y separar los componentes es lo que permite que un producto se reconozca como tuyo en un reloj y en un televisor sin que ninguna de las dos versiones sea la traducción torpe de la otra.

🧬

Comun por definicion

Modelo de dominio, datos, red, persistencia, reglas de negocio, autenticación, telemetría y valores de marca.

🎨

Propio por definicion

Árbol de composición, navegación, componentes aplicados y arquitectura de información de cada superficie.

Del lado propio queda menos volumen pero más trabajo por unidad. Queda el árbol de composición entero, porque los catálogos de componentes son distintos por decisión de la plataforma y forzar uno común produce lo peor de cada superficie. Queda la navegación, porque las profundidades admisibles y los gestos de retroceso son distintos. Queda el sistema de diseño aplicado, aunque los valores de marca sean comunes. Y queda, sobre todo, la arquitectura de información: qué se muestra, en qué orden y cuánto de ello, que es exactamente la decisión que cada superficie de este nivel ha obligado a replantear.

⚠️
El modelo de vista es la pieza tramposa

La tentación de compartir el modelo de vista entre teléfono y reloj es enorme y a veces correcta, pero se vuelve destructiva en cuanto la pantalla del reloj muestra un subconjunto distinto. Un modelo compartido que expone un estado con quince campos de los que la muñeca usa dos arrastra quince cargas, quince suscripciones y quince motivos de recomposición al dispositivo con menos batería del ecosistema. La regla honesta es compartir el cálculo y no necesariamente el contenedor.

Una topología que hace explícita la frontera

Convertir el criterio en estructura es lo que impide que se erosione. La topología que mejor aguanta separa tres anillos y prohíbe las dependencias hacia fuera.

La regla que sostiene la topología es que las dependencias apuntan siempre hacia dentro. El núcleo no sabe que existen las superficies; las capacidades conocen el núcleo pero no las aplicaciones; y cada aplicación conoce las capacidades que necesita e ignora por completo a sus hermanas. Esa direccionalidad es lo que permite añadir una superficie nueva sin tocar nada de lo anterior, que es la propiedad que en última instancia se está comprando con todo este trabajo de estructura.

El anillo interior es el núcleo común: modelo, datos, red, persistencia, dominio y utilidades. No depende de ninguna biblioteca de interfaz de ninguna superficie, y esa prohibición debe ser mecánica y no cultural. El anillo intermedio es el de las capacidades por funcionalidad, donde vive la lógica de presentación compartida de cada área del producto. Y el anillo exterior son los módulos de aplicación por superficie, cada uno con su manifiesto, su catálogo de interfaz, su navegación y su punto de entrada.

flowchart TD
A[Nucleo comun de modelo y datos] --> B[Capacidades por funcionalidad]
B --> C[App de telefono y tablet]
B --> D[App de reloj]
B --> E[App de television]
B --> F[Modulo de plantillas de coche]
C --> G[Compose Material 3]
D --> H[Compose para reloj y tiles]
E --> I[Compose para television o Leanback]
F --> J[Biblioteca de aplicaciones de vehiculo]
style A fill:#a6e3a1,color:#11111b
style B fill:#89b4fa,color:#11111b
// El nucleo comun no conoce ninguna superficie: la restriccion se declara, no se confia
plugins { alias(libs.plugins.android.library) }

android { namespace = "com.ejemplo.core.data" }

dependencies {
    api(projects.core.model)
    implementation(libs.retrofit)
    implementation(libs.room.runtime)
    // Ninguna dependencia de compose, de wear, de tv ni de car
}

Una pregunta recurrente es si las superficies deben ser módulos distintos o variantes de compilación del mismo. La respuesta práctica es que módulos, salvo para diferencias muy pequeñas. Las variantes comparten manifiesto base, dependencias y configuración, y las superficies de este nivel difieren precisamente en eso: en el nivel mínimo de API que pueden exigir, en las bibliotecas de interfaz que arrastran, en las declaraciones de características de hardware y en los puntos de entrada. Forzar todo eso dentro de un único módulo produce archivos de compilación con condicionales por todas partes, que son exactamente el tipo de complejidad que nadie audita y que rompe la compilación seis meses después sin que nadie sepa por qué.

Hay dos decisiones de empaquetado que conviene resolver pronto porque condicionan la topología. La aplicación de reloj se distribuye como un artefacto propio dentro del mismo paquete de publicación, con su identificador compartido y su versión coordinada, lo que permite que la instalación en el teléfono ofrezca la del reloj sin que el usuario haga nada. La aplicación de coche, en cambio, se comporta de forma distinta según de cuál de las dos plataformas del vehículo hablemos: en la proyección desde el teléfono, el servicio de plantillas viaja dentro de la aplicación de móvil y por tanto es un módulo más de ese grafo; en el sistema embarcado es una entrega independiente con su propio ciclo. Confundir ambos casos produce grafos de dependencias que no compilan o paquetes que la tienda rechaza.

La prohibición del anillo interior debe ser mecánica, y ese matiz decide si la arquitectura sobrevive. Una regla escrita en un documento de convenciones dura hasta la primera semana de prisa. Una regla codificada en el archivo de compilación, que hace fallar el proyecto cuando alguien añade una dependencia de interfaz al módulo de datos, dura indefinidamente y además enseña por qué existe en el momento exacto en que se intenta violar. Es preferible una restricción algo más estricta de lo estrictamente necesario y verificada por la herramienta, que una restricción perfecta confiada a la disciplina colectiva.

📝
El grafo de dependencias es la documentacion que nadie puede dejar desactualizada

A diferencia de un diagrama, el grafo de módulos no puede mentir: si compila, es cierto. Merece la pena generarlo periódicamente y mirarlo, porque las erosiones de la frontera aparecen ahí como flechas nuevas mucho antes de que nadie las note escribiendo código.

Merece una mención el caso en que parte del núcleo deba compartirse además con clientes que no son Android. La respuesta natural es un núcleo multiplataforma en Kotlin, y la buena noticia es que la topología descrita ya lo prepara: si el anillo interior no depende de interfaz alguna, extraerlo es una operación mecánica. Quien no ha hecho antes ese trabajo de separación descubre que el obstáculo nunca es la herramienta multiplataforma sino las cincuenta referencias a clases de Android escondidas en la capa de datos.

El coste no se mide en líneas

Aquí está el error de estimación que hunde la mayoría de los planes multi-dispositivo. Un equipo calcula que la interfaz de reloj son unas pocas pantallas y estima dos semanas. La estimación es correcta para el código y equivocada para el proyecto, porque el coste dominante de una superficie nueva no es escribirla sino sostenerla, y lo que se multiplica no son las líneas sino las matrices.

La forma más honesta de estimar es aceptar que el trabajo de construcción es un pago único y pequeño, y que todo lo demás es una suscripción. Una superficie no se entrega: se adopta, y a partir de ese momento cobra una cuota en cada entrega que el equipo haga durante el resto de la vida del producto. Con esa lente, las cifras cambian de orden y las decisiones también.

Se multiplica la matriz de pruebas: cada funcionalidad nueva debe verificarse ahora en varios factores de forma con emuladores distintos, hardware distinto y modos de interacción distintos. Se multiplica la matriz de dispositivos: los relojes tienen pantallas redondas y cuadradas de varios tamaños, los televisores tienen receptores de fabricantes con comportamientos de foco divergentes, los coches tienen pantallas de proporciones extremas. Se multiplica la matriz de niveles de API, porque cada superficie avanza a su propio ritmo y el mínimo que puedes exigir en un televisor no es el que puedes exigir en un teléfono. Se multiplica la matriz de revisión de tienda, con requisitos de calidad específicos y capturas propias por factor de forma. Y se multiplica la matriz de incidencias, que es la que de verdad consume el tiempo del equipo durante los años siguientes.

🧪

Pruebas

Cada cambio se verifica en todos los factores activos. El coste recurrente supera al de construcción en el primer año.

📦

Publicacion

Artefactos, fichas, capturas y requisitos de calidad propios por superficie, con ciclos de revisión independientes.

🐛

Soporte

Las incidencias llegan por superficie y con dispositivos que el equipo no tiene sobre la mesa. Es el coste que nadie estima.

Hay un coste adicional que no aparece en ninguna hoja de cálculo y que conviene nombrar: el de atención de diseño. Cada superficie plantea preguntas de producto que solo se responden observando usuarios reales en ese contexto, y esa observación no se delega ni se acelera. Nadie descubre desde una mesa de oficina cuál es el dato correcto para la muñeca, ni qué seis elementos deben aparecer en el salpicadero, ni qué fila del televisor merece estar arriba. El equipo que trata la superficie nueva como una tarea de implementación entrega algo que funciona y que nadie usa, y después concluye erróneamente que la superficie no tenía demanda.

Conviene también contabilizar el coste de divergencia. Con el tiempo, cada superficie acumula pequeñas decisiones propias que ya no se corresponden con las de las demás: un campo que allí no se muestra, una regla de ordenación distinta, un texto que se acortó. Ninguna de esas divergencias es un defecto por sí sola, pero su suma produce el fenómeno más caro de un producto multi-superficie, que es la incapacidad del equipo para responder a la pregunta de qué hace exactamente el producto. La única defensa conocida es que la definición del dominio viva en un único sitio y que ninguna superficie pueda redefinirla por su cuenta.

La conclusión operativa es incómoda y liberadora a la vez: es mejor sostener dos superficies con excelencia que cinco con abandono. Una aplicación de reloj publicada y sin mantener produce reseñas de una estrella que contaminan la ficha del producto entero, y una aplicación de televisión con trampas de foco genera más daño de marca que su ausencia. La decisión de entrar en una superficie debe llevar aparejado, desde el primer día, el compromiso de mantenimiento y el criterio bajo el cual se abandonaría.

Planificar por fases y con puertas

Antes del método conviene desactivar la forma más común de plantear la decisión, que es preguntarse a qué superficies vamos a dar soporte. Formulada así, la pregunta invita a responder con una lista de plataformas y a tratarlas como casillas que se marcan. La formulación productiva es la contraria: qué tareas de nuestro producto merecen vivir fuera del teléfono, y en qué superficie vive mejor cada una. La primera pregunta produce cinco aplicaciones mediocres; la segunda produce dos o tres piezas que la gente usa de verdad y una lista clara de lo que no se hace.

El método que funciona convierte cada superficie en una fase con una puerta de entrada explícita y una de salida. La puerta de entrada responde a tres preguntas concretas antes de escribir código: qué tarea del producto tiene sentido en esa superficie, qué señal indica que hay demanda, y quién la mantendrá cuando el entusiasmo inicial se agote.

La tercera de esas preguntas es la que se contesta peor y la que más proyectos hunde. La respuesta habitual —lo mantendremos entre todos— equivale a decir que no lo mantendrá nadie, porque en la práctica el conocimiento de una superficie se concentra en quien la construyó y desaparece con esa persona. Nombrar a alguien responsable, darle acceso a hardware real y reservarle tiempo explícito en cada ciclo es más determinante para la supervivencia de la superficie que cualquier decisión de arquitectura de este documento.

La puerta de salida es la que casi nadie escribe y la que más dinero ahorra. Debe fijar una métrica, un umbral y una fecha, y debe aceptarse antes de escribir la primera línea, cuando todavía es fácil imaginar el fracaso sin que duela. Puede ser una cifra de instalaciones activas, una valoración mínima en la ficha de ese factor de forma o un coste de soporte por encima del cual la superficie deja de compensar. Lo importante no es acertar con el umbral sino que exista, porque su función real es dar permiso al equipo para retirarse sin que la retirada se viva como una derrota personal de quien la propuso.

El orden recomendado no es arbitrario. Primero, la base adaptativa, que no es una superficie sino el requisito de todas y que ya se cobra sola en tablets y plegables. Después, la superficie donde el dato irreducible del producto tenga más valor, que para un producto de salud o de mensajería es el reloj, para uno de vídeo o audio es el televisor, y para uno de navegación o música es el coche. La realidad extendida va al final no por ser difícil sino porque, si la base adaptativa está hecha, ya se obtiene un resultado digno sin trabajo específico, y el trabajo específico rinde más cuando el resto está asentado.

Dentro de cada fase conviene además una secuencia interna que evita el error más común, que es publicar demasiado pronto. Primero, una única tarea completa y excelente, no un menú de funciones a medias. Después, un periodo de observación con usuarios reales en el contexto real, del que casi siempre sale que la tarea elegida no era la correcta. Y solo entonces, la ampliación. Publicar una versión amplia y mediocre en la primera entrega quema la única oportunidad de causar buena impresión en una ficha que después arrastra sus valoraciones durante años.

Hay un beneficio colateral de este método que conviene anticipar porque suele ser el que convence a la dirección. Obligar a responder qué tarea justifica cada superficie fuerza al equipo a destilar el producto, exactamente el ejercicio que la lección del reloj describía. Los equipos que atraviesan este proceso salen con una idea mucho más nítida de qué hace su producto que la que tenían al empezar, y esa claridad se manifiesta después en la aplicación de teléfono, en la comunicación y en la propia hoja de ruta. El soporte multi-dispositivo, bien hecho, es un instrumento de estrategia disfrazado de tarea de ingeniería.

💡
Una superficie por vez y con fecha de revision

Fija de antemano el momento en que revisarás si la superficie merece seguir viva y qué métrica lo decidirá. Sin esa fecha, ninguna superficie se retira jamás: se degrada en silencio durante años porque retirarla parece un fracaso, mientras consume revisión de código y tiempo de pruebas en cada entrega.

La frontera entre modulos es una decision de producto disfrazada de decision tecnica

Vale la pena cerrar el nivel con la observación que hace que todo lo anterior encaje, porque explica por qué las discusiones sobre arquitectura multi-superficie se enconan tanto y por qué se resuelven tan mal cuando se tratan como discusiones técnicas. Cuando un equipo debate si el modelo de vista de la pantalla de inicio debe compartirse entre el teléfono y el reloj, la conversación adopta el vocabulario de la ingeniería: acoplamiento, cohesión, duplicación, dependencias. Pero si se escucha con atención, ninguna de las dos posturas está discutiendo realmente sobre acoplamiento. Quien defiende compartir está afirmando, sin decirlo, que la pantalla de inicio es el mismo producto en ambos sitios y que debe evolucionar a la vez. Quien defiende separar está afirmando que son dos productos distintos que comparten datos y que evolucionarán por caminos propios. Esa es una pregunta de producto, no de arquitectura, y no tiene una respuesta correcta en abstracto: tiene una respuesta correcta para cada producto concreto y para cada momento de su historia. El motivo por el que el debate no se resuelve es que se está discutiendo la pregunta técnica en lugar de la de producto, y la técnica no tiene respuesta hasta que la otra se conteste. De aquí sale un principio muy práctico: la frontera entre módulos debería trazarse por ritmo de cambio compartido y no por similitud de contenido. Dos piezas pertenecen al mismo módulo cuando cambian juntas por la misma razón, no cuando se parecen. El modelo de un pedido cambia cuando cambia el negocio, y cambia igual para todas las superficies; por eso es común, y seguirá siéndolo dentro de diez años. La pantalla de inicio del reloj cambia cuando alguien descubre, observando a usuarios reales con el brazo levantado, que el dato de arriba debería ser otro; y esa razón de cambio no tiene absolutamente nada que ver con la razón por la que cambiará la pantalla de inicio del televisor. Compartirlas acopla dos procesos de aprendizaje independientes y garantiza que cada mejora en una exija negociar con la otra, que es la forma más eficaz conocida de detener ambas. Y aquí está la ironía final, que conviene decir en voz alta porque va contra el instinto de todo buen ingeniero: la duplicación entre superficies es a menudo la decisión correcta, y el equipo que la elimina toda acaba con un producto que no ofende a nadie y no encaja en ninguna parte. Lo que hay que evitar no es escribir dos veces una lista; es tener dos definiciones de qué es un pedido. La primera duplicación es el precio legítimo de servir bien a contextos distintos. La segunda es una bomba de relojería.

⚔️ Trazar la frontera en tu propio proyecto
  1. Recorre tus módulos y clasifica cada uno según si cambiaría al cambiar de superficie. Anota los que no sepas clasificar: ahí está tu deuda arquitectónica.
  2. Declara en el módulo de datos la prohibición explícita de dependencias de interfaz y ejecuta la compilación. Cada fallo es una fuga real que llevaba tiempo ahí.
  3. Elige una funcionalidad y aplícale la pregunta del ritmo de cambio a cada una de sus piezas. Redistribuye lo que cambie por razones distintas.
  4. Estima una superficie nueva rellenando las cinco matrices —pruebas, dispositivos, niveles de API, publicación e incidencias— antes de estimar una sola línea de código. Compara ambas cifras.
  5. Escribe la puerta de entrada y la de salida de esa superficie: qué tarea la justifica, qué métrica se revisará, en qué fecha y quién decide retirarla.