Cómo depurar un programa que se equivoca
Depurar es averiguar por qué un programa hace algo mal y después cambiarlo. Casi todo el trabajo no es escribir la corrección. Es saber qué mirar entre cien cosas.
Adivinar y cambiar código al azar puede llevarte toda la tarde. Comprobar una cosa cada vez lleva unos minutos, y esta lección te enseña cómo.
Al terminar esta lección
Nota
Puedo encontrar un error leyendo el mensaje y comprobando una cosa cada vez.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Error | un fallo del programa que lo hace actuar mal |
| Reproducir | hacer que el error ocurra otra vez, igual, cuando quieras |
| Traceback | la lista de llamadas que Python imprime cuando un programa se para con un error |
| Hipótesis | una idea sobre la causa que una sola comprobación puede refutar |
| Impresión de control | una línea que muestra un valor para ver lo que el programa guarda de verdad |
| Partir por la mitad | comprobar el medio de lo que queda, para descartar la mitad cada vez |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Primero haz que el error ocurra siempre
No puedes arreglar lo que no ves. Antes de cambiar nada, busca una entrada que provoque el error siempre, y guárdala.
Aquí un programa pequeño calcula el promedio de las ventas de un archivo, una línea por día. Sus datos tienen un hueco. Crea primero la carpeta y el archivo.
Crea una carpeta para la lección
mkdir -p ~/debugtestSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Entra en ella
cd ~/debugtestSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el archivo de ventas
printf 'lun 120\nmar 80\nmie n/d\njue 200\n' > sales.txtSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el programa del informe
printf 'def parse(line):\n day, amount = line.split()\n return day, int(amount)\n\n\ndef average(lines):\n result = 0\n for line in lines:\n day, amount = parse(line)\n result = result + amount\n return result / len(lines)\n\n\nprint(average(open("sales.txt").read().splitlines()))\n' > report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecútalo y mira cómo se para
python3 report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lee un traceback de abajo arriba
Un traceback lista las llamadas abiertas cuando el programa se paró. La llamada más nueva está abajo, así que lee desde abajo.
La última línea dice qué falló: ValueError, con un mensaje. La línea File de encima dice dónde: un archivo, un número de línea y una función. Las líneas anteriores dicen cómo llegó el programa allí.
Lista solo las líneas File
python3 report.py 2>&1 | grep "File"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Comprueba una cosa y luego la siguiente
El error muestra dónde se paró el programa, no por qué el dato era malo. Haz una hipótesis: una idea que una comprobación pueda refutar.
Aquí la idea es que una línea de datos no es un número. Busca en el archivo. Si no aparece nada, la idea era falsa, y eso también es avanzar.
Busca el texto en los datos
grep -n "n/d" sales.txtSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un arreglo rápido puede esconder otro error
La línea 3 dice mie n/d: ese día no hay cifra de ventas. Lo más rápido es contarlo como 0. Pruébalo y ejecuta el programa otra vez.
Cuenta una cifra que falta como 0
sed -i 's| return day, int(amount)| if amount == "n/d":\n return day, 0\n return day, int(amount)|' report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecútalo otra vez
python3 report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Sin error no significa sin defecto
El programa se ejecutó e imprimió 100.0. Pero el promedio de los tres días con cifra, 120, 80 y 200, es 133,3 (Python lo escribe 133.3). El programa se equivoca y no dice nada.
Ahora no hay traceback que leer. Añade una impresión de control donde se suman los números y mira lo que guarda de verdad.
Imprime cada día al sumarlo
sed -i 's| result = result + amount| print(day, amount)\n result = result + amount|' report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecútalo y lee cada línea
python3 report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Parte el problema por la mitad
El programa tiene cuatro etapas. Comprueba primero la del medio. Si el total es correcto, el defecto está después. Si no, está antes. Cada comprobación descarta la mitad.
Así un equipo encuentra cuál de mil cambios rompió una función.
Ejemplo resuelto: comprobar las etapas
| Etapa | Qué debería tener | Qué tenía | Veredicto |
|---|---|---|---|
| Leer el archivo | 4 líneas | 4 líneas | Bien |
| Leer cada cifra | 120, 80, n/d, 200 | 120, 80, 0, 200 | El 0 es un número inventado |
| Sumar | 400 | 400 | Bien |
| Dividir por los días contados | 400 / 3 = 133.3 | 400 / 4 = 100.0 | Contó un día sin cifra |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Escribe el programa otra vez, con la corrección
printf 'def parse(line):\n day, amount = line.split()\n if not amount.isdigit():\n return None\n return day, int(amount)\n\n\ndef average(lines):\n result = 0\n count = 0\n for line in lines:\n pair = parse(line)\n if pair is not None:\n result = result + pair[1]\n count = count + 1\n return result / count\n\n\nprint(round(average(open("sales.txt").read().splitlines()), 1))\n' > report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta el programa corregido
python3 report.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un error frecuente
Advertencia
Mucha gente cambia código antes de conocer la causa, y luego cambia otra cosa cuando falla. Tras cinco cambios nadie sabe cuál ayudó. Comprueba primero, cambia una cosa y ejecuta otra vez.
Más lejos: el cambio que lo rompió
Un equipo guarda cada cambio en Git. Cuando aparece un error, sabe que una versión antigua era buena y la más nueva es mala. La orden git bisect te pide probar la versión del medio y parte el historial hasta dejar un solo cambio.
Escribe los pasos para 1000 cambios. ¿Cuántas versiones pruebas como máximo?