昨天我有一个激烈辩论的一个同事上什么会是优选的错误报告的方法。主要是我们讨论的使用情况的例外或错误代码,用于报告错误之间的应用程序层或模块。

什么样的规则你用来决定是否你扔例外或返回的错误代码错误的报告?

有帮助吗?

解决方案 2

我通常更喜欢异常,因为它们有更多的上下文信息,并且能够以更清晰的方式向程序员传达(如果使用得当)错误。

另一方面,错误代码比异常更轻量级但更难维护。错误检查可能会无意中被省略。错误代码更难维护,因为您必须保留包含所有错误代码的目录,然后打开结果以查看引发的错误。错误范围在这里可能有所帮助,因为如果我们唯一感兴趣的是如果我们是否存在错误,则检查更简单(例如,大于或等于0的HRESULT错误代码是成功的,小于零就是失败)。它们可能会被无意中省略,因为没有编程强制开发人员会检查错误代码。另一方面,你不能忽视例外。

总结一下,我几乎在所有情况下都偏好错误代码。

其他提示

在高级别的东西中,例外;在低级别的东西,错误代码。

异常的默认行为是展开堆栈并停止程序,如果我正在写一个脚本并且我找到一个不在字典中的密钥它可能是一个错误,我希望程序停止让我知道这一切。

但是,如果我正在编写一段代码,我必须知道在每种可能情况下的行为,那么我想要错误代码。否则,我必须知道我的函数中的每一行都可以抛出的每个异常,以了解它将做什么(Read 基于航空公司的例外情况,以了解这是多么棘手)。编写对每种情况都适当做出反应的代码(包括不愉快的代码)是很繁琐和困难的,但那是因为编写无错误的代码既乏味又难,不是因为你传递了错误代码。

Raymond Chen Joel 已经提出了一些反对使用异常的雄辩论点。

我更喜欢的例外情况,因为

  • 他们断流动的逻辑
  • 他们受益于类层次,它提供了更多的功能/功能
  • 如果使用得当,可以表示一个广泛的错误(例如一个InvalidMethodCallException也是一个逻辑异常,因为这两个发生时,有一个代码中的缺陷,应该是检测之前运行时间),
  • 他们可以被用来加强的错误(即一FileReadException类定义可以包含码检查文件是否存在,或是锁,等等)

您的函数的调用者可以忽略错误代码(通常是!)。例外至少迫使他们以某种方式处理错误。即使他们处理它的版本是一个空的捕获处理程序(叹气)。

例外在错误代码毫无疑问的。你得到太多的相同利益的例外情况为你做了错误代码,但也更多,没有缺点的错误代码。唯一的敲例外情况是,它是稍微更多的开销;但在今天和这个时代,即开销应被认为是可以忽略不计为几乎所有应用程序。

这里有一些文章讨论、比较和对比两个技术:

有一些良好的联系,在那些可以给你进一步阅读。

我永远不会混合的两个模型...这太难以转换从一个到另一个您从一个部分,栈其是使用错误代码,以较高件使用的例外情况。

例外的是供"任何停止或抑制的方法或子程序做什么你叫它做"...不要传递消息后有关违规行为或不寻常的情况下,或者国家系统,等等。使用的返回值或参考(或)参数。

例外允许方法是书面的(和使用)与语义是依赖于方法的功能,即一个方法,该方法返回的雇员对象的雇员可以键入来做到这一点,并可以利用它通过电话。

Employee EmpOfMonth = GetEmployeeOfTheMonth();

错误代码,所有的方法返回错误代码,因此,对于那些需要的回报别的东西是用于通过调用代码,你必须通过一个参考的变量来填充数据和测试回报价值的错误,并处理它,在每一个功能或方法的呼吁。

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

如果你代码,以便每一个方法并且只有一个简单的事情,然后你应该扔一个例外,只要该方法无法完成该方法的期望的目标。例外的是更加丰富和更容易使用这种方式比错误代码。你的代码的更清洁的标准流程的"正常"的路径代码可以致力于严格的情况下方法能够完成什么你想要它做的...然后将代码,以清理,或处理"特殊"情况的时候有什么不好的事情发生,防止的方法成功完成可以被孤立的离开通常的代码。此外,如果不能处理异常在那里发生,并且必须通过它的堆到一个用户界面,(或者更糟糕的是,跨线从中间层的组成部分UI),然后与异常模型,你不需要代码的每一个干预方法在你叠认识,并通过除了堆...的异常模式不会这样对你自动....错误代码,这块拼图可以得到繁重的非常迅速。

过去我加入了错误代码阵营(做了太多的C编程)。但现在我已经看到了光明。

是的例外对系统来说是一个负担。但它们简化了代码,减少了错误数量(和WTF)。

所以使用异常但明智地使用它们。他们将成为你的朋友。

作为旁注。我已经学会了记录哪个方法可以抛出哪个异常。不幸的是,大多数语言都不需要这样做。但它增加了在适当级别处理正确例外的机会。

在某些情况下,以干净,清晰,正确的方式使用异常是很麻烦的,但绝大多数时间异常是显而易见的选择。最大的好处是异常处理错误代码是它改变了执行流程,这有两个重要原因。

当发生异常时,应用程序不再遵循它的“正常”执行路径。这一点非常重要的第一个原因是,除非代码的作者顺利完成并且真正地让他们变坏,否则程序将停止并且不会继续做不可预测的事情。如果未检查错误代码并且未采取适当的操作来响应错误的错误代码,程序将继续执行它正在执行的操作以及谁知道该操作的结果将是什么。有很多情况下让程序做“什么”可能会非常昂贵。考虑一个程序,该程序检索公司销售的各种金融工具的绩效信息,并将该信息传递给经纪人/批发商。如果出现问题并且程序继续运行,它可能会将错误的性能数据发送给经纪人和批发商。我不知道其他任何人,但我不想成为副总裁办公室的人,解释为什么我的代码导致该公司获得7位数的监管罚款。向客户发送错误消息通常比提供可能看起来“真实”的错误数据更可取,后一种情况更容易遇到错误代码等不那么激进的方法。

我喜欢异常和破坏正常执行的第二个原因是,它使得“正常事物正在发生”逻辑与“错误的逻辑”分开更容易,更容易。对我来说,这个:

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
}

还有其他关于异常的小事情也很好。有一堆条件逻辑来跟踪函数中调用的任何方法是否返回了错误代码,并返回错误代码更高的是很多锅炉板。事实上,很多锅炉板都可能出错。我对大多数语言的异常系统有更多的信心,而不是像弗雷德写的“大学新生”那样的if-else-if-else语句的老鼠窝,我有很多更好的事情要做我的时间比代码审查老鼠的巢。

你应该使用这两者。事实是决定何时使用的每一个.

还有一个 少数情况例外情况是显而易见的选择:

  1. 在一些情况 你不能做任何错误代码, 你只是 需要处理它的上级在呼叫堆, 通常只是记录错误,显示一些东西的用户或关闭程序。在这些情况下,错误代码需要你泡了错误代码的手动水平的水平,这显然是很容易做的例外。这一点是,这是为了 意想不到的和unhandleable 情况。

  2. 但有关情况1(其中一些意想不到的和unhandleable发生你只是想要记录的话),例外情况可能是有用的,因为你可能会 添加下文信息.例如,如果我得到一个SqlException在我的下级别数据的助手,我想抓住那个错误的低水平(这里我知道SQL命令,该命令所引起的错误),所以我可以捕获的信息 并重新引发 与额外的信息。请注意这个神奇的词在这里: 再次引发,而不吞下. 第一规则的例外处理: 不吞下的例外.此外,请注意我的内心抓不需要登录任何东西,因为外赶上将有整个堆栈跟踪,并可能记录。

  3. 在某些情况下你有一系列的命令, 如果他们中的任何失败 你应该 清理/处置资源(*)、是否这是一个不可恢复的状况(这应该被扔)或恢复的情况(在这种情况下,你可以处理在本地还是在叫码但你不需要例外)。显然,这是很容易把所有这些命令在一个单一的尝试,而不是检验错误代码之后,每一种方法,并清理/处理在最后块。请注意, 如果你想的错误泡了(这可能是你想要的),你甚至都不需要抓住它-你只是使用的最后清理/处置 -你应该只使用的捕/retrow如果你想要添加关信息(见子弹的2)。

    一个例子将是一个顺序SQL发言的内部事务的区块。再次,这也是一个"unhandleable"的情况下,甚至如果你决定要抓住它早日(待它在当地而不是起泡到顶部)它仍然是一个 致命的状况 从那里最好的结果是堕胎的一切,或至少中止一大过程的一部分。
    (*)这样的 on error goto 我们用旧的Visual Basic,

  4. 在构造只能扔的例外情况。

有的说,在所有其他情况你回一些信息 呼叫者可以/应该采取一些行动, 使用返回代码可能是一个更好的选择。这包括所有 预期的"错误", 因为也许他们应该处理由立即呼叫,并将几乎不需要冒了太多的水平上。

当然,它总是可以治疗预计错误作为例外情况,并抓住然后立即将一个级别上,也有可能涵盖每一行代码都在试图赶上并采取行动的每一可能的错误。海事组织,这是糟糕的设计,不仅因为它是从更加详细,但是特别因为可能的例外情况,可能会引发不是明显没有阅读的源代码和例外情况可能引发的任何深的方法,创造 goto语句看不见.他们打破代码结构,通过创建多个看不见的退出点代码很难读取和检查。换句话说,你不应该使用 作为例外情况 流控制, ,因为那会是他人难以理解和维护。它可以得到更难以理解所有可能的代码的流动进行测试。
再次: 为正确的清理/处置你可以使用的尝试最终没有抓任何东西.

最受欢迎的批评有关返回代码是"有人可以忽视的错误代码,但是在同一意义上的人也可以吞咽例外情况。 坏例外处理是很容易的 在这两种方法。但 写好的错误代码为基础的程序仍然是更容易比写一例外基于的程序.如果一个由任何原因决定将忽略你所有的错误(旧 on error resume next),可以很容易地做到这一点,与返回代码,你不能这样做没有很多尝试抓的样板。

第二个最受欢迎的批评有关返回代码是"很难泡沫",但这是因为人们不理解这一点的例外情况是不可恢复的情况,同时错误码都没有。

决定之间的异常和错误代码是一个灰色区域。它甚至有可能是你需要得到一个错误代码可重复使用的一些业务方法,然后你决定要包装成一个例外(可能增加信息),并让它泡起来。但它是一个设计错误的假设,所有的错误应该抛作为例外情况。

总的来说:

  • 我喜欢使用的例外情况时,我有一个意想不到的情况,在其没有太多要做,并且通常我们要终止一个大块代码或甚至整个行动或程序。这就像是旧的"上的错误转到".

  • 我喜欢用返回代码时,我有预期的情况来电码可以/应该采取一些行动。这包括大多数业务方法,Api,验证,等等。

此之间的差异和错误代码是一个设计原则的语言,它使用"恐慌"的致命意外情况,而常规的预计情况是返回的错误。

又去约,它也允许 多返回值 , ,这是一件好事,有很大帮助,在使用返回代码,因为你可以同时返回一个错误和别的东西。C#/Java我们可以实现,用的参数,组,或者(我最喜欢的)仿制药,它结合枚举可以提供明确的错误代码的来电:

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

如果我添加一个新的可能回报我的方法,我甚至可以检查所有的呼叫者如果它们涵盖新的价值在一个开关的声明为例。你真的不能这样做有例外。当你使用返回代码,则通常会提前告知所有可能的错误,并测试用于他们。异常,你通常不知道会发生什么事情。包装枚举的内的例外(而不是泛型)是一种备择性的(只要它是明确的种类型的例外情况,每种方法将把),但海事组织,它仍然糟糕的设计。

我可能会坐在这里,但是......

  1. 这取决于语言。
  2. 无论您选择哪种型号,都要始终如一地使用它。
  3. 在Python中,使用异常是标准做法,我很乐意定义自己的异常。在C中你根本没有例外。

    在C ++中(至少在STL中),异常通常仅针对真正异常的错误抛出(我几乎从未见过它们)。我认为没有理由在我自己的代码中做任何不同的事情。是的,很容易忽略返回值,但C ++也不会强迫您捕获异常。我认为你必须养成这样做的习惯。

    我所使用的代码库主要是C ++,我们几乎在任何地方使用错误代码,但是有一个模块可以引发任何错误的异常,包括非常普遍的错误,并且使用该模块的所有代码都非常糟糕。但这可能只是因为我们混合了异常和错误代码。始终使用错误代码的代码更容易使用。如果我们的代码一直使用异常,也许它不会那么糟糕。将两者混合似乎不太好用。

由于我使用C ++,并使用RAII使其安全使用,因此我几乎只使用异常。它将错误处理从正常的程序流中拉出来并使意图更加清晰。

我确实为特殊情况留下了例外情况。如果我预计某个错误会发生很多,我会在执行之前检查操作是否成功,或者调用使用错误代码的函数版本(如TryParse()

方法签名应该告诉您方法的作用。就像是     long errorCode = getErrorCode(); 可能没问题,但是     long errorCode = fetchRecord(); 令人困惑。

我的理由是,如果您正在编写一个真正需要性能的低级驱动程序,那么请使用错误代码。但是如果你在更高级别的应用程序中使用该代码并且它可以处理一些开销,那么用一个检查这些错误代码并引发异常的接口包装该代码。

在所有其他情况下,例外可能是要走的路。

我的方法是我们可以同时使用两者,即异常和错误代码。

我习惯于定义几种类型的异常(例如:DataValidationException或ProcessInterruptExcepion),并在每个异常中定义每个问题的更详细描述。

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;

}

通过这种方式,我使用Exceptions定义类别错误,并使用错误代码来定义有关问题的更详细信息。

异常是针对异常的情况 - 即,当它们不属于正常的代码流时。

混合异常和错误代码是非常合理的,其中错误代码表示某些内容的状态,而不是代码本身运行中的错误(例如,检查来自子进程的返回代码)。

但是当出现异常情况时,我认为例外是最具表现力的模式。

在某些情况下,您可能更喜欢或不得使用错误代码来代替异常,这些已经被充分涵盖(除了其他明显的约束,例如编译器支持)。

但是,在另一个方向上,使用Exceptions可以为错误处理构建更高级别的抽象,这可以使您的代码更具表现力和自然。我强烈建议阅读这篇由C ++专家Andrei Alexandrescu撰写的关于他所称的主题的优秀但被低估的文章,<!>“Enforcements <!>”: http://www.ddj.com/cpp/184403864 。虽然这是一篇C ++文章,但这些原则通常是适用的,我已经将强制执行概念非常成功地翻译成了C#。

首先,我同意Tom的答案,对于高级别的东西使用异常,以及对于低级别的东西使用错误代码,只要它不是面向服务的体系结构(SOA)。

在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
    };
}

当在服务响应中一致地使用它们时,它会在应用程序中创建处理成功/失败的非常好的模式。这样可以在服务内和服务中的异步调用中更轻松地处理错误。

我更喜欢所有错误情况的异常,除非失败是返回原始数据类型的函数的可预期的无错误结果。例如。如果找不到,则在较大字符串中查找子字符串的索引通常会返回-1,而不是引发NotFoundException。

返回可能被解除引用的无效指针(例如,在Java中导致NullPointerException)是不可接受的。

使用多个不同的数字错误代码(-1,-2)作为同一函数的返回值通常是错误的样式,因为客户端可能会执行<!>“== -1 <!>”;检查而不是<!> quot; <!> lt; 0 QUOT <!>;

这里要记住的一件事是API随着时间的推移而发展。良好的API允许以多种方式更改和扩展故障行为,而不会破坏客户端。例如。如果客户端错误句柄检查了4个错误情况,并且您向函数添加了第五个错误值,则客户端处理程序可能无法测试此错误并中断。如果您引发了例外,这通常会使客户更容易迁移到更新版本的库。

另一件需要考虑的事情是,在团队中工作时,为所有开发人员制定明确界限以做出这样的决定。例如。 <!>“高级别内容的例外情况,低级别内容的错误代码<!>”;是非常主观的。

在任何情况下,如果可能存在多个简单类型的错误,源代码绝不应该使用数字文字来返回错误代码或处理错误代码(如果x == -7则返回-7 ... ),但总是一个命名常量(返回NO_SUCH_FOO,如果x == NO_SUCH_FOO)。

如果您在大项目下工作,则不能仅使用例外或仅使用错误代码。在不同情况下,您应该使用不同的方法。

例如,您决定仅使用例外。但是一旦你决定使用异步事件处理。在这种情况下使用异常进行错误处理是个坏主意。但是在应用程序中到处使用错误代码是单调乏味的。

所以我认为同时使用异常和错误代码是正常的。

对于大多数应用程序,例外情况更好。例外情况是软件必须与其他设备通信。我工作的领域是工业控制。这里错误代码是首选和预期的。所以我的答案是它确实取决于具体情况。

我认为这也取决于你是否真正需要的信息,如栈跟踪结果。如果是的话,你肯定去除其提供对象,充满大量的信息有关的问题。但是,如果你们只是感兴趣的结果,并不在乎为何这一结果,然后去的错误代码。

例如当你处理文件和面IOException,客户可能会有兴趣知道,从那里这是引发,在打开文件或分析文件等。所以最好你回IOException或其特定子类。然而,情景喜欢你必须登录的方法和你想知道它是成功的,无论是你只是返回布尔或显示正确的消息,返回的错误代码。这里的客户是不是有兴趣知道哪个部分的逻辑所引起的错误代码。他只知道如果其证无效或账户锁等。

另一个用例我可以认为是在数据传播的网络。你的远程方法可以返回刚错误代码,而不是例外,以尽量减少数据传送。

我的一般规则是:

  • 只有一个错误可能出现在一个功能:使用错误代码(如参数的函数)
  • 多于一个特定的错误可能出现:扔异常

当您的方法返回除数值以外的任何内容时,错误代码也不起作用...

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top