Question

Hier, j’avais un débat animé avec un collègue sur la méthode de rapport d’erreur privilégiée. Nous discutions principalement de l’utilisation des exceptions ou des codes d’erreur pour signaler les erreurs entre les couches d’application ou les modules.

Quelles règles utilisez-vous pour décider d'exécuter ou non des exceptions ou de renvoyer des codes d'erreur pour le signalement des erreurs?

Était-ce utile?

La solution 2

Je préfère normalement les exceptions, car elles ont plus d'informations contextuelles et peuvent transmettre (lorsqu'elles sont correctement utilisées) l'erreur au programmeur de manière plus claire.

D'autre part, les codes d'erreur sont plus légers que les exceptions mais sont plus difficiles à gérer. Une vérification d'erreur peut être omise par inadvertance. Les codes d'erreur sont plus difficiles à gérer car vous devez conserver un catalogue avec tous les codes d'erreur, puis activer le résultat pour voir quelle erreur a été générée. Les plages d'erreur peuvent être utiles ici, car si la seule chose qui nous intéresse, si nous sommes en présence ou non d'une erreur, il est plus simple de vérifier (par exemple, un code d'erreur HRESULT supérieur ou égal à 0 est succès et moins de zéro est un échec). Ils peuvent être omis par inadvertance, car le développeur ne doit pas forcément vérifier les codes d'erreur. Par contre, vous ne pouvez pas ignorer les exceptions.

Pour résumer, je préfère les exceptions aux codes d'erreur dans presque toutes les situations.

Autres conseils

En matière de haut niveau, exceptions; dans les éléments de bas niveau, les codes d'erreur.

Le comportement par défaut d'une exception est de dérouler la pile et d'arrêter le programme. Si j'écris un script et que je recherche une clé qui n'est pas dans un dictionnaire, c'est probablement une erreur et je souhaite que le programme s'arrête. et laissez-moi savoir tout à ce sujet.

Si, toutefois, j'écris un morceau de code dont je dois connaître le comportement dans toutes les situations possibles, je souhaite des codes d'erreur. Sinon, je dois connaître toutes les exceptions pouvant être émises par chaque ligne de ma fonction pour savoir ce qu’elle fera (Lire L’exception qui a mis à la terre une compagnie aérienne pour avoir une idée de la difficulté de cette opération). Il est fastidieux et difficile d’écrire du code qui réagisse correctement à chaque situation (y compris aux malheureux), mais c’est parce qu’écrire du code sans erreur est fastidieux et difficile, et non pas parce que vous transmettez des codes d’erreur.

Les deux Raymond Chen et Joel ont présenté des arguments éloquents contre l'utilisation d'exceptions pour tout.

Je préfère les exceptions car

  • ils interrompent le flux de la logique
  • ils bénéficient de la hiérarchie des classes qui donne plus de fonctionnalités / fonctionnalités
  • lorsqu'il est utilisé correctement, il peut représenter un large éventail d'erreurs (par exemple, une exception InvalidMethodCallException est également une exception LogicException, car les deux se produisent lorsqu'il y a un bogue dans votre code qui devrait être détectable avant l'exécution) et
  • elles peuvent être utilisées pour améliorer l'erreur (c'est-à-dire qu'une définition de classe FileReadException peut ensuite contenir du code pour vérifier si le fichier existe, s'il est verrouillé, etc.)

Les codes d'erreur peuvent être ignorés (et le sont souvent!) par les appelants de vos fonctions. Les exceptions les forcent au moins à traiter l'erreur d'une manière ou d'une autre. Même si leur version consiste à avoir un gestionnaire de captures vide (soupir).

Exceptions sur les codes d'erreur, sans aucun doute à ce sujet. Les exceptions vous procurent les mêmes avantages que les codes d’erreur, mais aussi beaucoup plus, sans les inconvénients des codes d’erreur. Le seul inconvénient, c’est qu’il est légèrement plus onéreux; mais de nos jours, ces frais généraux doivent être considérés comme négligeables pour presque toutes les applications.

Voici quelques articles qui discutent, comparent et opposent les deux techniques:

Il existe de bons liens dans ceux qui peuvent vous donner des lectures supplémentaires.

Je ne mélangerais jamais les deux modèles ... Il est trop difficile de passer de l'un à l'autre lorsque vous passez d'une partie de la pile utilisant des codes d'erreur à une autre qui utilise des exceptions.

Les

exceptions concernent & "tout ce qui empêche ou empêche la méthode ou le sous-programme de faire ce que vous lui avez demandé de faire &"; ... NE PAS renvoyer de messages concernant des irrégularités, des circonstances inhabituelles, l’état du système, etc. Utilisez des valeurs de retour ou des paramètres ref (ou out) à cet effet.

Les exceptions permettent aux méthodes d'être écrites (et utilisées) avec une sémantique dépendant de la fonction de la méthode, c'est-à-dire qu'une méthode renvoyant un objet Employé ou une liste d'employés peut être tapée de cette manière, et vous pouvez l'utiliser en appelant .

Employee EmpOfMonth = GetEmployeeOfTheMonth();

Avec les codes d'erreur, toutes les méthodes renvoient un code d'erreur. Par conséquent, pour celles qui doivent renvoyer quelque chose d'autre à utiliser par le code appelant, vous devez transmettre une variable de référence à renseigner avec ces données et tester le retour. valeur pour le code d'erreur et la gérer à chaque appel de fonction ou de méthode.

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

Si vous codez pour que chaque méthode ne fasse qu'une et une seule chose simple, vous devriez alors lever une exception chaque fois que la méthode ne peut pas atteindre l'objectif souhaité de la méthode. Les exceptions sont beaucoup plus riches et faciles à utiliser de cette façon que les codes d'erreur. Votre code est beaucoup plus propre - Le flux standard du & "Normal &"; chemin de code peut être consacré strictement au cas où la méthode est capable d'accomplir ce que vous vouliez faire ... Et ensuite, le code à nettoyer, ou gérer les & "exceptionnels &"; Les circonstances dans lesquelles quelque chose de mauvais empêche la méthode de fonctionner correctement peuvent être supprimées du code normal. En outre, si vous ne pouvez pas gérer l’exception où elle s’est produite et que vous devez transmettre la pile à une interface utilisateur (ou pire, d’un composant de niveau intermédiaire à une interface utilisateur), vous pouvez alors utiliser le modèle d’exception. vous n'avez pas besoin de coder chaque méthode intermédiaire de votre pile pour reconnaître et faire passer l'exception sur la pile ... Le modèle d'exception le fait automatiquement pour vous .... Avec les codes d'erreur, cette pièce du puzzle peut devenir fastidieuse très rapidement .

Par le passé, j'ai rejoint le camp errorcode (trop de programmation en C). Mais maintenant j'ai vu la lumière.

Oui, les exceptions constituent un fardeau pour le système. Mais ils simplifient le code en réduisant le nombre d’erreurs (et de WTF).

Utilisez donc exception mais utilisez-les judicieusement. Et ils seront ton ami.

Comme note de côté. J'ai appris à documenter quelle exception peut être levée par quelle méthode. Malheureusement, la plupart des langues ne l'exigent pas. Mais cela augmente les chances de gérer les bonnes exceptions au bon niveau.

Il peut y avoir quelques situations où utiliser des exceptions de manière propre, claire et correcte est fastidieux, mais la grande majorité des exceptions de temps constituent un choix évident. Le principal avantage de la gestion des exceptions en ce qui concerne les codes d’erreur est qu’il modifie le flux d’exécution, ce qui est important pour deux raisons.

Lorsqu'une exception se produit, l'application ne suit plus son chemin d'exécution "normal". La première raison pour laquelle cela est si important est que, à moins que l'auteur du code ne soit vraiment mauvais, le programme s'arrête et ne continue pas à faire des choses imprévisibles. Si un code d'erreur n'est pas vérifié et que les actions appropriées ne sont pas entreprises en réponse à un code d'erreur erroné, le programme continuera à faire ce qu'il est en train de faire et qui sait quel sera le résultat de cette action. Dans de nombreux cas, le fait de faire ce programme peut coûter très cher. Envisagez un programme qui récupère des informations sur les performances de divers instruments financiers vendus par une société et fournit ces informations aux courtiers / grossistes. Si quelque chose ne va pas et que le programme continue, il pourrait envoyer des données de performance erronées aux courtiers et aux grossistes. Je ne connais personne d'autre, mais je ne veux pas être celui qui siège dans un bureau de vice-président pour expliquer pourquoi mon code a poussé la société à se voir imposer une amende réglementaire correspondant à 7 chiffres. Il est généralement préférable d'envoyer un message d'erreur aux clients plutôt que de fournir des données erronées qui pourraient paraître "réelles". Cette dernière situation est beaucoup plus facile à gérer avec une approche beaucoup moins agressive, telle que les codes d'erreur.

La deuxième raison pour laquelle j'aime les exceptions et leur rupture de l'exécution normale est qu'il est beaucoup plus facile de garder la logique «les choses normales se passent» séparément de la logique «quelque chose a mal tourné». Pour moi, ceci:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

... est préférable à ceci:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

Il y a d'autres petites choses au sujet des exceptions qui sont bien aussi. Avoir un code d'erreur renvoyé par toute une série de logiques conditionnelles permettant de savoir si l'une des méthodes appelées dans une fonction a été renvoyée et renvoyé ce code d'erreur plus haut, cela prend beaucoup de temps. En fait, il y a beaucoup de problèmes de chaudière. J'ai beaucoup plus confiance dans le système d'exception de la plupart des langues que dans un nid de rats énonçant des déclarations if-else-if-else selon lesquelles 'Fred a récemment écrit', et j'ai beaucoup mieux à faire avec mon temps que le code passe en revue ledit nid de rat.

You should use both. The thing is to decide when to use each one.

There are a few scenarios where exceptions are the obvious choice:

  1. In some situations you can't do anything with the error code, and you just need to handle it in an upper level in the call stack, usually just log the error, display something to the user or close the program. In these cases, error codes would require you to bubble up the error codes manually level by level which is obviously much easier to do with exceptions. The point is that this is for unexpected and unhandleable situations.

  2. Yet about situation 1 (where something unexpected and unhandleable happens you just wan't to log it), exceptions can be helpful because you might add contextual information. For example if I get a SqlException in my lower-level data helpers, I will want to catch that error in the low-level (where I know the SQL command that caused the error) so I can capture that information and rethrow with additional information. Please note the magic word here: rethrow, and not swallow. The first rule of exception handling: do not swallow exceptions. Also, note that my inner catch doesn't need to log anything because the outer catch will have the whole stack trace and may log it.

  3. In some situations you have a sequence of commands, and if any of them fail you should cleanup/dispose resources(*), whether or not this is an unrecoverable situation (which should be thrown) or a recoverable situation (in which case you can handle locally or in the caller code but you don't need exceptions). Obviously it's much easier to put all those commands in a single try, instead of testing error codes after each method, and cleanup/dispose in the finally block. Please note that if you want the error to bubble up (which is probably what you want), you don't even need to catch it - you just use the finally for cleanup/dispose - you should only use catch/retrow if you want to add contextual information (see bullet 2).

    One example would be a sequence of SQL statements inside a transaction block. Again, this also a "unhandleable" situation, even if you decide to catch it early (treat it locally instead of bubbling up to the top) it's still a fatal situation from where the best outcome is to abort everything or at least abort a large part of the process.
    (*) This is like the on error goto that we used in old Visual Basic

  4. In constructors you can only throw exceptions.

Having said that, in all other situations where you're returning some information on which the caller CAN/SHOULD take some action, using return codes is probably a better alternative. This includes all expected "errors", because probably they should be handled by the immediate caller, and will hardly need to be bubbled up too many levels up in the stack.

Of course it's always possible to treat expected errors as exceptions, and catch then immediately one level above, and it's also possible to encompass every line of code in a try catch and take actions for each possible error. IMO, this is bad design, not only because it's much more verbose, but specially because the possible exceptions that might be thrown are not obvious without reading the source code - and exceptions could be thrown from any deep method, creating invisible gotos. They break code structure by creating multiple invisible exit points that make code hard to read and inspect. In other words, you should never use exceptions as flow-control, because that would be hard for others to understand and maintain. It can get even difficult to understand all possible code flows for testing.
Again: for correct cleanup/dispose you can use try-finally without catching anything.

The most popular criticism about return codes is that "someone could ignore the error codes, but in the same sense someone can also swallow exceptions. Bad exception handling is easy in both methods. But writing good error-code-based program is still much easier than writing an exception-based program. And if one by any reason decides to ignore all errors (the old on error resume next), you can easily do that with return codes and you can't do that without a lot of try-catchs boilerplate.

The second most popular criticism about return codes is that "it's difficult to bubble up" - but that's because people don't understand that exceptions are for non-recoverable situations, while error-codes are not.

Deciding between exceptions and error codes is a gray area. It's even possible that you need to get an error code from some reusable business method, and then you decide to wrap that into an exception (possibly adding information) and let it bubble up. But it's a design mistake to assume that ALL errors should be thrown as exceptions.

To sum it up:

  • I like to use exceptions when I have an unexpected situation, in which there's not much to do, and usually we want to abort a large block of code or even the whole operation or program. This is like the old "on error goto".

  • I like to use return codes when I have expected situations in which the caller code can/should take some action. This includes most business methods, APIs, validations, and so on.

This difference between exceptions and error codes is one of the design principles of the GO language, which uses "panic" for fatal unexpected situations, while regular expected situations are returned as errors.

Yet about GO, it also allows multiple return values , which is something that helps a lot on using return codes, since you can simultaneously return an error and something else. On C#/Java we can achieve that with out parameters, Tuples, or (my favorite) Generics, which combined with enums can provide clear error codes to the caller:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

If I add a new possible return in my method, I can even check all callers if they are covering that new value in a switch statement for example. You really can't do that with exceptions. When you use return codes, you'll usually know in advance all possible errors, and test for them. With exceptions you usually don't know what might happen. Wrapping enums inside exceptions (instead of Generics) is an alternative (as long as it's clear the type of exceptions that each method will throw), but IMO it's still bad design.

I may be sitting on the fence here, but...

  1. It depends on the language.
  2. Whichever model you choose, be consistent about how you use it.

In Python, use of exceptions is standard practice, and I'm quite happy to define my own exceptions. In C you don't have exceptions at all.

In C++ (in the STL at least), exceptions are typically only thrown for truly exceptional errors (I virtually never see them myself). I see no reason to do anything different in my own code. Yes it's easy to ignore return values, but C++ doesn't force you to catch exceptions either. I think you just have to get into the habit of doing it.

The code base I work on is mostly C++ and we use error codes almost everywhere, but there's one module that raises exceptions for any error, including very unexceptional ones, and all the code that uses that module is pretty horrible. But that might just be because we've mixed exceptions and error codes. The code that consistently uses error codes is much easier to work with. If our code consistently used exceptions, maybe it wouldn't be as bad. Mixing the two doesn't seem to work so well.

Since I work with C++, and have RAII to make them safe to use, I use exceptions almost exclusively. It pulls error handling out of the normal program flow and makes the intent more clear.

I do leave exceptions for exceptional circumstances though. If I'm expecting that a certain error is going to happen a lot I'll check that the operation will succeed before performing it, or call a version of the function that uses error codes instead (Like TryParse())

Method signatures should communicate to you what the method does. Something like long errorCode = getErrorCode(); might be fine, but long errorCode = fetchRecord(); is confusing.

My reasoning would be if you are writing a low-level driver that really needs performance, then use error codes. But if you're using that code in a higher-level application and it can handle a bit of overhead, then wrap that code with an interface which checks those error codes and raises exceptions.

In all other cases, exceptions are probably the way to go.

My approach is that we can use both, i.e. Exceptions and Errors codes at the same time.

I'm used to define several types of Exceptions (ex: DataValidationException or ProcessInterruptExcepion) and inside each exception define a more detailed description of each problem.

A Simple Example in Java:

public class DataValidationException extends Exception {


    private DataValidation error;

    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{

    TOO_SMALL(1,"The input is too small"),

    TOO_LARGE(2,"The input is too large");


    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;

    private int code;

}

In this way i use Exceptions to define category errors, and error codes to define more detailed info about the problem.

Exceptions are for exceptional circumstances - ie, when they are not part of the normal flow of the code.

It's quite legitimate to mix Exceptions and error codes, where error codes represent the status of something, rather than an error in the running of the code per se (e.g. checking the return code from a child process).

But when an exceptional circumstance occurs I believe Exceptions are the most expressive model.

There are cases where you might prefer, or have, to use error codes in place of Exceptions, and these have been adequately covered already (other than other obvious constrains such as compiler support).

But going in the other direction, using Exceptions allows you to build even higher level abstractions to your error handling, that can make your code even more expressive and natural. I would highly recommend reading this excellent, yet underrated, article by C++ expert Andrei Alexandrescu on the subject of what he calls, "Enforcements": http://www.ddj.com/cpp/184403864. Although it's a C++ article the principles are generally applicable, and I have translated the enforcements concept to C# quite successfully.

First, I agree with Tom's answer that for high-level stuff use exceptions, and for low-level stuff use error codes, as long as it is not Service Oriented Architecture (SOA).

In SOA, where methods may be called across different machines, exceptions may not be passed over the wire, instead, we use success/failure responses with a structure like below (C#):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

And use like this:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

When these are used consistently in your service responses it creates a very nice pattern of handling success/failures in the application. This allows easier error handling in async calls within services as well as across services.

I would prefer Exceptions for all error cases, except when a failure is an expectable bug-free result of a function that returns a primitive datatype. E.g. finding the index of a substring within a larger string would usually return -1 if not found, instead of raising a NotFoundException.

Returning invalid pointers that might be dereferenced (e.g. causing NullPointerException in Java) is not acceptable.

Using multiple different numerical error codes (-1, -2) as return values for the same function is usually bad style, as clients might do a "== -1" check instead of "< 0".

One thing to keep in mind here is the evolution of APIs over time. A good API allows to change and extend failure behavior in several ways without breaking clients. E.g. if a client error handle checked for 4 error cases, and you add a fifth error value to your function, the client handler may not test this and break. If you raise Exceptions, this will usually make it easier for clients to migrate to a newer version of a library.

Another thing to consider is when working in a team, where to draw a clear line for alldevelopers to make such a decision. E.g. "Exceptions for high-level stuff, error codes for low-level stuff" is very subjective.

In any case, where more than one trivial type of error is possible, the source code should never use the numeric literal to return an error code or to handle it (return -7, if x == -7 ...), but always a named constant (return NO_SUCH_FOO, if x == NO_SUCH_FOO) .

If you work under big project, you can't use only exceptions or only error codes. In different cases you should use different approaches.

For example, you decide to use exceptions only. But once you decide to use async event processing. It is bad idea to use exceptions for error handling in this situations. But use error codes everywhere in application is tedious.

So my opinion that it is normal to use both exceptions and error codes simultaneous.

For most applications, exceptions are better. The exception is when the software has to communicate with other devices. The domain I work in is industrial controls. Here errors codes are preferred and expected. So my answer is that it does depend on the situation.

I think it also depends on whether you really need information like stack trace from the result. If yes, you definitely go for Exception which provide object full with lots of information about problem. However, if you are just interested in result and don't care why that result then go for error code.

e.g. When you are processing file and face IOException, client might interested in knowing from where this was triggered, in opening file or parsing file etc. So better you return IOException or its specific subclass. However, scenario like you have login method and you want to know it was successful or not, there either you just return boolean or to show correct message, return error code. Here Client is not interested in knowing which part of logic caused that error code. He just know if its Credential invalid or account lock etc.

Another usecase I can think of is when data travels on network. Your remote method can return just error code instead of Exception to minimize data transfer.

My general rule is:

  • Only one error could appear in a function: use error code (as parameter of the function)
  • More than one specific error could appear: throw exception

Error codes also don't work when your method returns anything other than a numeric value...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top