Juegos, tests y floats
Publicado el 19 de julio de 2022
Dice Kevlin Henney que, una vez superados los problemas culturales y entendido que los tests son beneficiosos para un proyecto de software, hay dos principales factores que impiden escribir tests automáticos para un programa. El primero es no saber cuál es el comportamiento correcto. No saber con exactitud cómo debería comportarse el programa. Un test es una función que dado un estado del programa y unas transformaciones sobre ese estado, espera un resultado determinado. Si no somos capaces de concretar de qué forma las reglas de nuestro programa transforman su estado, no podemos escribir tests. El segundo es cuando no sabemos cómo comprobar que algo es correcto. Hay código para el que es muy difícil escribir tests. Por ejemplo código que interactúa con el exterior, código que depende de grandes cantidades de datos, heurísticas que calculan aproximaciones de otras funciones...
El código que opera sobre floats suele ser difícil de probar porque genera problemas de ambos tipos. Como sabemos, los floats no representan fielmente los números reales, sino que son aproximaciones que redondean el resultado a la precisión disponible y acumulan errores. Escribir tests automáticos para floats es difícil porque hay que contemplar estas imprecisiones y tenerlas en cuenta. Es especialmente difícil comprobar los casos límite, los extremos en los que un predicado pasa de ser verdad a ser mentira. La razón de esto se debe a que, a diferencia de los enteros, donde cada número es distinto y tiene un significado distinto (no es lo mismo que una lista tenga 6 elementos que 7, hay una distinción muy clara y fácil de entender), dos floats sucesivos son, esencialmente, el mismo número. La diferencia de un float al siguiente es tan pequeña que el significado de la diferencia entre floats muy parecidos se pierde. Cualquier código escrito en términos de floats expresa una aproximación, un más o menos, pero los tests requieren expresar el comportamiento en términos de reglas estrictas, y esto es difícil. Sobre todo, porque los tests no están en sintonía con el código escrito. Los tests para poder existir tienen que ser más estrictos de lo que la especificación requiere en realidad, y eso lleva a que lleve mucho tiempo escribirlos, a que se rompan de forma imprevisible al hacer cambios y a que su significado no sea inmediatamente obvio al leerlos.
Por otro lado, esta división entre la naturaleza aproximada de los floats y la rigidez de los tests hace que a menudo el diseño, la especificación de lo que el código debe hacer, no siempre concuerde en granularidad con el código escrito. Es decir, la especificación es más abstracta que el código y no tiene respuestas para qué es lo que sucede ante situaciones raras. También, al ser por naturaleza difíciles de entender, los floats hacen que se multipliquen el número de situaciones raras e imprevisibles. Esto lleva a que muchas veces ni programadores ni diseñadores son capaces de expresar qué debería hacer el programa, lo que lleva a que sea imposible escribir tests.
Los juegos en tiempo real usan floats para expresar sus dos variables más importantes, el espacio y el tiempo. A diferencia de los juegos por turnos, que tienden a ser mucho más discretos, no sólo en su representación del tiempo donde la unidad es el turno, sino también en su representación del espacio, usando a menudo cuadrículas u otros tipos de tablero con posiciones discretas para su representación del espacio, los juegos en tiempo real suelen suceder en mundos continuos, en espacios cartesianos, y tienen además una representación continua del tiempo, lo que llamamos “tiempo real”. Esto significa que este tipo de juegos están inevitablemente condenados a que sus reglas seas más difíciles de probar automáticamente que las de los juegos por turnos. Por supuesto, en los juegos en tiempo real también hay fenómenos discretos, pero gran parte del meollo sucede en código que depende de variables continuas, lo que causa que las reglas sean mucho más difíciles de definir con precisión y también de poner a prueba. El tiempo impone además una dificultad adicional. ¿Cómo se escriben tests para procesos que suceden a lo largo del tiempo, a lo largo de varios segundos por ejemplo, que sean eficientes y que el desarrollador pueda ejecutar automáticamente de forma periódica sin que tarden demasiado?
Logo of RSS.