c# - ¿Manejo de excepciones en aplicaciones n-tier?
exception-handling (5)
¿Cuál es el enfoque o la mejor práctica sugerida para manejar las excepciones en aplicaciones escalonadas?
- ¿Dónde deberías colocar bloques
try/catch
? - ¿Dónde debería implementar el registro?
- ¿Existe un patrón sugerido para administrar excepciones en aplicaciones de n niveles?
Considere un ejemplo simple. Supongamos que tiene una IU, que llama a una capa de negocios, que llama a una capa de datos:
//UI
protected void ButtonClick_GetObject(object sender, EventArgs e)
{
try {
MyObj obj = Business.GetObj();
}
catch (Exception ex) {
Logger.Log(ex); //should the logging happen here, or at source?
MessageBox.Show("An error occurred");
}
}
//Business
public MyObj GetObj()
{
//is this try/catch block redundant?
try {
MyObj obj = DAL.GetObj();
}
catch (Exception ex) {
throw new Exception("A DAL Exception occurred", ex);
}
}
//DAL
public MyObj GetObj()
{
//Or is this try/catch block redundant?
try {
//connect to database, get object
}
catch (SqlException ex) {
throw new Exception("A SQLException occurred", ex);
}
}
¿Qué críticas harías del manejo de excepciones anterior?
Gracias
Aquí hay algunas reglas que sigo relacionadas con el manejo de excepciones:
- las excepciones solo se deben capturar en las capas que pueden implementar la lógica para manejarlas. La mayoría de los casos esto sucede en las capas superiores. Como regla general, antes de implementar una captura dentro de una capa, pregúntese si la lógica de cualquiera de las capas superiores depende de alguna manera de la existencia de la excepción. Por ejemplo, en su capa empresarial puede tener la siguiente regla: cargar datos desde un servicio externo si el servicio está disponible, si no cargarlo desde el caché local. Si llamar al servicio arroja una exption, puede capturarlo y registrarlo a nivel BL y luego devolver los datos del caché. La capa superior de la interfaz de usuario no tiene que realizar ninguna acción en este caso. Sin embargo, si el servicio y la llamada de la memoria caché fallan, la excepción tendrá que ir al nivel de la interfaz de usuario para que se muestre un mensaje de error al usuario.
- todas las excepciones dentro de una aplicación deben ser detectadas y si no hay una lógica especial para manejarlas, al menos deben estar registradas. Por supuesto, esto no significa que tenga que envolver el código de todos los métodos en los bloques try / catch. En su lugar, cualquier tipo de aplicación tiene un controlador global para excepciones no detectadas. Por ejemplo, en las aplicaciones de Windows se debe implementar el evento
Application.ThreadException
. En las aplicaciones ASP .Net, se debe implementar el controlador de eventosApplication_Error
de global.asax. Estos lugares son las ubicaciones más altas donde puede detectar excepciones en su código. En muchas aplicaciones, este será el lugar donde capturará la mayoría de las excepciones y además de iniciar sesión, aquí probablemente también implementará una ventana de mensaje de error genérica y amigable que se presentará al usuario. - puede implementar la funcionalidad de prueba / finalmente donde la necesite sin implementar un bloque catch. No se debe implementar un bloque catch si no necesita implementar ninguna lógica de manejo de excepciones. Por ejemplo:
SqlConnection conn = null;
try
{
conn = new SqlConnection(connString);
...
}
// do not implement any catch in here. db exceptions logic should be implemented at upper levels
finally
{
// finalization code that should always be executed.
if(conn != null) conn.Dispose();
}
- si necesita volver a lanzar una excepción de un bloque catch, hágalo simplemente usando el método
throw;
. Esto asegurará que el seguimiento de la pila se conserva. Utilizandothrow ex;
restablecerá la traza de la pila. - Si necesita volver a emitir una excepción con otro tipo que tenga más sentido para los niveles superiores, incluya la excepción original en la propiedad InnerException de la excepción recién creada.
- Si necesita lanzar excepciones desde su código, use tipos de excepción significativos.
El manejo de excepciones es difícil en cualquier aplicación. Debe pensar en todas y cada una de las excepciones y entrar rápidamente en un patrón que funcione para usted. Intento agrupar excepciones en una de las siguientes categorías ...
- Excepciones que solo deberían suceder si su código es incorrecto (o usted no lo entiende o no tiene control sobre ellos):
Ejemplo: Tal vez su marco de persistencia requiera que capture las excepciones de SQL que pueden surgir de un SQL mal formado, sin embargo, está ejecutando una consulta codificada.
Manejo: En mi experiencia, la mayoría de las excepciones caen en esta categoría. Por lo menos, registrarlos. Aún mejor, envíelos a un servicio de manejo de excepciones que los registra. Luego, en el futuro, si decide que desea registrarlos de manera diferente o hacer algo diferente con ellos, puede cambiarlos en un solo lugar. Tal vez también desee enviar una bandera a la capa de la interfaz de usuario que indique que se produjo algún tipo de error y que deberían volver a intentar su operación. Tal vez usted envíe un administrador por correo.
También deberá devolver algo a las capas superiores para que la vida en el servidor continúe. Quizás este sea un valor predeterminado, o tal vez sea nulo. Tal vez tengas alguna forma de cancelar toda la operación.
Otra opción es dar al servicio de manejo de excepciones dos métodos de manejo. Un método handleUnexpectedException()
notificaría al usuario, pero no volvería a emitir una excepción, y luego podría devolver un valor predeterminado si tiene la capacidad de desenrollar la pila usted mismo o continuar de alguna manera. Un método handleFatalException()
notificaría al usuario y volvería a emitir algún tipo de excepción para que pueda dejar que la excepción que lanza el desenrolle la pila por usted.
- Excepciones que en realidad son causadas por el usuario:
Ejemplo: el usuario está intentando actualizar un widget de foobar y darle un nuevo nombre, pero ya existe un widget de foobar con el nombre que desean.
Manejo: en este caso, debe devolver la excepción al usuario. En este caso, puede seguir adelante y seguir lanzando la excepción (o mejor aún, no la atrape) hasta que llegue a la capa de la interfaz de usuario y luego la capa de la interfaz de usuario debería saber cómo manejar la excepción. Asegúrese de documentar estas excepciones para que usted (o quien esté escribiendo su UI) sepa que existen y sepa que las espere.
- Excepciones que realmente puedes manejar:
Ejemplo: realiza una llamada de servicio remoto y el servicio remoto se agota, pero sabe que tienen un historial de hacerlo y que solo debe rehacer la llamada.
Manejo: A veces estas excepciones comienzan en la primera categoría. Después de que su aplicación ha estado en libertad por un tiempo, se da cuenta de que realmente tiene una buena manera de manejarlo. A veces, como con las excepciones del bloqueo optimista o las excepciones interrumpidas, capturar la excepción y hacer algo al respecto es solo una parte del negocio. En estos casos, manejar la excepción. Si su idioma (creo que Java) distingue entre excepciones marcadas y no marcadas, recomendaría que siempre se revisen las excepciones.
Para abordar su pregunta anterior, delegaría la excepción inicial a un servicio que notificaría al usuario y, dependiendo de qué tipo de objeto es MyObj (por ejemplo, la configuración), podría dejar que esto sea una excepción no fatal y devolver un valor predeterminado o si no puedo hacer eso (p. ej., cuenta de usuario), entonces puedo dejar que esto sea una excepción fatal para no tener que preocuparme por eso.
Estoy con la clase de excepción por separado para cada capa (DALException, BLException, ...) para registrar (por ejemplo: en el archivo) las excepciones en los límites de la capa (esto es para el administrador), porque el usuario solo debería ver un mensaje de error claro y comprensible. esas excepciones deberían tratarse en DAlBase, que heredada por todas las capas de acceso a datos, así como en todas las capas. con eso podemos centralizar el manejo de excepciones en pocas clases y el desarrollador solo lanzará layerexception (por ejemplo: DALException) ver más información MultiTier Exception Handling .
Lo primero que hay que arreglar sería nunca lanzar una Exception
general.
El segundo, a menos que haya una buena razón para envolver una excepción, simplemente throw;
En lugar de throw new...
en su cláusula catch.
El tercero (y esto no es una regla difícil y rápida), no detecte excepciones generales en ningún punto debajo de la capa de UI. La capa de la interfaz de usuario debe detectar excepciones generales para que el usuario final pueda mostrar un mensaje fácil de usar, no los detalles técnicos de lo que explotó. Si atrapa una excepción general más profunda en las capas, es posible que se la trague involuntariamente y crea errores que son bastante difíciles de localizar.
Por lo general, mi regla de oro es detectar excepciones en el nivel superior y registrarlas (o informarlas) allí, porque aquí es donde se tiene más información sobre el error, lo que es más importante, el seguimiento completo de la pila.
Sin embargo, puede haber algunas razones para detectar excepciones en otros niveles:
- La excepción se maneja realmente. P.ej. falla una conexión, pero el nivel lo vuelve a intentar.
- La excepción se vuelve a lanzar con más información (que aún no está disponible al mirar la pila). P.ej. el DAL podría informar a qué DB estaba tratando de conectarse, lo que
SqlException
no le diría. - La excepción se traduce en una excepción más general que forma parte de la interfaz para ese nivel y puede (o no) ser manejada más arriba. P.ej. el DAL puede detectar errores de conexión y lanzar una excepción
DatabaseUnavailableException
. El BL puede ignorar esto para operaciones que no son críticas o puede permitir que se propague por aquellas que lo son. Si el BLSqlException
cambio, estaría expuesto a los detalles de implementación del DAL. En cambio, la posibilidad de lanzarDatabaseUnavailableException
es parte de la interfaz del DAL.
Por lo general, no es útil registrar el mismo error en múltiples niveles, pero se me ocurre una excepción: cuando un nivel inferior no sabe si un problema es crítico o no, puede registrarlo como una advertencia. Si un nivel superior decide que es crítico, puede registrar el mismo problema que un error.