c# - tipos - ¿Cómo me recupero de una excepción sin marcar?
try catch c# (9)
Dado que actualmente está utilizando hibernación, lo más fácil es verificar esa excepción y envolverla en una excepción personalizada o en un objeto de resultado personalizado que pueda haber configurado en su marco. Si quieres abandonar Hibernate más tarde simplemente asegúrate de ajustar esta excepción en solo 1 lugar, el primer lugar donde atrapes la excepción de hibernación, ese es el código que probablemente tendrás que cambiar cuando hagas un cambio de todos modos, así que si la captura está en un lugar, entonces la sobrecarga adicional es casi nula.
¿ayuda?
Las excepciones no comprobadas son correctas si desea manejar cada falla de la misma manera, por ejemplo, registrándola y saltando a la siguiente solicitud, mostrando un mensaje al usuario y manejando el próximo evento, etc. Si este es mi caso de uso, todo lo que Lo que tengo que hacer es capturar algún tipo de excepción general en un nivel alto en mi sistema, y manejar todo de la misma manera.
Pero quiero recuperarme de problemas específicos, y no estoy seguro de la mejor manera de abordarlo con excepciones sin marcar. Aquí hay un ejemplo concreto.
Supongamos que tengo una aplicación web, construida con Struts2 e Hibernate. Si surge una excepción hasta mi "acción", la registro y presento una bonita disculpa al usuario. Pero una de las funciones de mi aplicación web es crear nuevas cuentas de usuario, que requieren un nombre de usuario único. Si un usuario elige un nombre que ya existe, Hibernate arroja una org.hibernate.exception.ConstraintViolationException
(una excepción sin org.hibernate.exception.ConstraintViolationException
) en las entrañas de mi sistema. Realmente me gustaría recuperarme de este problema en particular al pedirle al usuario que elija otro nombre de usuario, en lugar de darles el mismo mensaje de "hemos registrado su problema, pero por ahora está cubierto de basura".
Aquí hay algunos puntos a considerar:
- Hay mucha gente creando cuentas simultáneamente. No quiero bloquear la tabla de usuario completa entre un "SELECCIONAR" para ver si el nombre existe y un "INSERTAR" si no lo hace. En el caso de las bases de datos relacionales, podría haber algunos trucos para evitar esto, pero lo que realmente me interesa es el caso general en el que la verificación previa de una excepción no funcionará debido a una condición de carrera fundamental. Lo mismo podría aplicarse para buscar un archivo en el sistema de archivos, etc.
- Dada la propensión de mi CTO para la gestión de drive-by inducida por columnas de tecnología de lectura en "Inc.", necesito una capa de indirección alrededor del mecanismo de persistencia para poder descartar Hibernate y usar Kodo, o lo que sea, sin cambiar nada excepto el más bajo capa de código de persistencia De hecho, hay varias capas de abstracción en mi sistema. ¿Cómo puedo evitar que se filtren a pesar de las excepciones no revisadas?
- Una de las deficiencias declaradas de las excepciones comprobadas es tener que "manejarlas" en cada llamada en la pila, ya sea declarando que un método de llamada las arroja o atrapándolas y manipulándolas. Manejarlos a menudo significa envolverlos en otra excepción marcada de un tipo apropiado para el nivel de abstracción. Así que, por ejemplo, en un terreno con excepción comprobada, una implementación basada en el sistema de archivos de mi UserRegistry podría atrapar
IOException
, mientras que una implementación de base de datos atraparía aSQLException
, pero ambos generarían unaUserNotFoundException
queUserNotFoundException
la implementación subyacente. ¿Cómo aprovecho las excepciones no revisadas, ahorrándome la carga de este envoltorio en cada capa, sin filtrar los detalles de implementación?
Estoy de acuerdo con Nick La excepción que describió no es realmente una "excepción inesperada", por lo que debe diseñar su código teniendo en cuenta posibles excepciones en cuenta.
También recomendaría echar un vistazo a la documentación del Bloque de manejo de excepciones de Microsoft Enterprise Library. Tiene un buen esquema de patrones de manejo de errores.
Me gusta reempaquetar excepciones entre los "niveles" de mi aplicación, por ejemplo, una excepción específica de DB se vuelve a empaquetar dentro de otra excepción que es significativa en el contexto de mi aplicación (por supuesto, dejo la excepción original como miembro). Yo no destruyo el rastro de la pila).
Dicho esto, creo que un nombre de usuario no único no es una situación lo suficientemente "excepcional" como para justificar un lanzamiento. Yo usaría un argumento de retorno booleano en su lugar. Sin saber mucho sobre su arquitectura, es difícil para mí decir algo más específico o aplicable.
Puede capturar excepciones sin marcar sin necesidad de envolverlas. Por ejemplo, lo siguiente es Java válido.
try {
throw new IllegalArgumentException();
} catch (Exception e) {
System.out.println("boom");
}
Entonces, en su acción / controlador puede tener un bloque try-catch alrededor de la lógica donde se realiza la llamada Hibernate. Dependiendo de la excepción, puede generar mensajes de error específicos.
Pero supongo que en su día de hoy podría ser Hibernate y mañana en el marco de SleepLongerDuringWinter. En este caso, debe pretender tener su propio marco de ORM poco que se ajusta al marco de terceros. Esto le permitirá ajustar cualquier excepción específica del marco de trabajo en excepciones más significativas y / o comprobadas que sepa cómo darles mejor sentido.
La pregunta no está realmente relacionada con el debate controlado vs. sin control, lo mismo se aplica a ambos tipos de excepciones.
Entre el momento en que se lanza la excepción ConstraintViolationException y el punto en el que queremos controlar la violación mostrando un bonito mensaje de error, hay una gran cantidad de llamadas a métodos en la pila que deben cancelarse inmediatamente y no deberían preocuparse por el problema. Eso hace que el mecanismo de excepción sea la opción correcta en lugar de rediseñar el código de excepciones a valores de retorno.
De hecho, usar una excepción sin marcar en lugar de una excepción marcada es una opción natural, ya que realmente queremos que todos los métodos intermedios en la pila de llamadas ignoren la excepción y no la manejen .
Si queremos manejar la "violación de nombre único" solo mostrando un buen mensaje de error (página de error) para el usuario, realmente no hay necesidad de una DuplicateUsernameException específica. Esto mantendrá baja la cantidad de clases de excepción. En cambio, podemos crear una MessageException que se puede reutilizar en muchos escenarios similares.
A la brevedad posible, detectamos la excepción ConstraintViolationException y la convertimos en una MessageException con un bonito mensaje. Es importante convertirlo pronto, cuando podemos estar seguros, es realmente la "restricción de nombre de usuario único" que se violó y no otra restricción.
En algún lugar cercano al manejador de nivel superior, simplemente maneje la MessageException de una manera diferente. En lugar de "registramos su problema, pero por el momento está contaminado con una manguera", simplemente muestre el mensaje contenido en la MessageException, sin seguimiento de la pila.
La MessageException puede tomar algunos parámetros adicionales del constructor, como una explicación detallada del problema, la próxima acción disponible (cancelar, ir a una página diferente), icono (error, advertencia) ...
El código puede verse así
// insert the user
try {
hibernateSession.save(user);
} catch (ConstraintViolationException e) {
throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}
En un lugar totalmente diferente, hay un manejador de excepciones superior
try {
... render the page
} catch (MessageException e) {
... render a nice page with the message
} catch (Exception e) {
... render "we logged your problem but for now you''re hosed" message
}
Ver patrones para la generación, manejo y administración de errores
Del patrón Split Domain and Technical Errors
Un error técnico nunca debe generar un error de dominio (nunca los dos deben cumplir). Cuando un error técnico debe hacer que el procesamiento empresarial falle, se debe incluir como un SystemError.
Los errores de dominio siempre deben comenzar desde un problema de dominio y ser manejados por un código de dominio.
Los errores de dominio deben pasar "sin problemas" a través de límites técnicos. Puede ser que tales errores se deben serializar y reconstituir para que esto suceda. Proxies y fachadas deben asumir la responsabilidad de hacer esto.
Los errores técnicos deben manejarse en puntos particulares de la aplicación, como los límites (ver Log en el límite de distribución).
La cantidad de información de contexto transmitida con el error dependerá de qué tan útil será para el posterior diagnóstico y manejo (descubriendo una estrategia alternativa). Debe preguntarse si el seguimiento de la pila de una máquina remota es totalmente útil para el procesamiento de un error de dominio (aunque la ubicación del código del error y los valores de la variable en ese momento pueden ser útiles)
Por lo tanto, ajuste la excepción de hibernación en el límite para hibernar con una excepción de dominio no comprobada, como una "Excepcion de nombre de usuario único", y permita que la burbuja suba hasta el manejador de la misma. ¡Asegúrate de hacer javadoc la excepción arrojada aunque no sea una excepción marcada!
La OMI, al incluir excepciones (verificadas o no) tiene varios beneficios que valen la pena:
1) Te alienta a pensar sobre los modos de falla para el código que escribes. Básicamente, debes tener en cuenta las excepciones que el código que llamas puede arrojar y, a su vez, considerarás las excepciones que lanzarás para el código que llame al tuyo.
2) Te da la oportunidad de agregar información de depuración adicional en la cadena de excepciones. Por ejemplo, si tiene un método que arroja una excepción en un nombre de usuario duplicado, puede ajustar esa excepción con una que incluya información adicional sobre las circunstancias del error (por ejemplo, la dirección IP de la solicitud que proporcionó el nombre de usuario dupe) que no estaba disponible para el código de nivel inferior. El seguimiento de cookies de excepciones puede ayudarlo a solucionar un problema complejo (sin duda lo tiene para mí).
3) Te permite convertirte en independiente de la implementación desde el código de nivel inferior. Si está envolviendo excepciones y necesita cambiar Hibernate por otro ORM, solo tiene que cambiar el código de manejo de Hibernate. Todas las otras capas de código seguirán utilizando las excepciones envueltas y las interpretarán de la misma manera, aunque las circunstancias subyacentes hayan cambiado. Tenga en cuenta que esto se aplica incluso si Hibernate cambia de alguna manera (por ejemplo: cambian las excepciones en una nueva versión); no es solo para el reemplazo de tecnología al por mayor.
4) Lo alienta a usar diferentes clases de excepciones para representar diferentes situaciones. Por ejemplo, puede tener una DuplicateUsernameException cuando el usuario intenta reutilizar un nombre de usuario, y una DatabaseFailureException cuando no puede verificar nombres de usuario dupe debido a una conexión de DB interrumpida. Esto, a su vez, le permite responder a su pregunta ("¿cómo me recupero?") De maneras flexibles y potentes. Si obtiene una DuplicateUsernameException, puede decidir sugerir un nombre de usuario diferente para el usuario. Si obtiene una DatabaseFailureException, puede dejarla burbujear hasta el punto donde se muestra una página de "menos por mantenimiento" para el usuario y enviarle un correo electrónico de notificación. Una vez que tenga excepciones personalizadas, tendrá respuestas personalizables, y eso es algo bueno.
@erikson
Solo para agregar comida a tus pensamientos:
Controlado versus no verificado también se debate aquí
El uso de excepciones no comprobadas es compatible con el hecho de que se utilizan OMI para la excepción causada por la persona que llama de la función (y el llamante puede estar varias capas por encima de esa función, de ahí la necesidad de que otros fotogramas ignoren la excepción)
En cuanto a su problema específico, debe capturar la excepción sin marcar en el nivel alto y encapsularla, como dice @Kanook en su propia excepción, sin mostrar la pila de llamadas (como menciona @ Jan Soltis)
Dicho eso, si la tecnología subyacente cambia, eso tendrá un impacto en las capturas () que ya están presentes en su código y que no responde a su último escenario.
@Jan Checked versus unchecked es un tema central aquí. Cuestiono su suposición (n. ° 3) que la excepción debe ser ignorada en marcos intermedios. Si lo hago, terminaré con una dependencia específica de la implementación en mi código de alto nivel. Si reemplazo Hibernate, los bloques de captura en mi aplicación tendrán que ser modificados. Sin embargo, al mismo tiempo, si capté la excepción en un nivel inferior, no recibo mucho beneficio del uso de una excepción sin marcar.
Además, el escenario aquí es que quiero detectar un error lógico específico y cambiar el flujo de la aplicación volviendo a solicitar al usuario una ID diferente. Simplemente cambiar el mensaje que se muestra no es lo suficientemente bueno, y la capacidad de mapear a diferentes mensajes basados en el tipo de excepción ya está integrada en Servlets.