Pregunta

Ayer estaba teniendo un acalorado debate con un compañero de trabajo sobre cuál sería el método de informe de errores preferido. Principalmente estábamos discutiendo el uso de excepciones o códigos de error para informar errores entre capas o módulos de aplicación.

¿Qué reglas usa para decidir si arroja excepciones o devuelve códigos de error para informar errores?

¿Fue útil?

Solución 2

Normalmente prefiero excepciones, porque tienen más información contextual y pueden transmitir (cuando se usa correctamente) el error al programador de una manera más clara.

Por otro lado, los códigos de error son más livianos que las excepciones, pero son más difíciles de mantener. La comprobación de errores puede omitirse inadvertidamente. Los códigos de error son más difíciles de mantener porque debe mantener un catálogo con todos los códigos de error y luego encender el resultado para ver qué error se produjo. Los rangos de error pueden ser de ayuda aquí, porque si lo único que nos interesa es si estamos en presencia de un error o no, es más fácil verificarlo (por ejemplo, un código de error HRESULT mayor o igual a 0 es el éxito y menos de cero es un fracaso). Se pueden omitir inadvertidamente porque no hay un forzamiento programático para que el desarrollador verifique los códigos de error. Por otro lado, no puede ignorar las excepciones.

Para resumir, prefiero las excepciones sobre los códigos de error en casi todas las situaciones.

Otros consejos

En cosas de alto nivel, excepciones; en cosas de bajo nivel, códigos de error.

El comportamiento predeterminado de una excepción es desenrollar la pila y detener el programa, si escribo un script y busco una clave que no está en un diccionario, probablemente sea un error, y quiero que el programa se detenga y déjame saber todo sobre eso.

Sin embargo, si estoy escribiendo un código que debo conocer sobre el comportamiento en todas las situaciones posibles, entonces quiero códigos de error. De lo contrario, tengo que conocer cada excepción que pueda generar cada línea en mi función para saber qué hará (Lea La excepción que puso a tierra una aerolínea para tener una idea de lo complicado que es esto). Es tedioso y difícil escribir código que reaccione adecuadamente a cada situación (incluidas las infelices), pero eso es porque escribir código libre de errores es tedioso y difícil, no porque esté pasando códigos de error.

Ambos Raymond Chen y Joel ha presentado algunos argumentos elocuentes contra el uso de excepciones para todo.

Prefiero excepciones porque

  • interrumpen el flujo de la lógica
  • se benefician de la jerarquía de clases que brinda más características / funcionalidad
  • cuando se usa correctamente puede representar una amplia gama de errores (por ejemplo, una InvalidMethodCallException también es una LogicException, ya que ambas ocurren cuando hay un error en su código que debería ser detectable antes del tiempo de ejecución), y
  • se pueden usar para mejorar el error (es decir, una definición de clase FileReadException puede contener código para verificar si el archivo existe o está bloqueado, etc.)

Las personas que llaman de sus funciones pueden ignorar los códigos de error (¡y a menudo lo son!) Las excepciones al menos los obligan a lidiar con el error de alguna manera. Incluso si su versión de tratar con esto es tener un controlador de captura vacío (suspiro).

Excepciones sobre códigos de error, sin duda. Obtiene los mismos beneficios de las excepciones que con los códigos de error, pero también mucho más, sin las deficiencias de los códigos de error. El único golpe en las excepciones es que es un poco más elevado; pero hoy en día, esa sobrecarga debería considerarse insignificante para casi todas las aplicaciones.

Aquí hay algunos artículos que discuten, comparan y contrastan las dos técnicas:

Hay algunos buenos enlaces en aquellos que pueden darle más lectura.

Nunca mezclaría los dos modelos ... es demasiado difícil convertir de uno a otro a medida que te mueves de una parte de la pila que usa códigos de error, a una pieza más alta que usa excepciones.

Las excepciones son para " todo lo que detiene o inhibe el método o la subrutina de hacer lo que usted le pidió que hiciera " ... NO para devolver mensajes sobre irregularidades o circunstancias inusuales, o el estado del sistema, etc. Utilice valores de retorno o parámetros de ref (o fuera) para eso.

Las excepciones permiten que los métodos se escriban (y se utilicen) con una semántica que depende de la función del método, es decir, un método que devuelve un objeto Empleado o una Lista de empleados se puede escribir para hacer exactamente eso, y puede utilizarlo llamando .

Employee EmpOfMonth = GetEmployeeOfTheMonth();

Con los códigos de error, todos los métodos devuelven un código de error, por lo tanto, para aquellos que necesitan devolver algo más para ser utilizado por el código de llamada, debe pasar una variable de referencia para que se complete con esos datos y probar la devolución valor para el código de error, y manejarlo, en cada función o llamada de método.

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

Si codifica para que cada método haga una y solo una cosa simple, entonces debe lanzar una excepción cada vez que el método no pueda lograr el objetivo deseado del método. Las excepciones son mucho más ricas y fáciles de usar de esta manera que los códigos de error. Su código es mucho más limpio: el flujo estándar de & Quot; normal & Quot; la ruta del código se puede dedicar estrictamente al caso en que el método ES capaz de lograr lo que quería que hiciera ... Y luego el código para limpiar o manejar el & "; excepcional &"; Las circunstancias en las que sucede algo malo que impide que el método se complete con éxito pueden separarse del código normal. Además, si no puede manejar la excepción donde ocurrió y debe pasarla por la pila a una interfaz de usuario (o peor, a través del cable desde un componente de nivel medio a una interfaz de usuario), entonces con el modelo de excepción, no es necesario codificar todos los métodos de intervención en su pila para reconocer y pasar la excepción a la pila ... El modelo de excepción lo hace automáticamente ... Con códigos de error, esta pieza del rompecabezas puede volverse onerosa muy rápidamente .

En el pasado me uní al campo de código de error (hice demasiada programación en C). Pero ahora he visto la luz.

Sí, las excepciones son una carga para el sistema. Pero simplifican el código, reduciendo el número de errores (y WTF).

Así que usa la excepción pero úsalas sabiamente. Y serán tu amigo.

Como nota al margen. He aprendido a documentar qué excepción puede ser lanzada por qué método. Lamentablemente, esto no es requerido por la mayoría de los idiomas. Pero aumenta la posibilidad de manejar las excepciones correctas en el nivel correcto.

Puede haber algunas situaciones en las que el uso de excepciones de una manera limpia, clara y correcta sea engorroso, pero la gran mayoría de las veces las excepciones son la opción obvia. El mayor beneficio en el manejo de excepciones sobre los códigos de error es que cambia el flujo de ejecución, lo cual es importante por dos razones.

Cuando ocurre una excepción, la aplicación ya no sigue su ruta de ejecución 'normal'. La primera razón por la que esto es tan importante es que, a menos que el autor del código se salga de su camino para ser malo, el programa se detendrá y no continuará haciendo cosas impredecibles. Si no se verifica un código de error y no se toman las medidas adecuadas en respuesta a un código de error incorrecto, el programa seguirá haciendo lo que está haciendo y quién sabe cuál será el resultado de esa acción. Hay muchas situaciones en las que hacer que el programa haga "lo que sea" podría resultar muy costoso. Considere un programa que recupere información sobre el rendimiento de varios instrumentos financieros que vende una empresa, y entregue esa información a los corredores / mayoristas. Si algo sale mal y el programa continúa, podría enviar datos de rendimiento erróneos a los corredores y mayoristas. No conozco a nadie más, pero no quiero ser el que esté sentado en una oficina de vicepresidentes explicando por qué mi código hizo que la empresa obtuviera multas regulatorias por valor de 7 cifras. Entregar un mensaje de error a los clientes generalmente es preferible a entregar datos incorrectos que podrían parecer 'reales', y la última situación es mucho más fácil de encontrar con un enfoque mucho menos agresivo como los códigos de error.

La segunda razón por la que me gustan las excepciones y su ruptura de la ejecución normal es que hace que sea mucho, mucho más fácil mantener la lógica de 'cosas normales están sucediendo' separada de la lógica de 'algo salió mal'. Para mí, esto:

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

... es preferible a esto:

// 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
}

Hay otras pequeñas cosas acerca de las excepciones que también son agradables. Tener un montón de lógica condicional para realizar un seguimiento de si alguno de los métodos que se llama en una función tenía un código de error devuelto, y devolver ese código de error más arriba es una gran cantidad de placa de caldera. De hecho, hay muchas placas de caldera que pueden salir mal. Tengo mucha más fe en el sistema de excepción de la mayoría de los idiomas que un nido de ratas de declaraciones if-else-if-else que escribió Fred 'recién salido de la universidad', y tengo muchas cosas mejores que hacer con mi tiempo que el código revisando dicho nido de ratas.

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...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top