exceptions - c++ throw
Es afirmar el mal? (21)
Los creadores del lenguaje Go
write :
Ir no proporciona afirmaciones. Son innegablemente convenientes, pero nuestra experiencia ha sido que los programadores los utilizan como una muleta para evitar pensar en el manejo y el informe de errores adecuados. El manejo adecuado de errores significa que los servidores continúan la operación después de errores no fatales en lugar de fallar. El informe de errores adecuado significa que los errores son directos y al punto, lo que evita que el programador interprete una traza de gran impacto. Los errores precisos son particularmente importantes cuando el programador que ve los errores no está familiarizado con el código.
Cuál es tu opinión acerca de esto?
Admito haber utilizado afirmaciones sin tener en cuenta el informe de errores adecuado. Sin embargo, eso no quita que sean muy útiles cuando se usan correctamente.
Son especialmente útiles para si quieres seguir el principio de "Crash Early". Por ejemplo, supongamos que está implementando un mecanismo de conteo de referencias. En ciertas ubicaciones en su código usted sabe que el refcount debe ser cero o uno. Y también suponga que si el refcount es incorrecto, el programa no se bloqueará inmediatamente, pero durante el próximo ciclo de mensajes será difícil descubrir por qué las cosas salieron mal. Una afirmación hubiera sido útil para detectar el error más cerca de su origen.
Como información adicional, go proporciona una función integrada de panic
. Esto puede ser usado en lugar de assert
. P.ej
if x < 0 {
panic("x is less than 0");
}
panic
imprimirá el seguimiento de la pila, por lo que de alguna manera tiene el propósito de assert
.
Deben ser utilizados para detectar errores en el programa. No está mal la entrada del usuario.
Si se usan correctamente, no son malos.
Esto surge mucho, y creo que un problema que confunde las defensas de las afirmaciones es que a menudo se basan en la verificación de argumentos. Entonces considere este ejemplo diferente de cuando podría usar una aserción:
build-sorted-list-from-user-input(input)
throw-exception-if-bad-input(input)
...
//build list using algorithm that you expect to give a sorted list
...
assert(is-sorted(list))
end
Utiliza una excepción para la entrada porque espera que a veces obtenga una entrada incorrecta. Afirma que la lista está ordenada para ayudarlo a encontrar un error en su algoritmo, que por definición no espera. La afirmación está solo en la compilación de depuración, por lo que aunque la comprobación sea costosa, no le importa hacerlo en cada invocación de la rutina.
Aún tiene que hacer una prueba unitaria de su código de producción, pero esa es una manera diferente y complementaria de asegurarse de que su código sea correcto. Las pruebas unitarias aseguran que su rutina esté a la altura de su interfaz, mientras que las afirmaciones son una forma más precisa de asegurarse de que su implementación está haciendo exactamente lo que espera.
Las afirmaciones no son malas, pero pueden ser mal utilizadas fácilmente. Estoy de acuerdo con la afirmación de que "las afirmaciones a menudo se utilizan como una muleta para evitar pensar en el manejo y el informe de errores adecuados". He visto esto bastante a menudo.
Personalmente, me gusta usar aserciones porque documentan suposiciones que podría haber hecho al escribir mi código. Si estas suposiciones se rompen mientras se mantiene el código, el problema se puede detectar durante la prueba. Sin embargo, hago el punto de eliminar cada aserción de mi código cuando hago una compilación de producción (es decir, utilizando #ifdefs). Al eliminar las aseveraciones en la compilación de producción, elimino el riesgo de que alguien las use indebidamente como una muleta.
También hay otro problema con las afirmaciones. Las aserciones solo se verifican en tiempo de ejecución. Pero a menudo ocurre que la verificación que desea realizar podría haberse realizado en tiempo de compilación. Es preferible detectar un problema en tiempo de compilación. Para los programadores de C ++, boost proporciona BOOST_STATIC_ASSERT que te permite hacer esto. Para los programadores de C, este artículo ( texto del enlace ) describe una técnica que puede utilizarse para realizar aserciones en el momento de la compilación.
En resumen, la regla empírica que sigo es: no usar aserciones en una compilación de producción y, si es posible, solo usar aserciones para cosas que no pueden verificarse en tiempo de compilación (es decir, deben verificarse en tiempo de ejecución).
Me gusta usar afirmar mucho. Me resulta muy útil cuando estoy creando aplicaciones por primera vez (quizás para un nuevo dominio). En lugar de hacer una comprobación de errores muy sofisticada (que consideraría optimización prematura) codifico rápido y agrego un montón de afirmaciones. Después de saber más sobre cómo funcionan las cosas, hago una reescritura, elimino algunas afirmaciones y las cambio para mejorar el manejo de errores.
Debido a las afirmaciones, paso mucho menos tiempo codificando / depurando programas.
También he notado que las afirmaciones me ayudan a pensar en muchas cosas que podrían romper mis programas.
Mi problema con estas respuestas en defensa de aseverar es que nadie especifica claramente qué lo hace diferente de un error fatal regular, y por qué una aserción no puede ser un subconjunto de una excepción . Ahora, con esto dicho, ¿qué pasa si la excepción nunca se captura? ¿Eso lo convierte en una afirmación por nomenclatura? Y, ¿por qué querría imponer una restricción en el idioma para que se pueda generar una excepción que / nada / pueda manejar?
No me gusta lo que afirma con intensidad. Sin embargo, no me atrevería a decir que son malvados.
Básicamente, una afirmación hará lo mismo que una excepción no verificada, la única excepción es que la afirmación (normalmente) no debe conservarse para el producto final.
Si construye una red de seguridad para usted mismo mientras depura y construye el sistema, ¿por qué negaría esta red de seguridad para su cliente, su servicio de asistencia técnica o cualquier persona que pueda utilizar el software que está creando actualmente? Utilice excepciones exclusivamente para afirmaciones y situaciones excepcionales. Al crear una jerarquía de excepciones apropiada, podrá discernir muy rápidamente una de la otra. Excepto que esta vez, el aserto permanece en su lugar y puede proporcionar información valiosa en caso de fallo que de lo contrario se perdería.
Así que entiendo completamente a los creadores de Go al eliminar aserciones y forzar a los programadores a usar excepciones para manejar la situación. Hay una explicación simple para esto, la excepción es solo un mejor mecanismo para el trabajo, ¿por qué atenerse a las afirmaciones arcaicas?
No tanto el mal como contraproducente en general. Hay una separación entre la verificación de errores permanentes y la depuración. Assert hace que la gente piense que toda la depuración debe ser permanente y causa problemas masivos de legibilidad cuando se usa mucho. El manejo permanente de errores debe ser mejor que el que sea necesario, y dado que afirmar causa sus propios errores, es una práctica bastante cuestionable.
No, ni goto
ni assert
son malvados. Pero ambos pueden ser mal utilizados.
Afirmar es para los controles de cordura. Cosas que deberían matar al programa si no son correctas. No para validación o como reemplazo para el manejo de errores.
No, no hay nada de malo en assert
, siempre y cuando lo uses como está previsto.
Es decir, se supone que es para detectar casos que "no pueden suceder", durante la depuración, en oposición al manejo normal de errores.
- Afirmar: Un fallo en la propia lógica del programa.
- Manejo de errores: una entrada errónea o un estado del sistema que no se debe a un error en el programa.
Nunca uso assert (), los ejemplos suelen mostrar algo como esto:
int* ptr = new int[10];
assert(ptr);
Esto es malo, nunca hago esto, ¿y si mi juego está asignando un montón de monstruos? ¿Por qué debería bloquear el juego? En su lugar, debes manejar los errores con gracia, así que haz algo como:
CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
// we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
// initialize monsters.
}
Por esa lógica, los puntos de ruptura también son malos.
Las afirmaciones se deben utilizar como una ayuda de depuración, y nada más. "Mal" es cuando intentas usarlos en lugar de manejar los errores.
Los avisos están ahí para ayudarlo a usted, el programador, a detectar y solucionar problemas que no deben existir y verificar que sus suposiciones se mantengan verdaderas.
No tienen nada que ver con el manejo de errores, pero desafortunadamente, algunos programadores abusan de ellos como tales y luego los declaran "malvados".
Prefiero evitar el código que hace diferentes cosas en la depuración y el lanzamiento.
Sin embargo, interrumpir el depurador en una condición y tener toda la información de archivo / línea es útil, también la expresión exacta y el valor exacto.
Tener una afirmación que "evalúe la condición solo en la depuración" puede ser una optimización del rendimiento y, como tal, útil solo en el 0,0001% de los programas, donde las personas saben lo que están haciendo. En todos los demás casos, esto es perjudicial, ya que la expresión puede cambiar realmente el estado del programa:
assert(2 == ShroedingersCat.GetNumEars());
Haría que el programa haga cosas diferentes en depuración y lanzamiento.
Hemos desarrollado un conjunto de macros de aserción que arrojaría una excepción, y lo hacemos tanto en la versión de depuración como en la de lanzamiento. Por ejemplo, THROW_UNLESS_EQ(a, 20);
arrojaría una excepción con el mensaje what () que tenga tanto el archivo, la línea y los valores reales de a, y así sucesivamente. Solo una macro tendría el poder para esto. El depurador puede configurarse para interrumpir el "lanzamiento" del tipo de excepción específico.
Recientemente comencé a agregar algunas afirmaciones a mi código, y así es como lo he estado haciendo:
Mentalmente, divido mi código en código de límite y código interno. El código de límite es un código que maneja la entrada del usuario, lee archivos y obtiene datos de la red. En este código, solicito una entrada en un bucle que solo sale cuando la entrada es válida (en el caso de una entrada interactiva del usuario), o lanzo excepciones en el caso de datos corruptos de archivos / redes no recuperables.
El código interno es todo lo demás. Por ejemplo, una función que establece una variable en mi clase podría definirse como
void Class::f (int value) {
assert (value < end);
member = value;
}
y una función que obtiene información de una red puede leerse como tal:
void Class::g (InMessage & msg) {
int const value = msg.read_int();
if (value >= end)
throw InvalidServerData();
f (value);
}
Esto me da dos capas de controles. Cualquier cosa en la que los datos se determinan en tiempo de ejecución siempre obtiene una excepción o un manejo inmediato de errores. Sin embargo, esa comprobación adicional en Class::f
con la declaración de afirmación significa que si algún código interno alguna vez llama a Class::f
, todavía tengo una comprobación de validez. Es posible que mi código interno no pase un argumento válido (porque es posible que haya calculado el value
de algunas series complejas de funciones), por lo que me gusta tener la aserción en la función de configuración para documentar que, independientemente de quién esté llamando a la función, el value
no debe ser mayor que o igual a end
.
Esto parece encajar en lo que estoy leyendo en algunos lugares, que las afirmaciones deberían ser imposibles de violar en un programa que funciona bien, mientras que las excepciones deberían ser casos excepcionales y erróneos que todavía son posibles. Como en teoría estoy validando todas las entradas, no debería ser posible que se active mi afirmación. Si es así, mi programa está equivocado.
Respuesta corta: no, creo que las afirmaciones pueden ser útiles
Sí, afirma que son malvados.
A menudo se utilizan en lugares donde se debe utilizar el manejo adecuado de errores. ¡Acostúmbrate a escribir el manejo de errores de calidad de producción desde el principio!
Por lo general, se interponen en la forma de escribir pruebas unitarias (a menos que escriba una declaración personalizada que interactúe con su arnés de prueba). Esto es a menudo porque se usan donde se debe usar el manejo adecuado de errores.
La mayoría de ellos se compilan a partir de compilaciones de lanzamiento, lo que significa que ninguna de sus "pruebas" está disponible cuando está ejecutando el código que realmente libera; Dado que en situaciones de subprocesos múltiples, los peores problemas a menudo solo aparecen en el código de lanzamiento, esto puede ser malo.
A veces son una muleta para diseños rotos; es decir, el diseño del código permite que un usuario lo llame de una manera que no debería llamarlo y el aserto "evita" esto. ¡Arregla el diseño!
Escribí sobre esto más en mi blog en 2005 aquí: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html
Sentí ganas de darle una patada en la cabeza al autor cuando vi eso.
Uso afirmaciones todo el tiempo en el código y eventualmente las sustituyo todas cuando escribo más código. Los uso cuando no he escrito la lógica requerida y quiero que me avisen cuando me encuentre con el código en lugar de escribir una excepción que se eliminará a medida que el proyecto se acerque.
Las excepciones también se mezclan con el código de producción más fácilmente que no me gusta. Una afirmación es más fácil de notar que throw new Exception("Some generic msg or ''pretend i am an assert''");
Si las afirmaciones de las que está hablando significan que el programa vomita y luego existe, las afirmaciones pueden ser muy malas. Esto no quiere decir que siempre sean una cosa incorrecta de usar, son un constructo que se usa mal fácilmente. También tienen muchas mejores alternativas. Cosas así son buenos candidatos para ser llamados malvados.
Por ejemplo, un módulo de terceros (o cualquier módulo realmente) casi nunca debe salir del programa de llamada. Esto no le da al programador de llamadas ningún control sobre el riesgo que el programa debería tomar en ese momento. En muchos casos, los datos son tan importantes que incluso guardar los datos dañados es mejor que perderlos. Las afirmaciones pueden obligarte a perder datos.
Algunas alternativas a las afirmaciones:
- Utilizando un depurador,
- Consola / base de datos / otro registro
- Excepciones
- Otros tipos de manejo de errores.
Algunas referencias:
- http://ftp.gnu.org/old-gnu/Manuals/nana-1.14/html_node/nana_3.html
- http://www.lenholgate.com/blog/2005/09/assert-is-evil.html
- Go no proporciona afirmaciones y tiene buenas razones por las que: http://golang.org/doc/faq#assertions
- http://c2.com/cgi/wiki?DoNotUseAssertions
Incluso las personas que abogan por afirmar piensan que solo deben usarse en desarrollo y no en producción:
- http://codebetter.com/gregyoung/2007/12/12/asserts-are-not-evil/
- http://www.codeproject.com/Articles/6404/Assert-is-your-friend
- http://parabellumgames.wordpress.com/using-asserts-for-debugging/
Esta persona dice que las afirmaciones deben usarse cuando el módulo tiene datos potencialmente corruptos que persisten después de lanzar una excepción: http://www.advogato.org/article/949.html . Este es ciertamente un punto razonable, sin embargo, un módulo externo nunca debe prescribir cuán importantes son los datos dañados para el programa que realiza la llamada (al salir de "para" ellos). La forma correcta de manejar esto es lanzando una excepción que aclare que el programa ahora puede estar en un estado inconsistente. Y dado que los buenos programas consisten principalmente en módulos (con un pequeño código de pegamento en el ejecutable principal), las afirmaciones son casi siempre lo incorrecto.
assert
es muy útil y puede ahorrarle mucho retroceso cuando se producen errores inesperados al detener el programa a la primera señal de problemas.
Por otro lado, es muy fácil abusar de assert
.
int quotient(int a, int b){
assert(b != 0);
return a / b;
}
La versión correcta, correcta sería algo como:
bool quotient(int a, int b, int &result){
if(b == 0)
return false;
result = a / b;
return true;
}
Entonces ... a largo plazo ... en el panorama general ... debo aceptar que se puede abusar de la afirmación. Lo hago todo el tiempo.
assert
está siendo abusado para el manejo de errores porque es menos tipeo.
Así que como diseñadores de lenguaje, deberían ver que el manejo adecuado de errores se puede hacer con menos escritura. Excluir aseverar porque su mecanismo de excepción es detallado no es la solución. Oh, espera, Go tampoco tiene excepciones. Demasiado :)