Pregunta

Las excepciones no marcadas están bien 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 siguiente evento, etc.Si este es mi caso de uso, todo lo que tengo que hacer es detectar 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 cuál es la mejor manera de abordarlo con excepciones no controladas.He aquí un ejemplo concreto.

Supongamos que tengo una aplicación web, creada con Struts2 e Hibernate.Si aparece una excepción en mi "acción", la registro y le muestro 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 lanza un org.hibernate.exception.ConstraintViolationException (una excepción no controlada) en las entrañas de mi sistema.Realmente me gustaría recuperarme de este problema en particular pidiéndole al usuario que elija otro nombre de usuario, en lugar de darle el mismo mensaje "registramos su problema pero por ahora está arruinado".

Aquí hay algunos puntos a considerar:

  1. Hay mucha gente creando cuentas simultáneamente.No quiero bloquear toda la tabla de usuarios entre un "SELECCIONAR" para ver si el nombre existe y un "INSERTAR" si no es así.En el caso de las bases de datos relacionales, puede haber algunos trucos para solucionar este problema, pero lo que realmente me interesa es el caso general en el que la verificación previa de una excepción no funciona debido a una condición de carrera fundamental.Lo mismo podría aplicarse a la búsqueda de un archivo en el sistema de archivos, etc.
  2. Dada la propensión de mi CTO a la gestión automática inducida por la lectura de columnas de tecnología 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 nivel más bajo. capa de código de persistencia.De hecho, existen varias capas de abstracción de este tipo en mi sistema.¿Cómo puedo evitar que se filtren a pesar de excepciones no comprobadas?
  3. Una de las debilidades declaradas de las excepciones comprobadas es tener que "manejarlas" en cada llamada a la pila, ya sea declarando que un método de llamada las genera o capturándolas y manejándolas.Manejarlos a menudo significa envolverlos en otra excepción marcada de un tipo apropiado al nivel de abstracción.Entonces, por ejemplo, en un entorno de excepciones marcadas, una implementación de my UserRegistry basada en un sistema de archivos podría detectar IOException, mientras que una implementación de base de datos captaría SQLException, pero ambos lanzarían un UserNotFoundException que oculta la implementación subyacente.¿Cómo aprovecho las excepciones no comprobadas, ahorrándome la carga de este ajuste en cada capa, sin filtrar detalles de implementación?
¿Fue útil?

Solución

En mi opinión, las excepciones de envoltura (marcadas o no) tienen varios beneficios que valen la pena:

1) Te anima a pensar en los modos de falla del código que escribes.Básicamente, debes considerar las excepciones que puede generar el código que llamas y, a su vez, considerarás las excepciones que generarás para el código que llama al tuyo.

2) Le brinda la oportunidad de agregar información de depuración adicional a la cadena de excepciones.Por ejemplo, si tiene un método que genera una excepción en un nombre de usuario duplicado, puede incluir esa excepción con una que incluya información adicional sobre las circunstancias del error (por ejemplo, la IP de la solicitud que proporcionó el nombre de usuario duplicado) que no estaba disponible para el código de nivel inferior.El rastro de cookies de excepciones puede ayudarle a depurar un problema complejo (ciertamente lo ha hecho para mí).

3) Le permite independizarse de la implementación del código de nivel inferior.Si está ajustando excepciones y necesita cambiar Hibernate por algún otro ORM, solo tiene que cambiar su código de manejo de Hibernate.Todas las demás capas de código seguirán utilizando correctamente las excepciones empaquetadas 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 excepciones en una nueva versión);no es sólo para el reemplazo de tecnología al por mayor.

4) Le anima a utilizar diferentes clases de excepciones para representar diferentes situaciones.Por ejemplo, es posible que tenga una DuplicateUsernameException cuando el usuario intenta reutilizar un nombre de usuario y una DatabaseFailureException cuando no puede verificar si hay nombres de usuario duplicados debido a una conexión de base de datos rota.Esto, a su vez, le permite responder a su pregunta ("¿cómo me recupero?") de manera flexible y potente.Si obtiene una excepción DuplicateUsernameException, puede decidir sugerir un nombre de usuario diferente al usuario.Si recibe una excepción DatabaseFailureException, puede dejar que aumente hasta el punto en que muestre una página "inactiva por mantenimiento" al usuario y le envíe un correo electrónico de notificación.Una vez que tenga excepciones personalizadas, tendrá respuestas personalizables, y eso es algo bueno.

Otros consejos

Me gusta volver a empaquetar excepciones entre los "niveles" de mi aplicación, por lo que, por ejemplo, una excepción específica de la base de datos se vuelve a empaquetar dentro de otra excepción que sea significativa en el contexto de mi aplicación (por supuesto, dejo la excepción original como miembro para No golpeo el seguimiento 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 rechazo.En su lugar, usaría un argumento de retorno booleano.Sin saber mucho sobre su arquitectura, me resulta difícil decir algo más específico o aplicable.

Ver Patrones de Generación, Manejo y Gestión de errores

Del patrón Dominio dividido y errores técnicos

Un error técnico nunca debe causar un error de dominio que se generará (nunca los dos deberían encontrarse).Cuando un El error técnico debe causar negocios procesamiento para fallar, debería ser envuelto como un SystemError.

Los errores de dominio siempre deben comenzar desde un problema de dominio y ser manejado por código de dominio.

Los errores de dominio deben pasar "sin problemas" a través de la técnica Límites.Es posible que tales errores debe ser serializado y reconstituido para que esto suceda.Proxies y Las fachadas deben asumir la responsabilidad de haciendo esto.

Los errores técnicos deben en puntos concretos de la aplicación, como los límites (véase Registro en el límite de distribución).

El Cantidad de información de contexto transmitida volver con el error dependerá de cómo útil esto será para posteriores diagnóstico y manejo (averiguación una estrategia alternativa).Es necesario Cuestione si el seguimiento de pila de Una máquina remota es totalmente útil para El procesamiento de un error de dominio (aunque la ubicación del código de la error y valores variables en ese momento puede ser útil)

Por lo tanto, ajuste la excepción de hibernación en el límite para hibernar con una excepción de dominio no marcada, como una "UniqueUsernameException", y deje que se expanda hasta llegar al controlador de la misma.¡Asegúrese de javadoc la excepción lanzada aunque no sea una excepción marcada!

Dado que actualmente estás usando la 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 puedas haber configurado en tu marco.Si desea deshacerse de la hibernación más tarde, asegúrese de incluir esta excepción en solo 1 lugar, el primer lugar donde detecte la excepción de hibernación, ese es el código que probablemente tendrá que cambiar cuando haga un cambio de todos modos, así que si la captura está en un solo lugar, entonces los gastos generales adicionales son casi nulos.

¿ayuda?

Estoy de acuerdo con Nick.La excepción que usted describió no es realmente una "excepción inesperada", por lo que debe diseñar su código en consecuencia teniendo en cuenta las posibles excepciones.

También recomendaría echar un vistazo a la documentación de Microsoft Enterprise Library. Bloque de manejo de excepciones tiene un buen resumen de los patrones de manejo de errores.

Puede detectar excepciones no comprobadas sin necesidad de ajustarlas.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 de Hibernación.Dependiendo de la excepción, puede generar mensajes de error específicos.

Pero supongo que en su marco de hoy podría ser Hibernate y mañana SleepLongerDuringWinter.En este caso, debe fingir que tiene su propio pequeño marco ORM que envuelve el marco de terceros.Esto le permitirá agrupar cualquier excepción específica del marco en excepciones más significativas y/o comprobadas que sepa cómo entender mejor.

  1. La pregunta no está realmente relacionada con marcado vs.debate sin control, lo mismo se aplica a ambos tipos de excepción.

  2. Entre el punto donde se lanza la ConstraintViolationException y el punto donde queremos manejar la infracción mostrando un mensaje de error agradable hay una gran cantidad de llamadas a métodos en la pila que deberían abortar inmediatamente y no deberían preocuparse por el problema.Eso hace que el mecanismo de excepción sea la elección correcta en lugar de rediseñar el código de excepciones a valores de retorno.

  3. De hecho, usar una excepción no marcada 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 ignorar la excepción y no manejarlo .

  4. Si queremos manejar la "violación de nombre único" solo mostrando un mensaje de error agradable (página de error) al usuario, realmente no hay necesidad de una excepción DuplicateUsernameException específica.Esto mantendrá bajo el número de clases de excepción.En su lugar, podemos crear una MessageException que pueda reutilizarse en muchos escenarios similares.

    Tan pronto como sea posible, detectamos la ConstraintViolationException y la convertimos en una MessageException con un mensaje agradable.Es importante convertirlo pronto, cuando podamos estar seguros de que realmente se violó la "restricción de nombre de usuario único" y no alguna otra restricción.

    En algún lugar cercano al controlador de nivel superior, simplemente maneje MessageException de una manera diferente.En lugar de "registramos su problema pero por ahora está arruinado", simplemente muestre el mensaje contenido en MessageException, sin seguimiento de pila.

    MessageException puede tomar algunos parámetros de constructor adicionales, como una explicación detallada del problema, la siguiente 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 controlador 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
}

@Ene Lo controlado versus lo no controlado es una cuestión central aquí.Cuestiono su suposición (n.º 3) de que la excepción debe ignorarse en los fotogramas intermedios.Si hago eso, terminaré con una dependencia específica de la implementación en mi código de alto nivel.Si reemplazo Hibernate, los bloques catch de mi aplicación deberán modificarse.Sin embargo, al mismo tiempo, si detecto la excepción en un nivel inferior, no obtengo muchos beneficios al usar una excepción no marcada.

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 mostrado no es suficiente, y la capacidad de asignar diferentes mensajes según el tipo de excepción ya está integrada en los servlets.

@erikson

Sólo para agregar alimento a tus pensamientos:

Marcado versus no marcado también es debatido aquí

El uso de excepciones no comprobadas cumple con el hecho de que se utilizan en mi opinión como excepción. causado por el llamador de la función (y la persona que llama puede estar varias capas por encima de esa función, de ahí la necesidad de que otros marcos ignoren la excepción)

Con respecto a su problema específico, debe detectar la excepción no marcada en un nivel alto y encapsularla, como dijo @Kanook en su propia excepción, sin mostrar la pila de llamadas (como lo menciona @Jan Soltis).

Dicho esto, si la tecnología subyacente cambia, eso efectivamente tendrá un impacto en los catch() que ya están presentes en su código, y eso no responde a su último escenario.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top