MIGUEL Á. TOLEDO MARTÍNEZ • ERRORES COMUNES DE PROGRAMACIÓN • BUENAS PRÁCTICAS DE PROGRAMACIÓN • PROPUESTAS DE DESEMPEÑO • SUGERENCIAS DE PORTABILIDAD • OBSERVACIONES DE INGENIERÍA DE SOFTWARE • INDICACIONES DE PRUEBA Y DEPURACIÓN SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-1 MIGUEL Á. TOLEDO MARTÍNEZ ERRORES COMUNES DE PROGRAMACIÓN Bajo el título de buenas prácticas de programación se incluirán muchas sugerencias a lo largo de las lecciones para subrayar las prácticas que ayudarán a escribir programas más claros, comprensibles y fáciles de mantener, probar y depurar. Estas prácticas son sólo guías; sin duda usted desarrollará su propio estilo de programación. También subrayaremos varios errores comunes de programación (problemas que prever para evitar caer en estos errores en sus programas), consejos de desempeño (técnicas que le ayudarán a escribir programas más rápidos y de menor consumo de memoria), explicaciones de portabilidad (técnicas que le ayudarán a escribir programas que puedan ejecutarse con pocos o ningún cambio en diferentes tipos de computadora), observaciones sobre ingeniería de software (ideas y conceptos que afectan y mejoran la arquitectura general de un sistema de software y, en particular, en el caso de los sistemas de software grandes), así como indicaciones de prueba y depuración (sugerencias para probar sus programas y ayudarle a aislar y eliminas bugs) 1. 2. 3. 4. 5. 6. Los errores como la división entre cero suceden durante la ejecución del programa, por lo que se llaman errores en tiempo de ejecución. La división entre cero por lo general es un error fatal, es decir, un error que provoca que el programa termine de inmediato sin haber concluido de manera apropiada su trabajo. Los errores no fatales permiten que los programas se ejecuten hasta el fin, generalmente produciendo resultados incorrectos. (Nota: en algunos sistemas, la división entre cero no es un error fatal. Lea la documentación del sistema con el que esté trabajando). Si olvida incluir el archivo iostream.h en un programa que acepta entrada desde el teclado o envía datos a la pantalla, el compilador emitirá un mensaje de error. La omisión del punto y coma al final de una instrucción es un error de sintaxis. Los errores de sintaxis se producen cuando el compilador no puede reconocer una instrucción. El compilador normalmente emite un mensaje de error que ayuda al programador a localizar y corregir la instrucción incorrecta. Los errores de sintaxis son violaciones al lenguaje. También se les conoce como errores de compilación y errores en tiempo de compilación, debido a que aparecen durante la fase de compilación. Si intenta utilizar el operador de módulo, %, con operandos que no son enteros, se producirá un error de sintaxis. Si cualquiera de los operadores ==, !=, >= y <= aparece con espacios entre el par de símbolos, sucederá un error de sintaxis. La inversión del orden del par de operadores de los operadores !=, >= y <= (al escribirlos como =!, => o =<) normalmente es un error de sintaxis. En algunos casos, escribir != como =! no será un error de sintaxis, pero casi con toda seguridad será un error de lógica. No confunda el operador de igualdad == con el operador de asignación =. El primero debe leerse como es igual que y el segundo como obtiene, obtiene el valor de o se le asigna el valor de. Algunas personas prefieren leer el operador de igualdad como doble igual. Como veremos pronto, la confusión de estos operadores no necesariamente causa un error de sintaxis fácil de reconocer, pero puede provocar errores muy sutiles de lógica. 8. Es un error utilizar el operador AND lógico (&&) en vez del operando AND a nivel de bits (&) y viceversa. 9. Utilizar el operador OR lógico ( || ) en vez del operador OR a nivel de bits ( | ) y viceversa. 10. El resultado del desplazamiento de un valor es indefinido si el operando derecho es negativo o si el operando derecho es más grande que el número de bits en los que está almacenado el operando izquierdo. 7. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-2 MIGUEL Á. TOLEDO MARTÍNEZ 11. Las funciones de manipulación de cadenas diferentes a memmove() que copian caracteres tienen resultados indefinidos cuando el copiado se realiza entre partes de la misma cadena. 12. La colocación de un punto y como (;) justo después del paréntesis derecho tras la condición de una estructura if con frecuencia es un error de lógica (aunque no un error de sintaxis). El punto y coma provocará que el cuerpo de la estructura if esté vació,, por lo que la estructura misma no llevaría a cabo ninguna acción, sin importar si la condición es verdadera o no. Peor aún, la instrucción original del cuerpo de la estructura if ahora se volverá una instrucción en secuencia con la estructura if y siempre se ejecutará, causando muchas veces que el programa genere resultados incorrectos. 13. Dividir los identificadores insertando caracteres de espacios en blanco (por ejemplo, escribiendo main como ma in) es un error de sintaxis. 14. El empleo de una palabra reservada como identificador es un error de sintaxis. 15. Si olvida una o ambas llaves delimitadoras de una instrucción compuesta, pueden producirse errores de sintaxis o de lógica en el programa. 16. Un punto y coma tras la condición de una estructura if genera un error de lógica en las estructuras if de selección única y un error de sintaxis en las estructuras if de doble selección (si la parte if contiene una instrucción en su cuerpo). 17. Si no se indica en el cuerpo de una estructura while una acción que en algún momento provoque que la condición se vuelva false, lo común es que se presente un error llamado ciclo infinito, en el que la estructura de repetición nunca termina. 18. La palabra reservada while no se debe escribir con W mayúscula (como While), ya que es un error de sintaxis (recuerde que C++ es un lenguaje sensible a las mayúsculas y minúsculas). Todas las palabras reservadas de C++, como while, if y else, sólo utilizan minúsculas. 19. Si los contadores y totales no se inicializan, el resultado del programa probablemente sea incorrecto. Este es un ejemplo de error de lógica. 20. En los ciclos controlados por contador, debido a que el contador de ciclo tiene un valor mayor en uno que su último valor legítimo (es decir 11, en el caso de que se esté contando del 1 al 10), es común que, si se toma el valor del contador para efectuar un cálculo tras terminar el ciclo, se genere un error por diferencia de uno. 21. La selección de un valor centinela que también es un valor de datos válido es un error de lógica. 22. Los intentos por dividir entre cero causan errores fatales. 23. El uso de números de punto flotante de una manera que suponga que se representan con precisión puede dar resultados incorrectos. La mayoría de las computadoras sólo representan de manera aproximada a los números de punto flotante. 24. Utilizar el operador de incremento o decremento en una expresión que no sea un nombre de variable simple (por ejemplo, ++(x+1)) es un error de sintaxis. 25. Dado que los valores de punto flotante pueden ser aproximados, el control de los ciclos con contador mediante variables de punto flotante puede dar como resultado valores de contador imprecisos y pruebas de terminación incorrectas. 26. El empleo de un operador relacional incorrecto o de un valor final de contador de ciclo equivocado en la condición de una estructura while o for puede provocar errores por diferencia de uno. 27. Cuando la variable de control de una estructura for se define desde el principio en la sección de iniciación del encabezado de la estructura for, el empleo de la variable de control fuera del cuerpo de la estructura es un error de sintaxis. 28. Indicar comas en lugar de los dos puntos y coma en el encabezado for es un error de sintaxis. 29. Un punto y coma inmediatamente a la derecha del paréntesis derecho de un encabezado for hace que el cuerpo de dicha estructura sea una instrucción vacía. Comúnmente éste es un error de lógica. 30. No emplear el operador relacional adecuado para la condición de continuación de un ciclo que cuenta hacia abajo (como cuando se utiliza incorrectamente i <= 1 en un ciclo que cuenta hacia abajo hasta 1) podría generar un error de lógica que produciría resultados incorrectos al ejecutarse el programa. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-3 MIGUEL Á. TOLEDO MARTÍNEZ 31. Si no se incluye el archivo math.h en un programa que maneje funciones de la biblioteca matemática se generará un error de sintaxis. 32. Olvidar una instrucción break cuando es necesaria en una instrucción switch es un error de lógica. 33. La omisión del espacio entre la palabra case y el valor entero que se está probando en una estructura switch puede provocar un error de lógica. Por ejemplo, escribir case3: en lugar de case 3: simplemente crea una etiqueta no usada. El problema es que la estructura switch no efectuará las acciones correctas cuando la expresión de control del switch tenga un valor de 3. 34. Si no se procesan los caracteres de salto de línea y otros caracteres de espacio en blanco que aparecen en la entrada durante la lectura de caracteres uno por uno, es posible que sucedan errores de lógica. 35. Indicar varias etiquetas case iguales en una estructura switch es un error de programación. 36. Los ciclos infinitos suceden cuando la condición de continuación del ciclo de una estructura while, for o do/while nunca se vuelve false. Para evitarlos, asegúrese de que el valor de la condición cambie en algún lugar del encabezado o del cuerpo del ciclo, para que la condición pueda, en algún momento volverse false. 37. Aunque 3 < x < 7 es una condición matemáticamente correcta, en C++ no se evalúa correctamente. Para obtener en C++ la evaluación correcta, utilice (3 < x && x < 7) 38. En las expresiones que utilizan el operador &&, es posible que una condición (a la que llamaremos condición dependiente) requiera que otra condición sea true para que tenga sentido evaluar la condición dependiente. En este caso, dicha condición debe ponerse tras la otra condición o podría suceder un error. 39. Emplear el operador == para realizar una asignación o utilizar el operador = para realizar una igualdad son errores de lógica. 40. Es un error tratar de leer de un ostream (o de cualquier otro flujo de sólo salida) 41. Es un error tratar de escribir a un istream (o a cualquier otro flujo de sólo entrada) 42. Es un error no colocar paréntesis para forzar la precedencia adecuada cuando se utiliza el operador de inserción de flujo << o el operador de extracción de flujo >> que tienen una precedencia relativamente alta. 43. El establecimiento de anchura se aplica sólo para la siguiente inserción o extracción. Después de ello la anchura se establece implícitamente a 0, es decir, los valores de salida serán tan anchos como necesiten serlo. La función width sin argumentos devuelve el valor actual. Suponer que el establecimiento de anchura se aplica a todas las salidas es un error de lógica. 44. Cuando no se proporciona un campo lo suficientemente ancho para manejar las salidas, éstas se imprimirán tan anchas como necesiten serlo lo que causará, posiblemente, que sean difíciles de leer. 45. Es un error abrir un archivo existente para salida (ios::out) cuando, de hecho, el usuario quiere conservar dicho archivo, ya que el contenido de éste se descarta sin aviso alguno. 46. El uso de un objeto ofstream incorrecto para referirse a un archivo. 47. Es un error no abrir un archivo antes de intentar referenciarlo en un programa. BUENAS PRÁCTICAS DE PROGRAMACIÓN 1. 2. 3. Escriba sus programas de C++ sencillo y directamente. Esto a veces se conoce como mantelo simple. No estire el lenguaje intentando usos raros. Lea los manuales de la versión de C++ que esté empleando. Consulte estos manuales con frecuencia para asegurar que tenga presente el rico conjunto de características de C++ y que esté empleando correctamente estas características. Su computadora y su compilador son buenos maestros. Si tras la lectura de su manual del lenguaje C++ no está seguro del funcionamiento de alguna característica de C++, experimente con un pequeño programa de prueba y vea lo que sucede. Ajuste las opciones de su compilador para que le SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-4 MIGUEL Á. TOLEDO MARTÍNEZ 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. devuelva el máximo de avisos. Estudie cada mensaje que aparezca al compilar sus programas y corríjalos para eliminar los mensajes. Cada programa debe comenzar con un comentario que describa su propósito. Muchos programadores hacen que el último carácter impreso por una función sea un saldo de línea (\n) Esto asegura que la función dejará el cursor al inicio de una nueva línea. Las convenciones de esta naturaleza promueven la reutilización del software, meta clave en los entornos de desarrollo de software. Dentro de los corchetes que definen el cuerpo de una función, sangre el cuerpo de la función un nivel. Esto resalta la estructura funcional de los programas y ayuda a simplificar su lectura. Establezca una convención para el tamaño de las sangrías y luego aplíquela de manera uniforme. La tecla Tab sirve para crear sangrías, pero las tabulaciones pueden variar. Recomendamos manejar tabulaciones de ¼ de pulgada o (de preferencia) de tres espacios por cada nivel de sangría. Algunos programadores prefieren declarar las variables en líneas separadas. Este formato permite la fácil inserción de comentarios descriptivos junto a cada declaración. Para hacer más legibles sus programas, ponga espacios después de las comas (, ) La declaración de variables con nombres significativos ayuda a que los programas estén auto documentados, es decir, que resulte más fácil entenderlos simplemente leyéndolos en lugar de tener que consultar manuales o hacer referencia a demasiados comentarios. Evite los identificadores que comiencen con uno o dos caracteres de subrayado, pues podría ser que el compilador de C++ utilice nombres de este tipo para fines internos. Esto evitará que los nombres que usted determine se confundan con los nombres que el compilador seleccione. Siempre inserte una línea en blanco antes de una declaración que aparezca entre instrucciones ejecutables. Esto resalta las declaraciones y contribuye a la claridad del programa. Si prefiere poner declaraciones al inicio de una función, sepárelas de las instrucciones ejecutables de la función por medio de una línea en blanco que resalte el punto donde terminan las declaraciones e inician las instrucciones ejecutables. Ponga espacios en ambos lados de los operadores binarios. Con esto se resalta el operador y se simplifica la lectura del programa. Como en álgebra, para hacer más clara a una expresión es aceptable agregarle paréntesis innecesarios. Dichos paréntesis se llaman paréntesis redundantes. Estos se emplean normalmente para agrupar subexpresiones de expresiones más grandes. Sangre la instrucción del cuerpo de una estructura if para que resalte la estructura y simplificar la lectura del programa. En los programas no debe haber más que una instrucción por línea. Es posible distribuir una instrucción grande sobre varias líneas. Si tiene que dividir una instrucción sobre varias líneas, seleccione puntos de ruptura que tengan sentido, como después de una coma en el caso de una lista separada por comas o después de un operador en el caso de una expresión larga. Si necesita dividir una instrucción en varias líneas, sangre todas las líneas subsecuentes. Al escribir expresiones que contengan muchos operadores, consulte la tabla de precedencia de los operadores. Confirme que los operadores de la expresión se ejecutan en el orden que espera. Si no está seguro del orden de evaluación de una expresión compleja, coloque paréntesis para forzar el orden, justo igual como lo haría en una expresión algebraica. Además, observe que algunos operadores, como el de asignación (=), se asocian de derecha a izquierda, y no de izquierda a derecha. La consistencia en la aplicación razonable de convenciones de sangrado a sus programas simplificará notablemente su lectura. Sugerimos una tabulación fija de ¼ de pulgada o tres espacios en blanco por cada sangría. El seudo código se emplea con frecuencia para pensar el programa durante el proceso de diseño. Luego el programa en seudo código se convierte a su equivalente en C++. Sangre las instrucciones de ambos cuerpos de las estructuras if/else. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-5 MIGUEL Á. TOLEDO MARTÍNEZ 23. Si hay varios niveles de sangrado, todos deben sangrarse con espacios iguales. 24. Siempre ponga llaves en las estructuras if/else (y en cualquier estructura de control) para ayudarle a evitar su omisión accidental, especialmente si después le agrega instrucciones a una cláusula if o else. 25. Algunos programadores prefieren colocar primero las llaves izquierda y derecha y después introducir las instrucciones que van dentro de ellas. Con esto se evita la omisión de alguna de dichas llaves. 26. Inicialice los contadores y totales. 27. Declare cada variable en una línea diferente. 28. Al efectuar divisiones entre una expresión cuyo valor podría ser cero, busque explícitamente esta condición y manéjela de manera adecuada (imprimiendo un mensaje de error) en lugar de permitir que suceda el error fatal. 29. Mediante un mensaje pídale al usuario todas las entradas de teclado. Dicho mensaje debe especificar la forma de la entrada y los valores especiales que pueda tener (como el valor centinela que debe indicar el usuario para terminar algún ciclo) 30. En los ciclos controlados por un valor centinela, las solicitudes de entrada de información deben recordarle explícitamente al usuario dicho valor centinela. 31. No compare la igualdad o desigualdad de los valores de punto flotante. En cambio, pruebe que el valor absoluto de la diferencia sea menor que un valor pequeño especificado. 32. La iniciación de variables cuando se declaran ayuda al programador a evitar los problemas provocados por datos no inicializados. 33. Los operadores unarios deben ponerse junto a sus operandos, sin espacios intermedios. 34. Controle los ciclos con contadores por medio de variables enteras. 35. Sangre las instrucciones del cuerpo de cada estructura de control 36. Ponga una línea en blanco antes y después de cada estructura de control, con el fin de resaltarlas en el programa. 37. Si hay demasiados niveles de anidamiento puede volverse difícil entender el programa. Como regla general, trate de evitar codificar más de tres niveles de sangrado. 38. El espaciado vertical de las estructuras de control, así como el sangrado de los cuerpos de las estructuras de control dentro de sus encabezados, le da al programa una apariencia bidimensional que simplifica en gran medida su lectura. 39. Para ayudarle a evitar los errores por diferencia de uno, utilice el valor final en la condición de una estructura while o for y el operador relacional <=. Por ejemplo, para un ciclo que imprime los valores 1 a 10, la condición de continuación del ciclo debería ser contador <= 10, en lugar de contador < 10 (lo que es un error por diferencia de uno) o contador < 11 (que, sin embargo, es correcta) No obstante, muchos programadores prefieren el llamado conteo basado en cero, en el que para contar 10 veces, habría que inicializar contador a cero y la prueba de continuación del ciclo sería contador < 10. 40. En las secciones de iniciación e incremento de las estructuras for sólo ponga expresiones relacionadas con las variables de control. Las manipulaciones de otras variables deben aparecer antes del ciclo (si sólo se ejecutan una vez, como las instrucciones de inicialización) o en el cuerpo del ciclo (si se ejecutan una vez por repetición, como en el caso de las instrucciones de incremento o decremento) 41. Aunque el valor de la variable de control puede cambiarse en el cuerpo del ciclo for, evite hacerlo, pues esta práctica puede generar sutiles errores de lógica. 42. Aunque las instrucciones que preceden a un ciclo for y las del cuerpo de éste con frecuencia pueden insertarse en el encabezado del for, evite hacerlo, pues podría hacer más difícil la lectura del programa. 43. De ser posible, limite a una sola línea el tamaño de los encabezados de las estructuras de control. 44. No emplee variables de tipo float o double para efectuar cálculos monetarios. La imprecisión de los números de punto flotante puede provocar errores que generarán valores monetarios incorrectos. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-6 MIGUEL Á. TOLEDO MARTÍNEZ 45. 46. 47. 48. 49. 50. Nota: están apareciendo bibliotecas de clases de C++ para manejar adecuadamente los cálculos monetarios. Agregue un caso default en las instrucciones switch. Los casos no probados explícitamente en una instrucción switch que no tenga un caso default son ignorados. Al incluir el caso default se enfoca al programador en la necesidad de procesar condiciones excepcionales. Hay situaciones en las que no es necesario el procesamiento default. Aunque las cláusulas case y el caso default de la estructura switch pueden suceder en cualquier orden, se considera una buena práctica de programación poner la cláusula default al último. En las estructuras switch, cuando la cláusula default se lista al último, no es necesaria la instrucción break. Algunos programadores incluyen este break por claridad y simetría con los demás case. Algunos programadores siempre incluyen llaves en las estructuras do/while, incluso cuando no son necesarias. Esto ayuda a eliminar las ambigüedades entre la estructura while y la estructura do/while que contiene una sola instrucción. Algunos programadores sienten que break y continue violan la programación estructurada. Y no las utilizan debido a que los efectos de estas instrucciones pueden lograrse por medio de técnicas de programación estructurada. En los programas C++ utilice exclusivamente la forma de E/S de C++, a pesar de que los programadores de C++ tienen disponible la E/S estilo C. 51. Cuando envíe a la salida expresiones, colóquelas entre paréntesis para impedir problemas de precedencia de operadores entre los operadores de la expresión y el operador <<. 52. Abra un archivo un archivo para entrada (utilizando ios::in) solamente si el contenido del archivo no debe ser modificado. Esto impide una modificación no intencional del contenido del archivo. Este es un ejemplo del principio de menor privilegio. PROPUESTAS DE DESEMPEÑO 1. 2. 3. 4. 5. 6. 7. 8. Utilizar las funciones y clases de la biblioteca estándar, en lugar de escribir sus propias versiones comparables, puede mejorar el desempeño de sus programas, debido a que han sido escritas cuidadosamente para que su desempeño sea eficiente. La estructura if/else anidada puede ser mucho más rápida que una serie de estructuras if de selección única, pues existe la posibilidad de salir con rapidez de la estructura una vez que se satisfacen las condiciones. En la estructura ilf/else, pruebe hacia el principio de la estructura las condiciones que más probablemente sean true. Esto permite que dicha estructura se ejecute con mayor rapidez y salga antes que en los casos que suceden con menos frecuencia. Cuando se emplean operadores de asignación abreviados, los programadores pueden escribir sus programas con un poco más de rapidez y los compiladores pueden compilar los programas a una velocidad un poco mayor. Algunos compiladores generan códigos que se ejecuta más rápido cuando se utilizan dichos operadores. Muchos de las propuestas de desempeño que mencionados en estas lecciones generan mejoras marginales, por lo que el lector podría estar tentado a ignorarlos. Sin embargo, cuando se pone una mejora supuestamente marginal en un ciclo que se repite muchas veces, se suele lograr un mejor desempeño. Evite poner dentro de los ciclos expresiones cuyos valores no cambien. Pero si lo hace, muchos de los compiladores refinados optimizadores actuales colocarán automáticamente tales expresiones fuera de los ciclos, cuando se genere el código de lenguaje de máquina. Muchos compiladores contienen características de optimización que mejorar el código que usted escribe, pero sigue siendo mejor que escriba un buen código desde el principio. En situaciones en las que la prioridad es el desempeño y donde la memoria es escasa o la velocidad de ejecución es crucial, podría ser necesario manejar enteros pequeños. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-7 MIGUEL Á. TOLEDO MARTÍNEZ 9. 10. 11. 12. 13. Utilizar enteros de tamaño pequeño puede provocar la ejecución más lenta del programa si las instrucciones de la máquina que los manipulan no son tan eficientes como aquéllas que manipulan los enteros de tamaño natural (por ejemplo, cuando debe efectuarse la extensión del signo en ellos) Las instrucciones break y continue bien empleadas se ejecutan más rápido que sus técnicas estructuradas correspondientes. En las expresiones que utilizan el operador &&, si las condiciones individuales son independientes entre ellas, haga que la condición que más probablemente sea false quede a la izquierda. En las expresiones que utilizan el operador ||, haga que la condición que más probablemente sea true sea la de la izquierda. Con esto es posible reducir el tiempo de ejecución del programa. Utilice la E/S sin formato para obtener el mejor desempeño en el procesamiento de archivos de alto volumen. Cierre explícitamente cada archivo tan pronto como sepa que el archivo ya no volverá a hacer referencia al mismo. Esto puede reducir el uso de recursos de un programa que continuará ejecutándose después de que ya no necesite un archivo particular. Esta práctica también mejora la claridad del programa. SUGERENCIAS DE PORTABILIDAD 1. 2. 3. 4. 5. 6. 7. 8. 9. Debido a que C es un lenguaje estandarizado, independiente del hardware y ampliamente difundido, es frecuente que las aplicaciones escritas en C puedan ejecutarse con pocas o ninguna modificación en un gran rango de sistemas de cómputo. El empleo de funciones y clases de la biblioteca estándar, en lugar de escribirlas usted mismo, puede mejorar la portabilidad de los programas, debido a que dicho software está incluido en prácticamente todas las implementaciones de C++. Aunque es posible escribir programas portables, hay muchos problemas entre los distintos compiladores de C y C++ y las distintas computadoras que pueden hacer difícil lograr la portabilidad. El simple hecho de escribir programas en C y C++ no lo garantizan. El programador con frecuencia tendrá que encarar directamente las variaciones entre compiladores y computadoras. C++ permite identificadores de cualquier longitud, pero el sistema y/o tipo de implementación de C++ podrían aplicar algunas restricciones a la longitud de los identificadores. Utilice identificadores de 31 caracteres o menos, para asegurar la portabilidad. Las manipulaciones de datos al nivel de bits son dependientes de la máquina. El resultado de desplazar a la derecha un valor con signo depende de la máquina. Algunas máquinas rellanan con ceros y otras utilizan el bit de signo. El tipo size_t es un alias dependiente del sistema para el tipo unsigned long o el tipo unsigned int. El mensaje que strerror() genera es dependiente del sistema. En el nuevo estándar preliminar de C++, el alcance de la variable de control declarada en la sección de inicialización de una estructura for es diferente del alcance en los compiladores de C++ anteriores,. El código C++ creado con los viejos compiladores de C++ podría fallar al compilarse en los compiladores que son compatibles con el estándar preliminar de C++. Las siguientes son dos estrategias de defensa para evitar este problema: defina las variables de control con nombres diferentes en cada estructura for o, si prefiere emplear el mismo nombre para las variables de control de varias estructuras for, defina la variable de control fuera y antes del primer ciclo for. 10. Las combinaciones de teclas para dar el fin de archivo son dependientes del sistema. 11. Probar por la constante simbólica EOF en lugar de –1 hace a los programas más portables. El estándar ANSI indica que EOF es un valor entero negativo (pero no necesariamente –1) Por lo tanto, EOF podría tener valores diferentes, según el sistema. 12. Dado que el tamaño de los int varía según el sistema, emplee enteros long si espera procesar enteros que caigan fuera del rango ±32767 y si desea ejecutar el programa en diferentes sistemas de cómputo. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-8 MIGUEL Á. TOLEDO MARTÍNEZ 13. Por compatibilidad con las versiones anteriores de estándar C++, el valor booleano true también puede representarse mediante cualquier valor distinto de cero y el valor booleano false como 0. 14. Cuando le indique al usuario cómo terminar la entrada desde el teclado, pídale que teclee un fin de archivo para terminar la entrada, en vez de pedirle <ctrl>d (para UNIX y Macintosh) o <ctrl>z (PC y VAX). OBSERVACIONES DE INGENIERÍA DE SOFTWARE 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. Utilice un enfoque de bloques de construcción para crear programas. Evite reinventar la rueda. Utilice las partes existentes donde sea posible; a esto se le llama reutilización de software, lo cual es crucial en la programación orientada a objetos. Al programar en C++, normalmente utilizará los siguientes bloques de construcción: clases y funciones de la biblioteca estándar de C++, clases y funciones creadas por usted mismo y clases y funciones de diversas bibliotecas no estándar comunes. Cualquier programa en C++ puede construirse con los siete tipos de estructuras de control: if, if/else, switch, while, do/while y for, combinándolas únicamente de dos maneras (apilamiento de estructuras de control y anidamiento de estructuras de control) Las instrucciones compuestas pueden ponerse en cualquier lugar del programa en el que pueda ir una instrucción sencilla. Así como pueden ponerse instrucciones compuestas en cualquier lugar donde pueda ir una instrucción sencilla, también es posible no tener ninguna instrucción, es decir, tener una instrucción vacía. Esta se representa poniendo un punto y coma (;) donde normalmente iría una instrucción. Cada refinamiento, así como la parte superior misma, es una especificación completa del algoritmo; lo que varía es el nivel de detalle. Muchos programas pueden dividirse lógicamente en tres fases: Una de inicio, que inicializa las variables del programa; otra de procesamiento, que acepta los datos de entrada y ajusta las variables de programa correspondientes; y otra de terminación, que calcula e imprime el resultado final. El programador termina el proceso de refinamiento descendente paso a paso cuando especifica el algoritmo con el detalle suficiente para poder convertir el seudo código en código de C++; a partir de aquí, la implementación del programa en C++ suele ser directa. La experiencia ha mostrado que la parte más difícil de resolver un problema en una computadora es desarrollar el algoritmo de solución. Una vez que se ha especificado un algoritmo correcto, el proceso de generación de un programa C++ funcional a partir de dicho algoritmo suele ser directo. Muchos programadores experimentados escriben programas sin emplear herramientas de desarrollo como el seudo código. Estos programadores sienten que su meta es revolver el problema en la computadora y que la escritura del seudo código simplemente retrasa la obtención de resultados. Aunque esto podría funcionar en el caso de problemas sencillos y conocidos, puede provocar errores serios y retrasar proyectos grandes y complejos. A veces se pone un punto y como justo después de un encabezado for para crear un ciclo de retardo. Tal ciclo for con un cuerpo vacío efectúa la cantidad indicada de ciclos sin hacer nada más que contar. Puede utilizar un ciclo de retardo, por ejemplo, para reducir la velocidad de un programa que genera salidas a la pantalla a una velocidad demasiado alta para poderlas leer. Existe un conflicto entre conseguir una ingeniería de software de calidad y lograr el mejor desempeño del software. Es frecuente que se logre una de estas metas a expensas de la otra. El estilo de E/S de C++ es a prueba de tipos. C++ permite un tratamiento común de la E/S de tipos predefinidos y tipos definidos por el usuario. Este tipo de comunidad facilita el desarrollo de software en general y la reutilización del software en particular. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-9 MIGUEL Á. TOLEDO MARTÍNEZ INDICACIONES PARA PRUEBA Y DEPURACIÓN 1. Los programadores normalmente escriben condiciones como x == 7 con el nombre de la variable a la izquierda y la constante a la derecha. Si el programador las invierte de modo que la constante esté a la izquierda y el nombre de la variable a la derecha como en 7 == x, el reemplazo accidental del operador == con = quedará protegido por el compilador. Este tratará esto como error sintáctico, pues a la izquierda de una instrucción de asignación sólo se puede poner un nombre de variable. Esto cuando menos evitará el desastre potencial de un error de lógica en tiempo de ejecución. 2. Utilice su editor de texto para buscar todas las apariciones de = en el programa y comprueba que cada operador esté en el lugar correcto. SUGERENCIAS, OBSERVACIONES Y CONSEJOS: PRIMERA PARTE SUGERENCIAS-10