Mi caso específico aquí es que el usuario puede pasar una cadena a la aplicación, la aplicación la analiza y la asigna a objetos estructurados. A veces, el usuario puede escribir algo no válido. Por ejemplo, su aporte puede describir a una persona pero pueden decir que su edad es "manzana". El comportamiento correcto en ese caso es revertir la transacción y decirle al usuario que ocurrió un error y que tendrá que volver a intentarlo. Puede haber un requisito para informar sobre cada error que podamos encontrar en la entrada, no solo el primero.
En este caso, sostuve que deberíamos lanzar una excepción. No estuvo de acuerdo y dijo: "Las excepciones deben ser excepcionales: se espera que el usuario pueda ingresar datos no válidos, por lo que este no es un caso excepcional". Realmente no sabía cómo argumentar ese punto, porque por definición de la palabra, él Parece estar en lo cierto.
Pero, entiendo que esta es la razón por la cual se inventaron Excepciones en primer lugar. Solía ser que tenía que inspeccionar el resultado para ver si ocurría un error. Si no lo comprobaste, podrían pasar cosas malas sin que te des cuenta.
Sin excepciones, cada nivel de la pila necesita verificar el resultado de los métodos que llama y si un programador olvida ingresar en uno de estos niveles, el código podría proceder accidentalmente y guardar datos no válidos (por ejemplo). Parece más propenso a errores de esa manera.
De todos modos, siéntase libre de corregir cualquier cosa que haya dicho aquí. Mi pregunta principal es si alguien dice que las Excepciones deberían ser excepcionales, ¿cómo sé si mi caso es excepcional?
fuente
Respuestas:
Se inventaron excepciones para ayudar a facilitar el manejo de errores con menos desorden de código. Debe usarlos en los casos en que faciliten el manejo de errores con menos desorden de código. Este negocio de "excepciones solo para circunstancias excepcionales" se origina en un momento en que el manejo de excepciones se consideró un golpe de rendimiento inaceptable. Ese ya no es el caso en la gran mayoría de los códigos, pero la gente todavía dice la regla sin recordar la razón detrás de esto.
Especialmente en Java, que es quizás el lenguaje más amante de las excepciones jamás concebido, no debería sentirse mal por usar excepciones cuando simplifica su código. De hecho, la propia
Integer
clase de Java no tiene un medio para verificar si una cadena es un entero válido sin potencialmente arrojar unNumberFormatException
.Además, aunque no puede confiar solo en la validación de la interfaz de usuario, tenga en cuenta si su interfaz de usuario está diseñada correctamente, como usar un control giratorio para ingresar valores numéricos cortos, entonces un valor no numérico que lo convierta en el back-end realmente sería un Condición excepcional.
fuente
throw new ...
. O, arroje excepciones personalizadas, donde se sobrescribe fillInStackTrace (). Entonces no debería notar ninguna degradación del rendimiento, por no hablar de golpes .if (foo() == ERROR) { return ERROR; } else { // continue }
en cada nivel. Si lanza una excepción no marcada, no hay "ruidoso y redundante" si el error devuelve error ". Además, si pasa funciones como argumentos, el uso de un código de error puede cambiar la firma de la función a un tipo incompatible, aunque el error no ocurra.¿Cuándo se debe lanzar una excepción? Cuando se trata de código, creo que la siguiente explicación es muy útil:
Una excepción es cuando un miembro no puede completar la tarea que se supone que debe realizar como lo indica su nombre . (Jeffry Richter, CLR a través de C #)
¿Por qué es útil? Sugiere que depende del contexto cuando algo debe manejarse como una excepción o no. En el nivel de las llamadas a métodos, el contexto viene dado por (a) el nombre, (b) la firma del método y (b) el código del cliente, que usa o se espera que use el método.
Para responder a su pregunta, debe echar un vistazo al código, donde se procesa la entrada del usuario. Podría verse más o menos así:
¿El nombre del método sugiere que se realiza alguna validación? No. En este caso, un PersonData no válido debería generar una excepción.
Suponga que la clase tiene otro método que se ve así:
¿El nombre del método sugiere que se realiza alguna validación? Si. En este caso, un PersonData no válido no debería generar una excepción.
Para poner las cosas juntas, ambos métodos sugieren que el código del cliente debería verse así:
Cuando no está claro si un método debe arrojar una excepción, entonces quizás se deba a un nombre o firma de método mal elegidos. Quizás el diseño de la clase no está claro. A veces es necesario modificar el diseño del código para obtener una respuesta clara a la pregunta si se debe lanzar una excepción o no.
fuente
struct
llamado "ValidationResult" y estructuré mi código de la manera que usted describe.Validate
(devolver False si no es válido) y una vez duranteSave
(lanzar una excepción específica y bien documentada si no es válida). Por supuesto, el resultado de la validación podría almacenarse en caché dentro del objeto, pero eso agregaría complejidad adicional, ya que el resultado de la validación necesitaría ser invalidado en los cambios.Validate()
se llame dentro delSave()
método, y seValidationResult
podrían usar detalles específicos del mismo para construir un mensaje apropiado para la excepción.Sobre ese argumento:
Cualquier excepción que atrape, debe esperar porque, bueno, decidió atraparla. Y así, según esta lógica, nunca debes lanzar ninguna excepción que realmente planeas atrapar.
Por lo tanto, creo que "las excepciones deberían ser excepcionales" es una terrible regla general.
Lo que debe hacer depende del idioma. Diferentes idiomas tienen diferentes convenciones sobre cuándo se deben lanzar excepciones. Python, por ejemplo, arroja excepciones para todo y cuando estoy en Python, hago lo mismo. C ++, por otro lado, arroja relativamente pocas excepciones, y allí sigo su ejemplo. Puede tratar C ++ o Java como Python y lanzar excepciones para todo, pero está trabajando en desacuerdo con la forma en que el lenguaje espera que se use.
Prefiero el enfoque de Python, pero creo que es una mala idea usar otros idiomas.
fuente
"exceptions should be exceptional" is a terrible rule of thumb.
Bien dicho! Esa es una de esas cosas que la gente simplemente repite sin pensar en ellas.Siempre pienso en cosas como acceder al servidor de base de datos o una API web cuando pienso en excepciones. Espera que el servidor / API web funcione, pero en un caso excepcional podría no funcionar (el servidor está inactivo). Por lo general, una solicitud web puede ser rápida, pero en circunstancias excepcionales (alta carga) puede agotar el tiempo de espera. Esto es algo fuera de tu control.
Los datos de entrada de sus usuarios están bajo su control, ya que puede verificar lo que envían y hacer con ellos lo que quiera. En su caso, validaría la entrada del usuario incluso antes de intentar guardarla. Y tiendo a estar de acuerdo en que deben esperarse los usuarios que proporcionan datos no válidos, y su aplicación debe dar cuenta de ellos validando la entrada y proporcionando un mensaje de error fácil de usar.
Dicho esto, utilizo excepciones en la mayoría de mis configuradores de modelos de dominio, donde no debería haber absolutamente ninguna posibilidad de que entren datos no válidos. Sin embargo, esta es una última línea de defensa, y tiendo a construir mis formularios de entrada con reglas de validación enriquecidas , por lo que prácticamente no hay posibilidad de activar esa excepción del modelo de dominio. Entonces, cuando un setter espera una cosa y obtiene otra, es una situación excepcional, que no debería haber sucedido en circunstancias normales.
EDITAR (algo más a tener en cuenta):
Al enviar datos proporcionados por el usuario a la base de datos, usted sabe de antemano lo que debe y no debe ingresar en sus tablas. Esto significa que los datos se pueden validar contra algún formato esperado. Esto es algo que puedes controlar. Lo que no puede controlar es que su servidor falle en el medio de su consulta. Entonces, sabe que la consulta está bien y los datos se filtran / validan, prueba la consulta y aún falla, esta es una situación excepcional.
De manera similar con las solicitudes web, no puede saber si la solicitud expirará o no podrá conectarse antes de intentar enviarla. Por lo tanto, esto también garantiza un enfoque de prueba / captura, ya que no puede preguntarle al servidor si funcionará unos milisegundos más tarde cuando envíe la solicitud.
fuente
FileNotFoundException
cuando se le dé una entrada incorrecta (por ejemplo, un nombre de archivo inexistente). Esa es la única forma válida de amenazar este error, ¿qué más puede hacer sin dar como resultado la devolución de códigos de error?Referencia
Del programador pragmático:
Continúan examinando el ejemplo de abrir un archivo para leer, y el archivo no existe. ¿Debería plantear una excepción?
Más tarde, discuten por qué eligieron este enfoque:
Con respecto a su situación
Su pregunta se reduce a "¿Deben los errores de validación generar excepciones?" La respuesta es que depende de dónde esté ocurriendo la validación.
Si el método en cuestión está dentro de una sección del código donde se supone que los datos de entrada ya han sido validados, los datos de entrada no válidos deberían generar una excepción; Si el código está diseñado de tal manera que este método recibirá la entrada exacta ingresada por un usuario, se esperan datos no válidos y no se debe generar una excepción.
fuente
Aquí hay una gran cantidad de pontificación filosófica, pero en términos generales, las condiciones excepcionales son simplemente aquellas condiciones que no puede o no quiere manejar (aparte de la limpieza, informes de errores y similares) sin la intervención del usuario. En otras palabras, son condiciones irrecuperables.
Si le entrega a un programa una ruta de archivo, con la intención de procesar ese archivo de alguna manera, y el archivo especificado por esa ruta no existe, es una condición excepcional. No puede hacer nada al respecto en su código, aparte de informarlo al usuario y permitirle especificar una ruta de archivo diferente.
fuente
Hay dos preocupaciones que debe considerar:
discute una sola preocupación, llamémosla
Assigner
ya que esta preocupación es asignar entradas a objetos estructurados y expresa la restricción de que sus entradas sean válidasUna interfaz de usuario bien implementada tiene una preocupación adicional: la validación de la entrada del usuario y la retroalimentación constructiva sobre los errores (llamemos a esta parte
Validator
)Desde el punto de vista del
Assigner
componente, lanzar una excepción es totalmente razonable, ya que ha expresado una restricción que ha sido violada.Desde el punto de vista de la experiencia del usuario , el usuario no debería estar hablando directamente de esto
Assigner
en primer lugar. Deberían hablar a ella a través de laValidator
.Ahora, en la
Validator
entrada de usuario inválida no es un caso excepcional, es realmente el caso en el que está más interesado. Por lo tanto, aquí una excepción no sería apropiada, y aquí también es donde querría identificar todos los errores en lugar de rescatando a la primera.Notarás que no mencioné cómo se implementan estas preocupaciones . Parece que estás hablando de
Assigner
y tu colega está hablando de una combinaciónValidator+Assigner
. Una vez que te das cuenta que son dos preocupaciones separados (o separables), al menos se puede hablar con sensatez.Para abordar el comentario de Renan, solo estoy asumiendo que una vez que haya identificado sus dos preocupaciones separadas, es obvio qué casos deben considerarse excepcionales en cada contexto.
De hecho, si no es obvio si algo debe considerarse excepcional, diría que probablemente no haya terminado de identificar las preocupaciones independientes en su solución.
Supongo que eso hace la respuesta directa a
sigue simplificando hasta que sea obvio . Cuando tienes un montón de conceptos simples que entiendes bien, puedes razonar claramente sobre volver a componerlos en código, clases, bibliotecas o lo que sea.
fuente
Otros han respondido bien, pero aún aquí está mi respuesta corta. La excepción es una situación en la que algo en el entorno está mal, lo que no puede controlar y su código no puede avanzar en absoluto. En este caso, también deberá informar al usuario qué salió mal, por qué no puede ir más allá y cuál es la resolución.
fuente
Nunca he sido un gran admirador de los consejos de que solo debes lanzar excepciones en casos que son excepcionales, en parte porque no dice nada (es como decir que solo debes comer alimentos comestibles), pero también porque es muy subjetivo y a menudo no está claro qué constituye un caso excepcional y qué no.
Sin embargo, hay buenas razones para este consejo: lanzar y capturar excepciones es lento, y si está ejecutando su código en el depurador en Visual Studio con él configurado para notificarle cada vez que se lanza una excepción, puede terminar recibiendo correo no deseado por docenas si no cientos de mensajes mucho antes de llegar al problema.
Entonces, como regla general, si:
entonces su código nunca debe arrojar una excepción, incluso una que se detecte más tarde. Para atrapar datos no válidos, puede usar validadores a nivel de UI o código como
Int32.TryParse()
en la capa de presentación.Para cualquier otra cosa, debe atenerse al principio de que una excepción significa que su método no puede hacer lo que su nombre dice que hace. En general, no es una buena idea usar códigos de retorno para indicar un error (a menos que el nombre de su método indique claramente que lo hace, por ejemplo
TryParse()
) por dos razones. Primero, la respuesta predeterminada a un código de error es ignorar la condición de error y continuar independientemente; segundo, puede terminar fácilmente con algunos métodos usando códigos de retorno y otros métodos usando excepciones, y olvidando cuál es cuál. Incluso he visto bases de código donde dos implementaciones intercambiables diferentes de la misma interfaz adoptan enfoques diferentes aquí.fuente
Las excepciones deben representar condiciones que es probable que el código de llamada inmediata no esté preparado para manejar, incluso si el método de llamada lo hace. Considere, por ejemplo, que el código que lee algunos datos de un archivo, puede suponer legítimamente que cualquier archivo válido terminará con un registro válido, y no es necesario extraer ninguna información de un registro parcial.
Si la rutina de lectura de datos no usó excepciones, sino que simplemente informó si la lectura tuvo éxito o no, el código de llamada tendría que verse así:
etc. gastando tres líneas de código por cada trabajo útil. Por el contrario, si
readInteger
arroja una excepción al encontrar el final de un archivo, y si la persona que llama simplemente puede pasar la excepción, entonces el código se convierte en:Un aspecto mucho más simple y limpio, con un énfasis mucho mayor en el caso en que las cosas funcionan normalmente. Tenga en cuenta que en los casos en que el llamante inmediato esperaría manejar una condición, un método que devuelve un código de error a menudo será más útil que uno que arroje una excepción. Por ejemplo, para totalizar todos los enteros en un archivo:
versus
El código que solicita los enteros espera que una de esas llamadas falle. Hacer que el código use un bucle sin fin que se ejecutará hasta que eso ocurra es mucho menos elegante que usar un método que indique fallas a través de su valor de retorno.
Debido a que las clases a menudo no sabrán qué condiciones esperarán o no sus clientes, a menudo es útil ofrecer dos versiones de métodos que podrían fallar de una manera que algunas personas que llaman esperarán y otras no. Hacerlo permitirá que dichos métodos se usen limpiamente con ambos tipos de personas que llaman. Tenga en cuenta también que incluso los métodos "probar" deberían arrojar excepciones si surgen situaciones que la persona que llama probablemente no está esperando. Por ejemplo,
tryReadInteger
no debería lanzar una excepción si encuentra una condición de final de archivo limpia (si la persona que llama no esperaba eso, la persona que llama habría utilizadoreadInteger
) Por otro lado, probablemente debería arrojar una excepción si los datos no se pueden leer porque, por ejemplo, la tarjeta de memoria que los contenía estaba desconectada. Si bien dichos eventos siempre deben reconocerse como una posibilidad, es poco probable que el código de llamada inmediata esté preparado para hacer algo útil en respuesta; ciertamente no debe informarse de la misma manera que sería una condición de fin de archivo.fuente
Lo más importante al escribir software es hacerlo legible. Todas las demás consideraciones son secundarias, incluida la eficiencia y la corrección. Si es legible, el resto se puede ocupar en el mantenimiento, y si no es legible, entonces es mejor tirarlo. Por lo tanto, debe lanzar excepciones cuando mejore la legibilidad.
Cuando esté escribiendo algún algoritmo, solo piense en la persona en el futuro que lo leerá. Cuando llegue a un lugar donde podría haber un problema potencial, pregúntese si el lector quiere ver cómo maneja ese problema ahora , o ¿preferiría simplemente seguir con el algoritmo?
Me gusta pensar en una receta para pastel de chocolate. Cuando le dice que agregue los huevos, tiene una opción: puede asumir que tiene huevos y continuar con la receta, o puede comenzar una explicación de cómo puede obtener huevos si no tiene huevos. Podría llenar un libro completo con técnicas para cazar pollos salvajes, todo para ayudarlo a hornear un pastel. Eso es bueno, pero la mayoría de las personas no querrán leer esa receta. La mayoría de las personas preferiría asumir que los huevos están disponibles y seguir con la receta. Esa es una decisión que los autores deben hacer al escribir recetas.
No puede haber reglas garantizadas sobre qué es una buena excepción y qué problemas deben manejarse de inmediato, porque requiere que leas la mente de tu lector. Lo mejor que harás es reglas generales, y "las excepciones son solo para circunstancias excepcionales" es una muy buena. Por lo general, cuando un lector lee su método, está buscando lo que el método hará el 99% del tiempo, y preferiría no tener eso lleno de casos extraños en las esquinas, como tratar con usuarios que ingresan entradas ilegales y otras cosas que casi nunca suceden. Quieren ver el flujo normal de su software directamente, una instrucción tras otra, como si nunca ocurrieran problemas.
fuente
Es por eso que no puedes lanzar una excepción aquí. Una excepción interrumpe inmediatamente el proceso de validación. Por lo tanto, habría mucho trabajo alternativo para hacer esto.
Un mal ejemplo:
Método de validación para la
Dog
clase usando excepciones:Cómo llamarlo:
El problema aquí es que el proceso de validación, para obtener todos los errores, requeriría omitir las excepciones ya encontradas. Lo anterior podría funcionar, pero este es un uso indebido claro de las excepciones . El tipo de validación que se le solicitó debe realizarse antes de tocar la base de datos. Por lo tanto, no es necesario revertir nada. Y es probable que los resultados de la validación sean errores de validación (aunque con suerte cero).
El mejor enfoque es:
Llamada al método:
Método de validación:
¿Por qué? Hay toneladas de razones, y la mayoría de las razones se han señalado en las otras respuestas. En pocas palabras: es mucho más simple de leer y comprender por otros. En segundo lugar, ¿desea mostrar los rastros de la pila de usuarios para explicarle que configuró los suyos
dog
incorrectamente?Si , durante la confirmación en el segundo ejemplo, aún surge un error , a pesar de que su validador lo haya validado
dog
con cero problemas, entonces lanzar una excepción es lo correcto . Me gusta: Sin conexión a la base de datos, la entrada de la base de datos ha sido modificada por otra persona mientras tanto, o algo así.fuente