¿Qué habilidades se requieren para cazar insectos?

La depuración es una actividad cíclica que implica pruebas de ejecución y corrección de código. La prueba que se realiza durante la depuración tiene un objetivo diferente que la prueba final del módulo. La prueba final del módulo tiene como objetivo demostrar la corrección, mientras que la prueba durante la depuración está dirigida principalmente a localizar errores. Esta diferencia tiene un efecto significativo en la elección de las estrategias de prueba.

Condiciones previas para una depuración efectiva

Para evitar el tiempo excesivo dedicado a la depuración, el programador debe estar mentalmente preparado para el esfuerzo. Los siguientes pasos son útiles para prepararse para la depuración.

  • Comprenda el diseño y el algoritmo : si está trabajando en un módulo y no comprende su diseño o sus algoritmos, la depuración será muy difícil. Si no comprende el diseño, entonces no puede probar el módulo porque no sabe lo que se supone que debe hacer. Si no comprende los algoritmos, le resultará muy difícil localizar los errores que se revelan en las pruebas. Una segunda razón para la importancia de comprender los algoritmos es que puede necesitar esa comprensión para construir buenos casos de prueba. Esto es especialmente cierto para algoritmos para estructuras de datos complejas.
  • Verificar la corrección : existen varios métodos para verificar la corrección de una implementación antes de la ejecución.
  • Pruebas de corrección : una comprobación de código útil es examinar el código utilizando los métodos lógicos de las pruebas de corrección. Por ejemplo, si conoce condiciones previas, invariantes, condiciones de terminación y condiciones posteriores para un ciclo, entonces hay algunas comprobaciones fáciles que puede hacer. ¿La precondición, junto con cualquier código de entrada de bucle, implica que la invariante es inicialmente verdadera? ¿El cuerpo del asa preserva la invariante? ¿La ejecución del cuerpo del bucle avanza hacia la terminación del bucle? ¿El invariante, junto con la condición de terminación del bucle y el código de salida del bucle, implica la condición posterior? Incluso si estas comprobaciones no encuentran todos los errores, a menudo obtendrá una mejor comprensión del algoritmo al realizar las comprobaciones.
  • Rastreo de código : a menudo, los errores se pueden detectar mediante el rastreo a través de la ejecución de varias llamadas a los servicios del módulo, comenzando con una variedad de condiciones iniciales para el módulo. Por razones psicológicas poco conocidas, el rastreo funciona mejor si está describiendo su rastreo a otra persona. Para que sea eficaz, el rastreo de un procedimiento o función debe realizarse suponiendo que las llamadas a otros procedimientos y funciones funcionen correctamente, incluso si son llamadas recursivas. Si trazas un procedimiento o función llamada, te encontrarás con demasiados niveles de abstracción. Esto generalmente conduce a la confusión. Si hay alguna duda sobre los procedimientos y funciones llamados, se pueden rastrear por separado para verificar que funcionan de acuerdo con las especificaciones. Nuevamente, el rastreo puede no detectar todos los errores, pero puede mejorar su comprensión de los algoritmos.
  • Revisiones por pares : una revisión por pares implica que un compañero examine su código en busca de errores. Para ser efectivo, el par ya debe estar familiarizado con el algoritmo, o debe recibir el algoritmo y el código de antemano. Cuando el revisor se reúne con el escritor del código, el escritor del código debe presentar el código con explicaciones de cómo implementa correctamente el algoritmo. Si el revisor no comprende o no está de acuerdo con parte de la implementación, discute esa parte hasta que ambos estén de acuerdo sobre si es un error o no. El papel del revisor es solo como una ayuda para detectar errores. Se deja al implementador corregirlos. Gran parte del beneficio de una revisión por pares se deriva de la psicología de presentar cómo funciona algo. A menudo, el escritor del código descubre sus propios errores durante la revisión. En cualquier caso, es útil que un extraño revise su trabajo para obtener una perspectiva diferente y descubrir puntos ciegos que parecen ser inherentes a la evaluación de su propio trabajo. Al igual que el rastreo de código, las revisiones por pares pueden llevar mucho tiempo. Para el trabajo en clase, no es probable que una revisión por pares de un módulo completo se amortice en términos de valor de instrucción. Por lo tanto, las revisiones deben restringirse a segmentos cortos de código. Para la programación comercial, sin embargo, la calidad del código es mucho más importante. Por lo tanto, las revisiones por pares son una parte importante de un programa de garantía de calidad de software.
  • Anticípese a los errores : desafortunadamente, los humanos cometen errores con argumentos de corrección y a veces pierden casos en el rastreo de código, y los pares tampoco siempre detectan errores. Por lo tanto, un programador debe estar preparado para algunos errores restantes en el código después de los pasos enumerados anteriormente. Con suerte, no habrá demasiados.

Requisitos para la depuración

Para depurar efectivamente el código, necesita dos capacidades. Primero, debe poder acceder de manera eficiente a los servicios proporcionados por el módulo. Luego, debe poder recuperar información sobre los resultados de las llamadas, los cambios en el estado interno del módulo, las condiciones de error y lo que estaba haciendo el módulo cuando se produjo un error.

Conduciendo el módulo

Para depurar efectivamente un módulo, es necesario tener algún método para recurrir a los servicios provistos por el módulo. Hay dos métodos comunes para hacer esto.

  • Controladores cableados: un controlador cableado es un módulo de programa principal que contiene una secuencia fija de llamadas a los servicios proporcionados por el módulo que se está probando. La secuencia de llamadas puede modificarse reescribiendo el código del controlador y volviendo a compilarlo. Para probar módulos cuyo comportamiento está determinado por un número pequeño de casos, los controladores cableados ofrecen la ventaja de ser fáciles de construir. Sin embargo, si hay demasiados casos, tienen la desventaja de que se requiere un esfuerzo considerable para modificar la secuencia de llamadas.
  • Intérpretes de comandos : un intérprete de comandos controla el módulo bajo prueba al leer la entrada e interpretarla como comandos para ejecutar llamadas a los servicios del módulo. Los intérpretes de comandos se pueden diseñar para que los comandos se puedan ingresar de forma interactiva o leerse desde un archivo. La interpretación de comandos interactivos suele ser de gran valor en las primeras etapas de la depuración, mientras que el modo por lotes generalmente es mejor para las etapas posteriores de la depuración y las pruebas finales. La desventaja principal de los intérpretes de comandos es la complejidad de escribir uno, incluida la posibilidad de que se dedique mucho tiempo a la depuración del código del intérprete. Esto se ve mitigado por el hecho de que la mayoría del código difícil es reutilizable y se puede adaptar fácilmente para probar diferentes tipos de módulos. Para casi todos los módulos de estructura de datos, la flexibilidad que ofrecen los intérpretes de comandos los convierte en una opción preferida.

Obteniendo información sobre el módulo

Poder controlar la secuencia de llamadas a los servicios del módulo tiene poco valor a menos que también pueda obtener información sobre los efectos de esas llamadas. Si los servicios generan resultados, entonces hay información disponible sin ningún esfuerzo adicional. Sin embargo, para muchos módulos, incluidos los módulos de estructura de datos, el efecto principal de las llamadas a los servicios es un cambio en el estado interno del módulo. Esto lleva a la necesidad de tres tipos de información para la depuración.

  • Estado del módulo : los módulos de estructura de datos generalmente tienen servicios para insertar y eliminar datos. Estos servicios casi nunca generan resultados por sí solos y, a menudo, no devuelven ninguna información a través de parámetros. Por lo tanto, para probar o depurar el módulo, el programador debe agregar código que proporcione información sobre los cambios en el estado interno del módulo. Por lo general, el programador agrega procedimientos que pueden mostrar el contenido de datos del módulo. Estos procedimientos están disponibles para el módulo del controlador, pero generalmente se eliminan o se hacen privados cuando se completa la prueba. Para la depuración, es útil tener procedimientos que muestren la estructura interna y el contenido.
  • Errores de módulo : cuando un módulo tiene un estado interno complejo, con un código incorrecto, generalmente es posible que surjan estados no válidos. Además, es posible que las subrutinas privadas se llamen incorrectamente. Ambas situaciones son errores de módulo. Cuando sea práctico, se puede agregar código al módulo para detectar estos errores.
  • Estado de ejecución : para localizar la causa de los errores del módulo, es necesario saber qué servicios y subrutinas privadas se han llamado cuando se produce el error. Este es el estado de ejecución del módulo. Un método común para determinar el estado de ejecución es la adición de instrucciones de impresión de depuración que indican la entrada y salida de segmentos de código.

Principios de depuración

  • Informe las condiciones de error de inmediato : se dedica mucho tiempo de depuración a concentrarse en la causa de los errores. Cuanto antes se detecte un error, más fácil será encontrar la causa. Si se detecta un estado de módulo incorrecto tan pronto como surge, la causa a menudo se puede determinar con un esfuerzo mínimo. Si no se detecta hasta que los síntomas aparezcan en la interfaz del cliente, puede ser difícil reducir la lista de posibles causas.
  • Maximice la información útil y la facilidad de interpretación : es obvio que maximizar la información útil es deseable y que debe ser fácil de interpretar. La facilidad de interpretación es importante en las estructuras de datos. Algunos errores de módulo no se pueden detectar fácilmente agregando verificaciones de código porque dependen de toda la estructura. Por lo tanto, es importante poder mostrar la estructura en una forma que pueda escanearse fácilmente para verificar que sea correcta.
  • Minimice la información inútil y que distrae : demasiada información puede ser tan perjudicial como muy poca. Si tiene que trabajar con una impresión que muestra la entrada y la salida de cada procedimiento en un módulo, le resultará muy difícil encontrar el primer lugar donde algo salió mal. Idealmente, los informes de estado de ejecución del módulo deberían emitirse solo cuando se haya producido un error. Como regla general, se debe preferir la información de depuración que dice “el problema está aquí” a favor de los informes que dicen “el problema no está aquí”.
  • Evite el código de prueba complejo de un solo uso : una razón por la cual es contraproducente agregar verificaciones de corrección del módulo para errores que involucren a toda la estructura es que el código para hacerlo puede ser bastante complejo. Es muy desalentador pasar varias horas depurando un problema, solo para descubrir que el error estaba en el código de depuración, no en el módulo bajo prueba. El código de prueba complejo solo es práctico si las partes difíciles del código son reutilizables.

Ayudas de depuración

Ayudas incorporadas al lenguaje de programación.

  • Declaraciones de afirmación : algunos compiladores de Pascal y todos los compiladores de C que cumplen con el estándar ANSI tienen procedimientos de afirmación . El procedimiento de aserción tiene un único parámetro, que es una expresión booleana. Cuando se ejecuta una llamada a afirmar, se evalúa la expresión. Si se evalúa como verdadero, no pasa nada. Si se evalúa como falso, el programa termina con un mensaje de error. El procedimiento de aserción se puede usar para detectar e informar condiciones de error.
  • Trazabilidad : muchos compiladores de Pascal generan código que genera trazas cuando se produce un error de tiempo de ejecución. Un rastreo es un informe de la secuencia de subrutinas que están actualmente activas. A veces, un rastreo también indicará números de línea en las subrutinas activas. Si está disponible, un rastreo revela dónde se produjo el error de tiempo de ejecución, pero depende del programador determinar dónde se encuentra la causa.
  • Depuradores de uso general : muchos sistemas informáticos o compiladores vienen con programas de depuración. Por ejemplo, la mayoría de los sistemas operativos UNIX tienen depuradores de propósito general como sdb y dbx. Los programas de depuración proporcionan capacidades para recorrer un programa línea por línea y ejecutar un programa con puntos de interrupción establecidos por el usuario. Cuando una línea con un punto de interrupción está a punto de ejecutarse, el programa se interrumpe para que el usuario pueda examinar o modificar los datos del programa. Los programas de depuración también pueden proporcionar rastreos en caso de errores en tiempo de ejecución. Los depuradores a menudo son difíciles de aprender a usar de manera efectiva. Si son la única herramienta utilizada para la depuración, es probable que no ahorren mucho tiempo. Por ejemplo, la depuración de un módulo de estructura de datos con un depurador, pero sin un buen controlador de prueba, probablemente llevará mucho tiempo a obtener información fragmentaria sobre los errores.

Técnicas de depuración

Prueba incremental

En un buen diseño para un módulo complejo, el código se divide en numerosas subrutinas, la mayoría de las cuales no tienen más de 10 a 15 líneas de largo. Para un módulo diseñado de esta manera, las pruebas incrementales ofrecen ventajas significativas. Para las pruebas incrementales, las subrutinas se clasifican en niveles, siendo las subrutinas de nivel más bajo aquellas que no llaman a otras subrutinas. Si la subrutina A llama a la subrutina B, entonces A es una subrutina de nivel más alto que B. La estrategia de prueba incremental es probar las subrutinas individualmente, trabajando desde el nivel más bajo hasta los niveles más altos. Para realizar pruebas en los niveles inferiores, el controlador de prueba debe ser capaz de llamar a las subrutinas de bajo nivel directamente, o el programador debe ser capaz de proporcionar varios casos de entrada de prueba, cada uno de los cuales solo involucra un pequeño número de subrutinas de bajo nivel. La elaboración de estos casos de prueba requiere una comprensión profunda de los algoritmos del módulo, junto con una buena imaginación. La fortaleza de las pruebas incrementales es que en cualquier momento del proceso, solo hay un pequeño número de lugares donde pueden surgir errores. Esto automáticamente hace que la información de depuración sea más significativa y conduce a una determinación más rápida de la causa de un error. Una segunda razón para las pruebas incrementales es que reduce en gran medida las posibilidades de tener que lidiar con dos o más errores al mismo tiempo. Los errores múltiples a menudo generarán indicaciones de error confusas.

Controles de cordura

El código de bajo nivel en una estructura de datos compleja a menudo se escribe asumiendo que el código de nivel superior implementa correctamente el algoritmo deseado. Por ejemplo, el código de bajo nivel puede escribirse suponiendo que una determinada variable o parámetro no puede ser NULL. Incluso si el algoritmo justifica esa suposición, puede ser una buena idea realizar una prueba para ver si se cumple la condición porque el código de nivel superior puede implementarse incorrectamente. Este tipo de verificación se llama verificación de cordura. Si hay un procedimiento de aserción disponible, puede usarse para las comprobaciones. La ventaja de los controles de cordura es que brindan detección temprana de errores.

Constantes booleanas para activar o desactivar el código de depuración

Si el código de depuración se agrega a un módulo, a menudo es rentable encerrarlo en una declaración if que esté controlada por una constante booleana agregada al módulo. Al hacer esto, el código de depuración se puede desactivar fácilmente, pero estará disponible si es necesario más adelante. Se deben usar diferentes constantes para las diferentes etapas de las pruebas, de modo que se minimice la información inútil.

Variables de error para controlar el comportamiento del programa después de errores

Cuando se agregan declaraciones de impresión de depuración al código, existe la posibilidad de una tremenda explosión de información inútil. El problema es que se ejecutará una declaración de impresión por sí misma, haya o no un error. Por lo tanto, si el error no aparece hasta que se haya realizado una gran cantidad de llamadas de subrutina, la mayoría de los mensajes solo le dicen que todo está bien hasta ahora. Este problema se amplía enormemente si el código agregado muestra la estructura interna de una estructura de datos. Suponiendo que el módulo tiene comprobaciones de sanidad para detección de errores, se puede agregar una variable booleana de error al módulo. Debe inicializarse en falso, lo que indica que no hay error. Para la mayoría de las estructuras de datos, hay una operación Crear para la inicialización. La variable de error se puede inicializar al mismo tiempo. En lugar de salir, las comprobaciones de cordura se modifican para que establezcan la variable de error en verdadero. Luego, el código de depuración se puede incluir en las declaraciones if para que la información solo se imprima cuando se hayan detectado errores. Una posible aplicación de este método es obtener información de rastreo cuando no esté disponible de otra manera.

Técnicas de rastreo

Para obtener un rastreo, use un error booleano establecido por las comprobaciones de cordura. En varios lugares del módulo, agregue el código de depuración controlado por la variable de error que imprime la posición actual. Por lo general, es más económico ejecutar primero el código con una verificación de sanidad final. Entonces solo necesita agregar el código de depuración controlado en los lugares donde se llama a la subrutina que contiene la verificación de cordura.

Corrección de errores de código

Para la corrección de errores detectados por las pruebas, este es un principio muy importante a tener en cuenta: corrija la causa, no el síntoma .

Supongamos que ejecuta un código y obtiene un error de segmentación. Después de algunas comprobaciones, determina que se pasó un puntero NULL a un procedimiento que no verificó NULL, pero que de todos modos intentó hacer referencia a través del puntero. ¿Debería agregar una comprobación de puntero NULL al procedimiento, encerrando todo el cuerpo del procedimiento en una instrucción if? Esta pregunta no puede responderse sin una comprensión del diseño y el algoritmo. Es posible que si el algoritmo se implementa correctamente, el puntero no puede ser NULL, por lo que el procedimiento no realiza la comprobación. Si ese es el caso, agregar la instrucción if no soluciona la causa del problema. En cambio, empeora las cosas al ocultar los síntomas. El problema seguramente aparecerá en otro lugar, pero ahora los síntomas se eliminarán aún más de la causa. Código como el puntero NULL check debe agregarse solo si está seguro de que debe ser parte del algoritmo. Si agrega una comprobación de puntero NULL que no es requerida por el algoritmo, entonces debería informar una condición de error. En otras palabras, debería ser un control de cordura. [ocurrió un error al procesar esta directriz]

Debe saber cómo usar redes, trampas y repelente de insectos. Use zapatillas en caso de emergencia.

Jajaja