Pregunta

Al programar por contrato una función o método primero se verifica si se cumplen sus condiciones previas, antes de comenzar a trabajar en sus responsabilidades, ¿verdad?Las dos formas más destacadas de realizar estas comprobaciones son mediante assert y por exception.

  1. afirmar falla sólo en modo de depuración.Para asegurarse, es fundamental realizar pruebas (unitarias) de todas las condiciones previas del contrato por separado para ver si realmente fallan.
  2. La excepción falla en el modo de depuración y lanzamiento.Esto tiene la ventaja de que el comportamiento de depuración probado es idéntico al comportamiento de lanzamiento, pero conlleva una penalización en el rendimiento del tiempo de ejecución.

¿Cuál crees que es preferible?

Ver pregunta relacionada aquí

¿Fue útil?

Solución

Deshabilitar la afirmación en las versiones de lanzamiento es como decir "Nunca tendré ningún problema en una versión de lanzamiento", lo que a menudo no es el caso.Por lo tanto, afirmar no debería desactivarse en una versión de lanzamiento.Pero tampoco querrás que la versión de lanzamiento falle 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 detectarla y de poder activar el lanzamiento de excepciones en su depurador para detectarla, y en el modo de lanzamiento puede compensar el error en lugar de un bloqueo directo.Es el camino más seguro a seguir.

Otros consejos

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

El principio que sigo es este:Si una situación se puede evitar de manera realista mediante la codificación, utilice una afirmación.De lo contrario, utilice una excepción.

Las afirmaciones sirven para garantizar que se cumpla el Contrato.El contrato debe ser justo, de modo que el cliente debe estar en condiciones de garantizar su cumplimiento.Por ejemplo, puede establecer 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 consistentes.

Las excepciones son para situaciones que están fuera del control tanto del cliente como del servidor.Una excepción significa que algo salió mal y no se pudo haber hecho nada para evitarlo.Por ejemplo, la conectividad de la 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 agregar que la distinción Aserción/Excepción no es realmente la mejor manera de pensar en ello.En lo que realmente hay que pensar es en el contrato y en cómo se puede hacer cumplir.En mi ejemplo de URL anterior, lo mejor que puedes hacer es tener una clase que encapsule una URL y sea nula o válida.Es la conversión de una cadena en una URL lo que hace cumplir el contrato y se lanza una excepción si no es válido.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.

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

Piensa también en las consecuencias.Una afirmación normalmente cierra la aplicación.Si existe alguna expectativa realista de que se pueda recuperar la condición, probablemente debería utilizar una excepción.

Por otro lado, si el problema puede solo Si se debe a un error del programador, utilice una afirmación, porque desea saberlo lo antes posible.Una excepción podría ser detectada y manejada, y usted nunca se enteraría.Y sí, debes deshabilitar las afirmaciones en el código de lanzamiento porque ahí quieres que la aplicación se recupere si existe la más mínima posibilidad de que así sea.Incluso si el estado de su programa está profundamente dañado, es posible que el usuario pueda guardar su trabajo.

No es exactamente cierto que "la afirmación falla sólo en el modo de depuración".

En Construcción de software orientado a objetos, segunda edición de Bertrand Meyer, el autor deja abierta la puerta a la comprobación de las condiciones previas en el modo de liberación.En ese caso, lo que sucede cuando una aserción falla es que...¡Se genera 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 error y, en algunos casos, reiniciar la aplicación.

La motivación detrás de esto es que las condiciones previas suelen ser más baratas de probar que las invariantes y las condiciones posteriores, y que en algunos casos la corrección y la "seguridad" en la versión de lanzamiento son más importantes que la velocidad.es decir.Para muchas aplicaciones la velocidad no es un problema, pero robustez (la capacidad del programa para comportarse de forma segura cuando su comportamiento no es correcto, es decircuando se rompe un contrato) lo es.

¿Debería dejar siempre habilitadas las comprobaciones de condiciones previas?Eso depende.Tu decides.No existe una respuesta universal.Si está creando 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 necesites toda la velocidad que puedas conseguir, y si alguien obtiene 1000 puntos en lugar de 10 debido a un error que las condiciones previas no detectaron (porque no están habilitadas), mala suerte.

En ambos casos, idealmente debería haber detectado ese error durante las pruebas y debería realizar una parte importante 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 condiciones previas fallan en el código de producción en un escenario que no se detectó anteriormente debido a pruebas incompletas.

Para resumir, puedes tener afirmaciones y aun así obtener las excepciones automáticamente, si los dejas habilitados, al menos en Eiffel.Creo que para hacer lo mismo en C++ debes escribirlo tú mismo.

Ver también: ¿Cuándo deberían permanecer las afirmaciones en el código de producción?

Hubo una enorme hilo con respecto a la habilitación/deshabilitación de afirmaciones en versiones de lanzamiento en comp.lang.c++.moderated, que si tiene algunas semanas podrá ver cuán variadas son las opiniones al respecto.:)

Contrariamente a copró, Creo que si no está seguro de que una aserción se pueda deshabilitar en una versión de lanzamiento, entonces no debería haber sido una aserción.Las afirmaciones sirven para proteger contra la ruptura de las invariantes del programa.En tal caso, en lo que respecta al cliente de su código, habrá uno de dos resultados posibles:

  1. Muere con algún tipo de falla del tipo de sistema operativo, lo que resulta en una llamada para cancelar.(Sin afirmar)
  2. Muere mediante una llamada directa para abortar.(Con afirmar)

No hay diferencia para el usuario, sin embargo, es posible que las afirmaciones agreguen un costo de rendimiento innecesario en el código que está presente en la gran mayoría de 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 afirmaciones y otra sin ellas), es muy poco probable que afirmar sea la opción adecuada.

Personalmente, sin embargo, tampoco estoy seguro de hacer excepciones en este caso.Las excepciones se adaptan mejor a aquellos casos en los que puede tener lugar una forma adecuada de recuperación.Por ejemplo, puede ser que estés intentando asignar memoria.Cuando detecta una excepción 'std::bad_alloc', es posible que pueda liberar memoria e intentarlo nuevamente.

Expresé aquí mi opinión sobre el estado de la cuestión: ¿Cómo se valida el estado interno de un objeto? .En general, haga valer sus reclamos y desestime la violación por parte de otros.Para deshabilitar afirmaciones en versiones de lanzamiento, puede hacer:

  • Deshabilite las afirmaciones para comprobaciones costosas (como verificar si un rango está ordenado)
  • Mantenga habilitadas las comprobaciones triviales (como comprobar si hay un puntero nulo o un valor booleano)

Por supuesto, en las versiones de lanzamiento, las afirmaciones fallidas y las excepciones no detectadas deben manejarse de otra manera que en las versiones de depuración (donde podría simplemente llamar a std::abort).Escriba un registro del error en algún lugar (posiblemente en un archivo), informe al cliente que ocurrió un error interno.El cliente podrá enviarle el archivo de registro.

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

Las afirmaciones son notificaciones de "Hola programador, esto está roto", están ahí para recordarle errores que no habría notado cuando sucedieron.

Las excepciones son las notificaciones de "Hola usuario, algo salió mal" (obviamente, puedes codificarlas para detectarlas de modo que nunca se le informe al usuario), pero están diseñadas para ocurrir en tiempo de ejecución cuando el usuario Joe está usando la aplicación.

Entonces, si cree que puede solucionar todos los errores, utilice únicamente excepciones.Si crees que no puedes.....utilizar excepciones.Aún puede usar afirmaciones de depuración para reducir el número de excepciones, por supuesto.

No olvide que muchas de las condiciones previas serán datos proporcionados por el usuario, por lo que necesitará una buena forma de informarle que sus datos no son buenos.Para hacer eso, a menudo necesitarás devolver datos de error a través de la pila de llamadas a los bits con los que está interactuando.Las afirmaciones no serán útiles entonces, doblemente si su aplicación es de n niveles.

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

Prefiero el segundo.Si bien es posible que sus pruebas hayan funcionado bien, murphy dice que algo inesperado saldrá mal.Entonces, en lugar de obtener una excepción en la llamada al método errónea real, terminas rastreando una NullPointerException (o equivalente) 10 marcos de pila más profundo.

Las respuestas anteriores son correctas:Utilice excepciones para funciones API públicas.El único momento en el que es posible que desees infringir esta regla es cuando el cheque es computacionalmente costoso.En ese caso, usted poder ponlo en una afirmación.

Si cree que es probable que se infrinja esa condición previa, manténgala como excepción o refactorice la condición previa.

Deberías usar ambos.Las afirmaciones son para su comodidad como desarrollador.Las excepciones detectan cosas que te perdiste o que no esperabas durante el tiempo de ejecución.

me he encariñado Funciones de informe de errores de Glib en lugar de las viejas afirmaciones.Se comportan como declaraciones de afirmación, pero en lugar de detener el programa, simplemente devuelven un valor y dejan que el programa continúe.Funciona sorprendentemente bien y, como beneficio adicional, puedes ver qué sucede con el resto de tu programa cuando una función no devuelve "lo que se supone que debe".Si falla, sabrá que su verificación de errores es laxa en algún otro lugar más adelante.

En mi último proyecto, utilicé este estilo de funciones para implementar la verificación de condiciones previas y, si una de ellas fallaba, imprimía un seguimiento de la pila en el archivo de registro pero seguía ejecutando.Me ahorré toneladas de tiempo de depuración cuando otras personas encontraban 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 verificar 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;
}

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

Utilice aserciones para los casos en los que desee deshabilitarlo en producción y cometa el error de dejarlas ahí.La única razón real para deshabilitarlo en producción, pero no en desarrollo, es acelerar el programa.En la mayoría de los casos, esta aceleración no será significativa, pero a veces el código requiere tiempo crítico 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 desaceleración.

Si existe alguna posibilidad real de recuperación, utilice una excepción, ya que las afirmaciones no están diseñadas para recuperarse.Por ejemplo, el código rara vez está diseñado para recuperarse de errores de programación, pero sí para recuperarse de factores como fallas de red o archivos bloqueados.Los errores no deben manejarse 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 fáciles de recuperar.

Re argumento de que es más fácil depurar afirmaciones:El seguimiento de la pila de una excepción con el nombre adecuado es tan fácil de leer como una afirmación.Un buen código solo debe detectar tipos específicos de excepciones, por lo que las excepciones no deben pasar desapercibidas al ser detectadas.Sin embargo, creo que Java a veces te obliga a detectar todas las excepciones.

Ver también esta pregunta:

En algunos casos, las afirmaciones están deshabilitadas cuando se compilan para su lanzamiento.Es posible que no tenga control sobre esto (de lo contrario, podría construir con afirmaciones encendidas), 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 obtendrá lo que espera, y esto puede provocar problemas o incluso bloqueos en partes totalmente diferentes del programa, lo que hace que la depuración sea una pesadilla.

Por lo general, lanzo una excepción en el estado if para asumir el papel de afirmar en caso de que estén discapacitados

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff

Para mí, la regla general es utilizar expresiones de afirmación para encontrar errores internos y excepciones para errores externos.Puede beneficiarse mucho de la siguiente discusión de Greg de aquí.

Las expresiones de afirmación se utilizan para encontrar errores de programación:ya sea errores en la propia lógica del programa o en errores en su correspondiente implementación.Una condición de afirmación verifica que el programa permanezca en un estado definido.Un "estado definido" es básicamente aquel que concuerda con los supuestos del programa.Tenga en cuenta que un "estado definido" para un programa no tiene por qué ser un "estado ideal" o incluso un "estado habitual", o incluso un "estado útil", pero hablaremos de ese importante punto más adelante.

Para comprender cómo las afirmaciones encajan en un programa, considere una rutina en un programa C ++ que está a punto de desreferencia de un puntero.Ahora debería la rutina probar si el puntero es nulo antes de la derrovencia, o debería afirmar que el puntero no es nulo y luego continúa y lo desreferencia de todos modos.

Me imagino que la mayoría de los desarrolladores querrían hacer ambas cosas, agregar el afirmación, pero también verifique el puntero por un valor nulo, para no bloquear si la condición afirmada falla.En la superficie, realizar tanto la prueba como el cheque pueden parecer la decisión más sabia

A diferencia de sus condiciones afirmadas, el manejo de errores de un programa (excepciones) se refiere a no a los errores en el programa, sino a ingresar el programa obtiene de su entorno.A menudo son "errores" por parte de alguien, como un usuario que intenta iniciar sesión en una cuenta sin escribir una contraseña.Y a pesar de que el error puede evitar una finalización exitosa de la tarea del programa, no hay falla del programa.El programa no puede iniciar sesión al usuario sin una contraseña debido a un error externo, un error en la parte del usuario.Si las circunstancias eran diferentes, y el usuario escribió la contraseña correcta y el programa no lo reconoció;Luego, aunque el resultado aún sería el mismo, el fracaso ahora pertenecería al programa.

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

PD:es posible que desee consultar la pregunta similar: Excepción versus afirmación.

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