最终尝试使用(无捕获)vs枚举状态验证


9

我一直在阅读有关此问题的建议,即应如何在尽可能接近引发异常的地方处理异常。

我对最佳做法的两难选择是,是否应该使用try / catch / finally返回一个枚举(或一个表示值的int,0表示错误,1表示ok,2表示警告等,视情况而定),以便一个答案总是有序的,还是应该让异常通过,以便调用方可以处理它?

据我所知,根据具体情况,这可能会有所不同,因此最初的建议似乎很奇怪。

例如,在Web服务上,您总是希望返回一个状态,因此必须当场处理任何异常,但是可以说在通过http发布/获取某些数据的函数中,您希望例外(例如404情形)传递给触发它的人。如果不这样做,则必须创建某种方式来告知调用方结果的质量(错误:404)以及结果本身。

尽管可以在获取/发布数据的帮助程序函数中尝试捕获404异常,但是应该吗?难道只有我一个人使用smallint表示程序中的状态(并适当地记录它们),然后将此信息用于外部的健全性验证(一切正常/错误处理)吗?

更新:我期待主要分类出现致命/非致命的异常,但是我不想包括该异常,以免影响答案。让我澄清一下问题所在:处理引发的异常,而不是引发异常。预期的效果是:检测到错误,然后尝试从中恢复。如果无法恢复,请提供最有意义的反馈。

同样,对于http get / post示例,问题是,是否应该提供一个新对象来描述原始调用者发生了什么?如果此帮助程序在您正在使用的库中,您是否希望它为您提供操作的状态代码,还是将其包含在try-catch块中?如果您正在设计它,您是否会提供状态代码或引发异常,然后让上级将其转换为状态代码/消息?

简介:您如何选择一段代码而不是产生异常,而是返回状态代码以及可能产生的任何结果?


1
那不是在处理改变正在使用的错误处理形式的错误。在404情况下,您将使其通过,因为您无法处理它。
stonemetal

“一个表示值的int,0表示错误,1表示正常,2表示警告等”,请说这是一个现成的示例!用0表示确定绝对是标准的标准...
anaximander

Answers:


15

例外情况应使用例外。引发异常基本上是在声明:“我在这里无法处理这种情况;调用堆栈中更高级别的人可以为我抓住并处理吗?”

如果很明显,调用者将使用该值并对它进行有意义的操作,则最好返回一个值。如果抛出异常对性能有影响,那就是尤其如此,即,异常可能发生在紧密的循环中。抛出异常比返回值要花费更长的时间(至少两个数量级)。

绝对不要使用异常来实现程序逻辑。换句话说,不要抛出异常来完成某件事。抛出一个异常,指出它无法完成。


感谢您的答复,它是最有用的信息,但我的重点是异常处理,而不是异常引发。您是否应该在收到404异常后立即捕获它,还是应该让它在堆栈中更高?
Mihalis Bagos

我看到了您的修改,但并没有改变我的答案。如果对调用者有意义,则将异常转换为错误代码。如果不是,则处理异常;让它上升到堆栈上;或捕获它,对其进行处理(例如将其写入日志或其他操作),然后重新抛出。
罗伯特·哈维

1
+1:提供合理,平衡的解释。应该使用异常来处理特殊情况。否则,函数或方法应返回有意义的值(枚举或选项类型),并且调用方应正确处理它。也许有人会提到在函数式编程中很流行的第三种选择,即延续。
乔治

4

我曾经读过的一些好建议是,在给定您要处理的数据状态时,如果无法进行处理,则引发异常;但是,如果您有可能引发异常的方法,则还应尽可能提供断言数据是否确实是实际的方法。在调用该方法之前有效。

例如,如果提供的文件不存在,System.IO.File.OpenRead()将抛出FileNotFoundException,但是它还提供了.Exists()方法,该方法返回一个布尔值,该值指示是否存在该文件,您应该在调用该文件之前调用该方法。调用OpenRead()以避免任何意外的异常。

要回答问题的“何时应处理例外”部分,我想说的是您实际上可以在其中做些什么。如果您的方法无法处理其调用的方法引发的异常,请不要捕获它。让它将更高的呼叫链提升到可以处理的水平。在某些情况下,这可能只是记录器在侦听Application.UnhandledException。


许多人,尤其是python程序员,更喜欢EAFP,即“请求宽恕比获得许可要容易得多”
Mark Booth

1
+1表示有关避免与.Exists()一样的异常的评论。
codingoutloud

+1这仍然是个好建议。在我编写/管理的代码中,Exception是“ Exceptional”,是要供开发人员查看的Exception的9/10倍,它说,嘿,您应该是defensivley编程!另外1次,这是我们无法处理的事情,我们将其记录下来,并尽最大可能退出。一致性很重要,例如,按照惯例,我们通常会有一个真实的错误响应,以及用于标准票价/处理过程的内部消息。似乎引发所有异常的第三方api可以在调用时进行处理,并使用标准约定的流程返回。
加文·豪顿2015年

3

使用try / catch / finally返回一个枚举(或一个表示值的int,0表示错误,1表示ok,2表示警告,视情况而定),以便始终有一个正确的答案,

这是一个糟糕的设计。不要通过翻译成数字代码来“掩盖”异常。将其保留为适当,明确的例外。

还是应该让异常通过,以便调用部分处理该异常?

那就是例外。

处理尽可能靠近它的位置

是不是一个普遍的真理。有时候这是个好主意。有时它没有帮助。


这不是一个糟糕的设计。Microsoft在许多地方(即默认的asp.NET成员资格提供程序)实现了它。除此之外,我看不出此答案如何有助于对话
Mihalis Bagos 2012年

@MihalisBagos:我所能做的就是建议并非每种编程语言都采用Microsoft的方法。在无例外的语言中,返回值至关重要。C是最著名的例子。在有例外的语言中,返回“代码值”以指示错误是一个糟糕的设计。它导致(有时)笨拙的if语句,而不是(有时)更简单的异常处理。答案(“如何选择...”)很简单。 不要。我想说这增加了对话。但是,您可以随意忽略此答案。
S.Lott 2012年

我并不是说您的观点不重要,而是您的观点没有得到发展。有了这样的评论,我认为原因是,在可以使用异常的地方,我们应该仅仅因为可以?我不认为状态代码是掩盖的,而不是代码流案例的分类/组织
Mihalis Bagos 2012年

1
异常已经是一个分类/组织。我看不到用可以轻易与“正常”或“非例外”返回值混淆的返回值替换明确的异常的值。返回值应始终为非异常。我的意见不是很明确:很简单。不要将异常转换为数字代码。它已经是一个非常好的数据对象,可以满足我们可以想象的所有非常好的用例。
S.Lott

3

我同意S.Lott的观点。在尽可能接近源的位置捕获异常可能是一个好主意,也可能是一个坏主意,具体取决于情况。处理异常的关键是仅在您可以对其进行处理时才捕获它们。捕获它们并将数字值返回给调用函数通常是一个糟糕的设计。您只想让它们浮起,直到您可以恢复。

我一直认为异常处理距我的应用程序逻辑仅一步之遥。我问自己,如果引发此异常,那么我的应用程序处于可恢复状态之前,我必须爬取多远的调用堆栈?在很多情况下,如果我无法在应用程序中执行任何操作来恢复,那可能意味着我要等到顶级并记录异常,使工作失败并尝试彻底关闭后,才能捕获它。

除了何时将异常处理搁置,直到您知道如何处理异常处理之外,对于何时以及如何设置异常处理确实没有严格的规定。


1

例外是美好的事物。它们使您可以对运行时问题进行清晰的描述,而无需采取不必要的歧义。可以对异常进行类型化,子类型化,并可以按类型进行处理。可以将它们传递到其他地方进行处理,如果无法处理它们,则可以将它们重新引发以在应用程序的更高层进行处理。它们还将自动从您的方法中返回,而无需调用许多疯狂的逻辑来处理混淆的错误代码。

在代码中遇到无效数据后,应立即引发异常。您应该在try..catch..finally中包装对其他方法的调用,以处理可能引发的任何异常,如果您不知道如何响应任何给定的异常,请再次抛出该异常以指示更高的层次有一些错误应该在其他地方处理。

管理错误代码可能非常困难。通常,您最终会在代码中产生很多不必要的重复,并且/或者会出现很多混乱的逻辑来处理错误状态。成为用户并遇到错误代码会更加糟糕,因为代码本身不会很有意义,也不会为用户提供错误的上下文。另一方面,异常可以告诉用户一些有用的信息,例如“您忘记输入值”或“您输入了无效值,这是您可能使用的有效范围...”或“我不知道”知道发生了什么,请联系技术支持并告诉他们我刚刚崩溃了,并给他们提供以下堆栈跟踪信息...”。

所以我对OP的问题是,为什么在地球上您希望使用异常而不是返回错误代码?


0

所有好的答案。我还想补充一点,返回错误代码而不是引发异常会使调用者的代码更复杂。说方法A调用方法B调用方法C,C遇到错误。如果C返回错误代码,则B现在需要逻辑来确定它是否可以处理该错误代码。如果不能,则需要将其返回给A。如果A无法处理错误,那么您该怎么办?抛出异常?在此示例中,如果C简单地引发异常,B没有捕获到该异常,则该代码将更加简洁,因此它会自动中止而无需执行任何额外的代码,而A可以捕获某些类型的异常,同时让其他类型的异常继续进行调用堆。

这使我想到了一条良好的编码规则:

代码行就像金色的子弹。您想要使用尽可能少的时间来完成工作。


0

我使用了两种解决方案的组合:对于每个验证功能,我传递一条记录,其中填写了验证状态(错误代码)。在函数的末尾,如果存在验证错误,我将抛出一个异常,这样,我就不会为每个字段抛出异常,而是仅抛出一次。

我还利用了抛出异常将停止执行的优势,因为我不希望数据无效时继续执行。

例如

procedure Validate(var R:TValidationRecord);
begin
  if Field1 is not valid then
  begin
    R.Field1ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end; 
  if Field2 is not valid then
  begin
    R.Field2ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;
  if Field3 is not valid then
  begin
    R.Field3ErrorCode=SomeErrorCode;
    ErrorFlag := True; 
  end;

  if ErrorFlag then
    ThrowException
end;

如果仅依赖布尔值,则使用我的函数的开发人员应将此考虑在内:

if not Validate() then
  DoNotContinue();

但他可能忘记了,只能打电话给Validate()我(我知道他不应该,但也许他可以)。

因此,在上面的代码中,我获得了两个优点:

  1. 验证功能中只有一个例外。
  2. 异常,即使未捕获,也会停止执行,并在测试时出现

0

这里没有一个答案-就像没有一种HttpException。

从某种意义上说,底层HTTP库在收到4xx或5xx响应时会引发异常。上次我查看HTTP规范时发现这些错误。

至于抛出该异常-或包装并重新抛出-我认为这确实是用例的问题。例如,如果您正在编写包装程序以从API抓取一些数据并将其公开给应用程序,则可以决定从语义上说,请求返回HTTP 404的不存在资源的请求更有意义,以捕获该请求并返回null。另一方面,406错误(不可接受)可能值得抛出一个错误,因为这意味着发生了某些变化,应用程序应该崩溃并且正在燃烧并且尖叫着寻求帮助。

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.