Errores y depuración: aprende a arreglar tu propio código
Programar es, sobre todo, arreglar
Llegamos a la última entrega de la serie de Fundamentos de programación, y la dedico a lo que de verdad ocupa la mayor parte del tiempo de cualquiera que programa. No es escribir código nuevo. Es arreglar el que no funciona.
Te lo digo después de treinta años: el código no sale bien a la primera casi nunca. Falla, da resultados raros, se cuelga. Y eso no es el fracaso, es el trabajo. El principiante cree que un buen programador escribe código perfecto. El veterano sabe que un buen programador es, sobre todo, un buen cazador de bugs. Así que vamos a aprender a cazarlos sin entrar en pánico.
Los tres tipos de error
No todos los fallos son iguales. Saber de qué tipo es el que tienes delante ya es media solución. Hay tres familias:
- Error de sintaxis: has escrito algo mal, has roto las reglas del lenguaje. Un paréntesis sin cerrar, una coma de más, una palabra mal escrita. El programa ni siquiera arranca: se queja antes de empezar. Es el error más fácil, porque te dice exactamente dónde está. Los pillas en segundos.
- Error de ejecución (runtime): el código está bien escrito y arranca, pero revienta a mitad al hacer algo imposible. Dividir entre cero, pedir el elemento de una lista que no existe, usar una variable que vale null. El programa peta en marcha.
- Error de lógica: el peor de todos. El programa arranca, no revienta, pero hace algo distinto de lo que esperabas. Calcula mal el total, muestra los datos cambiados, deja pasar a un menor de edad. No hay mensaje de error, nadie te avisa: simplemente está mal. Estos son los que de verdad te hacen sudar, porque tienes que darte cuenta tú.
La buena noticia: los dos primeros el lenguaje te los chiva. El tercero es donde de verdad demuestras que sabes programar.
Leer un stack trace sin entrar en pánico
Cuando un programa revienta, escupe un mensaje largo, feo y normalmente en inglés, lleno de nombres de archivo y números. Es el stack trace (traza de la pila), y al principio dan ganas de cerrar los ojos. Error. Ese mazacote es tu mejor amigo, porque te está diciendo qué ha pasado y dónde.
Tres trucos para leerlo:
- Empieza por arriba. La primera línea suele resumir qué tipo de error es y un mensaje. “NullPointerException”, “IndexOutOfRange”, “TypeError: undefined is not a function”. Ya sabes la familia.
- Busca tu archivo. El stack trace lista por dónde ha pasado el programa. Mucho será código de librerías que no tocas; ignóralo. Busca el nombre de tu archivo y el número de línea. Ahí está el dedo señalando.
- Lee el mensaje literal. No lo sobrevueles. “No se puede leer la propiedad email de null” te está diciendo, con todas las letras, que algo que esperabas que fuera un usuario, era null. El mensaje casi siempre es honesto.
Y un consejo de oro de la era actual: si el mensaje no lo entiendes, cópialo y pégalo tal cual en un buscador o en una Inteligencia Artificial. Alguien, en algún lado, ha tenido exactamente ese error antes que tú. Garantizado.
Try/catch: capturar el fallo en vez de morir
A veces sabes que algo puede fallar y no quieres que el programa entero se caiga por ello: leer un archivo que quizá no existe, conectarte a internet que quizá esté caído. Para eso está el try/catch (“intenta / captura”):
INTENTAR
conectar al servidor
descargar datos
CAPTURAR error
mostrar "No se pudo conectar, inténtalo más tarde"
FIN INTENTAR
La idea: el programa intenta el bloque de riesgo. Si todo va bien, sigue. Si algo revienta dentro, en vez de morirse, salta al bloque capturar y maneja el problema con elegancia. Es la diferencia entre una app que se cierra de golpe y una que te dice “ha habido un problema, reintenta”. No abuses (envolver todo en try/catch esconde bugs), pero para lo que de verdad escapa a tu control, es imprescindible.
Validar las entradas: el mejor error es el que no ocurre
Muchísimos errores de ejecución vienen de un sitio: datos de entrada que no son lo que esperabas. El usuario deja un campo vacío, escribe letras donde pedías un número, manda un formato raro. Si tu código asume que todo viene perfecto, peta al primer dato inesperado.
Por eso, antes de trabajar con un dato que viene de fuera (un usuario, un archivo, internet), valídalo. ¿Está vacío? ¿Es del tipo que esperaba? ¿Está dentro del rango lógico? Las guard clauses que vimos en los condicionales son perfectas para esto: descarta pronto lo que no cuadra. Programar a la defensiva, dando por hecho que la entrada vendrá mal, te ahorra la mitad de los bugs.
Print-debugging: el detective de toda la vida
Y llegamos a cómo se cazan los bugs de verdad, sobre todo los de lógica. La técnica más vieja, más sencilla y, sí, más usada del mundo: el print-debugging. Consiste en meter mensajes por todo el código para ver qué está pasando por dentro y cuánto valen las variables en cada punto.
mostrar "Entrando en la función con edad = " + edad
... código ...
mostrar "El total calculado es = " + total
¿Burdo? Sí. ¿Funciona? Siempre. Cuando algo da un resultado raro, vas sembrando mensajes hasta que ves en qué punto exacto el valor deja de ser el que esperabas. Ahí está el bug, casi siempre. Te confieso que, después de treinta años, sigo usando esto cada día. No te dé vergüenza: es una herramienta legítima y rapidísima.
El debugger y los breakpoints: la versión profesional
La herramienta seria se llama debugger (depurador), y viene integrada en casi cualquier editor moderno, como Visual Studio Code. Te permite poner un breakpoint (punto de interrupción) en una línea: el programa se ejecuta y se congela justo ahí.
Con el programa pausado, puedes:
- Ver cuánto vale cada variable en ese instante exacto, sin imprimir nada.
- Avanzar línea a línea, viendo el efecto de cada una en directo.
- Entrar dentro de las funciones para seguir el rastro paso a paso.
Es como poner el programa a cámara lenta y mirarlo por dentro con lupa. Para bugs enrevesados es muchísimo más potente que el print-debugging, porque lo ves todo a la vez sin tocar el código. Mi consejo: empieza con prints, que son inmediatos, y aprende el debugger en cuanto puedas. Saber usar un debugger es de las cosas que más te separan de un principiante.
Cierre de la serie: lo que has aprendido
Y con esto cerramos los Fundamentos de programación. Para si quieres y mira atrás un momento, porque has recorrido mucho:
- Aprendiste a pensar como programador, que era lo más importante.
- A guardar información en variables y operar con operadores.
- A que el programa decida y repita.
- A organizar el código en funciones y los datos en listas y diccionarios.
- Y hoy, a arreglarlo todo cuando se rompe.
Eso son los cimientos. Con esto ya no eres alguien que “no sabe programar”. Eres alguien que tiene las bases y a quien le falta lo de siempre: práctica, práctica y más práctica. Y, sobre todo, alguien que ahora entiende lo que la Inteligencia Artificial le devuelve, en vez de copiar y pegar a ciegas. Esa es toda la diferencia.
El siguiente piso: Algoritmos
¿Y ahora qué? Ahora, si te ha picado el gusanillo, viene el segundo piso: la serie de Algoritmos. Si los Fundamentos te enseñan a escribir programas que funcionan, los Algoritmos te enseñan a escribir programas que funcionan bien: rápidos y eficientes aunque les eches montañas de datos.
Empieza por la notación Big-O (la forma de medir si un programa escala o se ahoga) y sigue con la resolución de problemas, la ordenación, la búsqueda y las estructuras avanzadas. Es donde programar se vuelve, además de útil, un juego intelectual de los buenos. Pero eso ya es otra historia, y otra serie.
Preguntas frecuentes
¿Cuáles son los tres tipos de error en programación?
De sintaxis: has roto las reglas del lenguaje (un paréntesis sin cerrar) y el programa ni arranca. De ejecución o runtime: el código arranca pero revienta a mitad al hacer algo imposible (dividir entre cero, acceder a un índice inexistente). De lógica: el programa funciona sin reventar, pero hace algo distinto de lo esperado. Este último es el más difícil, porque no avisa con ningún mensaje.
¿Qué es un stack trace y cómo se lee?
Un stack trace es el mensaje que escupe un programa al reventar, mostrando el tipo de error y el recorrido que hizo el código hasta fallar. Para leerlo: empieza por la primera línea (resume el error), busca el nombre de tu archivo y el número de línea (ahí está el problema) y lee el mensaje literal, que casi siempre dice la verdad. Si no lo entiendes, búscalo tal cual en internet.
¿Qué es mejor, print-debugging o usar un debugger?
Los dos sirven y se complementan. El print-debugging (meter mensajes para ver el valor de las variables) es inmediato y rapidísimo para problemas sencillos; lo usan hasta los veteranos. El debugger con breakpoints es más potente: congela el programa en una línea y te deja inspeccionar todas las variables y avanzar paso a paso sin tocar el código. Empieza con prints y aprende el debugger en cuanto puedas.
El libro para cerrar la serie
Depurar bien es oficio, y el libro que mejor te lo mete en la cabeza es El Programador Pragmático, de Hunt y Thomas. No va de un lenguaje, va de cómo pensar, cazar bugs y no tropezar dos veces con la misma piedra. Es el broche perfecto para todo lo que hemos visto y el empujón hacia la siguiente serie.
Este artículo contiene enlaces de afiliado.
Compártelo si te ha resultado útil.
Hasta aquí la serie de Fundamentos. ¿Cuál ha sido la entrega que más te ha servido, y qué te gustaría que profundizara en la de Algoritmos? Cuéntame.
Y… esto no es el final, es el primer cimiento.
Artículos relacionados
Listas y diccionarios: estructuras de datos para empezar
Una variable guarda una sola cosa: un número, un texto. Pero el mundo real va de muchas cosas a la vez: la lista de la compra, los datos de un usuario, el inventario entero de una tienda. Para eso están las estructuras de datos, y en esta séptima entrega de la serie de Fundamentos te explico las dos que vas a usar absolutamente cada día: las listas (o arrays), que guardan una colección ordenada de elementos a los que accedes por posición, y los diccionarios (o mapas), que guardan pares de clave y valor a los que accedes por nombre. Verás cómo se recorren, cómo se añade y se quita, el detalle de que empiezan a contar en cero, y una mención a conjuntos y tuplas. También te dejo el puente a las estructuras avanzadas de la serie de Algoritmos.
Las funciones en programación: parámetros, retorno y scope
Cuando llevas un rato programando notas que repites los mismos trozos de código por todas partes, y eso es justo lo que no hay que hacer. La solución son las funciones: empaquetar un trozo de programa, ponerle nombre y reutilizarlo siempre que quieras. En esta sexta entrega de la serie de Fundamentos te explico cómo se define y se llama una función, la diferencia entre parámetros y argumentos, qué es el valor de retorno, el concepto de ámbito (scope) local y global, la diferencia entre pasar datos por valor y por referencia, qué son los efectos secundarios, y el principio DRY que resume por qué todo esto importa. También asomamos la recursión, que veremos a fondo en la serie de Algoritmos.
Bucles en programación: for, while y do-while desde cero
Si tuvieras que saludar a cien usuarios, ¿escribirías cien veces la misma línea? Claro que no. Para repetir trabajo sin copiar y pegar existen los bucles, y son una de las herramientas que más usarás en tu vida programando. En esta quinta entrega de la serie de Fundamentos te explico los tres bucles básicos (for, while y do-while), cuándo usar cada uno, el for-each para recorrer colecciones cómodamente, el break y el continue para controlar la repetición por dentro, y el clásico susto del principiante: el bucle infinito que cuelga el programa, por qué ocurre y cómo evitarlo. Todo con pseudocódigo y ejemplos cortos para que lo veas claro.