¿Por qué uso C++? (III)

Esta entrada es la continuacón directa de esta otra.

Esto me lleva a un tema bastante candente y en el que mucha gente hace hincapié: los tests. Ignoro cómo se llama el tipo de test que hice, y realmente me importa bien poco por no decir nada. El que me conozca sabe que no creo en los tests organizados porque son eso, organizados. El comentario que siempre hago es que si te falla un test, ¿qué es lo que está fallando, el código de la rutina a verificar o el código que comprueba la rutina? Volvamos por un momento a lo de los enteros de 64 bits. Supongamos que el compilador con el que estamos verificando las operaciones tuviera un fallo con los enteros de 64 bits y que nuestro programa fallara. El tema no anda muy lejos del famoso bug numérico del Pentium ya que los micros modernos pueden trabajar con enteros de 64 bits si así lo quieres (y de hecho, la aplicación que comprobaba la salida numérica era de 64 bits para evitarme algún tipo de problema en el runtime de C++). O si lo queremos, nos fijamos en los fallos de la biblioteca de 32 bits del compilador para el microprocesador de destino, que fallaba cuando ponías más de una operación aritmética en una misma línea de código.

¿De qué nos sirven los tests? En este caso, de nada. Incluso se puede dar la circunstancia de que código defectuoso en la parte a verificar, más código defectuoso en la de verificación termine en unos resultados correctos pero que luego fallarán estrepitosamente cuando se pongan en producción. ¿Os dais cuenta de la paradoja?

Con esto no quiero decir que los tests no sirvan, lo que no sirve son los tests automatizados y tontos. Una rutina sencilla necesita un test sencillo, pero la probabilidad de que una rutina sencilla falle es muy baja, sobre todo si es trivial, y hacer tests de cosas complejas es tanto o más complejo que el propio código a comprobar. Imaginaros que por un momento simplemente hubiera puesto el generador aleatorio en marcha generando números al azar y comprobar si dichos números funcionaban bien. Ese test no me hubiera garantizado nada, porque no tendría control sobre el dominio generado.

Pero esperad, esperad un poco. ¿Generar números aleatorios de 64 bits con un generador que va desde 0 a 32767? ¿Cómo se hace eso? ¿__int64 i64=rand()<<24+rand()<<16+rand()<<8+rand()? Desde luego que así no, ya que estamos fijando una pauta binaria que se va a repetir en cada número y nos vamos a dejar otras fuera. Podríamos buscarnos el código de un generador aleatorio de caracteres y obtener 8 caracteres aleatorios en secuencia. ¿no? Pues no, porque seguimos obviando otras pautas binarias. Una opción es un generador aleatorio que obtuviera enteros de 64 bits, que los hay, pero incluso así tampoco íbamos a obtener todas las pautas. ¿La solución? Un generador binario (que genere unos y ceros) cargado con diferentes pautas secuenciales.

¿Veis que no es fácil? ¿Y si en lugar de operar con números binarios (es decir, almacenar los enteros en forma binaria) lo hacemos con BCD empaquetado? ¿Cómo debería ser el comprobador a partir de un entero nativo de 64 bits? ¿Y si nos equivocamos en el código que convierte de binario a BCD a la hora de hacer el test? ¿Y cómo comprobamos el comprobador de binario a BCD empaquetado?…

Creedme: la gran mayoría de tests no sirven de nada si no se estudia en detalle cómo hacerlos, y es por eso por lo que no creo en ellos. Evidentemente yo testeo, y no poco, pero no sigo ninguna metodología de ningún tipo, o más bien sigo la forma que mejor conviene al código que estoy haciendo en ese momento.

Esto nos lleva a otro tipo de tests todavía más difíciles de hacer, que son los tests de protocolos de comunicaciones y de máquina física. Suponed por un momento que tenemos un PC controlando un semáforo, y que el semáforo cuenta con un protocolo serie para decirnos no sólo cómo están todas las luces, sino para informarnos de si hay alguna fundida y de la densidad del tráfico en cada calle. ¿Cómo comprobamos eso? ¿Cómo comprobamos algo tan sencillo como el código que genera el checksum de cada mensaje si el código que lo comprueba va a ser el mismo que lo va a generar?

Bueno, esta tiene truco, porque lo que tenemos que comprobar es que la trama recibida sume cero o 0xff o el valor que se determine, pero os aseguro que se dan situaciones así. No digamos ya cuando tenemos que comprobar que los mensajes que recibimos sean reales, se correspondan con lo que realmente esté haciendo el semáforo. Y justo al revés, que lo que nosotros enviemos haga los cambios pertinentes en las luces. Podemos poner a un tío delante del semáforo y que nos vaya diciendo las cosas, pero en estos casos la gente se aburre como una ostra rápidamente y empieza a cometer errores. O podemos construirnos un “comprobador de semáforos”, poniendo una fotocélula delante de cada luz, y una cámara que cuente los coches en cada calle, y enviar eso al PC, y hacer un programa que compruebe si nuestro programa está haciéndolo bien…

¿Qué pasa si nos equivocamos con el programa de comprobación? ¿Cómo comprobamos el programa de comprobación? ¿Con el original? ¿Y si se combina un error en ambos que hace que el sistema resultante funcione bien en apariencia, ponemos el programa en producción, y a los diez minutos hemos armado la de dios en la calle?

Pero aun hay más. Si se estropea un sensor del programa de comprobación, ¿qué? ¿Qué pasará cuando el semáforo nos envíe datos incorrectos porque se haya averiado? ¿De cuántas formas se puede averiar? ¿Y si se avería de una que no hemos contemplado, y lo que es peor, nuestra máquina de estados de semáforos se queda pillada en un estado del que no puede salir?

Asusta, ¿eh? Bueno, no mucho, pero son situaciones de la Vida Real ™ que se parecen mucho a las que yo tengo que testear, y es por eso por lo que no entiendo cómo un programa que presenta unos datos sacados de una base de datos puede requerir tantos tests y tanta martingala. Comprendo que cuando hay cálculos y operaciones matemáticas podría haber algo de complejidad, pero muchas veces es el propio gestor de bases de datos el que hace las operaciones. También entiendo que pueda haber interrelaciones entre distintas partes, pero, sinceramente, un control de almacén, o una facturación son algo trivial (o al menos así lo creo) comparado con un protocolo de comunicaciones más o menos decente.

Tampoco he hablado del dominio de la aplicación y de los tests. En el caso de los semáforos, ¿a quién debemos dejar que programe los tests? ¿Cómo podemos testear todas las condiciones posibles, o más bien verificar todos los tipos de condiciones que se puedan generar? Y lo que es peor, ¿estamos seguros de que el software va a poder lidiar con todas las posibles condiciones, que no se va a generar una que termine no en un atasco, sino en un accidente con muertes? Ciertamente no es un tema sencillo del que no quiero hablar más aquí, pero que quizás tome en otro momento.

3 comentarios en “¿Por qué uso C++? (III)”

  1. Solo un comentario, un control de almacén o facturación son triviales y tienen menos complejidad que un protocolo… hasta que llega el usuario de verdad 🙁

  2. Fale, eso te lo acepto, je je. De momento gana el universo haciendo usuarios tontos.

    Pero piensa que con el protocolo tienes al propio protocolo y al usuario.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *