Fundamentos

Tutorial Fundamentos: Depuración de programas

Identifica y soluciona errores de programación. Aprende a usar depuradores y manejo de excepciones para un código robusto y eficiente.

Aprende Fundamentos GRATIS y certifícate

Identificación de errores comunes: sintácticos, lógicos y de ejecución

En el proceso de programación, es habitual encontrarse con errores que impiden que el programa funcione correctamente. Estos errores se clasifican en sintácticos, lógicos y de ejecución. Comprender cada tipo es esencial para depurar y mejorar la calidad del código.

Los errores sintácticos ocurren cuando el código viola las reglas gramaticales del lenguaje. Por ejemplo, en PseInt, si olvidamos cerrar una estructura condicional:

Algoritmo SinFinSi
    Escribir "Este programa tiene un error sintáctico"
    Si verdadero Entonces
        Escribir "Olvidé cerrar el Si"
FinAlgoritmo

Al ejecutar este algoritmo, PseInt mostrará un mensaje indicando que falta el FinSi. En Python, un error similar ocurre al omitir dos puntos o la indentación:

print("Este programa tiene un error sintáctico")
if True
    print("Olvidé los dos puntos")

Python generará un SyntaxError señalando el punto donde se encuentra el problema. Es crucial revisar la sintaxis cuidadosamente para evitar estos errores.

Los errores lógicos se producen cuando el programa se ejecuta sin problemas, pero los resultados no son los esperados. Supongamos que queremos calcular el área de un rectángulo en PseInt:

Algoritmo AreaRectangulo
    Definir base, altura, area Como Real
    base <- 5
    altura <- 10
    area <- base / altura
    Escribir "El área es: ", area
FinAlgoritmo

En lugar de multiplicar, estamos dividiendo la base entre la altura, lo cual es incorrecto. En Python, el código equivalente sería:

base = 5
altura = 10
area = base / altura
print("El área es:", area)

Para corregir el error lógico, debemos utilizar la operación adecuada:

area <- base * altura

Los errores de ejecución, o errores en tiempo de ejecución, aparecen mientras el programa se está ejecutando. Un ejemplo común es la división por cero. En PseInt:

Algoritmo DivisionCero
    Definir numerador, denominador, resultado Como Real
    numerador <- 10
    denominador <- 0
    resultado <- numerador / denominador
    Escribir "El resultado es: ", resultado
FinAlgoritmo

PseInt generará un error al intentar realizar la división. En Python, el error es similar:

numerador = 10
denominador = 0
resultado = numerador / denominador
print("El resultado es:", resultado)

Python lanzará un ZeroDivisionError. Para manejar estos casos, es recomendable implementar validaciones antes de efectuar operaciones críticas:

Si denominador <> 0 Entonces
    resultado <- numerador / denominador
    Escribir "El resultado es: ", resultado
SiNo
    Escribir "Error: división por cero"
FinSi

Identificar correctamente el tipo de error facilita su resolución. Los errores sintácticos suelen indicar exactamente dónde está el problema, gracias a los mensajes del intérprete. Los errores lógicos requieren una revisión detallada de la lógica del programa y pruebas con diferentes datos de entrada. Los errores de ejecución pueden anticiparse y evitarse mediante el uso de condicionales y gestión adecuada de situaciones excepcionales.

Es fundamental utilizar herramientas como depasos (debuggers) y agregar mensajes de impresión estratégicos para monitorear el comportamiento del programa. La atención al detalle y la práctica constante ayudan a minimizar la aparición de errores y a crear programas más robustos y eficientes.

Uso de depuradores (debuggers) y herramientas integradas

El uso de depuradores es fundamental para detectar y corregir errores en los programas. PseInt incorpora herramientas integradas que permiten realizar una ejecución controlada del código, facilitando la identificación de fallos y el entendimiento del flujo del programa.

Una de las funcionalidades clave de PseInt es la ejecución paso a paso, que permite avanzar por el programa una instrucción a la vez. Al utilizar esta opción, es posible observar cómo cambian los valores de las variables en cada paso, lo que ayuda a localizar incongruencias o errores lógicos.

Para iniciar la ejecución paso a paso, se debe seleccionar la opción "Ejecutar" en el menú principal y luego "Ejecutar paso a paso". 

Consideremos el siguiente ejemplo en PseInt:

Algoritmo CalculoFactorial
    Definir n, factorial Como Entero
    Escribir "Introduce un número entero positivo:"
    Leer n
    factorial <- 1
    Mientras n > 1 Hacer
        factorial <- factorial * n
        n <- n - 1
    FinMientras
    Escribir "El factorial es: ", factorial
FinAlgoritmo

Al depurar este algoritmo, podemos seguir cada iteración del bucle Mientras y observar cómo se actualizan n y factorial. Esto es especialmente útil si el resultado no es el esperado, ya que podemos identificar el paso exacto donde ocurre el problema.

Además, PseInt permite insertar puntos de ruptura o breakpoints en ciertas líneas del código. Al ejecutar el programa, la depuración se detendrá automáticamente en estos puntos, lo que es útil para saltar directamente a partes críticas del código sin tener que avanzar paso a paso desde el inicio.

Para establecer un punto de ruptura, se hace clic derecho en la línea deseada y se selecciona "Alternar punto de interrupción". Los puntos de ruptura facilitan la inspección del estado del programa en momentos específicos.

En Python, el proceso de depuración es similar y se puede realizar utilizando diversos entornos y herramientas. Por ejemplo, el módulo integrado pdb permite depurar programas desde la línea de comandos. A continuación, se muestra el código equivalente en Python:

def calculo_factorial():
    n = int(input("Introduce un número entero positivo:\n"))
    factorial = 1
    while n > 1:
        factorial *= n
        n -= 1
    print("El factorial es:", factorial)

calculo_factorial()

Para depurar este programa en Python usando pdb, se puede insertar la siguiente línea donde se desea iniciar la depuración:

import pdb; pdb.set_trace()

Por ejemplo:

def calculo_factorial():
    n = int(input("Introduce un número entero positivo:\n"))
    factorial = 1
    import pdb; pdb.set_trace()
    while n > 1:
        factorial *= n
        n -= 1
    print("El factorial es:", factorial)

calculo_factorial()

Al ejecutar el programa, se iniciará el modo interactivo de depuración, permitiendo inspeccionar variables y controlar la ejecución paso a paso.

Además de pdb, existen entornos de desarrollo integrados (IDEs) como PyCharm o Visual Studio Code que ofrecen interfaces gráficas para la depuración. Estas herramientas permiten establecer puntos de interrupción, visualizar el estado de las variables y navegar por la pila de llamadas de manera intuitiva.

Volviendo a PseInt, otra característica útil es la visualización de variables en tiempo real. Durante la depuración, se muestra una ventana con los valores actuales de todas las variables definidas en el programa. Esto facilita la detección de valores inesperados o cambios no deseados.

Es importante aprovechar las herramientas integradas que proporciona PseInt para mejorar la calidad del código y acelerar el proceso de detección de errores. La combinación de la ejecución paso a paso, los puntos de ruptura y la visualización de variables conforma un conjunto poderoso para la depuración efectiva de programas.

Familiarizarse con las funciones de depuración y las herramientas integradas no solo ayuda a resolver problemas más rápidamente, sino que también contribuye a desarrollar una mejor comprensión de cómo funciona el código y cómo mejorar su eficiencia y fiabilidad.

Estrategias de prueba y diagnóstico (print statements, logs)

Las instrucciones de impresión son una herramienta sencilla y efectiva para la identificación de errores en programas. Al insertar Escribir en puntos clave del código en PseInt, es posible monitorear el valor de las variables y el flujo de ejecución del algoritmo.

Por ejemplo, si sospechamos que una variable no está tomando el valor esperado, podemos agregar una instrucción Escribir para verificar su contenido:

Algoritmo SumaNumeros
    Definir a, b, suma Como Entero
    a <- 5
    b <- 10
    suma <- a + b
    Escribir "El valor de suma es: ", suma
FinAlgoritmo

Esta salida nos permite confirmar que suma contiene el resultado correcto. En caso de discrepancias, se pueden agregar más instrucciones Escribir para rastrear el origen del problema.

En Python, utilizamos la función print para el mismo propósito:

a = 5
b = 10
suma = a + b
print("El valor de suma es:", suma)

Las instrucciones de impresión ayudan a comprender cómo se modifican las variables a lo largo del programa. Al diagnosticar bucles o condiciones, es útil imprimir las variables de control o los resultados intermedios.

Supongamos que el siguiente algoritmo en PseInt no produce el resultado esperado:

Algoritmo Contador
    Definir i Como Entero
    Para i <- 1 Hasta 5 Hacer
        Escribir "Iteración: ", i
    FinPara
FinAlgoritmo

Si el ciclo no se ejecuta correctamente, podemos insertar Escribir adicionales:

Algoritmo ContadorDepurado
    Definir i Como Entero
    Escribir "Inicio del bucle"
    Para i <- 1 Hasta 5 Hacer
        Escribir "Valor de i dentro del bucle: ", i
    FinPara
    Escribir "Fin del bucle"
FinAlgoritmo

Así, podemos verificar el inicio y fin del bucle, y el valor de i en cada iteración. En Python, el código equivalente es:

print("Inicio del bucle")
for i in range(1, 6):
    print("Valor de i dentro del bucle:", i)
print("Fin del bucle")

Una estrategia complementaria es utilizar logs para registrar información detallada durante la ejecución. Aunque PseInt no tiene una funcionalidad de logging avanzada, podemos simularla escribiendo mensajes con un formato consistente.

Por ejemplo:

Algoritmo RegistroDeEventos
    Definir evento Como Cadena
    evento <- "Inicio del programa"
    Escribir "[INFO] ", evento
    // Código del programa
    evento <- "Finalización exitosa"
    Escribir "[INFO] ", evento
FinAlgoritmo

Esta convención permite distinguir mensajes de log de otras salidas y facilita la lectura cuando el programa genera mucha información.

En Python, contamos con el módulo logging para manejar registros de manera más robusta:

import logging

logging.basicConfig(level=logging.INFO)

logging.info("Inicio del programa")
# Código del programa
logging.info("Finalización exitosa")

El uso del módulo logging permite ajustar el nivel de detalle de los mensajes y dirigirlos a diferentes destinos, como archivos o la consola. Es una práctica recomendada para la depuración y el mantenimiento de aplicaciones más complejas.

Al utilizar print statements y logs, es importante ser selectivo para evitar sobrecargar la salida con información innecesaria. Es conveniente enfocarse en las variables y eventos críticos para el funcionamiento del programa.

Una buena práctica es incluir mensajes en bloques de código donde es más probable que ocurran errores, como cálculos complejos o interacciones con el usuario. Esto facilita el diagnóstico y reduce el tiempo dedicado a encontrar la causa de un fallo.

 Si estamos procesando una lista de datos, podemos imprimir el estado antes y después de aplicar una transformación. En Python:

lista = [2, 4, 6, 8, 10]
print("Lista original:", lista)
for i in range(len(lista)):
    lista[i] *= 2
print("Lista procesada:", lista)

La revisión de la salida nos permite verificar que la transformación se ha realizado correctamente en cada elemento.

Al finalizar el proceso de prueba y diagnóstico, es recomendable eliminar o comentar las instrucciones de impresión que ya no sean necesarias. De este modo, el programa mantiene una salida limpia y profesional.

En programas más extensos, es útil implementar niveles de detalle en los logs, como debug, info, warning y error. Aunque PseInt no soporta estos niveles de forma nativa, podemos adoptar una convención similar:

Escribir "[DEBUG] Variable x =", x
Escribir "[ERROR] División por cero detectada"

En Python, el módulo logging permite especificar estos niveles y filtrar los mensajes según las necesidades:

import logging

logging.basicConfig(level=logging.DEBUG)

logging.debug("Variable x = %d", x)
logging.error("División por cero detectada")

Esta estrategia mejora la capacidad de diagnóstico y facilita el mantenimiento del código a largo plazo.

Las instrucciones de impresión y los logs son herramientas esenciales en el arsenal de un programador para la prueba y diagnóstico de programas. Su uso consciente y estratégico contribuye a la detección temprana de errores y al desarrollo de software más robusto y confiable.

Manejo de excepciones

En programación, el manejo de excepciones es esencial para crear aplicaciones robustas y confiables. Una excepción es un evento que interrumpe el flujo normal de un programa cuando ocurre un error o condición inesperada. Gestionar estas situaciones permite que el programa responda adecuadamente sin finalizar abruptamente.

En PseInt, aunque no existe una estructura específica para el manejo de excepciones como en otros lenguajes, es posible implementar controles para anticipar y gestionar errores. Por ejemplo, para evitar una división por cero, podemos incorporar condiciones que validen los datos antes de realizar operaciones sensibles:

Algoritmo DivisionSegura
    Definir numerador, denominador, resultado Como Real
    Escribir "Introduce el numerador:"
    Leer numerador
    Escribir "Introduce el denominador:"
    Leer denominador
    Si denominador = 0 Entonces
        Escribir "Error: No es posible dividir entre cero."
    SiNo
        resultado <- numerador / denominador
        Escribir "El resultado es: ", resultado
    FinSi
FinAlgoritmo

En este algoritmo, verificamos si el denominador es cero antes de realizar la división, evitando así un error en tiempo de ejecución. Este tipo de validaciones son fundamentales para prevenir comportamientos inesperados.

En Python, el manejo de excepciones es más sofisticado gracias a las estructuras try, except, else y finally. Estas permiten capturar y gestionar excepciones de manera más directa y eficiente:

try:
    numerador = float(input("Introduce el numerador:\n"))
    denominador = float(input("Introduce el denominador:\n"))
    resultado = numerador / denominador
except ZeroDivisionError:
    print("Error: No es posible dividir entre cero.")
except ValueError:
    print("Error: Debes introducir un número válido.")
else:
    print("El resultado es:", resultado)
finally:
    print("Operación finalizada.")

En este ejemplo, el bloque try contiene el código que puede generar una excepción. Si ocurre una ZeroDivisionError (división por cero) o ValueError (entrada inválida), los bloques except correspondientes manejan el error. El bloque else se ejecuta si no hay excepciones, y finally se ejecuta siempre, haya ocurrido o no una excepción. Esta estructura proporciona un control detallado sobre el flujo del programa y mejora su fiabilidad.

Otra situación común es el manejo de archivos. Al intentar abrir un archivo que no existe, se puede generar una excepción. PseInt no tiene funciones integradas avanzadas para el manejo de archivos.

En Python, se maneja mediante excepciones específicas:

try:
    with open("datos.txt", "r") as archivo:
        contenido = archivo.read()
except FileNotFoundError:
    print("Error: El archivo no existe.")
else:
    print("Contenido del archivo:")
    print(contenido)

Aquí, usamos with open para abrir el archivo de forma segura. Si el archivo no se encuentra, se captura la excepción FileNotFoundError, evitando que el programa se detenga abruptamente. El uso de with también garantiza que el archivo se cierre correctamente después de su uso, mejorando la gestión de recursos.

El manejo de excepciones también es crucial en operaciones con listas o estructuras de datos.

En Python, podemos manejar esto usando excepciones:

lista = [10, 20, 30]
try:
    indice = int(input("Introduce el índice al que deseas acceder (0-2):\n"))
    valor = lista[indice]
except IndexError:
    print("Error: Índice fuera de rango.")
except ValueError:
    print("Error: Debes introducir un número entero.")
else:
    print("El valor es:", valor)

Este código captura la excepción IndexError si el índice está fuera de los límites de la lista y ValueError si la entrada no es un número entero. De esta forma, el programa puede informar al usuario del problema específico y continuar ejecutándose.

Es importante documentar y lograr claridad en el manejo de excepciones. En proyectos más grandes, definir excepciones personalizadas puede ser útil para representar errores específicos de la aplicación:

class ErrorPersonalizado(Exception):
    pass

def funcion_riesgosa():
    # Código que puede generar un error
    raise ErrorPersonalizado("Ocurrió un problema específico.")

try:
    funcion_riesgosa()
except ErrorPersonalizado as e:
    print("Error:", e)

Aunque en PseInt no podemos crear excepciones personalizadas, podemos diseñar el algoritmo para que devuelva códigos de error o mensajes específicos que indiquen el tipo de fallo ocurrido.

El manejo adecuado de excepciones es una práctica esencial en la programación moderna. Permite crear aplicaciones más resistentes, mejorar la experiencia del usuario y facilitar el mantenimiento y evolución del software. Al anticipar posibles errores y gestionar las excepciones de manera apropiada, se incrementa la confiabilidad y se minimiza el riesgo de fallos inesperados durante la ejecución.

Para seguir leyendo hazte Plus

¿Ya eres Plus? Accede a la app

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende Fundamentos GRATIS online

Todas las lecciones de Fundamentos

Accede a todas las lecciones de Fundamentos y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Fundamentos y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Entender los errores sintácticos, lógicos y de ejecución.
  • Utilizar depuradores para encontrar y corregir errores.
  • Implementar estrategias de prueba y diagnóstico eficaz.
  • Manejar excepciones para mejorar la estabilidad del código.