En el post anterior el tema de los bugs quedó sin mucha explicación, y dado que he hablado poco de ellos, más allá de una que otra queja, este post es entonces un post sobre bugs.
Para los pocos que no lo saben, los programadores llamamos bugs a las fallas en los programas. El nombre (bug es insecto en inglés) viene dado por razones históricas. En los albores de la computación los equipos fallaban por insectos que se introducían en los antiguos circuitos y tubos que lo formaban, en una época en que nadie soñaba con los circuitos integrados, chips y otras tecnologías modernas.
Todo el tema de programación orientada a objetos (oop), clases, encapsulamiento, cero variables globales, las metodologías de software engineering y otras ideas son mecanismos para evitar los bugs y otras alimañas que hacen que los programas no funcionen. Los resultados son mixtos.
Mi último juego, khpx, está implementado utilizando una idea descabellada: nada de clases, nada de encapsulamiento, y cientos de variables globales para mantener el estado del juego. Luego de programar decenas de aplicaciones y juegos en C++ y su parafernalia, llegué a la conclusión de que hay que usar C++ por las razones correctas, no necesariamente para evitar los bugs, pues C++ tiene un costo. Un programa en C++ es 50% más voluminoso y complejo que su equivalente en C.
No tengo una estadística que sustente ese 50%, puede ser 20% o 100%. El punto es el siguiente: ¿cuál programa tiene más probabilidad de tener bugs, uno de 1000 líneas o uno de 100? Algunos programadores sesudos argumentarán que los bugs blah, blah, blah y blah. En realidad la probabilidad de un bug no es directamente proporcional a nada, porque los bugs se rigen por la ley de Murphy, por lo que ambos programas, el de 1000 líneas y el de 100 líneas pueden tener bugs, o puede que no lo tengan. Y si ese es el caso, entonces ¿por qué voy a pasar el trabajo de escribir un programa de 1000 líneas, y la desventaja de tener que entenderlo y darle mantenimiento, en lugar de un programa de 100 líneas que con una mirada rápida puede revelar sus misterios rápidamente?
Mi respuesta es no, no tienes que pasar trabajo. Claro que hay unos beneficios adicionales de la programación orientada a objetos (oop): el código queda más organizado, se supone que es más fácil de entender para el grupo de trabajo, es más fácil de mantener. Puesto que soy un lobo estepario (un programador solo) esos beneficios no existen o al menos estoy hastiado del costo involucrado. Y para ello, baste mencionar como ejemplo el bug que tuve esta semana que involucró 12 horas de pesquisa y trabajo detectivesco para su solución (sí, en el post anterior decía que yo estaba muy sagaz resolviendo bugs, famosas últimas palabras, horas después apareció uno que me callaría la boca).
khpx está implementado con unos arreglos que guardan la información de los sprites. Hay un arreglo g_spr_obj_tbl que guarda los objetos que no requieren modificar el vertex buffer, g_modified_spr_obj_tbl guarda los objetos que lo requieren. Modificar el vertex buffer significa hacer la llamada Lock (), modificar los vértices de los meshes (estoy hablando en términos de DirectX) y Unlock () para terminar las modificaciones.
El punto es que esta semana estuve implementando el fadein/fadeout de la cónsola que muestra los datos de los sensores y controles de la nave, y cuando agregué los objetos para manejar los sensores y controles correspondientes a una batalla, comenzaron a suceder cosas inesperadas: él nuevo sprite perdía su posición en la pantalla y su su tamaño (scale). Era claro que cuando estaba de último en el arreglo fallaba, cuando lo movía a la posición penúltima funcionaba. 12 horas después y luego de revisar el codigo decenas de veces descubrí que el objeto que muestra la posición del scroll en los listados (SCROLL_BAR) estaba mal inicializado, debía estar en el arreglo de los objetos que no modifican el vertex buffer (g_spr_obj_tbl) y en cambio estaba en g_modified_spr_obj_tbl. El índice de este objeto está indicado con un define SCROLL_BAR cuyo valor es 10, que casualmente corresponde al último índice en g_modified_spr_obj_tbl. Esto producía un cambio en cualquiera que fuera el objeto que estuviera en esa posición, que resultó ser el nuevo sprite que yo estaba insertando esta semana.
Este bug me recordó mis proyectos usando oop, porque parece el tipo de bugs que sucede con frecuencia en ese tipo de proyectos. De hecho, en todo el tiempo que tengo programando khpx (un año) primera vez que sucede algo así. El encapsulamiento de la data persigue que este tipo de fallas no ocurran, pero todo depende de que coloques la data en la variable correcta. Si te equivocas de variable, el bug sucede, con o sin oop.
Claro que una alternativa a arreglos e índices son los containers, std::vector por ejemplo, y su iterator’s. El uso de índices y arreglos son peligrosos, o al menos deben ser utilizados con suma precaución. Hay ocasiones que requieres usar ciertos objetos por nombre y apellido, por ejemplo un sprite, y su uso incorrecto afecta todo el sistema. Si revisas el código de Doom (el juego de Id Software los creadores de Quake) está llenos de arreglos, y de accesos por índice. No hay ningún mecanismo especial para evitar este tipo de bugs, más allá de un programador alerta.
Hay múltiples moralejas, y hay múltiples formas de decirlo, pero una de las que más me gusta, está en este libro “Essential C” de Nick Parlante: ” The C programming model is that the programmer knows exactly what they want to do and how to use the language constructs to achieve that goal. The language lets the expert programmer express what they want in the minimum time by staying out of their way” (“El modelo de programación en C es que el programador sabe exactamente lo que quiere hacer y cómo usar las construcciones del lenguaje para lograr esa meta. El lenguaje permite que el programador experto exprese lo que quiere en el mínimo tiempo, manteniéndose fuera de su camino”). El lenguaje C es una herramienta que ayuda a resolver problemas, en múltiples formas, pero principalmente no estorbando. Y eso es realmente una gran ayuda.
Aclaratoria: como está dicho en los posts anteriores khpx, está programado en C++ pero utilizando al mínimo los paradigmas oop. Es prácticamente C pero con el STL. Utilizo intensamente los containers std::vector y std::map lo cual garantiza un código robusto y simple (y por añadidura el mínimo uso de arreglos, pero al parecer, no lo suficiente)