¿Es aseverar el mal? [cerrado]


199

Los Gocreadores del lenguaje escriben :

Ir no proporciona afirmaciones. Son innegablemente convenientes, pero nuestra experiencia ha sido que los programadores los usan como una muleta para evitar pensar en el manejo adecuado de los errores y la presentación de informes. El manejo adecuado de errores significa que los servidores continúan operando después de errores no fatales en lugar de fallar. El informe de errores adecuado significa que los errores son directos y al grano, evitando que el programador interprete una gran traza de bloqueo. 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?


44
tangente: Go es un lenguaje inusualmente obstinado. Esto no es necesariamente algo malo. Sin embargo, significa que debe tomar sus opiniones con un grano de sal más grande. También significa que si no está de acuerdo, va a rechinar los dientes mientras usa el lenguaje. Como evidencia de cómo Go se aferra a sus opiniones a pesar de la realidad, considere que necesita recurrir a la magia de la reflexión para determinar si dos colecciones son iguales.
allyourcode

@allyourcode Si te refieres reflect.DeepEqual, ciertamente no lo necesitas . Es conveniente, pero a costa del rendimiento (las pruebas unitarias son un buen caso de uso). De lo contrario, puede implementar cualquier verificación de igualdad que sea apropiada para su "colección" sin demasiados problemas.
Igor Dubinskiy

1
No, eso no es de lo que estoy hablando. No existe una rebanada1 == rebanada2 sin reflexión. Todos los demás idiomas tienen un equivalente a esta operación súper básica. La única razón por la que Go no lo hace es el prejuicio.
allyourcode el

Puede comparar dos sectores sin reflexión utilizando un forbucle en Go (al igual que C). Se podría ser muy bueno tener operaciones rebanada genéricos, aunque la comparación se complica cuando los punteros y las estructuras son los involucrados.
kbolino

Respuestas:


321

No, no tiene nada de malo assert, siempre y cuando lo uses según lo 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: una falla en la lógica del programa en sí.
  • Manejo de errores: una entrada errónea o estado del sistema que no se debe a un error en el programa.

109

No, ni gototampoco assertson malvados. Pero ambos pueden ser mal utilizados.

Afirmar es para controles de cordura. Cosas que deberían matar al programa si no son correctas. No es para validación o como un reemplazo para el manejo de errores.


¿Cómo usarlo gotosabiamente?
ar2015

1
@ ar2015 Encuentra uno de los patrones absurdamente ideados que algunas personas recomiendan evitar gotopor razones puramente religiosas, luego simplemente usa en gotolugar de ofuscar lo que estás haciendo. En otras palabras: si puede demostrar que realmente lo necesita goto, y la única alternativa es promulgar una carga de andamios sin sentido que finalmente haga lo mismo pero sin alterar a la Policía de Goto ... entonces simplemente use goto. Por supuesto, una condición previa de esto es el bit "si puedes demostrar que realmente lo necesitas goto". A menudo, la gente no. Eso todavía no significa que sea algo inherentemente malo.
underscore_d

2
gotose usa en el kernel de Linux para la limpieza del código
malat

61

Según esa lógica, los puntos de interrupción también son malos.

Las afirmaciones deben usarse como una ayuda de depuración, y nada más. "Mal" es cuando intentas usarlos en lugar de manejar errores.

Las afirmaciones 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".


40

Me gusta usar afirmar mucho. Me resulta muy útil cuando estoy creando aplicaciones por primera vez (tal vez para un nuevo dominio). En lugar de hacer una comprobación de errores muy sofisticada (que consideraría una optimización prematura) codifico rápido y agrego muchas afirmaciones. Después de saber más sobre cómo funcionan las cosas, reescribo y elimino algunas de las afirmaciones y las cambio para un mejor 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.


31

Como información adicional, go proporciona una función incorporada panic. Esto se puede usar en lugar de assert. P.ej

if x < 0 {
    panic("x is less than 0");
}

panicimprimirá el seguimiento de la pila, por lo que de alguna manera tiene el propósito de assert.


30

Deben usarse para detectar errores en el programa. No está mal la entrada del usuario.

Si se utiliza correctamente, son no el mal.


13

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. Considere este ejemplo diferente de cuándo podría usar una afirmació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 reciba malas entradas. 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 verificación es costosa, no le importa hacerlo en cada invocación de la rutina.

Todavía tiene que hacer una prueba unitaria de su código de producción, pero esa es una forma 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 que haga.


8

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 usan como una muleta para evitar pensar en el manejo y la notificación de errores adecuados". He visto esto con bastante frecuencia.

Personalmente, me gusta usar aserciones porque documentan suposiciones que podría haber hecho al escribir mi código. Si estos supuestos 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, usando #ifdefs). Al eliminar las afirmaciones en la construcción de producción, elimino el riesgo de que alguien las use como una muleta.

También hay otro problema con las afirmaciones. Las afirmaciones solo se verifican en tiempo de ejecución. Pero a menudo ocurre que la verificación que le gustaría 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 le permite hacer esto. Para los programadores de C, este artículo ( texto de enlace ) describe una técnica que se puede utilizar para realizar aserciones en tiempo de compilación.

En resumen, la regla general que sigo es: No use aserciones en una compilación de producción y, si es posible, solo use aserciones para cosas que no pueden verificarse en tiempo de compilación (es decir, deben verificarse en tiempo de ejecución).


5

Admito haber usado afirmaciones sin considerar los informes de errores adecuados. Sin embargo, eso no quita que sean muy útiles cuando se usan correctamente.

Son especialmente útiles si desea seguir el principio "Crash Early". Por ejemplo, suponga que está implementando un mecanismo de conteo de referencias. En ciertas ubicaciones de su código, sabe que el recuento debe ser cero o uno. Y también suponga que si el recuento es incorrecto, el programa no se bloqueará de inmediato, pero durante el siguiente bucle de mensajes en ese punto será difícil descubrir por qué las cosas salieron mal. Una afirmación habría sido útil para detectar el error más cerca de su origen.


5

Prefiero evitar el código que hace cosas diferentes en depuración y lanzamiento.

Sin embargo, es útil romper el depurador en una condición y tener toda la información de archivo / línea, 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 en realidad puede cambiar el estado del programa:

assert(2 == ShroedingersCat.GetNumEars()); haría que el programa hiciera cosas diferentes en depuración y lanzamiento.

Hemos desarrollado un conjunto de macros de aserción que arrojarían una excepción, y lo hacen tanto en versión de depuración como de lanzamiento. Por ejemplo, THROW_UNLESS_EQ(a, 20);lanzaría una excepción con el mensaje what () que tiene 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 se puede configurar para que se rompa al 'lanzar' el tipo de excepción específico.


44
El 90% de las estadísticas utilizadas en los argumentos son falsas.
João Portela

5

No me gusta afirma intensamente. Sin embargo, no iría tan lejos como para 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 mantenerse para el producto final.

Si construye una red de seguridad para usted mientras depura y construye el sistema, ¿por qué negaría esta red de seguridad a su cliente, a su servicio de asistencia o a cualquier persona que pueda utilizar el software que está construyendo actualmente? Use 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 esta vez, la afirmación permanece en su lugar y puede proporcionar información valiosa en caso de falla que de otro modo se perdería.

Así que entiendo completamente a los creadores de Go al eliminar las afirmaciones por completo 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é quedarse con las afirmaciones arcaicas?


Ir no tiene excepciones. La razón habitual para usar una afirmación en lugar de una excepción es porque desea que se elimine en la implementación por razones de rendimiento. Las afirmaciones no son arcaicas. Lamento sonar duro, pero esta respuesta no es remotamente útil para la pregunta original, ni correcta.
Nir Friedman


3

Recientemente comencé a agregar algunas afirmaciones a mi código, y así es como lo he estado haciendo:

Divido mentalmente 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 la entrada en un bucle que solo sale cuando la entrada es válida (en el caso de la entrada interactiva del usuario), o lanzo excepciones en el caso de datos corruptos de archivos / redes irrecuperables.

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 recibe información de una red podría 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 cheques. Cualquier cosa en la que los datos se determinen en tiempo de ejecución siempre recibe una excepción o un manejo inmediato de errores. Sin embargo, ese chequeo adicional Class::fcon la assertdeclaración significa que si alguna vez llama un código interno Class::f, todavía tengo un chequeo de cordura. Es posible que mi código interno no pase un argumento válido (porque puedo haber calculado a valuepartir de una serie compleja de funciones), por lo que me gusta tener la afirmación en la función de configuración para documentar que, independientemente de quién llame a la función, valueno 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 funcione bien, mientras que las excepciones deberían ser para casos excepcionales y erróneos que todavía son posibles. Debido a que, 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.


2

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 algo incorrecto, son una construcción que se usa mal fácilmente. También tienen muchas mejores alternativas. Cosas como esas son buenas candidatas para ser llamadas malvadas.

Por ejemplo, un módulo de terceros (o cualquier módulo realmente) casi nunca debería 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 datos corruptos es mejor que perderlos. Las afirmaciones pueden obligarlo a perder datos.

Algunas alternativas para afirmar:

  • Usando un depurador,
  • Consola / base de datos / otro registro
  • Excepciones
  • Otros tipos de manejo de errores

Algunas referencias:

Incluso las personas que abogan por la afirmación piensan que solo deberían usarse en el desarrollo y no en la producción:

Esta persona dice que las afirmaciones deben usarse cuando el módulo tiene datos potencialmente corruptos que persisten después de que se lanza una excepción: http://www.advogato.org/article/949.html . Sin duda, este es un punto razonable, sin embargo, un módulo externo nunca debe prescribir la importancia de los datos corruptos para el programa que realiza la llamada (al salir "para" ellos). La forma correcta de manejar esto es lanzando una excepción que deja en claro 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 algo incorrecto.


1

assert es muy útil y puede ahorrarle mucho retroceso cuando se producen errores inesperados al detener el programa ante los primeros signos de problemas.

Por otro lado, es muy fácil abusar assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

La versión correcta y correcta sería algo así como:

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

Entonces ... a la larga ... en el panorama general ... Debo estar de acuerdo en que assertse puede abusar de él. Lo hago todo el tiempo.


1

assert se está abusando para el manejo de errores porque se escribe menos.

Por lo tanto, como diseñadores de idiomas, deberían ver que el manejo adecuado de errores se puede hacer con una escritura aún menor. Excluir afirmar porque su mecanismo de excepción es detallado no es la solución. Oh, espera, Go tampoco tiene excepciones. Demasiado :)


1
No está mal :-) Excepciones o no, afirmaciones o no, los fanáticos de Go todavía están hablando de cuán cortos son los códigos.
Moshe Revah

1

Sentí ganas de patear al autor en la cabeza cuando vi eso.

Utilizo afirma todo el tiempo en código y eventualmente los reemplazo cuando escribo más código. Los uso cuando no he escrito la lógica requerida y quiero recibir una alerta cuando me encuentro con el código en lugar de escribir una excepción que se eliminará a medida que el proyecto se acerque a su finalización.

Las excepciones también se mezclan con el código de producción más fácilmente, lo que no me gusta. Una afirmación es más fácil de notar quethrow new Exception("Some generic msg or 'pretend i am an assert'");


1

Mi problema con estas respuestas al defender la afirmación es que nadie especifica claramente qué lo hace diferente de un error fatal regular y por qué una afirmación no puede ser un subconjunto de una excepción . Ahora, con esto dicho, ¿qué pasa si la excepción nunca se detecta? ¿Eso lo convierte en una afirmación por nomenclatura? Y, ¿por qué querrías imponer una restricción en el lenguaje para que se pueda generar una excepción que / nada / pueda manejar?


Si miras mi respuesta. Mi uso es diferenciar las 'excepciones' (afirmaciones) de las que quiero deshacerme de la depuración frente a las excepciones que mantengo. ¿Por qué querría deshacerme de ellos? porque sin ellos trabajando no estaría completo. El ejemplo es si manejo 3 casos y el 4to es todo. Puedo buscar fácilmente la afirmación para encontrarlos en el código y saber que está incompleta en lugar de usar una excepción que podría ser atrapada accidentalmente (por otro programador) o difícil de determinar si es una excepción o una verificación lógica que debo resolver en el código.

En mi opinión, esta es una mala idea, en el mismo nivel que las clases "selladas" y por la misma razón. Asume que las excepciones que desea mantener son aceptables para los usos de su código que aún no conoce. Todas las excepciones pasan por los mismos canales, y si el usuario no quiere atraparlos, puede elegir no hacerlo. Si lo hace, también debería tener la habilidad. De cualquier manera, solo está haciendo suposiciones o empujando su práctica con un concepto como una afirmación.
Evan Carroll

Decidí que los escenarios de ejemplo son los mejores. Aquí hay una simple. int func (int i) {if (i> = 0) {console.write ("El número es positivo {0}", i); } else {afirmar (falso); // a flojo para hacer negativos ATM} return i * 2; <- ¿Cómo haría esto sin afirmaciones y es una excepción realmente mejor? y recuerde, este código se implementará antes del lanzamiento.

Claras excepciones son mejores, digamos que tomo la entrada del usuario y llamo func()con un número negativo. Ahora, de repente con tus afirmaciones, me has quitado la alfombra y no me has dado la oportunidad de recuperarme, en lugar de decirme cortésmente que lo que estoy pidiendo no se puede hacer. No hay nada de malo en acusar al programa de portarse mal y emitir una citación: el problema es que está borrando el acto de hacer cumplir una ley y condenando al delincuente.
Evan Carroll

Ese es el punto, NO DEBE decir que lo que el usuario quiere hacer no se puede hacer. La aplicación debe finalizar con el mensaje de error y quien esté depurando recordará que es algo que DEBE HACERSE pero no lo es. NO desea que se maneje. Desea una terminación y se le recuerda que no manejó ese caso. y es extremadamente fácil ver qué casos quedan, ya que todo lo que necesita hacer es buscar una afirmación en el código. Manejar el error sería incorrecto ya que el código debería poder hacerlo una vez que esté listo para la producción. Permitir que un programador lo atrape y haga otra cosa está mal.

1

Sí, afirma que son malvados.

A menudo se utilizan en lugares donde se debe utilizar el manejo adecuado de errores. ¡Acostúmbrese a escribir el manejo adecuado de errores de calidad de producción desde el principio!

Por lo general, se interponen en la escritura de pruebas unitarias (a menos que escriba una afirmación personalizada que interactúe con su arnés de prueba). Esto a menudo se debe a que se usan donde se debe usar el manejo adecuado de errores.

En su mayoría, se compilan a partir de versiones de lanzamiento, lo que significa que ninguna de sus "pruebas" está disponible cuando está ejecutando el código que realmente publica; 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 llamarse y la afirmación "evita" esto. ¡Arregla el diseño!

Escribí más sobre esto en mi blog en 2005 aquí: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html


0

No tanto mal como generalmente contraproducente. Hay una separación entre la comprobación de errores permanente y la depuración La afirmación hace que la gente piense que toda depuración debe ser permanente y causa problemas de legibilidad masivos cuando se usa mucho. El manejo permanente de errores debería ser mejor que eso cuando sea necesario, y dado que la afirmación causa sus propios errores, es una práctica bastante cuestionable.


55
afirmar es bueno para declarar las condiciones previas en la parte superior de una función, y si está escrito claramente, actúa como parte de la documentación de la función.
Peter Cordes

0

Nunca uso aserción (), los ejemplos generalmente muestran algo así:

int* ptr = new int[10];
assert(ptr);

Esto es malo, nunca hago esto, ¿qué pasa 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.
}

14
newnunca vuelve nullptr, tira.
David Stone

Tenga en cuenta que puede usar std :: nothrow para eso.
marzo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.