In high-level stuff, exceptions; in low-level stuff, error codes.
The default behaviour of an exception is to unwind the stack and stop the program, if I'm writing a script an and I go for a key that's not in a dictionary it's probably an error, and I want the program to halt and let me know all about that.
If, however, I'm writing a piece of code which I must know the behaviour of in every possible situation, then I want error codes. Otherwise I have to know every exception that can be thrown by every line in my function to know what it will do (Read The Exception That Grounded an Airline to get an idea of how tricky this is). It's tedious and hard to write code that reacts appropriately to every situation (including the unhappy ones), but that's because writing error-free code is tedious and hard, not because you're passing error codes.
Both Raymond ChenandJoel have made some eloquent arguments against using exceptions for everything.
I normally prefer exceptions, because they have more contextual information and can convey (when properly used) the error to the programmer in a clearer fashion.
On the other hand, error codes are more lightweight than exceptions but are harder to maintain. Error checking can inadvertently be omitted. Error codes are harder to maintain because you have to keep a catalog with all error codes and then switch on the result to see what error was thrown. Error ranges can be of help here, because if the only thing we are interested in is if we are in the presence of an error or not, it is simpler to check (e.g., an HRESULT error code greater or equal to 0 is success and less than zero is failure). They can inadvertently be omitted because there is no programmatic forcing that the developer will check for error codes. On the other hand, you cannot ignore exceptions.
To summarize I prefer exceptions over error codes in almost all situations.
they benefit from class hierarchy which gives more features/functionality
when used properly can represent a wide range of errors (e.g. an InvalidMethodCallException is also a LogicException, as both occur when there's a bug in your code that should be detectable before runtime), and
they can be used to enhance the error (i.e. a FileReadException class definition can then contain code to check whether the file exists, or is locked, etc)
Error codes can be ignored (and often are!) by the callers of your functions. Exceptions at least force them to deal with the error in some way. Even if their version of dealing with it is to have an empty catch handler (sigh).
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...
public enum CreateUserResultCodeEnum
{
[Description("Username not available")]
NOT_AVAILABLE,
}
public (User user, CreateUserResultCodeEnum? error) CreateUser(string userName)
// (try to create user, check if not available...)
if (notAvailable)
return (null, CreateUserResultCodeEnum.NOT_AVAILABLE);
return (user, null);
}
// How to call and deconstruct tuple:
(var user, var error) = CreateUser("john.doe");
if (user != null) ...
if (error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...
// Or returning a single object (named tuple):
var result = CreateUser("john.doe");
if (result.user != null) ...
if (result.error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...
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:
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.
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.
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
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 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.
EDIT 2020-10-11:
Since C# 7.0 (March 2017) instead of Generics I prefer to use the new Tuples syntax which allows multiple return values (so we can use GO-like syntax where methods return a result OR an error).
public enum CreateUserResultCodeEnum
{
[Description("Username not available")]
NOT_AVAILABLE,
}
public (User user, CreateUserResultCodeEnum? error) CreateUser(string userName)
// (try to create user, check if not available...)
if (notAvailable)
return (null, CreateUserResultCodeEnum.NOT_AVAILABLE);
return (user, null);
}
// How to call and deconstruct tuple:
(var user, var error) = CreateUser("john.doe");
if (user != null) ...
if (error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...
// Or returning a single object (named tuple):
var result = CreateUser("john.doe");
if (result.user != null) ...
if (result.error == CreateUserResultCodeEnum.NOT_AVAILABLE) ...
EDIT 2021-01-09:
A few days ago I wrote this blog post about how we can (in some cases!) use multiple returns instead of exceptions (like golang convention explained above, not supposed to replace all your exceptions but supposed to give you arsenal to decide between when to use exceptions and when to use return codes).
By the end of the post I'm mixing two models - basically I'm using the ValueTuple syntax (which is very concise and elegant) but yet using Generics as the underlying structure.
Basically I use implicit conversion operator and type deconstructors to convert back and forth between ValueTuple and CommandResult<TEntity, TError>.
Exceptions over error codes, no doubt about it. You get much of the same benefits from exceptions as you do with error codes, but also much more, without the shortcomings of error codes. The only knock on exceptions is that it is slightly more overhead; but in this day and age, that overhead should be considered negligible for nearly all applications.
Here are some articles discussing, comparing and contrasting the two techniques:
I would never mix the two models...it's too hard to convert from one to the other as you move from one part of the stack which is using error codes, to a higher piece that is using exceptions.
Exceptions are for "anything that stops or inhibits the method or subroutine from doing what you asked it to do" ... NOT to pass messages back about irregularities or unusual circumstances, or the state of the system, etc. Use return values or ref (or out) parameters for that.
Exceptions allow methods to be written (and utilized) with semantics that are dependent on the method's function, i.e. a method that returns an Employee object or List of Employees can be typed to do just that, and you can utilize it by calling.
Employee EmpOfMonth = GetEmployeeOfTheMonth();
With error codes, all methods return an error code, so, for those that need to return something else to be used by the calling code, you have to pass a reference variable to be populated with that data, and test the return value for the error code, and handle it, on every function or method call.
Employee EmpOfMonth;
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
// code to Handle the error here
If you code so that each method does one and only one simple thing, then you should throw an exception whenever the method cannot accomplish the method's desired objective. Exceptions are much richer and easier to use in this way than error codes. Your code is much cleaner - The standard flow of the "normal" code path can be devoted strictly to the case where the method IS able to accomplish what you wanted it to do... And then the code to clean up, or handle the "exceptional" circumstances when something bad happens that prevents the method from completing successfully can be siloed away from the normal code. Additionally, if you can't handle the exception where it occurred, and must pass it up the stack to a UI, (or worse, across the wire from a mid-tier component to a UI), then with the exception model, you don't need to code every intervening method in your stack to recognize and pass the exception up the stack... The exception model does this for you automagically.... With error codes, this piece of the puzzle can get onerous very rapidly.
In the past I joined the errorcode camp (did too much C programming). But now I have seen the light.
Yes exceptions are a bit of a burden on the system. But they simplify the code, reducing the number of errors (and WTF's).
So use exception but use them wise. And they will be your friend.
As a side note. I have learned to document which exception can be thrown by which method. Unfortunately this is not required by most languages. But it increases the chance of handling the right exceptions at the right level.
try {
// Normal things are happening logic
catch (// A problem) {
// Something 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
}
// Some normal stuff logic
if (errorCode means error) {
// Some stuff went wrong logic
}
关于异常,还有其他一些不错的小事情。 使用一堆条件逻辑来跟踪函数中调用的任何方法是否返回了错误代码,并在更高的位置返回该错误代码是很多样板代码。 事实上,很多样板都可能出错。 我对大多数语言的异常系统比对“刚从大学毕业”的 Fred 写的一堆 if-else-if-else 语句更有信心,而且我还有很多更好的事情要做用我的时间比代码审查说老鼠巢。
There may be a few situations where using exceptions in a clean, clear, correct way is cumbersome, but the vast majority of the time exceptions are the obvious choice. The biggest benefit exception handling has over error codes is that it changes the flow of execution, which is important for two reasons.
When an exception occurs, the application is no longer following it's 'normal' execution path. The first reason why this is so important is that unless the author of the code goes well and truly out of their way to be bad, the program will halt and not continue doing unpredictable things. If an error code doesn't get checked and appropriate actions aren't taken in response to a bad error code, the program will keep on doing what it's doing and who knows what the result of that action will be. There are lots of situations where having the program do 'whatever' could wind up being very expensive. Consider a program that retrieves performance information for various financial instruments a company sells, and delivers that information to brokers/wholesalers. If something goes wrong and the program keeps going, it could ship erroneous performance data to the brokers and wholesalers. I don't know about anybody else, but I don't want to be the one sitting in a VPs office explaining why my code caused the company to get 7-figures worth of regulatory fines. Delivering an error message to customers is generally preferable to delivering wrong data that could look to be 'real', and the latter situation is much easier to run into with a much less aggressive approach like error codes.
The second reason why I like exceptions and their breaking of the normal execution is that it makes it much, much easier to keep the 'normal things are happening' logic separate from the 'something went wrong logic'. To me, this:
try {
// Normal things are happening logic
catch (// A problem) {
// Something went wrong logic
}
...is preferable to this:
// 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
}
There are other little things about exceptions that are nice, as well. Having a bunch of conditional logic to keep track of whether any of the methods being called in a function had an error code returned, and return that error code higher up is a lot of boiler plate. In fact, it's a lot of boiler plate that can go wrong. I have a lot more faith in the exception system of most languages than I do a rats nest of if-else-if-else statements that 'Fresh-out-of-college' Fred wrote, and I have a lot better things to do with my time than code reviewing said rat's nest.
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;
}
通过这种方式,我使用异常来定义类别错误,并使用错误代码来定义有关问题的更详细信息。
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.
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.
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.
但从另一个方向来看,使用异常允许您为错误处理构建更高级别的抽象,这可以使您的代码更具表现力和自然。 我强烈建议您阅读 C++ 专家 Andrei Alexandrescu 撰写的这篇优秀但被低估的文章,主题是他所谓的“强制执行”:http://www.ddj.com/cpp/184403864。 虽然这是一篇 C++ 文章,但原则是普遍适用的,而且我已经相当成功地将强制概念转换为 C#。
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.
在 SOA 中,方法可以跨不同的机器调用,异常可能不会通过网络传递,相反,我们使用成功/失败响应,其结构如下 (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; }
}
并像这样使用:
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
};
}
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.
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.
这里需要记住的一件事是 API 随着时间的推移而演变。 良好的 API 允许以多种方式更改和扩展故障行为,而不会破坏客户端。 例如,如果客户端错误句柄检查了 4 个错误情况,并且您向函数添加了第五个错误值,则客户端处理程序可能不会对此进行测试并中断。 如果您引发异常,这通常会让客户端更容易迁移到更新版本的库。
在任何情况下,如果可能出现不止一种微不足道的错误,源代码永远不应该使用数字文字来返回错误代码或处理它(返回 -7,如果 x == -7 ...),但是始终是一个命名常量(如果 x == NO_SUCH_FOO,则返回 NO_SUCH_FOO)。
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.
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.
发布评论
评论(22)
在高级的东西中,有例外; 在低级的东西中,错误代码。
异常的默认行为是展开堆栈并停止程序,如果我正在编写一个脚本并且我要查找不在字典中的键,那么这可能是一个错误,我希望程序停止并让我知道这一切。
但是,如果我正在编写一段我必须知道在每种可能情况下的行为的代码,那么我需要错误代码。 否则,我必须知道函数中每一行可能抛出的每个异常,才能知道它会做什么(阅读 导致航空公司停飞的异常以了解这有多么棘手)。 编写对每种情况(包括不愉快的情况)做出适当反应的代码是乏味且困难的,但这是因为编写无错误的代码是乏味和困难的,而不是因为您传递错误代码。
两者都是 Raymond Chen 和 Joel 提出了一些反对在所有事情上使用异常的雄辩论据。
In high-level stuff, exceptions; in low-level stuff, error codes.
The default behaviour of an exception is to unwind the stack and stop the program, if I'm writing a script an and I go for a key that's not in a dictionary it's probably an error, and I want the program to halt and let me know all about that.
If, however, I'm writing a piece of code which I must know the behaviour of in every possible situation, then I want error codes. Otherwise I have to know every exception that can be thrown by every line in my function to know what it will do (Read The Exception That Grounded an Airline to get an idea of how tricky this is). It's tedious and hard to write code that reacts appropriately to every situation (including the unhappy ones), but that's because writing error-free code is tedious and hard, not because you're passing error codes.
Both Raymond Chen and Joel have made some eloquent arguments against using exceptions for everything.
我通常更喜欢异常,因为它们有更多的上下文信息,并且可以以更清晰的方式向程序员传达(如果使用得当)错误。
另一方面,错误代码比异常更轻量,但更难维护。 错误检查可能会被无意中省略。 错误代码更难维护,因为您必须保留包含所有错误代码的目录,然后打开结果以查看引发的错误。 错误范围在这里可能会有所帮助,因为如果我们唯一感兴趣的是是否存在错误,那么检查起来会更简单(例如,HRESULT 错误代码大于或等于 0 表示成功,并且小于零则失败)。 它们可能会无意中被省略,因为没有程序强制开发人员检查错误代码。 另一方面,你不能忽视异常。
总而言之,在几乎所有情况下,我都更喜欢异常而不是错误代码。
I normally prefer exceptions, because they have more contextual information and can convey (when properly used) the error to the programmer in a clearer fashion.
On the other hand, error codes are more lightweight than exceptions but are harder to maintain. Error checking can inadvertently be omitted. Error codes are harder to maintain because you have to keep a catalog with all error codes and then switch on the result to see what error was thrown. Error ranges can be of help here, because if the only thing we are interested in is if we are in the presence of an error or not, it is simpler to check (e.g., an HRESULT error code greater or equal to 0 is success and less than zero is failure). They can inadvertently be omitted because there is no programmatic forcing that the developer will check for error codes. On the other hand, you cannot ignore exceptions.
To summarize I prefer exceptions over error codes in almost all situations.
我更喜欢异常,因为
I prefer exceptions because
错误代码可以被函数的调用者忽略(而且经常如此!)。 异常至少迫使他们以某种方式处理错误。 即使他们处理它的版本是有一个空的 catch 处理程序(叹气)。
Error codes can be ignored (and often are!) by the callers of your functions. Exceptions at least force them to deal with the error in some way. Even if their version of dealing with it is to have an empty catch handler (sigh).
你应该同时使用两者。 问题是决定何时使用每一个。
在少数情况下,异常是显而易见的选择:
在某些情况下您无法对错误代码执行任何操作,而您只需要要在调用堆栈的上层处理它,通常只需记录错误,向用户显示某些内容或关闭程序。 在这些情况下,错误代码将要求您手动逐级冒泡错误代码,这显然对于异常情况更容易做到。 重点是,这是针对意外且无法处理的情况。
然而,对于情况 1(发生意外且无法处理的情况,您只是不想记录它),异常可能会有所帮助,因为您可以添加上下文信息。 例如,如果我在较低级别的数据助手中收到 SqlException,我将希望在低级别捕获该错误(我知道导致错误的 SQL 命令),以便我可以捕获该信息并重新抛出 以及附加信息。 请注意这里的神奇词:重新扔掉,而不是吞下。
异常处理的第一条规则:不要吞下异常。 另外,请注意,我的内部捕获不需要记录任何内容,因为外部捕获将具有整个堆栈跟踪并且可能会记录它。
在某些情况下,您有一系列命令,如果其中任何一个失败,您应该清理/处置资源(*),无论这是否是不可恢复的情况(应该抛出)或可恢复的情况(在这种情况下,您可以在本地或调用者代码中处理,但不需要异常)。 显然,将所有这些命令放在一次尝试中要容易得多,而不是在每个方法之后测试错误代码,并在finally块中进行清理/处理。 请注意,如果您希望错误冒泡(这可能是您想要的),您甚至不需要捕获它 - 您只需使用finally进行清理/处置 - 您应该只如果您想添加上下文信息,请使用 catch/retrow(请参阅第 2 条)。
一个示例是事务块内的一系列 SQL 语句。 同样,这也是一种“无法处理”的情况,即使您决定尽早捕获它(在本地处理它而不是冒泡到顶部),它仍然是一种致命情况,最好的结果是中止一切或至少中止过程的很大一部分。
(*) 这就像我们在旧版 Visual Basic 中使用的
on error goto
在构造函数中只能抛出异常。
话虽如此,在您返回一些调用者可以/应该采取某些操作的信息的所有其他情况下,使用返回代码可能是更好的选择。 这包括所有预期的“错误”,因为它们可能应该由直接调用者处理,并且几乎不需要在堆栈中向上冒泡太多级别。
当然,始终可以将预期错误视为异常,然后立即捕获上一级,也可以将每一行代码包含在 try catch 中,并对每个可能的错误采取操作。 IMO,这是一个糟糕的设计,不仅因为它更加冗长,而且特别是因为如果不阅读源代码,可能引发的异常并不明显 - 并且可以从任何深层方法引发异常,从而创建 不可见的 goto。 他们通过创建多个不可见的退出点来破坏代码结构,使代码难以阅读和检查。 换句话说,您永远不应该将异常用作流量控制,因为这对其他人来说很难理解和维护。 理解所有可能的测试代码流可能会变得更加困难。
再次强调:为了正确清理/处置,您可以使用 try-finally 而不捕获任何内容。
关于返回代码最流行的批评是“有人可能会忽略错误代码,但从同样的意义上讲,有人也可能吞下异常。不良异常处理很容易在这两种方法中。但是编写良好的基于错误代码的程序仍然比编写异常容易得多 -如果出于任何原因决定忽略所有错误(旧的
错误继续下一个
),您可以轻松地使用返回代码来做到这一点,并且您可以'如果没有大量的 try-catch 样板文件,第二个最流行的批评是“很难冒泡”——但那是因为人们不明白异常是针对不可恢复的情况,而错误是针对的。 。
在异常和错误代码之间做出选择是一个灰色地带,您甚至可能需要从某些可重用的业务方法中获取错误代码,然后决定将其包装到异常中(可能添加信息)并让它使用 冒泡。 但假设所有错误都应作为异常抛出,这是一个设计错误。
总结一下:
当我遇到意外情况时,我喜欢使用异常,在这种情况下,没有太多事情可做,并且通常我们想要中止一大块代码,甚至整个操作或程序。 这就像旧的“错误时转到”。
当我预计调用者代码可以/应该采取某些操作的情况时,我喜欢使用返回代码。 这包括大多数业务方法、API、验证等。
异常和错误代码之间的这种区别是 GO 语言的设计原则之一,它对致命的意外情况使用“panic”,而常规的预期情况则作为错误返回。
然而关于GO,它还允许多个返回值,这对于使用return有很大帮助代码,因为您可以同时返回错误和其他内容。 在 C#/Java 上,我们可以不用参数、元组或(我最喜欢的)泛型来实现这一点,它们与枚举相结合可以向调用者提供清晰的错误代码:
如果我在方法中添加新的可能返回,我什至可以检查所有例如,如果调用者在 switch 语句中覆盖该新值。 除非有例外,你确实不能这样做。 当您使用返回代码时,您通常会提前知道所有可能的错误,并对其进行测试。 除了例外,您通常不知道会发生什么。 将枚举包装在异常中(而不是泛型)是一种替代方案(只要清楚每个方法将抛出的异常类型),但在我看来,这仍然是糟糕的设计。
编辑 2020 年 10 月 11 日:
自 C# 7.0(2017 年 3 月)开始,我更喜欢使用 新的元组语法允许多个返回值(因此我们可以使用类似 GO 的语法,其中方法返回结果或错误)。
编辑2021-01-09:
几天前我写了这篇博客文章,关于我们如何(在某些情况下!)使用多个返回而不是异常(就像上面解释的 golang 约定,不应该替换所有异常,而是应该给你提供选择的余地)何时使用异常以及何时使用返回码)。
在这篇文章的结尾,我混合了两个模型 - 基本上我使用 ValueTuple 语法(非常简洁和优雅),但仍然使用泛型作为底层结构。
基本上我使用隐式转换运算符和类型解构函数在
ValueTuple
和CommandResult
之间来回转换>。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:
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.
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.
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 BasicIn 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:
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.
EDIT 2020-10-11:
Since C# 7.0 (March 2017) instead of Generics I prefer to use the new Tuples syntax which allows multiple return values (so we can use GO-like syntax where methods return a result OR an error).
EDIT 2021-01-09:
A few days ago I wrote this blog post about how we can (in some cases!) use multiple returns instead of exceptions (like golang convention explained above, not supposed to replace all your exceptions but supposed to give you arsenal to decide between when to use exceptions and when to use return codes).
By the end of the post I'm mixing two models - basically I'm using the ValueTuple syntax (which is very concise and elegant) but yet using Generics as the underlying structure.
Basically I use implicit conversion operator and type deconstructors to convert back and forth between
ValueTuple
andCommandResult<TEntity, TError>
.错误代码上的异常,这是毫无疑问的。 您可以从异常中获得与使用错误代码相同的好处,而且还可以获得更多好处,并且没有错误代码的缺点。 唯一的例外是它的开销稍微多一些; 但在当今时代,对于几乎所有应用程序来说,这种开销都可以忽略不计。
以下是一些讨论、比较和对比这两种技术的文章:
有一些好的那些可以为您提供进一步阅读的链接。
Exceptions over error codes, no doubt about it. You get much of the same benefits from exceptions as you do with error codes, but also much more, without the shortcomings of error codes. The only knock on exceptions is that it is slightly more overhead; but in this day and age, that overhead should be considered negligible for nearly all applications.
Here are some articles discussing, comparing and contrasting the two techniques:
There are some good links in those that can give you further reading.
我永远不会混合这两种模型......当您从使用错误代码的堆栈的一部分移动到使用异常的更高部分时,从一种模型转换为另一种模型太困难了。
例外情况是“任何停止或禁止方法或子例程执行您要求它执行的操作”...不要传递有关不规则或异常情况或系统状态等的消息。使用返回值或引用(或输出)参数。
异常允许使用依赖于方法功能的语义来编写(和使用)方法,即可以键入返回 Employee 对象或Employees 列表的方法来执行此操作,并且您可以通过调用来使用它。
对于错误代码,所有方法都会返回错误代码,因此,对于那些需要返回其他内容以供调用代码使用的方法,您必须传递一个引用变量以填充该数据,并测试返回值错误代码,并在每个函数或方法调用时处理它。
如果您编写的代码使每个方法只执行一件简单的事情,那么每当该方法无法实现该方法的预期目标时,您就应该抛出异常。 通过这种方式,异常比错误代码更丰富且更容易使用。 您的代码更加干净 - “正常”代码路径的标准流程可以严格致力于该方法能够完成您想要它做的事情的情况......然后代码进行清理,或处理当发生阻止该方法成功完成的不良情况时,“异常”情况可以与正常代码隔离开来。 此外,如果您无法处理发生异常的位置,并且必须将其通过堆栈传递到 UI(或更糟糕的是,通过中间层组件到 UI 的线路),那么使用异常模型,您可以不需要对堆栈中的每个干预方法进行编码来识别异常并将其向上传递到堆栈...异常模型会自动为您完成此操作...如果存在错误代码,这个难题很快就会变得繁重。
I would never mix the two models...it's too hard to convert from one to the other as you move from one part of the stack which is using error codes, to a higher piece that is using exceptions.
Exceptions are for "anything that stops or inhibits the method or subroutine from doing what you asked it to do" ... NOT to pass messages back about irregularities or unusual circumstances, or the state of the system, etc. Use return values or ref (or out) parameters for that.
Exceptions allow methods to be written (and utilized) with semantics that are dependent on the method's function, i.e. a method that returns an Employee object or List of Employees can be typed to do just that, and you can utilize it by calling.
With error codes, all methods return an error code, so, for those that need to return something else to be used by the calling code, you have to pass a reference variable to be populated with that data, and test the return value for the error code, and handle it, on every function or method call.
If you code so that each method does one and only one simple thing, then you should throw an exception whenever the method cannot accomplish the method's desired objective. Exceptions are much richer and easier to use in this way than error codes. Your code is much cleaner - The standard flow of the "normal" code path can be devoted strictly to the case where the method IS able to accomplish what you wanted it to do... And then the code to clean up, or handle the "exceptional" circumstances when something bad happens that prevents the method from completing successfully can be siloed away from the normal code. Additionally, if you can't handle the exception where it occurred, and must pass it up the stack to a UI, (or worse, across the wire from a mid-tier component to a UI), then with the exception model, you don't need to code every intervening method in your stack to recognize and pass the exception up the stack... The exception model does this for you automagically.... With error codes, this piece of the puzzle can get onerous very rapidly.
过去我加入了错误代码阵营(做了太多的C编程)。 但现在我看到了曙光。
是的,异常会给系统带来一些负担。 但它们简化了代码,减少了错误(以及WTF)的数量。
因此,请使用异常,但要明智地使用它们。 他们将成为你的朋友。
作为旁注。 我学会了记录哪种方法可以抛出哪种异常。 不幸的是,大多数语言都不需要这样做。 但它增加了在正确级别处理正确异常的机会。
In the past I joined the errorcode camp (did too much C programming). But now I have seen the light.
Yes exceptions are a bit of a burden on the system. But they simplify the code, reducing the number of errors (and WTF's).
So use exception but use them wise. And they will be your friend.
As a side note. I have learned to document which exception can be thrown by which method. Unfortunately this is not required by most languages. But it increases the chance of handling the right exceptions at the right level.
在某些情况下,以干净、清晰、正确的方式使用异常可能会很麻烦,但绝大多数情况下,异常是显而易见的选择。 与错误代码相比,异常处理的最大好处是它改变了执行流程,这很重要,原因有两个。
当异常发生时,应用程序不再遵循其“正常”执行路径。 这如此重要的第一个原因是,除非代码的作者做得很好并且真正不遗余力地做坏事,否则程序将停止并且不会继续做不可预测的事情。 如果没有检查错误代码并且没有采取适当的操作来响应错误代码,则程序将继续执行其正在执行的操作,谁知道该操作的结果会是什么。 在很多情况下,让程序做“任何事”可能会变得非常昂贵。 考虑一个程序,该程序检索公司销售的各种金融工具的绩效信息,并将该信息传递给经纪人/批发商。 如果出现问题并且程序继续运行,它可能会向经纪人和批发商发送错误的绩效数据。 我不知道其他人怎么想,但我不想成为坐在副总裁办公室里解释为什么我的代码导致公司收到价值 7 位数的监管罚款的人。 向客户提供错误消息通常比提供看似“真实”的错误数据更可取,而后一种情况更容易使用错误代码等不那么激进的方法来遇到。
我喜欢异常及其对正常执行的破坏的第二个原因是,它使得将“正常事情正在发生”逻辑与“出了问题逻辑”分开变得非常非常容易。 对我来说,这个:
……比这个更好:
关于异常,还有其他一些不错的小事情。 使用一堆条件逻辑来跟踪函数中调用的任何方法是否返回了错误代码,并在更高的位置返回该错误代码是很多样板代码。 事实上,很多样板都可能出错。 我对大多数语言的异常系统比对“刚从大学毕业”的 Fred 写的一堆 if-else-if-else 语句更有信心,而且我还有很多更好的事情要做用我的时间比代码审查说老鼠巢。
There may be a few situations where using exceptions in a clean, clear, correct way is cumbersome, but the vast majority of the time exceptions are the obvious choice. The biggest benefit exception handling has over error codes is that it changes the flow of execution, which is important for two reasons.
When an exception occurs, the application is no longer following it's 'normal' execution path. The first reason why this is so important is that unless the author of the code goes well and truly out of their way to be bad, the program will halt and not continue doing unpredictable things. If an error code doesn't get checked and appropriate actions aren't taken in response to a bad error code, the program will keep on doing what it's doing and who knows what the result of that action will be. There are lots of situations where having the program do 'whatever' could wind up being very expensive. Consider a program that retrieves performance information for various financial instruments a company sells, and delivers that information to brokers/wholesalers. If something goes wrong and the program keeps going, it could ship erroneous performance data to the brokers and wholesalers. I don't know about anybody else, but I don't want to be the one sitting in a VPs office explaining why my code caused the company to get 7-figures worth of regulatory fines. Delivering an error message to customers is generally preferable to delivering wrong data that could look to be 'real', and the latter situation is much easier to run into with a much less aggressive approach like error codes.
The second reason why I like exceptions and their breaking of the normal execution is that it makes it much, much easier to keep the 'normal things are happening' logic separate from the 'something went wrong logic'. To me, this:
...is preferable to this:
There are other little things about exceptions that are nice, as well. Having a bunch of conditional logic to keep track of whether any of the methods being called in a function had an error code returned, and return that error code higher up is a lot of boiler plate. In fact, it's a lot of boiler plate that can go wrong. I have a lot more faith in the exception system of most languages than I do a rats nest of if-else-if-else statements that 'Fresh-out-of-college' Fred wrote, and I have a lot better things to do with my time than code reviewing said rat's nest.
我的方法是我们可以同时使用这两种代码,即异常代码和错误代码。
我习惯于定义几种类型的异常(例如:DataValidationException 或 ProcessInterruptExcepion),并在每个异常内部定义每个问题的更详细描述。
Java 中的一个简单示例:
通过这种方式,我使用异常来定义类别错误,并使用错误代码来定义有关问题的更详细信息。
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:
In this way i use Exceptions to define category errors, and error codes to define more detailed info about the problem.
我的理由是,如果您正在编写真正需要性能的低级驱动程序,那么请使用错误代码。 但是,如果您在更高级别的应用程序中使用该代码并且它可以处理一些开销,则可以使用一个接口包装该代码,该接口检查这些错误代码并引发异常。
在所有其他情况下,例外可能是正确的选择。
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.
我可能对此持观望态度,但是……
在 Python 中,使用异常是标准做法,我很高兴定义自己的异常。 在 C 语言中根本没有例外。
在 C++ 中(至少在 STL 中),通常只有真正的异常错误才会引发异常(我自己几乎从未见过它们)。 我认为没有理由在我自己的代码中做任何不同的事情。 是的,忽略返回值很容易,但 C++ 也不强迫您捕获异常。 我认为你只需要养成这样做的习惯即可。
我工作的代码库主要是 C++,我们几乎在任何地方都使用错误代码,但是有一个模块会针对任何错误引发异常,包括非常平常的错误,并且使用该模块的所有代码都非常糟糕。 但这可能只是因为我们混合了异常和错误代码。 一致使用错误代码的代码更容易使用。 如果我们的代码始终使用异常,也许情况不会那么糟糕。 将两者混合起来似乎效果不太好。
I may be sitting on the fence here, but...
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.
由于我使用 C++,并且有 RAII 来保证它们可以安全使用,所以我几乎只使用异常。 它将错误处理从正常的程序流程中拉出来,并使意图更加清晰。
不过,我确实为特殊情况保留了例外。 如果我预计某个错误会频繁发生,我会在执行操作之前检查该操作是否会成功,或者调用使用错误代码的函数版本(例如
TryParse())
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()
)方法签名应该告诉您该方法的用途。 就像是
长错误代码 = getErrorCode();
可能没问题,但是
长错误代码 = fetchRecord();
令人困惑。
Method signatures should communicate to you what the method does. Something like
long errorCode = getErrorCode();
might be fine, but
long errorCode = fetchRecord();
is confusing.
异常是针对异常情况的——即,当它们不属于正常代码流的一部分时。
混合异常和错误代码是非常合法的,其中错误代码代表某些事物的状态,而不是代码本身运行中的错误(例如检查子进程的返回代码)。
但当特殊情况发生时,我相信例外是最具表现力的模型。
在某些情况下,您可能更喜欢或不得不使用错误代码来代替异常,并且这些情况已经被充分涵盖(除了其他明显的限制,例如编译器支持)。
但从另一个方向来看,使用异常允许您为错误处理构建更高级别的抽象,这可以使您的代码更具表现力和自然。 我强烈建议您阅读 C++ 专家 Andrei Alexandrescu 撰写的这篇优秀但被低估的文章,主题是他所谓的“强制执行”:http://www.ddj.com/cpp/184403864。 虽然这是一篇 C++ 文章,但原则是普遍适用的,而且我已经相当成功地将强制概念转换为 C#。
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.
首先,我同意汤姆的答案,对于高级内容使用异常,对于低级内容使用错误代码,只要它不是面向服务的架构(SOA)。
在 SOA 中,方法可以跨不同的机器调用,异常可能不会通过网络传递,相反,我们使用成功/失败响应,其结构如下 (C#):
并像这样使用:
当这些在您的应用程序中一致使用时服务响应它创建了一个非常好的处理应用程序成功/失败的模式。 这使得服务内以及跨服务的异步调用中的错误处理更加容易。
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#):
And use like this:
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.
对于大多数应用程序来说,异常更好。 例外情况是软件必须与其他设备通信时。 我工作的领域是工业控制。 这里错误代码是首选和预期的。 所以我的回答是,这确实取决于情况。
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.
我更喜欢所有错误情况的异常,除非失败是返回原始数据类型的函数的预期无错误结果。 例如,在较大字符串中查找子字符串的索引,如果未找到,通常会返回 -1,而不是引发 NotFoundException。
返回可能被取消引用的无效指针(例如在 Java 中导致 NullPointerException)是不可接受的。
使用多个不同的数字错误代码(-1、-2)作为同一函数的返回值通常是不好的风格,因为客户端可能会执行“== -1”检查而不是“< 0”。
这里需要记住的一件事是 API 随着时间的推移而演变。 良好的 API 允许以多种方式更改和扩展故障行为,而不会破坏客户端。 例如,如果客户端错误句柄检查了 4 个错误情况,并且您向函数添加了第五个错误值,则客户端处理程序可能不会对此进行测试并中断。 如果您引发异常,这通常会让客户端更容易迁移到更新版本的库。
另一件需要考虑的事情是,在团队中工作时,如何为所有开发人员制定明确的界限来做出这样的决定。 例如,“高级内容的异常,低级内容的错误代码”是非常主观的。
在任何情况下,如果可能出现不止一种微不足道的错误,源代码永远不应该使用数字文字来返回错误代码或处理它(返回 -7,如果 x == -7 ...),但是始终是一个命名常量(如果 x == NO_SUCH_FOO,则返回 NO_SUCH_FOO)。
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.
我认为这还取决于您是否真的需要结果中的堆栈跟踪等信息。 如果是,那么您肯定会选择 Exception,它提供了完整的对象以及有关问题的大量信息。 但是,如果您只对结果感兴趣而不关心为什么会出现该结果,那么请查找错误代码。
例如,当您处理文件并面对 IOException 时,客户端可能有兴趣知道从哪里触发此异常,在打开文件或解析文件等。因此,最好返回 IOException 或其特定子类。 但是,像您有登录方法并且您想知道它是否成功的情况,您要么只返回布尔值,要么显示正确的消息,返回错误代码。 在这里,客户端不感兴趣知道哪部分逻辑导致了该错误代码。 他只知道其凭据是否无效或帐户锁定等。
我能想到的另一个用例是数据在网络上传输时。 您的远程方法可以仅返回错误代码而不是异常,以最大限度地减少数据传输。
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:
当您的方法返回除数值以外的任何内容时,错误代码也不起作用...
Error codes also don't work when your method returns anything other than a numeric value...