Me han dicho que las excepciones solo deben usarse en casos excepcionales. ¿Cómo puedo saber si mi caso es excepcional?

94

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 puede decir que su edad es "manzana". El comportamiento correcto en ese caso es revertir la transacción y avisar al usuario que ocurrió un error y tendrá que intentarlo nuevamente. Puede haber un requisito para informar sobre cada error que podamos encontrar en la entrada, no solo el primero.

En este caso, argumenté que deberíamos lanzar una excepción. No estuvo de acuerdo y dijo: "Las excepciones deben ser excepcionales: se espera que el usuario ingrese datos no válidos, por lo que este no es un caso excepcional" No sabía realmente cómo argumentar ese punto, porque por definición de la palabra, él parece estar en lo cierto

Pero, entiendo que es por eso que se inventaron las excepciones en primer lugar. Solía ser usted tenía para inspeccionar el resultado para ver si se produjo un error. Si no lo comprobaste, podrían pasar cosas malas sin que te des cuenta.

Sin excepciones, todos los niveles de la pila deben verificar el resultado de los métodos a los que llaman y si un programador se olvida de verificar 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 puedo saber si mi caso es excepcional?

    
pregunta Daniel Kaplan 24.01.2013 - 09:48

13 respuestas

81

Se inventaron excepciones para facilitar el manejo de errores con menos desorden de códigos. Debe usarlos en los casos en que faciliten el manejo de errores con menos desorden de códigos. Estas "excepciones solo para circunstancias excepcionales" se derivan de una época 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 difunde la regla sin recordar la razón detrás de ella.

Especialmente en Java, que es quizás el lenguaje más concebido para las excepciones, no debes sentirte mal por usar las excepciones cuando simplifica tu código. De hecho, la propia clase Integer de Java. no tiene un medio para verificar si una cadena es un número entero válido sin lanzar potencialmente un NumberFormatException .

Además, aunque no puede confiar en 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 de giro para ingresar valores numéricos cortos, luego un valor no numérico convertirlo en la parte de atrás realmente sería una condición excepcional.

    
respondido por el Karl Bielefeldt 24.01.2013 - 18:18
71

¿Cuándo debería lanzarse 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 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, debería echar un vistazo al código, donde se procesa la entrada del usuario. Podría verse algo como esto:

public void Save(PersonData personData) { … }

¿El nombre del método sugiere que se haga alguna validación? No. En este caso, un PersonData no válido debe lanzar una excepción.

Supongamos que la clase tiene otro método que se parece a esto:

public ValidationResult Validate(PersonData personData) { … }

¿El nombre del método sugiere que se haga alguna validación? Sí. En este caso, un PersonData no válido no debería lanzar una excepción.

Para unir las cosas, ambos métodos sugieren que el código del cliente debería tener este aspecto:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

Cuando no está claro si un método debe lanzar una excepción, tal vez se deba un nombre o firma mal elegido. Tal vez 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.

    
respondido por el Theo Lenndorff 24.01.2013 - 14:05
30

Siempre pienso en cosas como acceder al servidor de base de datos o una API web cuando pienso en excepciones. Usted espera que la API de servidor / web funcione, pero en un caso excepcional puede que no (el servidor está inactivo). Una solicitud web suele ser rápida, pero en circunstancias excepcionales (alta carga) puede agotar el tiempo de espera. Esto es algo fuera de su control.

Los datos de entrada de los usuarios están bajo su control, ya que puede verificar lo que envían y hacer con lo que desee. En su caso, validaría la entrada del usuario incluso antes de intentar guardarla. Y tiendo a aceptar que los usuarios que proporcionen datos no válidos deben esperarse, y su aplicación debe tenerlos en cuenta al validar la entrada y proporcionar un mensaje de error fácil de usar.

Dicho esto, uso excepciones en la mayoría de los configuradores de mi modelo de dominio, donde no debería haber ninguna posibilidad de que ingresen datos no válidos. Sin embargo, esta es la última línea de defensa, y tiendo a crear mis formularios de entrada con reglas de validación enriquecidas, por lo que prácticamente no hay posibilidad de activar esa excepción de modelo de dominio. Entonces, cuando un colocador está esperando una cosa y obtiene otra, es una situación excepcional, que no debería haber ocurrido en circunstancias normales.

EDITAR (algo más a considerar):

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 pueden ser validados contra algún formato esperado. Esto es algo que puedes controlar. Lo que no puede controlar es que su servidor esté fallando en medio de su consulta. Entonces, sabe que la consulta está bien y que los datos se filtran / validan, puede probar la consulta y sigue fallando, esta es una situación excepcional.

De manera similar con las solicitudes web, no puede saber si la solicitud se interrumpirá o no podrá conectarse antes de intentar enviarla. Por lo tanto, esto también garantiza un enfoque de prueba / captura, ya que no puede preguntar al servidor si funcionará unos milisegundos más tarde cuando envíe la solicitud.

    
respondido por el Ivan Pintar 24.01.2013 - 10:03
29
  

Las excepciones deben ser excepcionales: se espera que el usuario pueda   ingrese datos inválidos, por lo que este no es un caso excepcional

Sobre ese argumento:

  • Se espera que un archivo no exista, por lo que no es un excepcional caso.
  • Se espera que la conexión con el servidor se pierda, por lo que no es un caso excepcional
  • Se espera que el archivo de configuración esté distorsionado, por lo que no es un caso excepcional.
  • Se espera que su solicitud a veces se caiga, por lo que no es un caso excepcional

Cualquier excepción que atrapes, debes esperar porque, bueno, decidiste atraparla. Y, por esta lógica, nunca debe lanzar ninguna excepción que realmente planee atrapar.

Por lo tanto, creo que "las excepciones deben ser excepcionales" es una terrible regla de oro.

Lo que debes hacer depende del idioma. Diferentes idiomas tienen diferentes convenciones acerca de cuándo se deben lanzar excepciones. Python, por ejemplo, lanza excepciones para todo y cuando en Python, sigo el ejemplo. C ++, por otro lado, lanza relativamente pocas excepciones, y ahí sigo el ejemplo. Puedes tratar a C ++ o Java como Python y lanzar excepciones para todo, pero estás 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.

    
respondido por el Winston Ewert 24.01.2013 - 15:57
15

Referencia

De El programador pragmático:

  

Creemos que las excepciones rara vez deben usarse como parte del flujo normal de un programa; Las excepciones deben reservarse para eventos inesperados. Suponga que una excepción no detectada terminará su programa y pregúntese: "¿Se ejecutará este código si elimino todos los controladores de excepciones?" Si la respuesta es "no", tal vez se estén utilizando excepciones en circunstancias no excepcionales.

Continúan examinando el ejemplo de abrir un archivo para leer, y el archivo no existe. ¿Debería eso generar una excepción?

  

Si el archivo debería haber estado allí, entonces se justifica una excepción. [...] Por otro lado, si no tiene idea de si el archivo debería existir o no, entonces no parece excepcional si no lo puede encontrar, y es apropiado devolver el error.

Más tarde, discuten por qué eligieron este enfoque:

  

La excepción [A] n representa una transferencia de control no local inmediata: es un tipo de goto en cascada. Los programas que utilizan excepciones como parte de su procesamiento normal sufren todos los problemas de legibilidad y mantenimiento del código de espagueti clásico. Estos programas rompen la encapsulación: las rutinas y sus interlocutores se acoplan más estrechamente a través del manejo de excepciones.

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 se han validado, 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 reciba la entrada exacta ingresada por un usuario, se esperan datos no válidos y no se debe generar una excepción.

    
respondido por el Mike Partridge 24.01.2013 - 20:00
10

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, el informe de errores, etc.) sin 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, esa es una condición excepcional. No puede hacer nada al respecto en su código, aparte de informarle al usuario y permitirle especificar una ruta de archivo diferente.

    
respondido por el Robert Harvey 24.01.2013 - 16:24
7

Hay dos preocupaciones que debe considerar:

  1. discute una única inquietud, 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álidas

  2. un bien implementado interfaz de usuario tiene una preocupación adicional: validación de la entrada del usuario & comentarios constructivos sobre los errores (llamemos a esta parte Validator )

Desde el punto de vista del componente Assigner , lanzar una excepción es totalmente razonable, ya que ha expresado una restricción que se ha violado.

Desde el punto de vista de la experiencia de usuario , el usuario no debería estar hablando directamente con este Assigner en primer lugar. Deberían hablar con él a través de el Validator .

Ahora, en Validator , la entrada de un usuario no válido no es ni un caso excepcional, realmente es el caso en el que está más interesado. Entonces, una excepción no sería apropiada, y esto es también donde querría identificar los errores de todos en lugar de rescatarlos en el primero.

Notarás que no mencioné cómo se implementan estas preocupaciones . Parece que estás hablando de Assigner y tu colega está hablando de un Validator+Assigner combinado. Una vez que te das cuenta de que hay dos preocupaciones separadas (o separables), al menos puedes discutirlas con sensatez.

Para abordar el comentario de Renan, simplemente asumo 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, argumentaría que probablemente no haya terminado de identificar las inquietudes independientes en su solución.

Supongo que eso responde directamente a

  

... ¿cómo puedo saber si mi caso es excepcional?

sigue simplificando hasta que sea obvio . Cuando tienes un montón de conceptos simples que entiendes bien, puedes razonar claramente acerca de volverlos a componer en código, clases, bibliotecas o lo que sea.

    
respondido por el Useless 24.01.2013 - 12:24
4

Otros han respondido bien, pero esta es mi breve respuesta. La excepción es una situación en la que algo en el entorno tiene un error, que no puede controlar y su código no puede avanzar en absoluto. En este caso, también tendrá que informar al usuario qué salió mal, por qué no puede ir más lejos y cuál es la resolución.

    
respondido por el Manoj R 24.01.2013 - 15:14
2

Las excepciones deben representar condiciones que es probable que el código de llamada inmediato no esté preparado para manejar, incluso si el método de llamada podría hacerlo. Considere, por ejemplo, el código que lee algunos datos de un archivo, puede asumir legítimamente que cualquier archivo válido terminará con un registro válido, y no se requiere que extraiga ninguna información de un registro parcial.

Si la rutina de lectura de datos no usaba excepciones, sino que simplemente informaba si la lectura había tenido éxito o no, el código de llamada tendría que verse así:

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

etc. Pasando tres líneas de código por cada pieza útil de trabajo. Por el contrario, si readInteger lanzará una excepción al encontrar el final de un archivo, y si la persona que llama puede simplemente pasar la excepción, el código se convierte en:

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

Un aspecto mucho más simple y limpio, con mucho mayor énfasis en el caso en que las cosas funcionan normalmente. Tenga en cuenta que en los casos en que el interlocutor 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 sumar todos los enteros en un archivo:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

versus

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

El código que pregunta por los enteros está esperando que una de esas llamadas falle. Hacer que el código use un bucle sin fin que se ejecutará hasta que eso suceda 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 la forma en que algunas personas que llaman esperan y otras no. Si lo hace, permitirá que estos métodos se utilicen de forma limpia con ambos tipos de llamantes. Tenga en cuenta también que incluso los métodos de "prueba" deberían generar 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 limpia de fin de archivo (si la persona que llama no esperaba eso, la persona que llama habría usado readInteger ). Por otro lado, probablemente debería lanzar una excepción si no se pudieron leer los datos porque, por ejemplo, El dispositivo de memoria que lo contenía estaba desenchufado. Si bien estos 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.

    
respondido por el supercat 24.01.2013 - 19:30
2

Nunca he sido un gran fanático de los consejos de que solo debe emitir excepciones en los casos que son excepcionales, en parte porque no dice nada (es como decir que solo debe comer alimentos que sean comestibles), pero también porque es muy subjetivo, y a menudo no está claro qué constituye un caso excepcional y qué no.

Sin embargo, existen 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 esto configurado para notificarle cada vez que se lance una excepción, puede terminar siendo correo no deseado por docenas, si no cientos de mensajes, mucho antes de llegar al problema.

Como regla general, si:

  • su código está libre de errores, y
  • los servicios de los que depende están todos disponibles, y
  • su usuario está usando su programa en la forma en que fue diseñado para ser utilizado (incluso si alguna de las entradas que proporcionan no es válida)

entonces su código nunca debe lanzar una excepción, incluso una que se detecte más tarde. Para capturar datos no válidos, puede usar validadores en el nivel de la interfaz de usuario 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 indica error (a menos que el nombre de tu 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; en segundo lugar, puede terminar fácilmente con algunos métodos usando códigos de retorno y otros 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 toman diferentes enfoques aquí.

    
respondido por el jammycakes 25.01.2013 - 14:29
2

Lo más importante en la escritura de software es hacerlo legible. Todas las demás consideraciones son secundarias, lo que incluye hacerlo eficiente y hacerlo correcto. Si es legible, el resto puede ser atendido en mantenimiento, y si no es legible, entonces es mejor que lo deseche. Por lo tanto, debe lanzar excepciones cuando mejore la legibilidad.

Cuando estés escribiendo un algoritmo, solo piensa en la persona que lo leerá en el futuro. Cuando llegue a un lugar donde podría haber un problema potencial, pregúntese si el lector desea ver cómo maneja ese problema ahora , o si el lector preferiría simplemente continuar 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 todo un libro con técnicas para cazar pollos silvestres, todo para ayudarte a hornear un pastel. Eso es bueno, pero la mayoría de la gente no va a querer leer esa receta. La mayoría de la gente preferiría simplemente asumir que los huevos están disponibles y continuar con la receta. Esa es una decisión de juicio que los autores deben hacer cuando escriben recetas.

No puede haber reglas garantizadas sobre qué hace una buena excepción y qué problemas deben manejarse de inmediato, porque requiere que lea la mente de su lector. Lo mejor que puedes hacer son las reglas generales, y "las excepciones son solo para circunstancias excepcionales" es bastante buena. Por lo general, cuando un lector está leyendo su método, está buscando lo que el método hará el 99% del tiempo, y preferirían que no se atascaran con extraños casos en las esquinas, como tratar con usuarios que ingresan información ilegal y otras cosas que casi nunca suceden. Quieren ver el flujo normal de su software directamente, una instrucción tras otra, como si los problemas nunca ocurrieran. Comprender su programa será lo suficientemente difícil como para no tener que lidiar constantemente con tangentes para resolver cada pequeño problema que pueda surgir.

    
respondido por el Geo 19.11.2013 - 08:35
2
  

Puede haber un requisito para informar sobre cada error que podamos encontrar en la entrada, no solo el primero.

Es por esto que no puedes lanzar una excepción aquí. Una excepción interrumpe inmediatamente el proceso de validación. Así que habría mucho trabajo para hacer esto.

Un mal ejemplo:

Método de validación para la clase Dog usando excepciones:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

Cómo llamarlo:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

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 esto es un claro mal uso de las excepciones . El tipo de validación que se le solicitó debe tener lugar antes de que se toque la base de datos. Así que no hay necesidad de retroceder 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:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

Método de validación:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

¿Por qué? Hay miles de razones, y la mayoría de las razones se han señalado en las otras respuestas. Para simplificar: Es mucho más simple leer y entender por otros. En segundo lugar, ¿desea mostrar los rastros de la pila del usuario para explicarle que configuró su dog incorrectamente?

Si , durante la confirmación en el segundo ejemplo, aún surge un error , a pesar de que su validador validó el dog con cero problemas, luego lanzó una excepción es lo correcto . Me gusta: no hay conexión a la base de datos, la entrada de la base de datos ha sido modificada por otra persona mientras tanto, o similar.

    
respondido por el Paramaeleon 15.02.2016 - 10:31
-3

Esta respuesta se encuentra mejor en las guías de sistemas operativos y en las guías de programación de la CPU. De hecho, esta respuesta es muy simple y hay una diferencia física entre afirmaciones y excepciones. No encuentro ninguna filosofía detrás de la respuesta "real". La legibilidad puede venir después de la razón física, pero esta parte no es discutible, y estas son:

Las excepciones son cuando una función pasa un error a una función que llama. Básicamente, si su programa no puede manejar algo, pero el sistema operativo puede, lo "tirará" al sistema operativo (la función de llamada, también, y los padres). Si las declaraciones no pasan errores a las funciones de llamada.

Las excepciones marcarán físicamente el Registro de excepciones / Registro de depuración en el hardware. Toda la máquina sabrá que tiene que detener lo que está haciendo para arreglar su proceso.

Y eso es todo. Sin filosofía, sin argumentos, sin estilos. Ahora "Excepcional" tiene una definición real de "El proceso de llamada debe manejar el error". Ahora que somos conscientes de las diferencias físicas entre If y Try / Catch, la filosofía puede venir a continuación. Pero esta es la diferencia real, real, en la medida en que mi investigación ha encontrado desde la arquitectura hasta los conceptos del sistema operativo

    
respondido por el Stephen J 20.12.2017 - 22:17

Lea otras preguntas en las etiquetas