Ilustración conceptual de un registro con error separado para revisión mientras continúan los registros válidos.

Errores y excepciones: fallar de una forma que permita recuperarse

El programa termina sin que aparezca ningún error. También entrega una lista vacía, aunque nadie ha conseguido leer el archivo de entrada. A primera vista parece una herramienta resistente: nada la interrumpe. En realidad ha convertido «no he podido comprobarlo» en un resultado que se parece demasiado a «no hay nada que revisar». La tranquilidad de la pantalla sale bastante cara cuando alguien la utiliza para tomar una decisión.

Gestionar una excepción consiste en decidir qué hacer ante un fallo concreto, conservar contexto y evitar que el resultado prometa más de lo que se ha ejecutado. Vamos a trabajar con superficies de espacios ficticios y una exportación inexistente en Python y C#. Separaremos un registro inválido, ante el que podemos continuar revisando otros, de una entrada completa que no ha llegado a abrirse. El objetivo no es que la aplicación jamás se queje. Es que sus quejas sirvan para algo más que decorar un archivo de logs que nadie quiere abrir.

Tabla de contenidos

Continuar tiene sentido cuando el fallo queda localizado

Una excepción interrumpe la ejecución normal del tramo donde aparece y busca un manejador adecuado. Puede proceder de una operación del lenguaje, de una biblioteca o de una decisión explícita de nuestro código. Capturarla permite reaccionar, pero no demuestra que el trabajo pendiente se haya completado. Antes de escribir un except conviene decidir qué información sigue siendo fiable y qué respuesta podemos ofrecer.

En nuestro caso recibiremos identificador y superficie como texto decimal con punto, expresada en metros cuadrados. Una entrada como "pendiente" no sirve para calcular. Podemos registrar ese fallo y revisar los siguientes espacios porque cada registro se interpreta de forma independiente. Si el proceso exigiese una importación indivisible, tendríamos que parar o rechazar el conjunto; continuar no sería automáticamente la opción más correcta.

La función siguiente convierte el texto, añade el identificador a un error de formato y rechaza valores negativos o no finitos. El recorrido captura los errores de valor previstos para estos datos. Puedes guardar el programa completo y ejecutarlo con Python 3.12; los registros no proceden de un modelo real.

				
					import math

def leer_superficie(identificador, texto):
    try:
        valor = float(texto)
    except ValueError as causa:
        raise ValueError(f"{identificador}: superficie no interpretable") from causa
    if not math.isfinite(valor) or valor < 0:
        raise ValueError(f"{identificador}: superficie fuera del dominio admitido")
    return valor

registros = [("E01", "18.5"), ("E02", "pendiente"), ("E03", "22.0")]
for identificador, texto in registros:
    try:
        superficie = leer_superficie(identificador, texto)
    except ValueError as error:
        print(error)
    else:
        print(identificador, superficie)
				
			

La salida contiene E01 18.5, E02: superficie no interpretable y E03 22.0. No sustituimos el fallo de E02 por cero, ni lo hacemos desaparecer devolviendo una colección vacía. El recorrido continúa porque hemos escogido una política de revisión por registro. Si mostrásemos un resumen, tendría que indicar que hay dos lecturas válidas y una no interpretada, en lugar de anunciar «tres espacios comprobados correctamente».

Este ejemplo no comprueba la superficie mínima de ningún uso ni una norma. El dominio didáctico admite cero y excluye negativos, NaN e infinito. Si una regla de proyecto exige un valor estrictamente positivo, debe expresarlo de forma adicional. Un error de lectura, un valor numérico fuera de dominio y un incumplimiento de requisitos no son necesariamente el mismo diagnóstico, aunque todos merezcan atención.

Añade contexto sin destruir la causa del error

Un mensaje como «no se puede convertir el texto» puede servir mientras miramos una sola llamada. Dentro de miles de registros necesitamos saber cuál falló. El identificador E02 permite localizar la entrada sin volcar indiscriminadamente todos sus datos al mensaje. La expresión raise ... from causa mantiene además la relación con la excepción original para que un diagnóstico técnico pueda seguir el origen.

El mensaje orientado al usuario y la información de diagnóstico no tienen por qué ser idénticos. Una interfaz puede mostrar qué registro revisar y conservar el detalle técnico en un lugar apropiado. Ese detalle puede incluir la operación y el tipo de error; no necesita contener credenciales, tokens ni todo el documento. Añadir contexto significa escoger información útil, no imprimir cada objeto que tengamos cerca por si acaso.

La guía de errores y excepciones de Python explica la captura selectiva y el encadenamiento. En el primer programa capturamos ValueError donde convertimos el texto y donde decidimos continuar el recorrido. Si por un fallo del programa llamamos a una función inexistente y aparece NameError, ese manejador no lo convierte en una superficie inválida. El error debe llegar a una capa capaz de informar del fallo real.

Tampoco conviene envolver un proceso entero en una captura de valor si contiene muchas operaciones que pueden producir el mismo tipo de excepción por motivos distintos. Aquí el try de conversión es pequeño y su responsabilidad es reconocible. Si añadimos lectura, escritura y presentación dentro de ese mismo bloque, un mensaje sobre superficies podría acabar describiendo un fallo que ocurrió en otro sitio. Acotar el tramo reduce la cantidad de adivinación necesaria al investigar.

Si falta el archivo, la importación todavía no ha empezado

No poder abrir la fuente completa es un fallo distinto del de una fila inválida. No tenemos filas que clasificar ni un conjunto vacío demostrado: tenemos una lectura que no se ha podido realizar. Para reproducirlo sin depender de los archivos del lector, el siguiente programa crea una carpeta temporal vacía y trata de leer un archivo que no hemos creado dentro de ella. La carpeta temporal se elimina al salir del bloque.

				
					from pathlib import Path
from tempfile import TemporaryDirectory

def leer_exportacion(ruta):
    try:
        return ruta.read_text(encoding="utf-8")
    except FileNotFoundError as causa:
        raise FileNotFoundError(f"No se encuentra la exportacion: {ruta.name}") from causa

with TemporaryDirectory() as carpeta:
    ruta = Path(carpeta) / "espacios.csv"
    try:
        contenido = leer_exportacion(ruta)
    except FileNotFoundError as error:
        print("Importacion no iniciada")
        print(error)
				
			

Las líneas son Importacion no iniciada y No se encuentra la exportacion: espacios.csv. La función no devuelve "" para que el resto continúe como si hubiese leído un archivo vacío. Tampoco crea un archivo nuevo en silencio para resolver la ausencia de la entrada. La recuperación podría consistir en pedir otra ruta o esperar a que se entregue el archivo; ninguna de las dos consiste en certificar datos que todavía no hemos recibido.

La captura está limitada a FileNotFoundError. Un problema de permisos, un fallo de decodificación o una ruta que corresponde a otro tipo de recurso necesita su propio tratamiento si queremos recuperarnos de él. No los presentamos todos como «archivo no encontrado». En una aplicación habría que traducir esos estados a respuestas útiles en su frontera de entrada; aquí dejamos visible que solo sabemos manejar el caso elegido.

Los recursos también necesitan cerrarse cuando algo falla. En Python, los gestores de contexto con with permiten expresar esa vida útil; al abrir un archivo real suelen ser una alternativa más clara que repetir cierres en cada rama. La existencia de un bloque finally no obliga a escribirlo en todas las funciones. Lo necesario es saber quién libera cada recurso y qué ocurre al interrumpirse el trabajo.

C# permite conservar la excepción original como causa interna

En C# podemos añadir contexto creando una excepción con la original como InnerException. El siguiente programa reproduce la lectura por registro en una consola de .NET 8. El formato decimal es invariante y usa punto. La salida también fija esa cultura para que el ejemplo se pueda contrastar sin depender de la configuración regional del equipo.

				
					using System;
using System.Globalization;

var registros = new (string Id, string Texto)[]
{
    ("E01", "18.5"), ("E02", "pendiente"), ("E03", "22.0")
};
foreach (var registro in registros)
{
    try
    {
        double superficie = LeerSuperficie(registro.Id, registro.Texto);
        Console.WriteLine($"{registro.Id} {superficie.ToString("0.0", CultureInfo.InvariantCulture)}");
    }
    catch (FormatException error)
    {
        Console.WriteLine(error.Message);
    }
}

static double LeerSuperficie(string id, string texto)
{
    double valor;
    try
    {
        valor = double.Parse(texto, NumberStyles.Float, CultureInfo.InvariantCulture);
    }
    catch (FormatException causa)
    {
        throw new FormatException($"{id}: superficie no interpretable", causa);
    }
    if (!double.IsFinite(valor) || valor < 0)
        throw new FormatException($"{id}: superficie fuera del dominio admitido");
    return valor;
}
				
			

Obtendrás las mismas tres líneas que en Python. Para este contrato pequeño hemos elegido FormatException como señal que el recorrido sabe mostrar y continuar. En una biblioteca más amplia podríamos representar los fallos de validación esperables con resultados explícitos y reservar excepciones para otras situaciones. La elección debe explicar qué puede manejar el llamador; no hace falta lanzar una excepción para cada casilla que no cumple una regla habitual.

La guía de tratamiento de excepciones de C# desarrolla try, catch y finally. Si solo necesitamos volver a propagar la misma excepción desde un catch, throw; conserva información de su recorrido; no hace falta reconstruirla con un mensaje más corto. Cuando añadimos una excepción nueva, conservar la causa interna permite distinguir el contexto que hemos añadido de lo que ocurrió originalmente.

Ni la cultura invariante ni el tipo de excepción hacen idénticos todos los formatos numéricos de Python y .NET. Los ejemplos comparten las entradas mostradas y el dominio elegido. Si una fuente utiliza coma decimal, separadores de miles u otra representación, su contrato tendrá que especificarlo y sus pruebas tendrán que cubrirlo. Un catch no convierte un formato desconocido en una política internacional de intercambio.

Recuperarse exige decir qué parte del trabajo sigue siendo válida

Una recuperación útil puede ofrecer corregir una fila, seleccionar otro archivo o repetir una lectura transitoria. También puede detener el proceso con una explicación suficiente. Elegir entre esas acciones depende del alcance del fallo y de los efectos ya realizados. No reintentaría una escritura simplemente porque una captura genérica permita volver a ejecutar la función: quizá el primer intento sí produjo parte del cambio.

En estos programas solo leemos y calculamos datos ficticios, así que continuar tras una fila inválida no deja una escritura a medias. En un modelo, una base de datos o una API habrá que estudiar transacciones, resultados parciales y operaciones repetidas. La política debe impedir que la interfaz anuncie éxito cuando no sabemos qué terminó. Este artículo prepara la distinción; no demuestra una recuperación de escrituras reales.

Para probar nuestros ejemplos, cambia el texto de E02 por NaN, -2 y un decimal correcto. Comprueba que los dos primeros obtienen el diagnóstico de dominio y que el último vuelve al camino de lectura válida. Introduce además un error de programación deliberado fuera de la conversión: debe seguir siendo reconocible como tal. Si todo termina en «dato incorrecto», el manejador ha empezado a ocultar información que necesitamos para reparar la herramienta.

¿Qué preferirías ver al fallar una importación: un mensaje general o una lista que identifique qué se ha leído, qué no y por qué? Si mantienes una herramienta, cuenta en los comentarios qué información te ha permitido resolver un fallo de verdad. Yo acepto seguir explicando excepciones si a cambio dejamos de llamar «robusto» a un programa cuya principal habilidad es no reconocer que se ha equivocado.

Artículos relacionados

Respuestas

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *