Estoy tratando de convencer al líder de mi equipo para que permita usar excepciones en C ++ en lugar de devolver un bool isSuccessful
o una enumeración con el código de error. Sin embargo, no puedo contrarrestar esta crítica suya.
Considera esta biblioteca:
class OpenFileException() : public std::runtime_error {
}
void B();
void C();
/** Does blah and blah. */
void B() {
// The developer of B() either forgot to handle C()'s exception or
// chooses not to handle it and let it go up the stack.
C();
};
/** Does blah blah.
*
* @raise OpenFileException When we failed to open the file. */
void C() {
throw new OpenFileException();
};
Considere un desarrollador que llama a la
B()
función. Comprueba su documentación y ve que no devuelve excepciones, por lo que no trata de atrapar nada. Este código podría bloquear el programa en producción.Considere un desarrollador que llama a la
C()
función. No verifica la documentación, por lo que no detecta ninguna excepción. La llamada no es segura y podría bloquear el programa en producción.
Pero si buscamos errores de esta manera:
void old_C(myenum &return_code);
El compilador advertirá a un desarrollador que use esa función si no proporciona ese argumento, y él diría "Ajá, esto devuelve un código de error que debo verificar".
¿Cómo puedo usar las excepciones de forma segura para que haya algún tipo de contrato?
fuente
Either
/Result
mónada para devolver el error de una maneraRespuestas:
Esta es una crítica legítima de las excepciones. A menudo son menos visibles que el simple manejo de errores, como devolver un código. Y no hay una manera fácil de hacer cumplir un "contrato". Parte del punto es permitirle permitir que las excepciones se detecten en un nivel superior (si tiene que detectar cada excepción en cada nivel, ¿qué tan diferente es devolver un código de error, de todos modos?). Y esto significa que su código podría ser llamado por algún otro código que no lo maneje adecuadamente.
Las excepciones tienen inconvenientes; Tiene que presentar un caso basado en el costo-beneficio.
Estos dos artículos me parecieron útiles: la necesidad de excepciones y todo lo que está mal con las excepciones. Además, esta publicación de blog ofrece opiniones de muchos expertos sobre excepciones, con un enfoque en C ++ . Si bien la opinión de expertos parece inclinarse a favor de las excepciones, está lejos de ser un consenso claro.
En cuanto a convencer al líder de su equipo, esta podría no ser la batalla adecuada para elegir. Especialmente no con código heredado. Como se señaló en el segundo enlace anterior:
Agregar un poco de código que usa excepciones a un proyecto que principalmente no lo hace probablemente no será una mejora. No usar excepciones en un código bien escrito está lejos de ser un problema catastrófico; puede no ser un problema en absoluto, dependiendo de la aplicación y del experto que pregunte. Tienes que elegir tus batallas.
Probablemente este no sea un argumento en el que gaste esfuerzo, al menos hasta que se inicie un nuevo proyecto. E incluso si tiene un nuevo proyecto, ¿lo usará o lo usará algún código heredado?
fuente
Literalmente, hay libros enteros escritos sobre este tema, por lo que cualquier respuesta será, en el mejor de los casos, un resumen. Estos son algunos de los puntos importantes que creo que valen la pena, según su pregunta. No es una lista exhaustiva.
Las excepciones están destinadas a NO ser atrapadas por todo el lugar.
Siempre que haya un controlador de excepción general en el bucle principal, dependiendo del tipo de aplicación (servidor web, servicio local, utilidad de línea de comando ...), generalmente tiene todos los controladores de excepción que necesita.
En mi código, solo hay unas pocas declaraciones catch, si es que hay alguna, fuera del ciclo principal. Y ese parece ser el enfoque común en C ++ moderno.
Las excepciones y los códigos de retorno no son mutuamente excluyentes.
No debe hacer de este un enfoque de todo o nada. Las excepciones deben usarse para situaciones excepcionales. Cosas como "Archivo de configuración no encontrado", "Disco lleno" o cualquier otra cosa que no se pueda manejar localmente.
Las fallas comunes, como verificar si un nombre de archivo proporcionado por el usuario es válido, no es un caso de uso para excepciones; Utilice un valor de retorno en esos casos en su lugar.
Como puede ver en los ejemplos anteriores, "archivo no encontrado" puede ser una excepción o un código de retorno, dependiendo del caso de uso: "es parte de la instalación" versus "el usuario puede hacer un error tipográfico".
Entonces no hay una regla absoluta. Una pauta aproximada es: si se puede manejar localmente, conviértalo en un valor de retorno; Si no puede manejarlo localmente, inicie una excepción.
La comprobación estática de excepciones no es útil.
Como las excepciones no se deben manejar localmente de todos modos, generalmente no es importante qué excepciones se pueden lanzar. La única información útil es si se puede lanzar alguna excepción.
Java tiene una comprobación estática, pero generalmente se considera un experimento fallido, y la mayoría de los lenguajes ya que, especialmente C #, no tienen ese tipo de comprobación estática. Esta es una buena lectura sobre las razones por las cuales C # no lo tiene.
Por esas razones, C ++ ha dejado de estar
throw(exceptionA, exceptionB)
a favor denoexcept(true)
. El valor predeterminado es que una función puede lanzar, por lo que los programadores deben esperar eso a menos que la documentación explícitamente prometa lo contrario.Escribir código seguro de excepciones no tiene nada que ver con escribir controladores de excepciones.
¡Prefiero decir que escribir código seguro de excepción se trata de cómo evitar escribir manejadores de excepción!
La mayoría de las mejores prácticas tienen la intención de reducir el número de manejadores de excepciones. Escribir código una vez e invocarlo automáticamente, por ejemplo, a través de RAII, da como resultado menos errores que copiar y pegar el mismo código en todo el lugar.
fuente
IOException
s también . La división marcada es arbitraria e inútil.Los programadores de C ++ no buscan especificaciones de excepción. Buscan garantías de excepción.
Supongamos que un código arroja una excepción. ¿Qué suposiciones puede hacer el programador que seguirán siendo válidas? Por la forma en que está escrito el código, ¿qué garantiza el código después de una excepción?
¿O es posible que una determinada pieza de código pueda garantizar que nunca se lance (es decir, nada menos que el proceso del sistema operativo se termine)?
La palabra "retroceder" aparece con frecuencia en las discusiones sobre excepciones. Poder volver a un estado válido (que está documentado explícitamente) es un ejemplo de garantía de excepción. Si no hay una garantía de excepción, un programa debería finalizar en el acto porque ni siquiera se garantiza que cualquier código que ejecute a partir de entonces funcione según lo previsto; por ejemplo, si la memoria se ha dañado, cualquier otra operación es un comportamiento técnicamente indefinido.
Varias técnicas de programación en C ++ promueven garantías de excepción. RAII (gestión de recursos basada en el alcance) proporciona un mecanismo para ejecutar código de limpieza y garantizar que los recursos se liberen tanto en casos normales como en casos excepcionales. Hacer una copia de los datos antes de realizar modificaciones en los objetos le permite a uno restaurar el estado de ese objeto si la operación falla. Y así.
Las respuestas a esta pregunta de StackOverflow dan una idea de los grandes alcances que los programadores de C ++ van a comprender todos los modos de falla posibles que podrían sucederle a su código e intentan proteger la validez del estado del programa a pesar de las fallas. El análisis línea por línea del código C ++ se convierte en un hábito.
Cuando se desarrolla en C ++ (para uso de producción), uno no puede darse el lujo de pasar por alto los detalles. Además, el blob binario (no de código abierto) es la ruina de los programadores de C ++. Si tengo que llamar a un blob binario, y el blob falla, entonces la ingeniería inversa es lo que haría un programador de C ++ a continuación.
Referencia: http://en.cppreference.com/w/cpp/language/exceptions#Exception_safety ; consulte la sección Seguridad de excepciones.
C ++ tuvo un intento fallido de implementar especificaciones de excepción. Un análisis posterior en otros idiomas dice que las especificaciones de excepción simplemente no son prácticas.
Por qué es un intento fallido: para aplicarlo estrictamente, debe ser parte del sistema de tipos. Pero no lo es. El compilador no verifica la especificación de excepción.
Por qué C ++ eligió eso y por qué las experiencias de otros lenguajes (Java) prueban que la especificación de excepción es discutible: a medida que uno modifica la implementación de una función (por ejemplo, necesita hacer una llamada a una función diferente que puede generar un nuevo tipo de excepción), una aplicación estricta de la especificación de excepción significa que también debe actualizar esa especificación. Esto se propaga: puede terminar teniendo que actualizar las especificaciones de excepción para docenas o cientos de funciones para lo que es un cambio simple. Las cosas empeoran para las clases base abstractas (el equivalente en C ++ de las interfaces). Si la especificación de excepción se aplica en las interfaces, las implementaciones de interfaces no podrán invocar funciones que arrojen diferentes tipos de excepciones.
Referencia: http://www.gotw.ca/publications/mill22.htm
Comenzando con C ++ 17, el
[[nodiscard]]
atributo se puede usar en valores de retorno de función (consulte: https://stackoverflow.com/questions/39327028/can-ac-function-be-declared-such-that-the-return-value -no puede ser ignorado ).Entonces, si hago un cambio de código y eso introduce un nuevo tipo de condición de falla (es decir, un nuevo tipo de excepción), ¿es un cambio radical? ¿Debería haber obligado a la persona que llama a actualizar el código, o al menos ser advertido sobre el cambio?
Si acepta los argumentos de que los programadores de C ++ buscan garantías de excepción en lugar de especificaciones de excepción, entonces la respuesta es que si el nuevo tipo de condición de falla no rompe ninguna de las garantías de excepción que el código promete anteriormente, no es un cambio importante.
fuente
std::exception
generarán las excepciones estándar ( ) y su propia excepción base. Pero cuando se aplica a un proyecto completo, simplemente significa que cada función tendrá esta misma especificación: ruido.catch
se basa en el tipo del objeto de excepción, cuando en muchos casos lo que importa no es tanto la causa directa de la excepción sino más bien lo que implica sobre El estado del sistema. Desafortunadamente, el tipo de excepción generalmente no dice nada acerca de si causó que el código salga de manera disruptiva de un código de una manera que dejó un objeto en un estado parcialmente actualizado (y por lo tanto inválido).Es totalmente seguro No necesita detectar excepciones en todos los lugares, solo puede lanzar un try / catch en un lugar donde realmente puede hacer algo útil al respecto. Solo es inseguro si permite que se escape de un hilo, pero generalmente no es difícil evitar que eso suceda.
fuente
C()
y, en consecuencia, no protege contra el código después de que se omita la llamada. Si se requiere ese código para garantizar que alguna invariancia siga siendo cierta, esa garantía se vuelve falsa en la primera excepción que salgaC()
. No existe un software perfectamente seguro para las excepciones, y ciertamente no donde nunca se esperaban excepciones.C()
es un gran error. El valor predeterminado en C ++ es que cualquier función puede lanzar; requiere un explícitonoexcept(true)
para decirle al compilador lo contrario. En ausencia de esta promesa, un programador debe asumir que una función puede lanzar.C()
funciones que se romperán en presencia de excepciones. Esto es realmente algo muy imprudente, está condenado a generar muchos problemas.Si está creando un sistema crítico, considere seguir los consejos de su líder de equipo y no use excepciones. Esta es la regla AV 208 en los estándares de codificación C ++ de vehículos aéreos de combate conjunto de Lockheed Martin . Por otro lado, las pautas de MISRA C ++ tienen reglas muy específicas sobre cuándo las excepciones pueden y no pueden usarse si está creando un sistema de software compatible con MISRA.
Si está creando sistemas críticos, es probable que también esté ejecutando herramientas de análisis estático. Muchas herramientas de análisis estático le avisarán si no verifica el valor de retorno de un método, lo que hace que los casos de falta de manejo de errores sean evidentes. Que yo sepa, el soporte de herramientas similares para detectar el manejo adecuado de excepciones no es tan fuerte.
En última instancia, diría que el diseño por contrato y programación defensiva, junto con el análisis estático, es más seguro para los sistemas de software críticos que las excepciones.
fuente