Para algunos lenguajes (es decir, C ++), la fuga de recursos no debería ser una razón
C ++ está basado en RAII.
Si tiene un código que podría fallar, devolver o lanzar (es decir, el código más normal), entonces debe tener su puntero envuelto dentro de un puntero inteligente (asumiendo que tiene una muy buena razón para no tener su objeto creado en la pila).
Los códigos de retorno son más detallados
Son detallados y tienden a convertirse en algo como:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
Al final, tu código es una colección de instrucciones identificadas (vi este tipo de código en el código de producción).
Este código bien podría traducirse en:
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
Que separan limpiamente el código y el procesamiento de errores, lo que puede ser algo bueno .
Los códigos de retorno son más frágiles
Si no es alguna advertencia oscura de un compilador (ver el comentario de "phjr"), pueden ignorarse fácilmente.
Con los ejemplos anteriores, suponga que alguien se olvida de manejar su posible error (esto sucede ...). El error se ignora cuando se "devuelve" y posiblemente explote más tarde (es decir, un puntero NULL). El mismo problema no ocurrirá con excepción.
El error no se ignorará. Sin embargo, a veces quieres que no explote ... Así que debes elegir con cuidado.
Los códigos de devolución a veces deben traducirse
Digamos que tenemos las siguientes funciones:
- doSomething, que puede devolver un int llamado NOT_FOUND_ERROR
- doSomethingElse, que puede devolver un bool "falso" (por error)
- doSomethingElseAgain, que puede devolver un objeto Error (con las variables __LINE__, __FILE__ y la mitad de la pila.
- do TryToDoSomethingWithAllThisMess que, bueno ... Use las funciones anteriores y devuelva un código de error del tipo ...
¿Cuál es el tipo de retorno de do TryToDoSomethingWithAllThisMess si falla una de sus funciones llamadas?
Los códigos de retorno no son una solución universal
Los operadores no pueden devolver un código de error. Los constructores de C ++ tampoco pueden.
Códigos de retorno significa que no puede encadenar expresiones
El corolario del punto anterior. ¿Y si quiero escribir?
CMyType o = add(a, multiply(b, c)) ;
No puedo, porque el valor de retorno ya se usa (y, a veces, no se puede cambiar). Entonces, el valor de retorno se convierte en el primer parámetro, enviado como referencia ... O no.
Se escriben excepciones
Puede enviar diferentes clases para cada tipo de excepción. Las excepciones de recursos (es decir, sin memoria) deben ser ligeras, pero cualquier otra cosa podría ser tan pesada como sea necesario (me gusta la excepción de Java que me da toda la pila).
Luego, cada captura puede especializarse.
Nunca uses catch (...) sin volver a lanzar
Por lo general, no debe ocultar un error. Si no vuelve a lanzar, como mínimo, registre el error en un archivo, abra un cuadro de mensaje, lo que sea ...
La excepción son ... NUKE
El problema con la excepción es que usarlos en exceso producirá un código lleno de intentos / capturas. Pero el problema está en otra parte: ¿Quién intenta / captura su código usando el contenedor STL? Aún así, esos contenedores pueden enviar una excepción.
Por supuesto, en C ++, nunca deje que una excepción salga de un destructor.
La excepción es ... sincrónica
Asegúrese de atraparlos antes de que saquen su hilo de rodillas o se propaguen dentro de su bucle de mensajes de Windows.
¿La solución podría ser mezclarlos?
Entonces supongo que la solución es lanzar cuando algo no debería suceder. Y cuando algo pueda suceder, utilice un código de retorno o un parámetro para permitir que el usuario reaccione.
Entonces, la única pregunta es "¿qué es algo que no debería suceder?"
Depende del contrato de tu función. Si la función acepta un puntero, pero especifica que el puntero no debe ser NULL, entonces está bien lanzar una excepción cuando el usuario envía un puntero NULL (la pregunta es, en C ++, cuándo el autor de la función no usó referencias en su lugar de punteros, pero ...)
Otra solución sería mostrar el error.
A veces, tu problema es que no quieres errores. Usar excepciones o códigos de retorno de error es genial, pero ... Quieres saberlo.
En mi trabajo, utilizamos una especie de "Afirmar". Dependiendo de los valores de un archivo de configuración, sin importar las opciones de compilación de depuración / liberación:
- registrar el error
- abre un cuadro de mensaje con un "Oye, tienes un problema"
- abre un cuadro de mensaje con un "Oye, tienes un problema, quieres depurar"
Tanto en el desarrollo como en las pruebas, esto permite al usuario identificar el problema exactamente cuando se detecta y no después (cuando algún código se preocupa por el valor de retorno o dentro de una captura).
Es fácil de agregar al código heredado. Por ejemplo:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
conduce una especie de código similar a:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(Tengo macros similares que están activas solo en la depuración).
Tenga en cuenta que en producción, el archivo de configuración no existe, por lo que el cliente nunca ve el resultado de esta macro ... Pero es fácil activarlo cuando sea necesario.
Conclusión
Cuando codifica utilizando códigos de retorno, se está preparando para fallar y espera que su fortaleza de pruebas sea lo suficientemente segura.
Cuando codifica utilizando una excepción, sabe que su código puede fallar y, por lo general, coloca la captura de contrafuego en la posición estratégica elegida en su código. Pero por lo general, su código se trata más de "lo que debe hacer" que de "lo que temo que suceda".
Pero cuando codifica, debe usar la mejor herramienta a su disposición y, a veces, es "Nunca esconda un error y muéstrelo lo antes posible". La macro que hablé anteriormente sigue esta filosofía.