另一方面,错误代码比异常更轻量级,但更难维护。错误检查可能在不经意间被忽略。错误代码更难维护,因为您必须保存包含所有错误代码的目录,然后打开结果以查看抛出了什么错误。在这里,错误范围可能有所帮助,因为如果我们唯一感兴趣的是是否存在错误,那么检查起来就更简单(例如,大于或等于0的 HRESULT T 错误代码是成功的,小于0的错误代码是失败的)。它们可能无意中被忽略,因为没有编程强制要求开发人员检查错误代码。另一方面,您不能忽略异常。
在 C + + 中(至少在 STL 中) ,通常只针对真正异常的错误抛出异常(我自己实际上从来没有看到过)。我认为没有理由在我自己的代码中做任何不同的事情。是的,忽略返回值很容易,但是 C + + 也不强迫您捕捉异常。我认为你只需要养成这样做的习惯。
我工作的代码基础大多是 C + + ,我们几乎在任何地方都使用错误代码,但是有一个模块会对任何错误产生异常,包括非常普通的错误,所有使用该模块的代码都非常糟糕。但这可能只是因为我们混合了异常和错误代码。始终使用错误代码的代码更容易使用。如果我们的代码始终使用异常,也许就不会那么糟糕了。把两者混合起来似乎效果不太好。
但是从另一个角度来看,使用 Exceptions 可以为错误处理构建更高级别的抽象,从而使代码更具表现力和自然性。我强烈推荐阅读这篇由 C + + 专家 Andrei Alexandrescu 撰写的优秀但被低估的文章,文章的主题被他称为“增强”: http://www.ddj.com/cpp/184403864。虽然这是一篇 C + + 文章,但是这些原则通常是适用的,而且我已经成功地将增强的概念转换成了 C # 。
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
}
在 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
};
}
异常和错误代码之间的这种差异是 GO 语言的设计原则之一,GO 语言对致命的意外情况使用“惊慌”,而常规的预期情况作为错误返回。
然而关于 GO,它还允许使用 多个返回值,这对使用返回代码有很大帮助,因为您可以同时返回一个错误和其他内容。在 c #/Java 中,我们可以在没有参数的情况下实现这一点,比如 Tuples,或者(我最喜欢的)泛型(Generics) ,它们与枚举相结合,可以为调用者提供明确的错误代码:
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...
由于 C # 7.0(2017年3月)而不是泛型,我更喜欢使用 新的元组语法,它允许多个返回值(因此我们可以使用类 GO 的语法,其中方法返回一个结果或一个错误)。
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) ...