EDITAR 20/11/2009 :
Estaba leyendo este artículo de MSDN sobre cómo mejorar el rendimiento del código administrado y esta parte me recordó esta pregunta:
El costo de rendimiento de lanzar una excepción es significativo. Aunque el manejo estructurado de excepciones es la forma recomendada de manejar las condiciones de error, asegúrese de usar las excepciones solo en circunstancias excepcionales cuando ocurren condiciones de error. No utilice excepciones para el flujo de control regular.
Por supuesto, esto es solo para .NET, y también está dirigido específicamente a aquellos que desarrollan aplicaciones de alto rendimiento (como yo); así que obviamente no es una verdad universal. Aún así, somos muchos los desarrolladores de .NET, así que sentí que valía la pena señalarlo.
EDITAR :
Bien, en primer lugar, aclaremos una cosa: no tengo ninguna intención de pelear con nadie por la cuestión del rendimiento. En general, de hecho, me inclino a estar de acuerdo con aquellos que creen que la optimización prematura es un pecado. Sin embargo, permítanme señalar dos puntos:
El cartel pide una justificación objetiva detrás de la sabiduría convencional de que las excepciones deben usarse con moderación. Podemos discutir la legibilidad y el diseño adecuado todo lo que queramos; pero estos son asuntos subjetivos con personas dispuestas a discutir de ambos lados. Creo que el cartel lo sabe. El hecho es que usar excepciones para controlar el flujo del programa es a menudo una forma ineficiente de hacer las cosas. No, no siempre , pero a menudo . Es por eso que es un consejo razonable utilizar las excepciones con moderación, al igual que es un buen consejo comer carne roja o beber vino con moderación.
Existe una diferencia entre optimizar sin una buena razón y escribir código eficiente. El corolario de esto es que hay una diferencia entre escribir algo que es robusto, si no optimizado, y algo que es simplemente ineficiente. A veces pienso que cuando las personas discuten sobre cosas como el manejo de excepciones, en realidad están hablando entre ellos, porque están discutiendo cosas fundamentalmente diferentes.
Para ilustrar mi punto, considere los siguientes ejemplos de código C #.
Ejemplo 1: detección de entrada de usuario no válida
Este es un ejemplo de lo que yo llamaría abuso de excepción .
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
Este código es, para mí, ridículo. Por supuesto que funciona . Nadie está discutiendo con eso. Pero debería ser algo como:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
Ejemplo 2: Comprobación de la existencia de un archivo
Creo que este escenario es muy común. Ciertamente, parece mucho más "aceptable" para mucha gente, ya que se trata de E / S de archivos:
string text = null;
string path = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
inputChecksOut = true;
} catch (FileNotFoundException) {
path = GetInput();
}
}
Esto parece bastante razonable, ¿verdad? Estamos intentando abrir un archivo; si no está allí, detectamos esa excepción e intentamos abrir un archivo diferente ... ¿Qué hay de malo en eso?
Nada en realidad. Pero considere esta alternativa, que no arroja ninguna excepción:
string text = null;
string path = GetInput();
while (!File.Exists(path)) path = GetInput();
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
Por supuesto, si el desempeño de estos dos enfoques fuera realmente el mismo, esto realmente sería una cuestión puramente doctrinal. Entonces, echemos un vistazo. Para el primer ejemplo de código, hice una lista de 10000 cadenas aleatorias, ninguna de las cuales representaba un número entero adecuado, y luego agregué una cadena entera válida al final. Usando los dos enfoques anteriores, estos fueron mis resultados:
Usando try
/ catch
block: 25.455 segundos
Usando int.TryParse
: 1.637 milisegundos
Para el segundo ejemplo, hice básicamente lo mismo: hice una lista de 10000 cadenas aleatorias, ninguna de las cuales era una ruta válida, luego agregué una ruta válida al final. Estos fueron los resultados:
Usando try
/ catch
block: 29,989 segundos
Usando File.Exists
: 22,820 milisegundos
Mucha gente respondería a esto diciendo: "Sí, bueno, lanzar y atrapar 10,000 excepciones es extremadamente poco realista; esto exagera los resultados". Claro que lo hace. La diferencia entre lanzar una excepción y manejar una entrada incorrecta por su cuenta no será notoria para el usuario. El hecho es que el uso de excepciones es, en estos dos casos, de 1,000 a más de 10,000 veces más lento que los enfoques alternativos que son igualmente legibles, si no más.
Por eso incluí el ejemplo del GetNine()
método a continuación. No es que sea intolerablemente lento o inaceptablemente lento; es que es más lento de lo que debería ser ... sin una buena razón .
Nuevamente, estos son solo dos ejemplos. Por supuesto , habrá ocasiones en las que el impacto en el rendimiento del uso de excepciones no sea tan grave (Pavel tiene razón; después de todo, depende de la implementación). Todo lo que digo es: seamos realistas, muchachos. En casos como el anterior, lanzar y atrapar una excepción es análogo a GetNine()
; es solo una forma ineficaz de hacer algo que fácilmente podría hacerse mejor .
Estás pidiendo una justificación como si esta fuera una de esas situaciones en las que todos se subieron al carro sin saber por qué. Pero, de hecho, la respuesta es obvia y creo que ya la sabe. El manejo de excepciones tiene un rendimiento horrendo.
De acuerdo, tal vez esté bien para su escenario comercial particular, pero en términos relativos , lanzar / capturar una excepción introduce mucha más sobrecarga de la necesaria en muchos, muchos casos. Lo sabes, yo lo sé: la mayoría de las veces , si estás usando excepciones para controlar el flujo del programa, solo estás escribiendo código lento.
También podría preguntar: ¿por qué este código es malo?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
Apuesto a que si perfilara esta función, encontraría que funciona bastante rápido para su aplicación comercial típica. Eso no cambia el hecho de que es una forma horriblemente ineficiente de lograr algo que podría hacerse mucho mejor.
Eso es lo que la gente quiere decir cuando habla de "abuso" de excepción.