Diseño por contrato utilizando aserciones o excepciones?


Cuando se programa por contrato una función o método primero comprueba si se cumplen sus condiciones previas, antes de comenzar a trabajar en sus responsabilidades, ¿verdad? Las dos formas más prominentes de hacer estas comprobaciones son por assert y por exception.

  1. assert falla solo en modo de depuración. Para asegurarse de que es crucial probar (unit) todas las condiciones previas de contrato separadas para ver si realmente fallan.
  2. la excepción falla en el modo de depuración y liberación. Esto tiene la ventaja de que la depuración probada el comportamiento es idéntico al comportamiento de liberación, pero incurre en una penalización de rendimiento en tiempo de ejecución.

¿Cuál crees que es preferible?

Ver la pregunta releated aquí

Author: Community, 2008-09-22

14 answers

Deshabilitar assert en compilaciones de versiones es como decir "Nunca tendré ningún problema en una compilación de versiones", que a menudo no es el caso. Por lo tanto, assert no debe deshabilitarse en una compilación de versiones. Pero tampoco quieres que la versión se bloquee cada vez que se produzcan errores, ¿verdad?

Así que usa excepciones y úsalas bien. Utilice una jerarquía de excepciones buena y sólida y asegúrese de que captura y puede poner un gancho en la excepción que lanza en su depurador para atraparla, y en el modo de liberación puede compense el error en lugar de un accidente directo. Es la forma más segura de hacerlo.

 41
Author: coppro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-30 00:43:53

La regla general es que debe usar aserciones cuando intenta detectar sus propios errores, y excepciones cuando intenta detectar los errores de otras personas. En otras palabras, debe usar excepciones para verificar las precondiciones para las funciones de la API pública y siempre que obtenga datos externos a su sistema. Debe usar asserts para las funciones o datos internos de su sistema.

 191
Author: Dima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-22 20:06:07

El principio que sigo es el siguiente: Si una situación puede ser evitada de manera realista mediante la codificación, entonces use una afirmación. De lo contrario, utilice una excepción.

Las afirmaciones son para asegurar que el Contrato se está cumpliendo. El contrato debe ser justo, por lo que el cliente debe estar en condiciones de garantizar que cumple. Por ejemplo, puede indicar en un contrato que una URL debe ser válida porque las reglas sobre lo que es y lo que no es una URL válida son conocidas y coherentes.

Las excepciones son para situaciones que están fuera del control tanto del cliente como del servidor. Una excepción significa que algo ha salido mal, y no hay nada que se podría haber hecho para evitarlo. Por ejemplo, la conectividad de red está fuera del control de las aplicaciones, por lo que no se puede hacer nada para evitar un error de red.

Me gustaría añadir que la distinción Aserción / Excepción no es realmente la mejor manera de pensarlo. Lo que realmente quiere estar pensando es el contrato y cómo puede ser cumplida. En mi ejemplo de URL anterior, lo mejor es tener una clase que encapsula una URL y es Null o una URL válida. Es la conversión de una cadena en una URL la que hace cumplir el contrato, y se lanza una excepción si no es válida. Un método con un parámetro de URL es mucho más claro que un método con un parámetro de cadena y una aserción que especifica una URL.

 22
Author: Ged Byrne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-23 20:24:33

Las afirmaciones son para detectar algo que un desarrollador ha hecho mal (no solo a ti mismo, sino también a otro desarrollador de tu equipo). Si es razonable que un error del usuario pueda crear esta condición, entonces debería ser una excepción.

Del mismo modo piensa en las consecuencias. Una afirmación normalmente apaga la aplicación. Si hay alguna expectativa realista de que la condición podría recuperarse, probablemente debería usar una excepción.

Por otro lado, si el problema puede solo se debe a un error del programador y luego usa una aserción, porque quieres saberlo lo antes posible. Una excepción podría ser atrapada y manejada, y nunca se enteraría de ella. Y sí, debe deshabilitar las afirmaciones en el código de liberación porque allí desea que la aplicación se recupere si existe la menor posibilidad de que pueda. Incluso si el estado de su programa está profundamente roto el usuario solo podría ser capaz de guardar su trabajo.

 6
Author: DJClayworth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-25 21:24:23

No es exactamente cierto que "assert falla solo en modo de depuración."

In Object Oriented Software Construction, 2nd Edition by Bertrand Meyer, the author leaves a door open for checking preconditions in release mode. En ese caso, lo que sucede cuando una afirmación falla es eso... se plantea una excepción de violación de afirmación! En este caso, no hay recuperación de la situación: sin embargo, se podría hacer algo útil, y es generar automáticamente un informe de errores y, en algunos casos, para reiniciar la aplicación.

La motivación detrás de esto es que las precondiciones son típicamente más baratas de probar que los invariantes y las postcondiciones, y que en algunos casos la corrección y la "seguridad" en la compilación de la versión son más importantes que la velocidad. es decir, para muchas aplicaciones la velocidad no es un problema, pero la robustez (la capacidad del programa para comportarse de una manera segura cuando su comportamiento no es correcto, es decir, cuando se rompe un contrato) lo es.

Si siempre te vas comprobaciones de precondición habilitadas? Depende. Depende de ti. No hay una respuesta universal. Si está haciendo software para un banco, podría ser mejor interrumpir la ejecución con un mensaje alarmante que transferir $1,000,000 en lugar de $1,000. ¿Pero qué pasa si estás programando un juego? Tal vez necesite toda la velocidad que pueda obtener, y si alguien obtiene 1000 puntos en lugar de 10 debido a un error que las condiciones previas no atraparon (porque no están habilitadas), mala suerte.

En ambos casos debe lo ideal es que haya detectado ese error durante las pruebas, y debería realizar una parte significativa de las pruebas con las aserciones habilitadas. Lo que se discute aquí es cuál es la mejor política para esos raros casos en los que las precondiciones fallan en el código de producción en un escenario que no se detectó anteriormente debido a pruebas incompletas.

Para resumir, puede tener aserciones y aún obtener las excepciones automáticamente, si las deja habilitadas, al menos en Eiffel. Creo que hacer lo mismo en C++ necesita escribirlo usted mismo.

Ver también: ¿Cuándo deben permanecer las aserciones en el código de producción?

 4
Author: Daniel Daranas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:25:53

Hubo un enorme subproceso con respecto a la habilitación/desactivación de aserciones en compilaciones de release sobre comp.lang.c++.moderado, que si dispones de unas semanas podrás ver lo variadas que son las opiniones al respecto. :)

Al contrario de coppro, creo que si no está seguro de que una aserción puede ser deshabilitada en una compilación de release, entonces no debería haber sido una aserción. Las aserciones son para proteger contra invariantes del programa que se rompen. En tal caso, en la medida en que el cliente de su código se refiere a que habrá uno de dos posibles resultados:

  1. Muere con algún tipo de fallo de tipo de sistema operativo, lo que resulta en una llamada para abortar. (Sin afirmación)
  2. Muere a través de una llamada directa para abortar. (Con assert)

No hay diferencia para el usuario, sin embargo, es posible que las aserciones agreguen un costo de rendimiento innecesario en el código que está presente en la gran mayoría de las ejecuciones donde el código no falla.

La respuesta a la pregunta en realidad depende mucho más de quiénes serán los clientes de la API. Si está escribiendo una biblioteca que proporciona una API, entonces necesita algún tipo de mecanismo para notificar a sus clientes que han utilizado la API incorrectamente. A menos que proporcione dos versiones de la biblioteca (una con asserts, otra sin), assert es muy poco probable que sea la elección adecuada.

Personalmente, sin embargo, no estoy seguro de que iría con excepciones para este caso tampoco. Las excepciones son más adecuadas cuando una forma adecuada de la recuperación puede tener lugar. Por ejemplo, puede ser que esté tratando de asignar memoria. Cuando cojas una excepción 'std:: bad_alloc' podría ser posible liberar memoria e intentarlo de nuevo.

 2
Author: Richard Corden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:17:53

He esbozado mi punto de vista sobre el estado de la materia aquí: ¿Cómo valida el estado interno de un objeto? . En general, hacer valer sus reclamaciones y tirar por violación por parte de otros. Para deshabilitar las aserciones en compilaciones de versiones, puede hacer:

  • Deshabilitar aserciones para comprobaciones costosas (como comprobar si se ordena un rango)
  • Mantenga habilitadas las comprobaciones triviales (como comprobar si hay un puntero nulo o un valor booleano)

Por supuesto, en las compilaciones de versiones, las aserciones fallidas y las excepciones no capturadas deben manejarse de otra manera que en compilaciones de depuración (donde podría llamar a std::abort). Escriba un registro del error en algún lugar (posiblemente en un archivo), dígale al cliente que se produjo un error interno. El cliente podrá enviarle el archivo de registro.

 2
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:46:51

Estás preguntando sobre la diferencia entre los errores en tiempo de diseño y en tiempo de ejecución.

Las aserciones son notificaciones 'hey programmer, this is broken', están ahí para recordarte errores que no habrías notado cuando ocurrieron.

Las excepciones son notificaciones 'hey usuario, algo salió mal' (obviamente puedes codificar para atraparlos para que el usuario nunca se lo diga), pero estas están diseñadas para ocurrir en tiempo de ejecución cuando Joe usuario está usando la aplicación.

Entonces, si crees que puedes conseguir todo sus errores fuera, utilice excepciones solamente. Si crees que no puedes..... usa excepciones. Por supuesto, aún puede usar aserciones de depuración para reducir el número de excepciones.

No olvide que muchas de las condiciones previas serán datos proporcionados por el usuario, por lo que necesitará una buena manera de informar al usuario que sus datos no eran buenos. Para hacer eso, a menudo tendrá que devolver datos de error en la pila de llamadas a los bits con los que está interactuando. Asserts no será útil entonces-doblemente si su aplicación es n-tier.

Por último, usaría ninguno de los dos: los códigos de error son muy superiores para los errores que crees que ocurrirán regularmente. :)

 1
Author: gbjbaanb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-22 20:05:37

Prefiero el segundo. Mientras que sus pruebas pueden haber funcionado bien, Murphy dice que algo inesperado saldrá mal. Por lo tanto, en lugar de obtener una excepción en la llamada al método erróneo real, termina rastreando una NullPointerException (o equivalente) 10 marcos de pila más profundos.

 0
Author: jdmichal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-22 20:08:12

Las respuestas anteriores son correctas: use excepciones para funciones API públicas. La única vez que usted podría desear doblar esta regla es cuando el cheque es computacionalmente caro. En ese caso, puede ponerlo en una afirmación.

Si cree que es probable la violación de esa precondición, manténgala como excepción, o refactorice la precondición.

 0
Author: Mike Elkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-22 20:51:18

Debe usar ambos. Las afirmaciones son para su conveniencia como desarrollador. Las excepciones capturan cosas que se perdieron o que no esperaban durante el tiempo de ejecución.

Me he encariñado con las funciones de reporte de errores de glib en lugar de simples afirmaciones antiguas. Se comportan como declaraciones assert, pero en lugar de detener el programa, simplemente devuelven un valor y dejan que el programa continúe. Funciona sorprendentemente bien, y como un bono se llega a ver lo que sucede con el resto de su programa cuando una función no devuelve "lo que se supone que debe". Si se bloquea, usted sabe que su comprobación de errores es laxa en algún otro lugar en el camino.

En mi último proyecto, utilicé este estilo de funciones para implementar la comprobación de precondiciones, y si una de ellas fallaba, imprimía un seguimiento de pila en el archivo de registro, pero continuaba ejecutándose. Me ahorró toneladas de tiempo de depuración cuando otras personas se encontrarían con un problema al ejecutar mi compilación de depuración.

#ifdef DEBUG
#define RETURN_IF_FAIL(expr)      do {                      \
 if (!(expr))                                           \
 {                                                      \
     fprintf(stderr,                                        \
        "file %s: line %d (%s): precondition `%s' failed.", \
        __FILE__,                                           \
        __LINE__,                                           \
        __PRETTY_FUNCTION__,                                \
        #expr);                                             \
     ::print_stack_trace(2);                                \
     return;                                                \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                   \
 {                                                              \
    fprintf(stderr,                                             \
        "file %s: line %d (%s): precondition `%s' failed.",     \
        __FILE__,                                               \
        __LINE__,                                               \
        __PRETTY_FUNCTION__,                                    \
        #expr);                                                 \
     ::print_stack_trace(2);                                    \
     return val;                                                \
 };               } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif

Si necesitara comprobar los argumentos en tiempo de ejecución, haría esto:

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.
                                            // Goes away when debug off.

    if( ptr != NULL )
    {
       ...
    }

    return ptr;
}
 0
Author: indiv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-22 21:23:13

Traté de sintetizar varias de las otras respuestas aquí con mis propios puntos de vista.

Use aserciones para los casos en los que desee deshabilitarlas en producción, errando hacia dejarlas. La única razón real para desactivar en la producción, pero no en el desarrollo, es acelerar el programa. En la mayoría de los casos, esta velocidad no será significativa, pero a veces el código es crítico en el tiempo o la prueba es computacionalmente costosa. Si el código es de misión crítica, entonces las excepciones pueden ser mejores a pesar de la lentitud abajo.

Si hay alguna posibilidad real de recuperación, use una excepción ya que las aserciones no están diseñadas para ser recuperadas. Por ejemplo, el código rara vez está diseñado para recuperarse de errores de programación, pero está diseñado para recuperarse de factores como fallas de red o archivos bloqueados. Los errores no deben ser manejados como excepciones simplemente por estar fuera del control del programador. Más bien, la previsibilidad de estos errores, en comparación con los errores de codificación, los hace más amables para recuperación.

Re argumento de que es más fácil depurar aserciones: El seguimiento de pila de una excepción correctamente nombrada es tan fácil de leer como una aserción. Un buen código solo debe capturar tipos específicos de excepciones, por lo que las excepciones no deben pasar desapercibidas debido a ser capturadas. Sin embargo, creo que Java a veces te obliga a coger todas las excepciones.

 0
Author: Casebash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-25 07:40:23

Ver también esta pregunta:

I algunos casos, asserts están deshabilitados al compilar para release. Usted puede no tiene control sobre esto (de lo contrario, podría construir con asserts on), por lo que podría ser una buena idea hacerlo así.

El problema con "corregir" los valores de entrada es que la persona que llama no obtener lo que esperan, y esto puede conducir a problemas o incluso se bloquea en partes totalmente diferentes del programa, haciendo que la depuración pesadilla.

Normalmente lanzo una excepción en la instrucción if para asumir el rol de la afirmación en caso de que estén desactivados

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff
 0
Author: Rik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:29

La regla general, para mí, es que use expresiones assert para encontrar errores internos y excepciones para errores externos. Usted puede beneficiarse mucho de la siguiente discusión de Greg de aquí.

Las expresiones Assert se utilizan para encontrar errores de programación: errores en la lógica del programa en sí o errores en su implementación correspondiente. Una condición assert verifica que el programa permanezca en un estado definido. Un" estado definido " es básicamente uno que está de acuerdo con las suposiciones del programa. Tenga en cuenta que un "estado definido" para un programa no necesita ser un "estado ideal" o incluso "un estado habitual", o incluso un "estado útil", pero más sobre ese punto importante más adelante.

Para entender cómo encajan las aserciones en un programa, considere una rutina en un programa C++ que está a punto de desreferenciar un puntero. Ahora el prueba de rutina si el puntero es NULO antes de la desreferenciación, o debe afirmar que el puntero no es NULO y luego seguir adelante y eliminar la referencia, independientemente?

Me imagino que la mayoría de los desarrolladores querrían hacer ambas cosas, agregar la afirmación, pero también compruebe el puntero para un valor NULO, con el fin de no bloquearse si la condición declarada falla. En la superficie, realizando tanto el la prueba y el control pueden parecer la decisión más sabia

A diferencia de sus condiciones afirmadas, el manejo de errores de un programa (excepciones) no se refiere a errores en el programa, sino a entradas que el programa obtiene de su ambiente. Estos son a menudo "errores" de parte de alguien, como un usuario intentar iniciar sesión en una cuenta sin escribir una contraseña. Y a pesar de que el error puede impedir una finalización exitosa del programa tarea, no hay ningún fallo del programa. El programa no puede iniciar sesión en el usuario sin una contraseña debido a un error externo - un error en el usuario parte. Si las circunstancias eran diferentes, y el usuario escribió en el contraseña correcta y el programa no pudo reconocerla; entonces, aunque desenlace sería el mismo, el fracaso ahora pertenecería a programa.

El propósito del manejo de errores (excepciones) es doble. La primera es comunicar al usuario (o algún otro cliente) que un error en la entrada del programa tiene ha sido detectado y lo que significa. El segundo objetivo es restaurar la aplicación después de que se detecta el error, a un estado bien definido. Nota que el programa en sí no está en error en esta situación. Concedido, el el programa puede estar en un estado no ideal, o incluso un estado en el que puede hacer nada útil, pero no hay errorl de programación. Al contrario, dado que el estado de recuperación de errores es uno anticipado por el programa de diseño, es uno que el programa puede manejar.

PD: es posible que desee revisar la pregunta similar: Excepción Vs Aserción.

 0
Author: herohuyongtao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:29