c++

You are currently browsing articles tagged c++.

Normalmente, cuando uno desarrolla un programa, lo que hace para ver si funciona es probar el programa con unos datos de entrada y ver si la salida es correcta.Existe una técnica de programación que consiste en escribir pruebas automáticas para programas que aún no han sido escritos, de forma que al principio las pruebas evidentemente fallan (y si no fallan, es que no valen para nada) y, cuando el programa esté completo, funcionan.Esto puede parecer una carga de trabajo excesiva pero, siempre que se use con sentido común, tiene varias ventajas:

  • Se define el prototipo de entrada y salida de datos desde el principio.
  • Se descubren errores prematuros que podrían pasar desapercibidos y hacernos dar mil vueltas por el código tratando de averiguar dónde está el problema.
  • Constituyen una prueba de que tu programa funciona, diga lo que diga el profesor.
  • Como son automáticos (los hace el ordenador, no tú), puedes ejecutarlos siempre que escribas algún trozo de código nuevo, o cambies algo, para asegurarte que no has roto nada. Esto aumenta la confianza entre miembros de equipos de desarrollo.

Querría matizar que hay que usarlos con sentido común. Si no somos capaces de descubrir si el problema está en nuestra función de prueba automática o en el código de la aplicación que hemos escrito, no valen para nada. No obstante, coger práctica en la escritura de pruebas conlleva un tiempo de aprendizaje.

Un ejemplo sencillo

Para que todo este rollo deje de sonar a chino, vayamos con un ejemplo facilito:

char pasar_caracter_a_mayusculas(char caracter) {
        int const SEPARACION_MINUS_MAYUS = 'A'-'a';
        caracter += SEPARACION_MINUS_MAYUS;
        return caracter;
        }

string pasar_texto_a_mayusculas(string texto) {
        // leer cada caracter y pasarlo a mayúsculas
        for (int i=0; i<texto.length(); i++) {
                texto[i] = pasar_caracter_a_mayusculas(texto[i]);
                }
        return texto;
        }

¿Ves el error? Porque hay uno… Está claro que es difícil cazarlo a simple vista, pero si probamos la función con un texto al azar nos daremos cuenta enseguida del problema:

int main(int argc, char* argv[]) {
        string text = "Los programadores.";
        cout << pasar_texto_a_mayusculas(text);
        return 1;
        }

Lo ejecutamos y aparece:

,OSPROGRAMADORES

Vale, tenemos un error en el código: se intentan pasar a mayúsculas tanto los caracteres que no son letras como los que ya son mayúsculas. En vez de empezar a retocar el código directamente, vamos a escribir una función que demuestra que nuestra función “pasar_texto_a_mayusculas” tiene un fallo:

bool prueba_pasar_texto_a_mayusculas(string texto_entrada, string texto_salida) {
        if (pasar_texto_a_mayusculas(texto_entrada) == texto_salida)
                return true;  // Test OK!
        else
                return false;  // Test Failed!
        }

int main(int argc, char* argv[]) {
        if (prueba_pasar_texto_a_mayusculas("Los programadores.", "LOS PROGRAMADORES."))
                cout << "pasar_texto_a_mayusculas funciona!\n";
        else
                cout << "pasar_texto_a_mayusculas falla!\n";
        return 1;
        }

Lo ejecutamos y aparece por la pantalla:

pasar_texto_a_mayusculas falla!

Ahora que hemos demostrado que hay un problema, vamos a escribir el código para arreglarlo. Cuando lo arreglemos, la prueba lo demostrará:

char pasar_caracter_a_mayusculas(char caracter) {
    int const SEPARACION_MINUS_MAYUS = 'A'-'a';
    if (caracter >= 'a' && caracter <= 'z')  // caracter es una letra minúscula
        caracter += SEPARACION_MINUS_MAYUS;
    return caracter;
    }

Si ejecutamos de nuevo nuestro programa veremos:

pasar_texto_a_mayusculas funciona!

¡Genial!

Observaciones

En este ejemplo, el error no estaba en la función que hemos comprobado, sino en otra a la que la primera llamaba. Idealmente, se debería escribir una función de prueba por cada función “real” que se implementa, para que sepamos inmediatamente en qué función está el fallo.

No basta con probar la función con un caso general: es muy importante probarla con casos límite, como textos vacíos, todo mayúsculas, todo minúsculas, etc. La mayoría de las veces, los problemas aparecen en esos casos límite.

De nuevo, hay que usar el sentido común: si no te sientes cómodo escribiendo funciones de prueba y tardas horas en hacerlo, olvídalo. Es sólo una técnica que puede ser tanto una ayuda maravillosa como una lacra absurda. Lo importante es saber que existe, por si algún día nos interesase usarla.

Las funciones de prueba son todas muy parecidas: consisten en pasar una serie de argumentos de entrada a una función y comprobar que la salida es la esperada. Usa el clásico copiar & pegar siempre que puedas. También existen frameworks (programas que sirven para escribir programas basados en ellos) que facilitan la tarea, aunque yo no he usado ninguno de C++ todavía.

Agradecimientos

Gracias a la comunidad de Python y Zope/Plone por hacer que me llegue a gustar tanto el Test-Driven Development como para que me anime a escribir sobre ello, y a la gente que ha confiado en mí para que pueda publicar esto en el foro de MareMonstrum. :-)

Related articles

Tags: , , ,

Muchas veces al programar nos enfrascamos demasiado en la inicialización de variables, uso de estructuras, etc. y perdemos la noción de lo que estamos haciendo realmente, llegando incluso a programar funciones que no hacen lo que queremos ni de lejos.

Una buena idea es escribir en varias fases, de forma iterativa, escribiendo primero el flujo general del programa, y a partir de ahí ir perfilándolo por partes, progresivamente, en vez de intentar hacerlo todo de una vez. Si es necesario, se puede empezar con comentarios.

Supongamos que queremos pasar un texto a mayúsculas. ¿Qué datos de entrada y salida queremos? Básicamente, queremos pasar un texto como entrada y que nos salga convertido todo a mayúsculas, lo que se traduce como:

string pasar_a_mayusculas(string texto);

Vale, ya tenemos lista la interfaz de la función. Lo siguiente es preguntarnos cómo se pasa un texto entero de mayúsculas a minúsculas: pasando a mayúsculas cada uno de sus caracteres:

string pasar_a_mayusculas(string texto) {
    // leer cada letra y pasarla a mayúsculas
    }

Vamos avanzando. Ahora describamos la última tarea:

string pasar_a_mayusculas(string texto) {
    // leer cada letra y pasarla a mayúsculas
    for (i=0; i<n_letras; i++) {
        texto[i] = pasar_letra_a_mayusculas(texto[i]);
        }
    return texto;
    }

char pasar_letra_a_mayusculas(char letra) {
    letra += SEPARACION_MINUS_MAYUS;
    return letra;
    }

La distancia entre mayúsculas y minúsculas es simplemente (A-a), o (Z-z), etc.:

char pasar_letra_a_mayusculas(char letra) {
    int const SEPARACION_MINUS_MAYUS = 'A'-'a';
    letra += SEPARACION_MINUS_MAYUS;
    return letra;
    }

Si lo probamos con el texto “Los programadores.”, obtenemos “,OS PROGRAMADORES(corcheas)”. Vaya, nos hemos equivocado en algo, pero no pasa nada. Tenemos que convertir a mayúsculas sólo lo que sean letras minúsculas y dejar intacto el resto de caracteres.:

char pasar_letra_a_mayusculas(char letra) {
    int const SEPARACION_MINUS_MAYUS = 'A'-'a';
    if // letra es una letra minúscula
        letra += SEPARACION_MINUS_MAYUS;
    return letra;
    }

Vale, tenemos montado un lío de nombres, vamos a renombrar nuestras funciones para hacerlo todo más claro:

char pasar_caracter_a_mayusculas(char caracter) {
    int const SEPARACION_MINUS_MAYUS = 'A'-'a';
    if // caracter es una letra minúscula
        caracter += SEPARACION_MINUS_MAYUS;
    return caracter;
    }

string pasar_texto_a_mayusculas(string texto) {
    // leer cada caracter y pasarlo a mayúsculas
    for (int i=0; i<texto.length(); i++) {
        texto[i] = pasar_caracter_a_mayusculas(texto[i]);
        }
    return texto;
    }

Vale, ahora se entiende mejor. Nuestro siguiente objetivo consiste en averiguar si el caracter es una letra minúscula. Eso quiere decir que está entre ‘a’ y ‘z’:

char pasar_caracter_a_mayusculas(char caracter) {
    int const SEPARACION_MINUS_MAYUS = 'A'-'a';
    if (caracter >= 'a' && caracter <= 'z')  // caracter es una letra minúscula
        caracter += SEPARACION_MINUS_MAYUS;
    return caracter;
    }

Parece que ya está. Lo probamos de nuevo y sale “LOS PROGRAMADORES.” ¡Bingo!

Con este ejemplo se pretende (de)mostrar cómo seguir un proceso iterativo y separar las tareas grandes en otras más pequeñas ayuda a no perder la visión general de qué es lo que se está haciendo y dejar un código más limpio, elegante y legible.

Es importante señalar que, a lo largo del desarrollo del programa nos hemos equivocado (se ha equivocado el autor). No pasa nada: como lo hemos desarrollado por partes, hemos descubierto rápidamente dónde estaba el error, que hemos corregido con una simple linea.

Related articles

Tags: , ,