Aikido

¿Por qué se deberían manejar los errores en los bloques catch en lugar de dejarlos vacíos?

Legibilidad

Regla
Manejar errores en captura bloques. 
Vacío catch vacíos en silencio se tragan errores, 
haciendo depuración la depuración. 
Lenguajes soportados: Java, C, C++, PHP, JavaScript, 
TypeScript, Go, Python

Introducción

Los bloques catch vacíos son uno de los anti-patrones más peligrosos en código de producción. Cuando las excepciones son capturadas pero no manejadas, el error desaparece sin dejar rastro. La aplicación continúa ejecutándose con un estado corrupto, datos inválidos u operaciones fallidas que deberían haber detenido la ejecución. Los usuarios experimentan fallos silenciosos donde las funcionalidades no operan, pero no reciben mensajes de error. Los equipos de operaciones no tienen registros con los que depurar. La única indicación de que algo va mal aparece horas o días después, cuando los fallos en cascada hacen que el sistema sea inutilizable.

Por qué es importante

Depuración y respuesta a incidentes: Los bloques `catch` vacíos eliminan los registros de errores. Los ingenieros no tienen un seguimiento de pila, mensaje de error o indicación de cuándo o dónde ocurrió el fallo, lo que hace que los problemas sean casi imposibles de reproducir.

Corrupción silenciosa de datos: Cuando las operaciones de base de datos o las llamadas a la API fallan dentro de bloques `catch` vacíos, la aplicación continúa como si hubieran tenido éxito. Los registros se actualizan parcialmente, las transacciones quedan incompletas y, cuando se descubre la corrupción, el rastro de auditoría ha desaparecido.

Vulnerabilidades de seguridad: Los bloques catch vacíos ocultan fallos de seguridad como errores de autenticación o comprobaciones de autorización. Un atacante que desencadena una excepción en una ruta crítica para la seguridad podría eludir por completo las protecciones si el error es silenciado.

Fallos en cascada: Cuando los errores se ocultan, la aplicación continúa en un estado inválido. Las operaciones subsiguientes que dependen del resultado de la operación fallida también fallarán, creando una cadena de fallos que desvía a los ingenieros de la causa raíz real.

Ejemplos de código

❌ No conforme:

async function updateUserProfile(userId, profileData) {
    try {
        await db.users.update(userId, profileData);
        await cache.invalidate(`user:${userId}`);
        await searchIndex.update(userId, profileData);
    } catch (error) {
        // TODO: handle error
    }

    return { success: true };
}

Por qué está mal: Si alguna operación falla, el error se ignora silenciosamente y la función devuelve éxito. La base de datos podría actualizarse, pero la invalidación de la caché podría fallar, dejando datos obsoletos. O la actualización del índice de búsqueda falla, haciendo que el usuario no sea localizable, sin un registro o alerta que indique el problema.

✅ Conforme:

async function updateUserProfile(userId, profileData) {
    try {
        await db.users.update(userId, profileData);
        await cache.invalidate(`user:${userId}`);
        await searchIndex.update(userId, profileData);
        return { success: true };
    } catch (error) {
        logger.error('Failed to update user profile', {
            userId,
            error: error.message,
            stack: error.stack
        });
        throw new ProfileUpdateError(
            'Unable to update profile',
            { cause: error }
        );
    }
}

Por qué esto es importante: Cada error se registra con contexto, proporcionando información de depuración. El error se propaga al llamador, permitiendo un manejo de errores adecuado en el nivel apropiado. Los sistemas de monitorización pueden alertar sobre estos errores, y la aplicación falla rápidamente en lugar de continuar con un estado inválido.

Conclusión

Los bloques catch vacíos nunca son aceptables en código de producción. Toda excepción capturada necesita ser registrada como mínimo, y la mayoría deben propagarse a los llamadores o desencadenar acciones de recuperación específicas. Si realmente necesita ignorar un error, documente el motivo con un comentario que explique la justificación de negocio. Por defecto, los errores siempre deben manejarse explícitamente, no descartarse en silencio.

Preguntas frecuentes

¿Tiene preguntas?

¿Qué ocurre si realmente necesito ignorar ciertos errores?

Documéntalo explícitamente con un comentario que explique por qué el error puede ignorarse de forma segura. Registra el error a nivel de depuración para que aparezca en los logs detallados, pero sin activar alertas. Considera si ignorar el error podría llevar a un estado inválido. Incluso para errores "esperados" como fallos de caché o tiempos de espera de red, el registro ayuda a los equipos de operaciones a comprender los patrones de comportamiento del sistema.

¿Siempre debo registrar errores en los bloques catch?

El logging suele ser una buena idea porque no se pueden depurar problemas sin ver qué falló. Hay casos en los que se puede rastrear el problema sin logs, como relanzar inmediatamente el error para que sea gestionado en otro lugar, o si la aplicación debe fallar y reiniciarse ante fallos críticos. Pero un logging adecuado siempre ayuda.

¿Cuál es la diferencia entre el registro de logs y el relanzamiento de errores?

El logging registra lo sucedido para la depuración y monitorización. Relanzar propaga el error a los llamadores para que puedan decidir cómo responder. Haga ambas cosas: registre el error con contexto en el punto de fallo y luego reláncelo (posiblemente envuelto en un tipo de error más específico) para permitir que los llamadores gestionen la recuperación. No registre el mismo error en múltiples niveles, ya que crea ruido.

¿Cómo gestiono los errores que ocurren en bloques finally?

Los bloques `finally` rara vez deberían lanzar errores. Si deben realizar operaciones propensas a errores (como cerrar recursos), envuélvalas en su propio `try-catch`. Registre cualquier error, pero no permita que enmascaren el error original. Algunos lenguajes proporcionan sintaxis para manejar tanto el error principal como los errores del bloque `finally`; utilice estos mecanismos para preservar ambos contextos de error.

¿Qué hay del impacto en el rendimiento de registrar cada error?

El logging es económico comparado con el coste de depurar problemas en producción sin logs. Los frameworks de logging modernos están altamente optimizados. Si tiene tantos errores que el logging afecta al rendimiento, solucione los errores en lugar de ocultarlos. Las altas tasas de error indican problemas graves que los bloques catch vacíos solo empeorarán.

¿Los bloques catch siempre deben lanzar errores, o pueden devolver valores de error?

Depende del lenguaje y la arquitectura. En JavaScript con promesas, lanzar una excepción desde un bloque `catch` la propaga al siguiente manejador de errores. Devolver un objeto de error desde un `catch` resuelve la promesa con ese error, lo cual suele ser incorrecto. Familiarízate con la semántica del manejo de errores de tu lenguaje. Generalmente, deja que los errores se propaguen a menos que puedas recuperarte de forma significativa.

¿Cómo gestiono los errores en operaciones asíncronas que no tienen try-catch?

Utiliza manejadores .catch() en promesas, oyentes de eventos de error en emisores de eventos o callbacks de error en APIs basadas en callbacks. Nunca ignores los manejadores de rechazo o los callbacks de error. Los rechazos de promesas no manejados deben ser monitorizados a nivel de proceso y tratados como fallos críticos. Node.js moderno puede terminar ante rechazos no manejados, lo cual es mejor que un fallo silencioso.

Asegúrate ahora.

Proteja su código, la nube y el entorno de ejecución en un único sistema central.
Encuentre y corrija vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.