Lors de l'envoi d'une demande à un autre module et d'attendre un résultat, il me semble qu'il y a deux façons de traiter avec les "chemins non heureux".
Je dirais que le premier semble mieux en général. Il garde le code propre et lisible. Si vous vous attendez à ce que le résultat soit correct, lancez simplement une exception pour gérer une divergence de chemin heureuse.
Mais que quand vous n'avez aucune idée de ce que le résultat sera?
Par exemple, appeler un module valide un billet de loterie. Le chemin heureux serait que vous gagnez, mais ce n'est probablement pas. (Comme indiqué par @ben Cottrell dans les commentaires, "Ne pas gagnant" est également le chemin heureux, peut-être pas pour l'utilisateur final, bien que)
Serait-il préférable de considérer que le chemin heureux consiste à obtenir un résultat de la LotteryTicketValidator
et de gérer simplement des exceptions pour quand le billet n'a pas pu être traité?
Un autre pourrait être une authentification de l'utilisateur lors de la connexion. Pouvons-nous supposer que l'utilisateur a saisi les informations d'identification correctes et lancez une exception lorsque les informations d'identification sont invalides ou devrions-nous nous attendre à obtenir une sorte de LoginResult
objet?
en bref : Cela dépend.
Prendre le billet de loterie exemple:bool isWinningTicket(const LotteryTicket& ticketToCheck)
On pourrait affirmer qu'un ticket invalide est (par défaut) et non un ticket gagnant, alors regardons le "Serveurs de validation inaccessible" Scénario. Dans ce cas, la fonction ne peut pas fournir ce qu'il a promis - A vrai/faux Répondre Si le ticket est un ticket gagnant - il devrait donc organiser une exception pour vous en informer.
Pourquoi? Considérons les alternatives, commebool checkIfTicketIsWinningTicket(const LotteryTicket& ticketToCheck, bool& isWinning)
Stocke le vrai/faux (si le ticket gagnant) aboutit au paramètre extérieur bool& isWinning
Et indique le succès/échec de la fonction elle-même en revenant vrai/faux, pas d'exception, pas de tracas ... Sauf (pardon le jeu de mots) - Vous pourriez, par accident, oubliez de vérifier la valeur de retour, que la fonction a échoué et donnez de faux positifs/négatifs (chacun dont fait malheureux des personnes différentes).
Si vous oubliez d'attraper l'exception dans le premier exemple ... Vous obtiendrez une erreur d'exécution et cela devrait vous faire conscience que quelque chose ne va pas avec votre code (à moins que vous ne soyez hors de votre chemin pour écrire catch(...) { /*do nothing*/ }
Mais alors personne ne peut vous aider de toute façon). Dans le second cas, votre code échouera en silence et vous remarquerez seulement lorsque des personnes de la loterie en colère ou des titulaires de billet en colère frappent votre porte ...
En ce qui concerne l'exemple de connexion: Si votre fonction void loginUser(User userToLogIn, Password passwordOfUser)
- promet de se connecter valide Les utilisateurs (c.-à-d. Les personnes devraient appeler Bool isValidUser(User u, Password p)
à l'avance et après loginUser()
s'appelle que l'utilisateur sera connecté) puis une exception pour les combinaisons d'utilisateur/mot de passe non valides est correcte. Si la fonction fonctionne sur un "Voir si la combinaison est valide et connectez-vous s'il s'agit de" base ", vous ne devez pas jeter une exception pour cela (et donner la fonction un nom différent).
Vous devriez essayer de ne jamais revenir exceptions à moins qu'il n'y a pas d'autre option. Le problème avec traitement standard des erreurs est que vous venez de jeter quelque chose vers le haut, l'utilisateur de la méthode n'est pas informé que l'échec est une option en lisant les paramètres de la fonction, nous ne savons pas ce que la valeur de retour est si l'on ne se coincent pas l'exception .
La meilleure chose que vous pouvez faire quand il y a plusieurs de la valeur de retour est l'envelopper dans un objet de résultat. Lorsque vous rédigez votre auditeur vous devez gérer l'objet de retour pour se rendre à la valeur de rendement réel. Cela signifie que chaque fois que la méthode est appelée les utilisateurs sont obligés de réfléchir à la façon dont ils veulent gérer différents états de résultats et des valeurs. les erreurs ne sont donc pas jetés sur une pile sans fin jusqu'à ce qu'il atteigne le consommateur avec un popup ennuyant ou quelque part d'échec invisible en arrière-plan.
Ce genre de manipulation des résultats est l'un des concepts de base dans le Rust langue de programmation, qui n'utilise pas tous les types de référence null ou d'autres comportements indésirables. Je l'ai utilisé une approche similaire en c # comme résultat à Rust, où une méthode retourne un objet de résultat. Si le résultat est OK, vous pouvez atteindre la valeur, sinon il est obligatoire de gérer l'erreur. la Belle chose sur le codage de cette façon est que vous résoudre les problèmes dans votre code avant même tester ou exécuter.
Exemple de ce que mon apparence de code comme:
enum GetError { NotFound, SomeOtherError, Etc }
public Result<ValueObject, GetError> GetValue() {
if (SomeError)
return new Err<ValueObject, GetError>(GetError.SomeError);
else return new Ok<ValueObject, GetError>();
}
value = GetValue()
.OnOk( // OnOk is used to get the ok value and use it
(ok) => {
// do something with the ok value here...
}
)
.OnErr( // OnErr is used to get the error value and act on it
(err) => {
// do something with the err value here...
}
)
.Finalize( // finalize is used to make a value out of the result.
(err) => {
// if the result is ok it returns the ok value
// if the result is an error it needs to be handle here
return new ValueObject();
});
Comme vous pouvez le voir, il n'y a pas grand-chose qui peut aller mal. L'inconvénient est que le code peut devenir fastidieux à des endroits donc en utilisant de tels résultats ne sont pas toujours la meilleure option. La seule chose que je suis en train de dire est, vous en tant que programmeur décider comment des situations d'erreur vos poignées et se comporte de code, et non l'inverse.