Pour certains langages (c'est-à-dire C ++), la fuite de ressources ne devrait pas être une raison
C ++ est basé sur RAII.
Si vous avez du code qui pourrait échouer, retourner ou lancer (c'est-à-dire la plupart du code normal), alors vous devriez avoir votre pointeur enveloppé dans un pointeur intelligent (en supposant que vous avez une très bonne raison de ne pas créer votre objet sur la pile).
Les codes de retour sont plus détaillés
Ils sont verbeux et ont tendance à se développer en quelque chose comme:
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
}
Au final, votre code est une collection d'instructions identifiées (j'ai vu ce genre de code dans le code de production).
Ce code pourrait bien être traduit 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
}
Quel code distinct et traitement des erreurs, ce qui peut être une bonne chose.
Les codes de retour sont plus fragiles
Si ce n'est pas un avertissement obscur d'un compilateur (voir le commentaire de "phjr"), ils peuvent facilement être ignorés.
Avec les exemples ci-dessus, supposons que quelqu'un oublie de gérer son erreur possible (cela se produit ...). L'erreur est ignorée lorsqu'elle est "retournée", et risque d'exploser plus tard (c'est-à-dire un pointeur NULL). Le même problème ne se produira pas avec exception.
L'erreur ne sera pas ignorée. Parfois, vous voulez qu'elle n'explose pas, cependant ... Vous devez donc choisir avec soin.
Les codes de retour doivent parfois être traduits
Disons que nous avons les fonctions suivantes:
- doSomething, qui peut renvoyer un int appelé NOT_FOUND_ERROR
- doSomethingElse, qui peut renvoyer un booléen "false" (en cas d'échec)
- doSomethingElseAgain, qui peut renvoyer un objet Error (avec à la fois les variables __LINE__, __FILE__ et la moitié des variables de pile.
- doTryToDoSomethingWithAllThisMess qui, eh bien ... Utilisez les fonctions ci-dessus, et retournez un code d'erreur de type ...
Quel est le type de retour de doTryToDoSomethingWithAllThisMess si l'une de ses fonctions appelées échoue?
Les codes de retour ne sont pas une solution universelle
Les opérateurs ne peuvent pas renvoyer un code d'erreur. Les constructeurs C ++ ne le peuvent pas non plus.
Les codes de retour signifient que vous ne pouvez pas enchaîner les expressions
Le corollaire du point ci-dessus. Et si je veux écrire:
CMyType o = add(a, multiply(b, c)) ;
Je ne peux pas, car la valeur de retour est déjà utilisée (et parfois, elle ne peut pas être modifiée). La valeur de retour devient donc le premier paramètre, envoyé comme référence ... Ou pas.
Les exceptions sont saisies
Vous pouvez envoyer différentes classes pour chaque type d'exception. Les exceptions de ressources (c'est-à-dire en mémoire) devraient être légères, mais tout le reste pourrait être aussi lourd que nécessaire (j'aime l'exception Java qui me donne toute la pile).
Chaque prise peut alors être spécialisée.
Ne jamais utiliser la capture (...) sans relancer
En règle générale, vous ne devez pas masquer une erreur. Si vous ne relancez pas, à tout le moins, enregistrez l'erreur dans un fichier, ouvrez une boîte de message, peu importe ...
Les exceptions sont ... NUKE
Le problème avec l'exception est que leur utilisation excessive produira du code plein d'essais / captures. Mais le problème est ailleurs: qui essaie / capture son code en utilisant le conteneur STL? Pourtant, ces conteneurs peuvent envoyer une exception.
Bien sûr, en C ++, ne laissez jamais une exception quitter un destructeur.
Les exceptions sont ... synchrones
Assurez-vous de les attraper avant qu'ils ne mettent votre thread à genoux ou ne se propagent à l'intérieur de votre boucle de messages Windows.
La solution pourrait être de les mélanger?
Donc je suppose que la solution est de jeter quand quelque chose ne devrait pas arriver. Et quand quelque chose peut arriver, utilisez un code de retour ou un paramètre pour permettre à l'utilisateur d'y réagir.
Donc, la seule question est "qu'est-ce que quelque chose qui ne devrait pas arriver?"
Cela dépend du contrat de votre fonction. Si la fonction accepte un pointeur, mais spécifie que le pointeur doit être non-NULL, alors il est correct de lever une exception lorsque l'utilisateur envoie un pointeur NULL (la question étant, en C ++, quand l'auteur de la fonction n'a-t-il pas utilisé des références à la place de pointeurs, mais ...)
Une autre solution serait d'afficher l'erreur
Parfois, votre problème est que vous ne voulez pas d'erreurs. Utiliser des exceptions ou des codes de retour d'erreur est cool, mais ... vous voulez en savoir plus.
Dans mon travail, nous utilisons une sorte de "Assert". Cela dépendra des valeurs d'un fichier de configuration, quelles que soient les options de compilation de débogage / publication:
- consigner l'erreur
- ouvrir une boîte de message avec un "Hey, vous avez un problème"
- ouvrir une boîte de message avec un "Hey, vous avez un problème, voulez-vous déboguer"
Dans le développement et les tests, cela permet à l'utilisateur de localiser le problème exactement quand il est détecté, et non après (lorsque certains codes se soucient de la valeur de retour, ou à l'intérieur d'une capture).
Il est facile d'ajouter au code hérité. Par exemple:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
conduit une sorte de code similaire à:
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.
}
(J'ai des macros similaires qui ne sont actives que sur le débogage).
Notez qu'en production, le fichier de configuration n'existe pas, donc le client ne voit jamais le résultat de cette macro ... Mais il est facile de l'activer en cas de besoin.
Conclusion
Lorsque vous codez à l'aide de codes de retour, vous vous préparez à l'échec et espérez que votre forteresse de tests est suffisamment sécurisée.
Lorsque vous codez à l'aide d'une exception, vous savez que votre code peut échouer et placer généralement la capture de contre-feu à la position stratégique choisie dans votre code. Mais généralement, votre code concerne plus "ce qu'il doit faire" que "ce que je crains qu'il se passe".
Mais quand vous codez, vous devez utiliser le meilleur outil à votre disposition, et parfois, c'est "Ne cachez jamais une erreur, et montrez-la le plus tôt possible". La macro dont j'ai parlé ci-dessus suit cette philosophie.