Python Reparando Bugs
Python Reparando Bugs
DESPARASITANDO SERPIENTES
Da igual lo buenos programadores que seamos, tarde o temprano daremos con ese BUG que ser nuestro peor enemigo. Veamos cmo podemos emplear herramientas para derrotarlo con mayor facilidad. POR JOSE MARA RUZ
quivocarse es humano y, a pesar de todo el mito que rodea a los programadores, hasta el mejor de ellos comete errores diariamente. En muchas ocasiones es mucho ms complicado eliminar un BUG que crear el propio programa. Cuenta le leyenda que el nombre de BUG viene de la misma palabra que en ingls significa bicho. Dicen que los primeros ordenadores eran grandes mquinas que generaban gran cantidad de calor, por lo que innumerables insectos y otras alimaas se introducan en ellos. De vez en cuando alguno tocaba dos cables y quedaba frito, provocando un fallo en el sistema. Actualmente se conoce como BUG a todo error o situacin no controlada que impida a un programa realizar su tarea con normalidad. Lo cierto es que estamos bastante acostumbrados a que los BUGS sean parte de nuestra vida. Ventanas que no se cierran, programas que consumen todo el espacio en memoria o videojuegos que se quedan bloqueados. Python es un lenguaje dinmico, como muchos otros. La principal ventaja es que nos permite programar a alto nivel, desentendindonos de toda la gestin de recursos a
bajo nivel que hace tan pesada la programacin en otros lenguajes como por ejemplo C. Pero no todo el monte es organo. Tambin hay una parte negativa: Python no es un lenguaje demasiado estricto. Podemos hacer lo que queramos con las variables sin que el intrprete se queje hasta el ltimo momento. Esta caracterstica impide la posibilidad de verificar automticamente todo el cdigo en el momento en que es compilado. Un cdigo totalmente errneo, en el que por ejemplo se suman letras y nmeros, puede pasar desapercibido en nuestro programa hasta el da que se ejecuta y genera un error que dejar al usuario con la boca abierta y ciertas dudas sobre nuestra vala como programadores. Casi a la vez que surgieron los lenguajes de programacin aparecieron unos programas que van unidos a ellos: los debuggers. Existen muchos debuggers diferentes. GNU desarroll DDD, pero hace tiempo que no se ve actividad en este proyecto (ver Recurso [1]). Valgrind ha conseguido mucha fama en proyectos que emplean C++ ( ver Recurso [2]). En este artculo vamos a echar un vistazo a las herramientas que podemos usar para
localizar los fallos en nuestros programas Python y en particular a la que viene de serie con Python: el PDB, Python DeBugger (podemos ver la documentacin de PDB en el Recurso [3]).
50
Nmero 34
WWW.LINUX- MAGAZINE.ES
mos que haga, por ejemplo abrir un fichero, en las variables suceden todo tipo de cosas mientras el programa est en ejecucin. El problema es que no vemos esas variables mientras el programa est funcionando, as que tenemos que imaginarnos qu est pasando. En condiciones ideales se puede perder el tiempo tratando de localizar los fallos a ojo de buen cubero, pero bajo estrs y con plazos, toda ayuda es poca. Python adems nos permite almacenar cualquier valor dentro de una variable. Las variables en los lenguajes dinmicos como Python son casi mgicas. En ellas podemos almacenar un nmero:
>>> mivariable = 32
Y sigue siendo la misma mivariable, pero de alguna manera su naturaleza ha cambiado. Ahora imagina que esta situacin ocurre en un programa que has creado. Mientras tecleas piensas, mivariable contiene una distancia y operas con ella, slo que, sin que te des cuenta, en realidad mivariable contiene un objeto de la clase Persona qu pasara si intentas sumarle 18?
>>> a + 18 Traceback (most recent call last): File <stdin>, line 1, in ?
ERROR! El intrprete de Python nos advierte de que algo no marcha bien: hay un error de tipo, no es posible sumar un nmero entero y una instancia de un objeto tal como hemos definido la clase. Desagradable verdad? Por lo menos este tipo de BUG, que es tan fcil de encontrar que el propio intrprete de Python lo encuentra. Qu ocurra si el programa no fallase, sino que no nos diese el resultado esperado? Y si el programa le dijese a un cliente que su edad actual es -323134.32 aos? Este es sin duda el peor tipo de BUG existente: el semntico. El intrprete de Python es perfecto localizando errores sintcticos, aqullos que tienen que ver con las propias palabas que escribimos. Si hacemos referencia a una variable que no est definida, Python trata de buscar su valor, ve que no existe la variable y se queja. Los errores semnticos son harina de otro costal, porque se refieren a fallos que aparecen debido a que no se entiende lo que est pasando. Un ejemplo simple es el que hemos visto antes, cuando hemos tratado de sumar una variable con una instancia de un objeto que no responde a la suma con un nmero. Solucionar un BUG semntico puede llevar desde segundos a aos. De hecho, existe mucho software, tanto comercial como libre, con BUGS que no han sido resueltos en aos (como por ejemplo el fallo de Excel, que ya vimos en el artculo dedicado a Gnumeric). Ahora que el problema ha sido planteado con ms detenimiento con-
viene ver con qu arsenal contamos en nuestra batalla contra los BUGS.
Depurando Cdigo
Digamos que estamos haciendo un script para mostrar usando caracteres las vacaciones que han escogido una serie de empleados de una empresa. Como no queremos que el cdigo sea largo ni demasiado complicado, lo hemos reducido al mnimo. La funcin tomar una lista de tuplas, que representa las vacaciones de una persona en un mes. Cada tupla representa un periodo de vacaciones, con una fecha de inicio y una fecha de fin. La idea es muy simple, aceptamos esa lista y despus devolvemos una cadena donde los das de trabajo se representan por un espacio, y los de vacaciones con un #. No muy complicado verdad? As que nos ponemos manos a la obra.Es algo sencillo, y no nos llevar ni 10 minutos, acabamos escribiendo el cdigo del Listado [1]. Pero nos interrumpen antes de probar la funcin, y cuando volvemos al ordenador y ejecutamos un programa de prueba se queda bloqueado! No puede ser!. En tan pocas lneas de cdigo no puede haber un error tan grave. Despus de unos minutos de frustracin abandonamos la inspeccin visual y pasamos a trabajar PDB. PDB es el Python DeBugger y viene de serie con Python. Eso est muy bien, porque en caso de necesitarlo, siempre lo tendremos a mano. A diferencia de otros debuggers PDB se puede usar como si fuese una librera. Podemos integrarla en nuestros programas y eliminarla cuando ya los hayamos arreglado. Como es posible observar en el Listado 1, hemos importado PDB y hemos pasado como parmetro a pdb.run() una cadena en la que invocamos la funcin vacaciones() con un argumento acorde. Si ejecutamos el programa veremos lo siguiente en nuestro terminal:
josemaria@linuxmagazine$U ./p.py > <string>(1)?() (Pdb)
pdb.run(vacaciones([(1,3),(6, 10)]))
WWW.LINUX- MAGAZINE.ES
Nmero 34
51
Muy bien, el PDB comienza a hacer su trabajo. En lugar de no hacer nada, como hara la funcin vacaciones() si el programa se limitase a ejecutarla, entramos en lo que parece el prompt del shell de PDB. Esta shell tiene sus propios comandos, que no tienen nada que ver con los Python. Para ver los comandos disponibles podemos emplear el comando h:
(Pdb) h Documented commands (type help <topic>): ================================= EOF break condition disable help list q step w a bt cont down ignore n quit tbreak whatis alias c continue enable j next r u where args cl d exit jump p return unalias b clear debug h l pp s up Miscellaneous help topics: ========================== exec pdb Undocumented commands: ====================== retval rv (Pdb)
El comando l sirve para mostrar visualmente en qu parte del cdigo estamos en un momento dado. Como nos pica la curiosidad ejecutamos l:
(Pdb) l [EOF]
-> cadena = (Pdb) s > /home/josemaria/p.py(7) vacaciones() -> for i in range(1,31): (Pdb) s > /home/josemaria/p.py(8) vacaciones() -> encontrado = False (Pdb) s > /home/josemaria/p.py(9) vacaciones() -> max = len(l) (Pdb)
Vaya. Resulta que no hemos comenzado an, por lo que no estamos en ninguna parte. El comando que probablemente usemos ms a menudo es s Por qu? pues porque es el comando que hace que PDB avance una lnea y la ejecute:
(Pdb) s Call > /home/josemaria/p.py(5)U vacaciones() -> def vacaciones (l):
Hemos avanzado 4 pasos y puede que nos hayamos perdido. Dnde estamos? Qu estamos haciendo? Hacia dnde vamos? El comando l resuelve todas nuestras dudas:
(Pdb) l 4 5 def vacaciones (l): 6 cadena = 7 for i in range(1,31): 8 encontrado = False 9 -> max = len(l) 10 k=0 11 while(not(encontrado) or k<max): 12 rango = l[k] 13 inf,sup=rango 14 if ((i >= inf) and (i <= sup)): (Pdb)
Muy bien, comenzamos a ejecutar el trozo de cdigo Python que pasamos a pdb.run(). Por el momento no pasa nada interesante, aunque estara bien ver qu contiene la variable l, para ello podemos usar el comando p que hace las funciones de print:
(Pdb) p l [(1, 3), (6, 10)]
Buff! estos son demasiados comandos. Como suele ocurrir la primera vez que un principiante en Linux pulsa dos veces tabulador en BASH, lo que vemos nos asusta. En este caso no son tantos comandos (en un Linux estndar hay miles de ejecutables), pero s ms extraos. Debuggear es algo que SIEMPRE se hace bajo presin, por lo que todo el tiempo que ahorremos es de oro. As que los creadores de PDB nos ahorran segundos reduciendo los comandos a letras.
Efectivamente, l contiene el parmetro que hemos pasado a la funcin. Ya estamos en marcha, as que avancemos unos cuantos pasos ms:
(Pdb) s > /home/josemaria/p.py(6) vacaciones()
52
Nmero 34
WWW.LINUX- MAGAZINE.ES
Bueno, llegamos a una encrucijada. Cuando ejecutamos la funcin sin PDB parece como si el programa nunca acabase. Esto implica que hay algo que se repite eternamente. En este programa hay dos bucles, que son los nicos elementos que pueden repetirse eternamente. El primero es un bucle for con un principio, 1, y un fin, 31, por lo que podemos descartarlo como culpable. El segundo sospechoso es ese bucle while, que en un primer momento no tiene porqu acabar, puede que jams pare. Si un bucle while no para es porque las condiciones que lo controlan siempre se dan. En este cdigo se supone que el if dentro del bucle while hace que ste pare alguna vez, as que algo debe fallar ah dentro. Comencemos comprobando los valores de las variables que controlan el bucle:
(Pdb) p encontrado False (Pdb) p k 0 (Pdb) p max 2 (Pdb)
variable encontrado sea True. Si todo va bien el, siguiente paso despus de evaluar las condiciones del while ser salir del mismo y pasar a las siguiente instruccin fuera del while:
(Pdb) s > /home/josemaria/p.py(12)U vacaciones() -> rango = l[k] (Pdb)
Pero qu pasa aqu? Esto no debera pasar. La nica explicacin posible es que la condicin del while est es eso un or? Debera ser un and! Deberamos salir si hemos encontrado que el da pertenece a un rango, o si no quedan rangos que comprobar. Ah estaba nuestro BUG, tres simples letras, se cambian y problema solucionado. Ahora que ya sabemos lo que pasa, slo queda salir del debugger usando el comando q. Nuestro nuevo cdigo ya est listo para ser usado (ver Figura 1).
remos seleccionar en el men Run la opcin Run Module. Con este paso cargaremos el fichero y comenzaremos a ejecutarlo. Como antes seleccionamos la opcin Debugger, IDLE se cargar en la ventana de debugging y podremos comenzar a ver la evolucin del programa conforme pulsemos sobre el botn Step. No es que IDLE sea un gran avance respecto al uso de PDB, pero desde luego simplifica el debugging.
Conclusin
Los debuggers no son una excusa para crear programas sin fijarnos demasiado en los problemas. Pero si no tenemos claro qu est ocurriendo o si ya no sabemos qu hacer, entonces un debugger como PDB puede ayudarnos a tener una imagen ms clara de lo que pasa en nuestro programa. Existen bugs que no pueden cazarse con PDB, pero son tan extraordinariamente raros, que es posible que jams nos encontremos con uno. Los debuggers nos permiten programar sin miedo a no entender lo que estamos haciendo, y es precisamente eso lo que nos permitir avanzar y aprender ms rpidamente. PerdI mosle el miedo a los BUGS!
RECURSOS
[1] Documentacin del depurador PDB: http://docs.python.org/lib/ module-pdb.html [2] Valgrind captura errores que comprometen la memoria en: http://valgrind. org/ [3] Data Display Debugger: http://www. gnu.org/software/ddd/ [4] El entorno de desarrollo IDLE: http:// www.python.org/idle/
Resulta que hemos entrado en una tupla que representa unas vacaciones, el da representado por i pertenece a las vacaciones. Por tanto, hemos hecho que la
WWW.LINUX- MAGAZINE.ES
Nmero 34
53