El verdadero coste de los bloques try/catch


Durante mi carrera, tanto universitaria como profesional, me he encontrado con varios profesores y colegas con ciertas reticencias a usar los bloques try/catch así como las excepciones. Por una parte estaban los que alegaban que iba contra la claridad del código, ya que consideraban más limpio y elegante verificar las precondiciones y tratar los posibles errores nada más detectarlos, en lugar de amontonar todo el tratamiento de fallos en uno o más bloques catch que pueden encontrarse a cientos de líneas de código de distancia de la sentencia que los propició.

Este podría ser un ejemplo de código que horrorizaría a más de uno por su uso exagerado del catch:



public void funcionExcepcional()
{ try { // Código que podría lanzar una excepción } catch(System.Net.WebException exp) { // Procesar una WebException } catch(System.Exception) { // Procesar una excepción de sistema distinta de WebException } catch { // Procesar otro tipo de excepción } finally { // Código que se ejecutará después de procesar cualquier excepción }
}

Pero sin duda, los detractores más fervientes del uso de esta técnica eran los que solían apelar a la poca eficiencia del manejo de excepciones frente a la táctica de comprobar las precondiciones a priori y sólo ejecutar el código “peligroso” cuando éstas se cumplan. ¿Qué hay de cierto en esta afirmación? ¿Tenemos constancia empírica de que esto siempre sea así?

Veamos lo que dice MSDN en sus consejos para mejorar el rendimiento de aplicaciones .NET, en el apartado de «Lanzar menos excepciones» (la traducción y las negritas son mías):

[...] Hay que tener en cuenta que esto no tiene nada que ver con los bloques try/catch: sólo se incurre en costes cuando realmente se lanza la excepción. Puedes usar tantos bloques try/catch como quieras. Usar excepciones a la ligera es lo que te hace perder rendimiento. Por ejemplo, deberías evitar prácticas como usar excepciones para controlar el flujo del programa.

Como vemos, aparte de apelar a la lógica a la hora de establecer el flujo normal de ejecución del programa, afirma que mientras no se lancen las excepciones la eficiencia del programa será la misma, tenga todos los try y catch que tenga.

Pero como a los informáticos no nos gustan las afirmaciones a la ligera y sólo creemos en los resultados empíricos, vamos a ver la prueba que Oren Eini, programador de RavenDB, hizo para comprobarlo. En primer lugar, tenemos unas sentencias de código en C# para calcular un valor aproximado de Pi:

var startNew = Stopwatch.StartNew();
var mightBePi = Enumerable.Range(0, 100000000).Aggregate(0d, (tot, next) => tot + Math.Pow(-1d, next)/(2*next + 1)*4);
Console.WriteLine(startNew.ElapsedMilliseconds);

El resultado obtenido por consola es de 6015 milisegundos. A continuación, se repite la ejecución enmarcando en un try la parte más costosa del código, es decir, el cálculo de Pi.

var startNew = Stopwatch.StartNew();
double mightBePi = Double.NaN;
try
{
    mightBePi = Enumerable.Range(0, 100000000).Aggregate(0d, (tot, next) => tot + Math.Pow(-1d, next)/(2*next + 1)*4);
}
catch (Exception e)
{
    Console.WriteLine(e);
}
Console.WriteLine(startNew.ElapsedMilliseconds);

El resultado fue de 5999. ¿Significa esto que al meterlo dentro de un bloque try se ejecuta más rápido? Pues no, la diferencia es tan pequeña que la podemos achacar a simples variaciones en el estado inicial del propio compilador (aquello de la teoría del caos y que nunca conseguirás dos resultados exactamente iguales). Lo que sí queda claro es que no se ha perdido eficiencia ninguna por añadir el manejo de excepciones.

En este punto habrá quien diga “Pero en la documentación de MSDN decían que sí se pierde rendimiento si la excepción salta, ¿qué hay de eso?”. La respuesta es simple: las excepciones están para eso mismo, para denotar situaciones excepcionales en nuestro código, y al ejecutarse se está rompiendo el flujo normal del programa, por lo que seguramente en ese punto lo importante ya no es la eficiencia, sino retomar el camino para poder volver pasar por ese bloque sin que salte la excepción.

Concluyendo: usad las excepciones a vuestro antojo, intentando que el código sea lo más lógico y legible posible, evitadlas cuando sean innecesarias y usadlas cuando podáis evitaros con ello tener que encadenar un montón de sentencias condicionales de una forma elegante. Pero, en cualquier caso, no las desechéis por su eficiencia, ya que perderéis una capacidad muy potente de vuestro lenguaje por un motivo que ni siquiera es cierto.

Vía | Ayende @ Rahien – What is the cost of try/catch

Portada de Genbeta