¿Qué técnicas eficientes ha intentado rastrear un algoritmo o un código de programa manualmente, sin usar una computadora?

Hasta ahora, todas las respuestas se han ocupado de lenguajes modernos, donde la ejecución del rastreo se complica rápidamente. Si comenzó a programar hace casi 50 años, siempre trazó su código manualmente … antes de perforarlo en tarjetas de cartón de 80 columnas.

Para ser más exactos, desarrolló su código sin usar una computadora. Hubo formularios de diseño, formularios de codificación y diagramas de flujo, todos completados a mano y luego “revisados ​​en el escritorio” para verificar su precisión antes de perforar tarjetas (o hacer que se perforen), según sus formularios de codificación.

Hoy en día, habré roto lo que sea que esté trabajando en pedazos … con puntos de entrada bien definidos y una o más salidas bien definidas. Obviamente, pero aún a veces se pasa por alto, es el hecho de que una llamada a función / método que siempre regresa no es una salida. A menos que sea muy simple, o que ya haya sido probado y documentado, tales funciones generalmente se convierten en otra pieza del rompecabezas. La verdadera diversión es tratar con funciones / métodos que a veces regresan, pero no siempre.

Bien definido significa que los datos, tanto entrantes como salientes, y el estado del sistema están bien documentados en términos de lo que se espera. Esa parte puede ser, y a menudo sigue siendo, manual … o al menos mental. Las pruebas, donde buscamos eliminar datos indeseables o cambios de estado, es mejor dejarlas en la computadora.

La técnica más común y ampliamente aplicable es escribir una tabla de variables y posiblemente devolver valores cuando las funciones están involucradas. Más adelante, puede analizar la tabla para sacar conclusiones sobre el comportamiento del programa.

Las columnas de la tabla representan las variables y las filas representan el estado a través del tiempo. La primera fila corresponde al estado inicial, y cada fila consecutiva se agrega a medida que cambia un valor (generalmente una operación de escritura o una actualización de una variable).

También es común reducir el tamaño de la tabla al tener solo filas para cada iteración (por ejemplo, cuando se realiza un bucle o una llamada a una función cuando se trata de recursividad).

Si algunas de las variables se inicializan en una etapa posterior. Utilizar una ? símbolo para indicar que el valor es desconocido hasta ese punto.

Puede construir la tabla de estado simplemente simulando la ejecución del programa (siguiendo las reglas del lenguaje) usted mismo.

En algunos casos, dependiendo del tema, es más útil visualizar el problema usando una representación de estructura, como una tabla de valores clave, un diagrama, una recta numérica o una matriz / tablero.

Por ejemplo, es muy fácil ver cómo funcionan los algoritmos BFS / DFS en el tablero 2d con objetivos y obstáculos. Puede marcar la progresión de la búsqueda (distancias) utilizando números ubicados en cuadrados.

La mayoría de las estructuras básicas de datos (matrices, listas, mapas, árboles, etc.) deben incluirse en algunas de las técnicas de visualización mencionadas.

Cuando se trata de problemas de gráficos (red), siempre es útil dibujar un caso simple con algunos bordes y vértices e indicar el recorrido y el estado mediante numeración y posiblemente codificación de colores.

Es importante realizar estos pasos con un conjunto bastante simple de entradas (generalmente enteros pequeños) para que se pueda hacer rápidamente. Si observa un bucle y el valor de límite superior de N. Intente reducir N, pero no lo haga trivial (por ejemplo, no use 0 si el bucle no se ejecutará para 0) o demasiado pequeño (por ejemplo, 1 iteración no revelar mucho).

Sin embargo, cuando se trata de la recursividad, es muy importante comenzar con el caso trivial y el primer caso base (el que realmente hace al menos una sola llamada recursiva).

También es una práctica útil encontrar algunos conjuntos especiales de entradas que son casos extremos (p. Ej. Casos triviales o especiales) o que podrían dar lugar a un comportamiento no deseado (p. Ej. División por cero, bucle interminable, desbordamiento del búfer, etc.)

Practicar todas estas técnicas y codificar los algoritmos usted mismo eventualmente conducirá a reconocer patrones en implementaciones similares para que pueda reconocer lo que sucede solo al leer el código.

Ejecutar en seco sobre papel. Frente a cada instrucción del programa, imprima valores de variables importantes a medida que cambian a lo largo de su ejecución.

Si la recursión está involucrada, dibuja el árbol.

Si es aritmética de puntero complejo, dibuje visualmente

Por ejemplo, las imágenes y la representación visual en la página de problemas de la lista de enlaces en stanford.edu son un buen caso.

Por lo general, elijo valores de muestra teniendo cuidado de considerar todos los casos de borde a lo largo del camino y los ejecuto todo el código. Creo que esa es la mejor manera de entender códigos complejos.
Hubo un momento en que no estaba satisfecho con mi código a menos que lo haya completado completamente. Ahora confío principalmente en las pruebas uni.

More Interesting

¿Qué niveles de matemáticas están involucrados en la programación y las ciencias de la computación?

¿Cuál es la mejor fuente en línea para el aprendizaje de algoritmos?

¿Cuáles son los conceptos de software que todo programador debe saber?

¿Podemos diseñar un algoritmo de aprendizaje automático para resolver la programación competitiva?

¿Cómo puedo cambiar el tamaño de una imagen a un ancho y alto específicos sin dejar de mantener su relación de aspecto? Estoy buscando ideas de algoritmos.

¿Cuáles son las diferencias entre DFS y BFS?

¿Cómo se construye exactamente una estructura de datos de árbol en JavaScript?

Lenguaje ensamblador: ¿Por qué las instrucciones INC y DEC no establecen la bandera de acarreo?

¿Cómo podemos verificar de forma recursiva si una lista vinculada individualmente es un palíndromo?

Dado un gráfico no dirigido y dos conjuntos de nodos, ¿cuál es el mejor algoritmo para verificar que cada elemento del primer conjunto sea adyacente a cada elemento del segundo conjunto?

¿Cuál es la diferencia entre los siguientes dos fragmentos de código?

¿Por qué es importante almacenar y organizar datos de manera eficiente dentro de estructuras específicas al programar?

¿Cómo se resolvería el problema lingüístico 'Summer Eyes', de NACLO 2009?

¿Cuáles son los algoritmos de clasificación considerados algoritmos codiciosos?

Dada una matriz con 100 elementos (números del 0 al 99), si saco un elemento aleatorio, ¿cómo encontrarías el que saqué? ¿Cómo resolvería esto si 1: la matriz está ordenada o 2: la matriz no está ordenada?