Saltar al contenido
Development

Una sección de tracklist, y por qué tomó 30 minutos

Por Victor Da Luz
iosswiftdev-logdeep-cut-atlas

La función de hoy era pequeña: la hoja de lanzamiento de la pestaña Descubrir debía mostrar las canciones del álbum, para poder evaluar un lanzamiento antes de agregarlo a la lista de reproducción o descartarlo. Lo que valía la pena contar es cuán poco pensamiento nuevo requirió.

El cambio

La hoja necesitaba datos de canciones que la app todavía no obtenía en ningún lado. Una nueva lectura en el protocolo del servicio:

func fetchTracks(forCatalogAlbum album: LibraryAlbum) async throws -> [LibraryTrack]

La implementación real es una única solicitud al catálogo con la relación .tracks, mapeada a través de un mapeador de canciones que ya existía para el flujo de listas de reproducción. El mock devuelve fixtures por álbum y recibe un caso de inyección de fallos como cualquier otro método, para que las pruebas puedan fallar exactamente esta llamada y ninguna otra.

El view model recibió un loadTracks() y una propiedad de estado. No definí un nuevo enum de estado, porque el código base ya tiene uno exactamente para este tipo de problema: SuggestionsState (loading, loaded, empty, failed) impulsa las secciones “más de este artista” en otras dos hojas. Reutilizarlo hizo que el spinner de carga, el texto de estado vacío y la fila de error con reintento del tracklist quedaran consistentes con el resto de la app sin necesidad de ninguna decisión de diseño.

Una decisión de enrutamiento que vale la pena señalar. Las dos acciones existentes de esta hoja (agregar a la lista de reproducción, no interesa) delegan al view model padre de Descubrir, porque modifican el feed y tienen que mantenerse consistentes con él. La lectura del tracklist, en cambio, va directo al servicio. Esa división, lecturas directas y escrituras que modifican el feed a través del padre, es la convención sobre la que ya estaba construida la hoja. Seguirla dejó el padre intacto salvo por una línea en el factory method.

El presupuesto de lint hizo su trabajo

La clase de servicio estaba exactamente en 300 líneas de código, el límite de type-body de SwiftLint, después de el trabajo de rendimiento. Agregar un método de 7 líneas significaba que algo tenía que ceder. Los tres mappers estáticos de DTO se movieron a una extensión privada en el mismo archivo, lo cual mantiene funcionando el acceso private (en Swift, private tiene alcance de archivo para extensiones en el mismo archivo) y dejó el cuerpo de la clase bien por debajo del límite.

Antes estos límites me parecían molestos. Este forzó una división que debería haber hecho de todos modos: los mappers son funciones puras que en realidad nunca fueron parte del trabajo de la clase.

Verificación

Pasan 186 pruebas, tres de ellas nuevas: las canciones cargan en orden, un álbum sin tracklist muestra el estado vacío, y un fallo inyectado muestra el error y luego tiene éxito al reintentar. Después la pasada habitual en el dispositivo, porque MusicKit no devuelve nada en el simulador: compilé hacia el teléfono con devicectl, abrí algunos lanzamientos y observé cómo se llenaban tracklists reales.

En total fueron siete archivos y 160 líneas, la mayoría pruebas y fixtures. Cuando una función cuesta tan poco, generalmente no es porque la función fuera trivial. Es porque las últimas diez funciones dejaron convenciones sobre las que esta pudo apoyarse.

Lecturas relacionadas