Excepciones
El framework de .net utiliza un mecanismo para informar de situaciones que
impiden el flujo normal del código como un error, se trata de las excepciones.
Las excepciones se pueden producir por muchas razones, en general situaciones
inesperadas, por ejemplo querer acceder a un archivo que no existe, esto produce
una horrible excepción, veamos un poco de código
using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt"))
{
reader.ReadLine();
reader.Close();
}
Si lo ejecutamos recibimos una fea fea excepción del tipo FileNotFoundException,
ok esto es lo que queríamos, ahora bien, qué hacemos ahora?
Alternativa ante las excepciones
Inicialmente podemos hacer dos cosas cuando nos encontramos con una porción de
código que puede lanzar una excepción
-Atraparla en un bloque catch
-No hacer nada
Comencemos por el primer caso
try
{
using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt"))
{
reader.ReadLine();
reader.Close();
}
}catch
{
throw;
}
Qué pasa acá, bien, lo mismo que no hacer nada, porque en el bloque volvemos a
lanzar la excepción por lo tanto es lo mismo que no hacer nada
try
{
using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt"))
{
reader.ReadLine();
reader.Close();
}
}catch(Exception ex)
{
throw ex;
}
Este ejemplo es peor que el anterior porque no sólo no hacemos nada sino que
además al colocar Exeception ex y luego hacer throw borramos toda la información
que se recopiló hasta el momento del error (el stack trace) por lo tanto, no
sólo no la manipulamos sino que además perdemos valiosa información.
Enseñanza:
-Si atrapamos una excepción y la relanzamos haciendo referencia al objeto
Exception borramos el stack trace.
try
{
using (StreamReader reader = new StreamReader(@"c:\archivoQueNoExiste.txt"))
{
reader.ReadLine();
reader.Close();
}
}catch(Exception ex)
{
GuardarInformacionDelError(ex);
}
Esto es bastante más elegante, ocurre un error y guardamos la información para
un análisis posterior, sin embargo esto nos lleva a una enseñanza mejor, por qué
no hacer mejor esto:
string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{
using (StreamReader reader = new StreamReader(nombre))
{
reader.ReadLine();
reader.Close();
}
}
Nada de nada, no ocurre la excepción porque estamos verificando de antemano la
existencia del archivo, la enseñanza es que es mejor evitar las excepciones,
porque son costosas a nivel recursos y son feas también.
Manejando excepciones
En caso de querer manejar la excepción o que ocurra una excepción inesperada por
ejemplo:
string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{
using (StreamWriter writer = new StreamWriter(nombre))
{
writer.Write("hola mundo");
writer.Close();
}
}
y que el archivo sea de sólo lectura o algo así (obviamente también podemos
verificarlo antes, es un ejemplo), entonces tenemos un bloque de código en el
catch para manipular la excepción, pero qué hacemos? Primero que nada podemos
guardar información de error, segundo lanzar otra excepción, una personalizada,
indicando un problema con información más "amigable"
try
{
string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{
using (StreamWriter writer = new StreamWriter(nombre))
{
writer.Write("hola mundo");
writer.Close();
}
}
}catch (Exception ex)
{
GuardarInformacionDelError(ex);
throw new ExcepcionPersonalizada("Ocurrión un error sentimos las molestias");
}
Un lujo asiático, vamos a hacer un resumen de lo que aprendimos
-Siempre es mejor verificar para evitar las excepciones que esperar que ocurran
-Si ocurren y vamos a atraparlas hay que manipularlas correctamente, de muy poco
sirve relanzar la misma excepción
Tipificando excepciones
Las excepciones son objeto de una clase, todas heredan de la clase base
Exception y las podemos diferenciar por el tipo, nada de códigos de error ni
cosas raras
Esta tipificación nos permite poder hacer cosas simpáticas filtrando las
excepciones, veámos el siguiente ejemplo:
try
{
string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{
using (StreamWriter writer = new StreamWriter(nombre))
{
writer.Write("hola mundo");
writer.Close();
}
}
}catch (UnauthorizedAccessException)
{
throw new ExcepcionPersonalizada("No se puede acceder al archivo");
}catch (Exception ex)
{
GuardarInformacionDelError(ex);
throw new ExcepcionPersonalizada("Ocurrió un error inesperado");
}
En este caso en caso de ocurrir una excepcion del tipo UnauthorizedAccessException
vamos a lanzar otra indicando que no se puede acceder al achivo, ahora cuando
ocurra cualquier otra excepción se va a guardar la información y se va a lanzar
una excepción personalizada indicando que pasó algo raro. Ahora, sabemos que
UnautorizedAccessException hereda de SystemException qué pasa si hacemos esto?:
try
{
string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{
using (StreamWriter writer = new StreamWriter(nombre))
{
writer.Write("hola mundo"); writer.Close();
}
}
}catch (UnauthorizedAccessException)
{
throw new ExcepcionPersonalizada("No se puede acceder al archivo");
}catch (SystemException)
{
throw new ExcepcionPersonalizada("Error de sistema");
}catch (Exception ex)
{
GuardarInformacionDelError(ex);
throw new ExcepcionPersonalizada("Ocurrió un error inesperado");
}
Bien, se ejecuta el bloque que tiene UnauthorizedAccessException porque siempre se
ejecuta primero el bloque con el tipo más especializado, es decir, como la
excepción es del tipo UnauthorizedAccessException por más que esta herede de
SystemException se ejecuta su bloque para que podamos hacer algo con ese tipo en
particuar de excepción, otra cosa con todas las otras excepciones que hereden de
systemException y por útlimo otra cosa con el resto.
Otra enseñanza
-Cuando tenemos varios bloque catch se ejecuta el que tiene el tipo que mejor
encaja con el de la excepción que se produjo y sólo ese.
Ahora bien, qué pasa si hacemos esto:
try
{
string nombre = @"c:\archivoQueNoExiste.txt";
if (File.Exists(nombre))
{
using (StreamWriter writer = new StreamWriter(nombre))
{
writer.Write("hola mundo"); writer.Close();
}
}
}catch (ApplicationException)
{
throw new ExcepcionPersonalizada("No se puede acceder al archivo");
}
Nadie atrapa la excepción porque estamos sólo teniendo en cuenta las del tipo
ApplicationException y deribadas y no es el caso de UnauthorizedAccessException,
entonces la excepción "burbujea" al método que invocó al actual y seguirá
haciendo esto hasta que algún bloque maniuple la excepción, si esto nunca ocurre
la aplicación pincha, o más técnicamente, el CLR la finaliza con un carte muy
feo que habla acerca de nuestro descuído.
Otra enseñanza
-Si no atrapamos una excepción esta burbujea a la capa superior y así
sucesivamente hasta que alguien la manipula o el CLR interrumpe la aplicación
No utilizar excepciones como control de flujo para reglas de negocios
Un tema polémico, no es recomendable utilizar excepciones personalizadas para
controlar el flujo de la aplicación por dos razones:
-Son costosas a nivel recursos
-Y se llaman excepciones porque deben ocurrir sólo en ocasiones excepcionales
Auque puede haber casos en los que se utilicen, por ejemplo, escribimor un
servicio web para que consuma alquien a través de internet, poco seguro sería si
la validación de los datos del usuario devolviese un valor booleano si la
autenticación del mismo fuera errónea, creo que más de uno pensaría en que es
sencillo saltarla, en este caso lo más recomendable es lanzar una excepción para
que se interrumpa la comunicación directamente.
Crear excepciones personalizadas
Bueno, no hay mucho para decir, simplemente hay que seguir una regla, que
nuestras excepciones hereden de ApplicationException porque Exception está
reservada para excepciones del CLR, esto nos va a permitir un mejor filtrado,
más aún si definimos nuestra propia excepción base y hacemos heredas todas
nuestras excepciones de esta.
Hasta la próxima.