Saltar al contenido
Development

Un segfault que no era un error, y la API que finalmente eliminé

Por Victor Da Luz
iosswifttestingdev-logdeep-cut-atlas

Esta app luego se renombró a Deep Cut Atlas. Se la llama “Discoverer” en todo lo que sigue, porque así se llamaba el día en que ocurrió esto.

Se suponía que este iba a ser el issue de limpieza fácil. Una revisión del repo había señalado un puñado de cosas en la capa de servicio de MusicKit de Discoverer: unos métodos que ya nadie llamaba, dos tipos de error que mostraban mensajes feos por defecto en vez de texto real, algo de lógica de coincidencia duplicada, y un par de búsquedas de listas de reproducción que hacían más trabajo del necesario. Nada de eso era un error funcional. Todo era el tipo de deuda pequeña que solo necesitaba que alguien fuera y la eliminara de verdad.

Eliminar el código muerto fue curiosamente satisfactorio. Cuatro métodos, cero llamadas, confirmado con grep antes de tocar nada. Un parámetro al que siempre se le pasaba el mismo valor fijo en cada sitio de llamada, lo que significaba que toda una rama de código detrás de él nunca se había ejecutado fuera del único lugar donde realmente hacía falta. Eliminar código que demostrablemente no hace nada es uno de los pocos refactors que se siente completamente seguro, porque no se está adivinando el comportamiento, solo se está eliminando un camino que nadie toma.

Después corrí la suite de pruebas y cada una falló al instante. No “las pruebas que toqué.” Todas, incluyendo las que no tenían relación - ayudantes de strings, utilidades de fragmentación de arreglos, cosas que no tenían nada que ver con mi cambio. Cada una reportó un tiempo de ejecución de cero segundos, lo cual es la señal de que algo hizo fallar todo el proceso de pruebas en vez de que una prueba individual realmente fallara.

El registro de fallo señalaba una violación real de acceso a memoria dentro de una función completamente distinta a la que yo estaba editando, y la función que aparecía como quien la llamaba en realidad no llama a esa función en ninguna parte del código actual. Esa incoherencia - un llamador nombrado que no podía llamar de ninguna forma a la función nombrada como destino - fue lo que me hizo sospechar en vez de simplemente intentar arreglar un error fantasma. Eliminar métodos de una interfaz compartida cambia la disposición subyacente en memoria de cómo se despachan esas llamadas, y una compilación incremental puede terminar pegando piezas compiladas de antes y de después del cambio. El resultado corre, pero salta al lugar equivocado mientras el depurador sigue mostrando las etiquetas viejas.

Una limpieza y recompilación completa lo arregló al instante, sin cambios de código. Ese es todo el truco: cuando se acaba de cambiar qué métodos existen en algo, y la siguiente corrida de pruebas da un segfault que no tiene sentido, no conviene empezar a depurar la función nombrada. Limpiar primero.

El resto de la limpieza cayó como debe caer una limpieza: en silencio. Dos tipos de error ahora dicen algo que una persona realmente entendería, en vez de un número crudo de caso de enum. Un bloque de coincidencia de strings duplicado se convirtió en un solo ayudante compartido y probado. Dos búsquedas de listas de reproducción que antes traían todas las listas y buscaban la que querían ahora piden esa directamente. Nada de esto cambia lo que hace la app. Todo hace más chico el trabajo de la próxima persona.

Lecturas relacionadas