什么时候抛出异常?


435

我为我的应用程序不希望遇到的每种情况创建了异常。 UserNameNotValidExceptionPasswordNotCorrectException等等。

但是,我被告知我不应该为这些条件创建例外。在我的UML中,那些是主流的例外,那么为什么不应该例外呢?

创建例外有任何指导或最佳做法吗?


58
请重新打开,这是一个非常明智和有效的问题。任何问题都涉及一定程度的意见,但在这种情况下,我怀疑这是“最佳实践”的问题。
Tim Long

19
+1重新打开。正如许多其他有趣的主题“取决于”一样,在做出决策时分析权衡非常有用。人们将答案与答案中的事实相混淆的事实并不能否认这一点。筛选泥浆是一项练习,应留给读者。
阿隆

12
我也同意应该重新讨论这个问题,因为它与最佳做法有关。顺便说一句,最佳实践始终是可以帮助他人的意见。
Ajay Sharma 2015年

6
微软说:“不要返回错误代码。异常是报告框架错误的主要方法。” 和“ ...如果成员无法成功执行其设计要执行的操作,则应将其视为执行失败并应引发异常。”。msdn.microsoft.com/library/ms229030%28v=vs.100%29.aspx
Matsen75,2015年

4
这些可能是完全明智的例外,它仅取决于将其抛出的方法。IsCredentialsValid(username,password)如果用户名或密码无效,则称为的方法不应引发异常,而应返回false。但是可以说,如果身份验证失败,则从数据库读取数据的方法可以合法地抛出此类异常。简而言之:如果某个方法无法执行应执行的任务,则应抛出异常。
JacquesB '16

Answers:


629

我的个人指导原则是:当发现当前代码块的基本假设为假时,将引发异常。

示例1:说我有一个函数,应该检查一个任意类,如果该类继承自List <>,则返回true。该函数会询问以下问题:“此对象是List的后代吗?” 此函数永远不会抛出异常,因为它的操作中没有灰色区域-每个类都可以从List <>继承或不从List <>继承,因此答案始终是“是”或“否”。

示例2:说我有另一个函数,该函数检查List <>,如果其长度大于50,则返回true;如果长度小于50,则返回false。此功能会询问以下问题:“此列表是否有50多个项目?” 但是这个问题是一个假设-假设给定的对象是一个列表。如果我将其设置为NULL,则该假设为假。在这种情况下,如果函数返回无论是还是假的,那么它是打破自己的规则。该函数无法返回任何内容并声称它已正确回答了问题。因此它不会返回-引发异常。

这可与“加载的问题”逻辑谬误相提并论。每个功能都会问一个问题。如果给出了输入,则该问题将成为谬论,然后引发异常。使用返回void的函数更难画这条线,但最重要的是:如果违反了函数关于其输入的假设,则应抛出异常而不是正常返回。

等式的另一面是:如果您发现函数经常抛出异常,那么您可能需要完善它们的假设。


15
究竟!当且仅当函数前提条件(关于参数的假设)被破坏时,才会引发异常!
Lightman

11
在语言学中,这有时被称为预设失败。经典的例子是由于伯特兰·罗素(Bertrand Russell)造成的:“是法国国王的秃头”无法回答是或否,(分别是“法国国王秃头的”既不是真也不是假),因为它包含错误的预设,即有一位法国国王。预设失败经常出现在明确的描述中,这在编程时很常见。例如,当列表为空时,“列表的头”会出现预设失败,然后抛出异常是适当的。
Mohan

这可能是最好的解释!
gaurav

谢谢。所以。许多。“是法国国王秃头”。在研究美浓的丛林之前,我已经听说过这个。...:)谢谢。@Mohan
ErlichBachman

285

因为它们是正常发生的事情。异常不是控制流机制。用户经常输入错误的密码,这不是例外情况。例外情况应该是一种罕见的UserHasDiedAtKeyboard情况,类型情况。


3
嗯不 如果不需要最大性能,则可以将异常用作控制流机制,这对于大多数Web应用程序都是如此。Python使用异常'StopIteration'终止迭代器,并且效果很好。成本是没有什么比IO等
Seun Osewa

9
+1个极好的答案。我不得不使用API​​的开发人员感到非常沮丧,不得不为每一个小事情抛出异常。很少有情况确实需要例外。如果定义了25种不同的异常,请再次查看设计,这可能是错误的。
2011年

1
当用户尝试通过操作网页代码不允许其执行的非法操作时,是否应该有例外,例如,删除StackOverflow上某人的其他帖子?
Rajat Gupta

30
例外是控制流机制。你可以扔他们。你可以抓住他们。您将控件移至另一段代码。那就是控制流程。语言完全具有异常的唯一原因是,您可以编写简单的代码而无需询问“事情是否失败了?”。经过你做的一切。例如,Haskell没有例外,因为单子和do-notation可以为您自动进行错误检查。
杰西

2
异常不仅仅是控制流机制。他们向(方法)客户提供有关必须意识到和处理的异常结果的有用信息。也就是说,如果使用得当,异常会使API更加健壮
idelvall

67

我的一些小指导方针深受伟大的“代码完成”书的影响:

  • 使用异常通知不应该忽略的事情。
  • 如果错误可以在本地处理,请不要使用异常
  • 确保异常与例程的其余部分处于相同的抽象级别。
  • 对于真正例外的情况,应保留例外

35

如果用户名无效或密码不正确,也不例外。这些是您在正常操作流程中应该期望的。异常是不属于正常程序操作的部分,很少见。

编辑:我不喜欢使用异常,因为您不能仅通过查看调用就知道方法是否引发异常。这就是为什么仅在您不能以适当的方式处理这种情况(例如“内存不足”或“计算机着火”)时才应使用异常的原因。


“我不喜欢使用异常,因为您不能仅通过查看调用就知道方法是否抛出异常。”这就是为什么支持这些语言的语言都有经过检查的异常。
Newtopian

1
检查异常有其自身的一系列问题。我还是宁愿使用“特殊情况”的例外,而不是正常工作流程中的某些事情。
EricSchaefer,2009年

2
回应您的编辑。我总是将我的xml文档放在摘要部分的末尾,该函数引发的异常使我可以在intellisense中看到该信息。
马修·维尼斯

1
当用户尝试通过操作网页代码不允许其执行的非法操作时,是否应该有例外,例如,删除StackOverflow上某人的其他帖子?
拉贾特·古普塔

1
听起来您正在谈论处理错误(内存,计算机着火)与处理代码异常(缺少记录,无效的输入类型等)。我认为两者之间有明显的区别。
查克·伯吉斯

28

经验法则是在通常无法预测的情况下使用异常。例如数据库连接性,磁盘上缺少文件等。对于可以预测的情况,即,尝试使用错误密码登录的用户,应该使用返回布尔值并知道如何妥善处理情况的函数。您不希望仅仅因为有人输错密码而引发异常而突然终止执行。


6
您不必在发生异常时停止程序执行...引发异常,然后调用方捕获异常并应对其进行处理(如果可能),记录并出错并继续。只是不断在调用堆栈上
引发

2
但是如果可以直接处理,为什么还要扔掉它们。如果密码错误或任何错误,我只允许if返回一个错误并给出一个错误
My1

磁盘上缺少文件 ”大多数语言框架(例如.NET框架)也提供API来检查文件是否存在。为什么在直接访问文件之前不使用它们!
user1451111

23

其他人则建议不要使用异常,因为如果用户键入错误,正常的登录流程将导致错误的登录。我不同意,也没有理由。将其与打开文件进行比较。.如果该文件不存在或由于某种原因不可用,则框架将引发异常。使用上面的逻辑,这是Microsoft的错误。他们应该返回了错误代码。解析,webrequests等都相同。

我不认为正常登录过程中的登录错误是很正常的。通常,用户键入正确的密码,并且该文件确实存在。例外情况是例外情况,对于这些情况使用例外是完全可以的。通过在堆栈的n个层中传播返回值来使代码复杂化,这是在浪费能量,并且会导致代码混乱。做可能可行的最简单的事情。不要通过使用错误代码来过早地进行优化,从定义上讲,很少会发生异常情况,除非抛出异常,否则异常不会花费任何代价。


除非您可以在调用open前检查文件是否存在(当然,这取决于您的框架)。因此,该功能存在,因此,如果文件在检查之间消失,而您尝试打开它,则是例外。
blowdart

7
现有文件并不意味着例如允许用户写入文件。检查每个可能的问题确实很乏味且容易出错。+您正在复制代码(DRY)。
比约恩·雷彭

无效密码异常的一个要点是,与人类输入密码相比,与返回码解决方案相比,任何缓慢都不会被察觉。
paperhorse

7
“通过将返回值传播到堆栈的n层而使代码复杂化,这是浪费能量,并且会导致代码混乱。” 对我来说,这是使用异常的非常有力的理由。好的代码通常由小的功能组成。您不想将错误代码一遍又一遍地传递给另一个小函数。
beluchin 2011年

我认为,这种混淆是由以下假设引起的:login-type方法的可预测结果可能是密码可能不正确,实际上可以使用密码来确定密码,并且在这种情况下不希望出现异常。而在file open有特殊结果的类型场景中-如果系统由于错误的输入参数或某些外部因素而无法传递结果,则这是对异常的合理使用。
theMayer

17

异常的代价会有些高昂,例如,如果您有一个用户提供了无效的密码,则通常最好将其传递回故障标志或其他指示其无效的标志。

这是由于异常的处理方式,真正的错误输入和唯一的紧急停止项目应为异常,但不是失败的登录信息。


14

我认为您只能在无法采取任何措施摆脱当前状态时抛出异常。例如,如果您正在分配内存而没有要分配的任何内存。在您提到的情况下,您可以清楚地从这些状态中恢复,并可以将错误代码相应地返回给调用方。


您将看到很多建议,包括对该问题的答案,包括仅在“例外”情况下才应抛出异常。从表面上看,这是合理的,但却是有缺陷的建议,因为它用另一个主观问题(“什么是例外”)代替了一个问题(“我何时应该抛出例外”)。相反,请遵循Herb Sutter的建议(对于C ++,可在Dobbs博士的文章《何时和如何使用异常》中找到,以及在他与Andrei Alexandrescu一起写的书中,C ++编码标准):仅当且仅当抛出异常

  • 不满足前提条件(通常使以下条件之一变为不可能)或
  • 替代方案将无法满足后置条件,或者
  • 替代方案将无法保持不变。

为什么这样更好?它不是用关于前置条件,后置条件和不变式的几个问题代替问题吗?由于几个相关的原因,这样做更好。

  • 前提条件,后置条件和不变式是我们程序(其内部API)的设计特征,而对的决定throw则是实现细节。它迫使我们记住,我们必须分开考虑设计及其实现,而在实现一种方法的同时,我们的工作就是生产出满足设计约束的产品。
  • 它迫使我们根据前置条件,后置条件和不变式进行思考,这是我们的方法的调用者应该做出的唯一假设,并且必须精确地表达它们,从而使程序各组件之间的松散耦合成为可能。
  • 然后,这种松散的耦合使我们可以在必要时重构实现。
  • 后置条件和不变式是可测试的;因为后置条件是我们的单元测试代码可以检查(断言)的谓词,因此它可以轻松地对单元代码进行测试。
  • 根据后置条件思考自然会产生一个成功的设计,作为后置条件,这是使用异常的自然风格。程序的正常(“快乐”)执行路径是线性排列的,所有错误处理代码都移到了这些catch子句中。

10

我会说,关于何时使用异常没有严格的规定。但是,有使用或不使用它们的充分理由:

使用异常的原因:

  • 常见情况的代码流更加清晰
  • 可以将复杂的错误信息作为对象返回(尽管也可以使用引用传递的错误“ out”参数来实现)
  • 语言通常提供一些用于在发生异常时管理整洁的工具(在Java中尝试/最终在Java中使用,在C#中使用,在C ++中使用RAII)
  • 如果没有异常抛出,执行有时会比检查返回码更快
  • 在Java中,必须声明或捕获已检查的异常(尽管这可能是反对的原因)

不使用例外的原因:

  • 如果错误处理很简单,有时会过分杀伤力
  • 如果未记录或声明异常,则调用代码可能无法捕获这些异常,这可能比调用代码只是忽略返回代码的情况更糟(应用程序退出vs静默失败-取决于情况)
  • 在C ++中,使用异常的代码必须是异常安全的(即使您不抛出或捕获它们,而是间接调用抛出函数)
  • 在C ++中,很难判断何时会抛出函数,因此如果使用它们,您必须对异常安全性抱有幻想
  • 与检查返回标志相比,抛出和捕获异常通常要贵得多

通常,与C ++或C#相比,我更倾向于在Java中使用异常,因为我认为已声明或未声明的异常从根本上来说是函数形式接口的一部分,因为更改异常保证可能中断调用代码。在Java IMO中使用它们的最大优点是,您知道调用方必须处理异常,这会增加正确行为的机会。

因此,在任何语言中,我总是会从一个通用类中在代码或API层中派生所有异常,以便调用代码始终可以保证捕获所有异常。我也认为在编写API或库时抛出特定于实现的异常类是不好的(即包装来自较低层的异常,以便在接口的上下文中可以理解调用者收到的异常)。

请注意,Java区分了常规异常和运行时异常,因为后者无需声明。仅当您知道错误是程序中的错误导致的时,我才使用运行时异常类。


5

异常类就像“普通”类。当它是不同类型的对象,具有不同的字段和不同的操作时,可以创建一个新类。

根据经验,应尝试在例外数量和例外粒度之间取得平衡。如果您的方法抛出超过4-5个不同的异常,则可以将其中一些合并为更多的“一般”异常(例如,在您的情况下为“ AuthenticationFailedException”),然后使用异常消息详细说明出了什么问题。除非您的代码以不同的方式处理它们中的每一个,否则您无需创建许多异常类。如果这样做的话,也许您应该只返回一个包含所发生错误的枚举。这样比较干净。


5

如果代码在循环中运行可能会一遍又一遍地引发异常,则抛出异常不是一件好事,因为对于大的N来说,它们相当慢。但是,如果性能不佳,则抛出自定义异常也没有错。一个问题。只要确保您拥有它们都继承的基本异常,即BaseException之类。BaseException继承了System.Exception,但是您所有的异常都继承了BaseException。您甚至可以使用异常类型树来对相似类型进行分组,但这可能会或可能不会显得过大。

因此,简短的答案是,如果它不会导致明显的性能下降(除非您抛出很多异常,否则不会出现),那么请继续。


我真的很喜欢您对循环内异常的评论,并想自己尝试一下。我编写了运行循环int.MaxValue时间并在其中生成“被零除”异常的示例程序。在IF / ELSE版本中,我正在除法运算之前检查股息是否不为零,它在6082 ms和15407722个滴答中完成,而在TRY / CATCH版本中,我正在生成异常并捕获异常,它在28174385中完成ms和71371326155的滴答声:比if / else版本高4632倍。
user1451111

3

我同意japollock的看法-当您不确定手术的结果时,请接受。调用API,访问文件系统,数据库调用等。每当您越过编程语言的“边界”时。

我想补充一下,随意抛出一个标准异常。除非您要做“不同”的事情(忽略,发送电子邮件,记录日志,显示推特鲸鱼图片很杂物等),否则不要为自定义异常而烦恼。


3

抛出异常的经验法则很简单。当您的代码已进入“无法恢复的无效”状态时,您可以这样做。如果数据遭到破坏,或者您无法回退到该点为止的处理,则必须终止它。确实,您还能做什么?您的处理逻辑最终将在其他地方失败。如果您可以以某种方式恢复,请执行此操作,并且不要抛出异常。

在您的特殊情况下,如果您被迫做一些愚蠢的事情,例如接受取款,然后检查用户名/密码,则应抛出异常以通知已发生不良情况并防止进一步损坏,从而终止该过程。


2

通常,您希望对应用程序中可能发生的“异常”事件抛出异常。

在您的示例中,这两个异常都看起来像您是通过密码/用户名验证来调用它们的。在这种情况下,有人会输错用户名/密码并没有什么异常。

它们是UML主流的“例外”,但在处理中更“分支”。

如果您尝试访问passwd文件或数据库而无法访问,那将是一个例外情况,并且可能引发异常。


如果无法访问passwd文件或数据库,那将是一个例外情况,并且会引发异常。 ”大多数语言框架(例如.NET框架)也提供API,以检查文件是否存在。为什么在直接访问文件之前不使用它们!
user1451111

2

首先,如果您的API用户对特定的细粒度故障不感兴趣,那么为它们指定特殊的异常就没有任何价值。

由于通常无法知道什么可能对您的用户有用,因此更好的方法是拥有特定的异常,但要确保它们从通用类继承(例如std :: exception或C ++中的派生类)。这样,您的客户可以选择特定的异常,如果他们愿意,可以捕获更一般的异常。


2

异常适用于异常行为,错误,故障等事件。功能行为,用户错误等应改为由程序逻辑处理。由于错误的帐户或密码是登录例程中逻辑流程的预期组成部分,因此它应该能够毫无例外地处理这些情况。


2

我在使用例外方面有哲学上的问题。基本上,您期望会发生特定的情况,但是您没有明确地处理它,而是将问题推到了“别处”。任何人都可以猜到“其他地方”在哪里。


2

我会说,一般而言,所有原教旨主义都会导致地狱。

您当然不希望以异常驱动流结束,但是完全避免异常也是一个坏主意。您必须在两种方法之间找到平衡。我不会为每种异常情况创建一个异常类型。那没有生产力。

我通常更喜欢创建两种在整个系统中使用的基本异常类型:LogicalExceptionTechnicalException。如果需要,可以通过子类型进一步区分这些类型,但是通常没有必要。

技术异常表示真正出乎意料的异常,例如数据库服务器关闭,与Web服务的连接引发IOException等。

另一方面,逻辑异常用于将不太严重的错误情况传播到上层(通常是一些验证结果)。

请注意,即使是逻辑异常,也不打算定期使用它来控制程序流程,而是要强调流程真正结束的情况。在Java中使用时,两种异常类型都是RuntimeException子类,并且错误处理高度面向方面。

因此,在登录示例中,创建诸如AuthenticationException之类的内容并通过枚举值(如UsernameNotExistingPasswordMismatch等)来区分具体情况可能是明智的。然后,您将不会陷入庞大的异常层次结构,并且可以将catch块保持在可维护的水平上。您还可以轻松地采用某种通用的异常处理机制,因为您已对异常进行了分类,并且非常了解向用户传播的内容以及传播方式。

我们的典型用法是在用户输入无效时在Web Service调用期间引发LogicalException。异常被编组到SOAPFault详细信息,然后再次在客户端上被编组到异常,这导致在一个特定的网页输入字段上显示验证错误,因为该异常已正确地映射到该字段。

这当然不是唯一的情况:您无需点击Web服务即可引发异常。在任何特殊情况下(例如需要快速失败的情况),您都可以自由进行操作-这完全由您决定。


2

对我来说,当必需的技术或业务规则失败时,应引发异常。例如,如果一个汽车实体与4个轮胎的数组相关联...如果一个或多个轮胎为null ...则应引发“ NotEnoughTiresException”异常,因为它可以在系统的不同级别捕获并且具有显着性通过记录的意义。此外,如果我们只是尝试对空值进行流量控制并防止汽车实例化。我们可能永远找不到问题的根源,因为首先轮胎不应该是空的。



1

抛出异常会导致堆栈崩溃,这会对性能产生一些影响(公认的现代托管环境对此有所改善)。在嵌套情况下仍然反复抛出和捕获异常将是一个坏主意。

可能比这更重要的是,异常是针对特殊条件的。它们不应用于普通的控制流,因为这会损害代码的可读性。


1

我遇到三种情况。

  1. 输入错误或丢失也不例外。使用客户端js和服务器端regex来检测,设置属性并转发回带有消息的同一页面。

  2. AppException。通常这是您在代码中检测到并抛出的异常。换句话说,这些是您期望的(文件不存在)。记录它,设置消息,然后转发回常规错误页面。该页面通常包含一些有关发生的情况的信息。

  3. 意外的异常。这些是您不知道的。记录详细信息,并将其转发到常规错误页面。

希望这可以帮助


1

安全性与您的示例相混淆:您不应告诉攻击者用户名存在,但密码错误。这是您无需共享的额外信息。只需说“用户名或密码不正确”。


1

一个简单的答案是,每当不可能执行某项操作时(由于任一应用程序或因为它可能违反业务逻辑)。如果调用了某个方法而无法执行该方法编写的操作,则抛出Exception。一个很好的例子是,如果无法使用提供的参数创建实例,则构造函数始终会引发ArgumentExceptions。另一个示例是InvalidOperationException,当由于另一个成员或类成员的状态而无法执行操作时,抛出该异常。

在您的情况下,如果调用诸如Login(用户名,密码)之类的方法,则如果用户名无效,则抛出UserNameNotValidException确实是正确的;如果密码不正确,则抛出PasswordNotCorrectException是正确的。用户无法使用提供的参数登录(即不可能,因为它会违反身份验证),因此请抛出异常。虽然我可能让您的两个Exception从ArgumentException继承。

话虽如此,如果您不希望因为登录失败很常见而抛出异常,一种策略是改为创建一种返回表示不同失败类型的方法。这是一个例子:

{ // class
    ...

    public LoginResult Login(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            return new UserInvalidLoginResult(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            return new PasswordInvalidLoginResult(user, password);
        }
        else
        {
            return new SuccessfulLoginResult();
        }
    }

    ...
}

public abstract class LoginResult
{
    public readonly string Message;

    protected LoginResult(string message)
    {
        this.Message = message;
    }
}

public class SuccessfulLoginResult : LoginResult
{
    public SucccessfulLogin(string user)
        : base(string.Format("Login for user '{0}' was successful.", user))
    { }
}

public class UserInvalidLoginResult : LoginResult
{
    public UserInvalidLoginResult(string user)
        : base(string.Format("The username '{0}' is invalid.", user))
    { }
}

public class PasswordInvalidLoginResult : LoginResult
{
    public PasswordInvalidLoginResult(string password, string user)
        : base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
    { }
}

大多数开发人员都被教导要避免抛出异常,因为抛出异常会导致开销。意识到资源非常好,但是通常不会以牺牲应用程序设计为代价。这可能是您被告知不要抛出两个异常的原因。是否使用异常通常可以归结为异常发生的频率。如果这是一个相当普遍或相当可预期的结果,那么这是大多数开发人员会避免使用Exception,而是由于假定的资源消耗而创建另一个方法来指示失败的时候。

这是一个避免使用Try()模式在上述情况下使用异常的示例:

public class ValidatedLogin
{
    public readonly string User;
    public readonly string Password;

    public ValidatedLogin(string user, string password)
    {
        if (IsInvalidUser(user))
        {
            throw new UserInvalidException(user);
        }
        else if (IsInvalidPassword(user, password))
        {
            throw new PasswordInvalidException(password);
        }

        this.User = user;
        this.Password = password;
    }

    public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
    {
        if (IsInvalidUser(user) || 
            IsInvalidPassword(user, password))
        {
            return false;
        }

        validatedLogin = new ValidatedLogin(user, password);

        return true;
    }
}

1

在我看来,最基本的问题应该是,如果出现某种情况,是否可以期望调用者继续正常的程序流程。如果您不知道,则可以使用单独的doSomething和trySomething方法(前者返回错误,而后者不返回错误),或者具有接受参数的例程以指示如果失败则应引发异常。考虑一个类,用于将命令发送到远程系统并报告响应。某些命令(例如,重新启动)将导致远程系统发送响应,但在一定时间内无响应。因此,能够发送“ ping”命令并​​找出远程系统是否在合理的时间长度内做出响应,而不必抛出异常,则非常有用。t(呼叫者可能希望前几次“ ping”尝试失败,但最终还是可以的)。另一方面,如果有一系列命令,例如:

  exchange_command(“ open tempfile”);
  exchange_command(“写入临时文件数据{任何内容”“);
  exchange_command(“写入临时文件数据{任何内容”“);
  exchange_command(“写入临时文件数据{任何内容”“);
  exchange_command(“写入临时文件数据{任何内容”“);
  exchange_command(“ close tempfile”);
  exchange_command(“将临时文件复制到实文件”);

人们会希望任何操作失败都会中止整个序列。尽管可以检查每个操作以确保操作成功,但如果命令失败,则让exchange_command()例程抛出异常会更有用。

实际上,在上述情况下,具有一个参数来选择许多故障处理模式可能会有所帮助:从不抛出异常,仅针对通信错误抛出异常,或者在命令未返回“成功”的任何情况下抛出异常”指示。


1

“ PasswordNotCorrectException”不是使用异常的好例子。用户输入错误的密码是可以预料的,因此恕我直言几乎不是例外。您甚至可能从中恢复,并显示一条不错的错误消息,因此这只是有效性检查。

未处理的异常最终将停止执行-这很好。如果返回的错误,空值或错误代码,则必须自己处理程序的状态。如果忘记检查某处的状况,则程序可能会继续使用错误的数据运行,并且可能很难弄清发生了什么情况以及在哪里发生

当然,使用空的catch语句可能会导致相同的问题,但是至少发现它们比较容易,并且不需要您了解逻辑。

因此,根据经验:

在您不想要的地方或根本无法从错误中恢复的地方使用它们。


0

对于这种情况,您可以使用一些通用的例外。例如,当方法的参数出现任何问题(ArgumentNullException除外)时,应使用ArgumentException。通常,您不需要像LessThanZeroException,NotPrimeNumberException等异常。请考虑您的方法的用户。她将要专门处理的条件数量等于您的方法需要引发的异常类型的数量。这样,您可以确定要处理的异常的详细程度。

顺便说一句,请始终尝试为库的用户提供一些避免异常的方法。TryParse是一个很好的例子,它存在,因此您不必使用int.Parse并捕获异常。对于您的情况,您可能需要提供一些方法来检查用户名是否有效或密码是否正确,以便您的用户(或您)将不必进行大量异常处理。希望这将导致更多可读的代码和更好的性能。


0

最终,决定权归于使用异常处理来解决应用程序级错误是否更有用,还是通过自己返回的状态代码(例如返回状态代码)机制。我不认为有哪个更好的硬性规定,但我会考虑:

  • 谁在调用您的代码?这是某种公共API还是内部库?
  • 您使用什么语言?例如,如果使用的是Java,则抛出一个(已检查的)异常会给您的调用方以某种方式处理此错误情况的负担,而不是可以忽略的返回状态。那可能是好事或坏事。
  • 如何处理同一应用程序中的其他错误情况?调用者将不希望处理一个以特殊方式处理错误的模块,这不同于系统中的任何其他模块。
  • 所讨论的例程可能导致多少错误,如何处理它们?考虑一系列处理不同错误的catch块与切换错误代码之间的区别。
  • 您是否有关于需要返回的错误的结构化信息?抛出异常为您提供了比仅返回状态更好的放置此信息的位置。

0

主要有两类例外:

1)系统异常(例如,数据库连接丢失)或2)用户异常。(例如,用户输入验证,“密码不正确”)

我发现创建自己的用户异常类很有帮助,当我要抛出用户错误时,我想以不同的方式处理(即向用户显示资源错误),那么我在主要错误处理程序中要做的就是检查对象类型:

            If TypeName(ex) = "UserException" Then
               Display(ex.message)
            Else
               DisplayError("An unexpected error has occured, contact your help  desk")                   
               LogError(ex)
            End If

0

在确定异常是否合适时需要考虑的一些有用的事情:

  1. 候选异常发生后,您希望运行什么级别的代码-也就是说,调用栈应该展开多少层。通常,您希望尽可能在发生异常的地方处理异常。对于用户名/密码验证,通常可以在同一代码块中处理失败,而不是让异常冒出来。因此,异常可能不合适。(OTOH,在三次失败的登录尝试之后,控制流可能会转移到其他位置,这里可能有例外。)

  2. 您是否想在错误日志中看到此事件?并非所有异常都写入错误日志,但询问错误日志中的该条目是否有用很有用-即,您将尝试对此做某事,或者是您忽略的垃圾。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.