抛出exception来控制流程 – 代码味道?

考虑一下这段代码(特别是Java):

public int doSomething() { doA(); try { doB(); } catch (MyException e) { return ERROR; } doC(); return SUCCESS; } 

doB()定义为:

 private void doB() throws MyException 

基本上, MyException仅在doB()满足某些条件(这不是灾难性的,但确实需要以某种方式提高此条件)的情况下存在,以便doSomething()知道退出时出错。

您是否发现使用exception,在这种情况下控制流量,可以接受? 或者这是代码味道? 如果是这样,你会如何重构这个?

这完全取决于错误条件是什么,以及方法的工作是什么。 如果返回ERROR是一种处理调用函数错误的有效方法,为什么它会变坏?

然而,通常它一种气味。 考虑一下:

 bool isDouble(string someString) { try { double d = Convert.ParseInt32(someString); } catch(FormatException e) { return false; } return true; } 

这是一个非常大的代码味道,因为你不期望双重值。 您只想知道字符串是否包含double。

有时,您使用的框架没有其他方法可以执行您想要的操作。 对于上述情况,有一种更好的方法:

 bool isDouble(string someString) { bool success; Convert.TryParseInt32(someString, ref success); return success; } 

这种例外有一个特殊的名字,由我最近阅读的博客创造。 但遗憾的是,我忘记了它的名字。 如果你知道的话请评论。 最后但并非最不重要的是,上面是伪代码。 我不是ac#developer所以上面的代码不能编译,我敢肯定,但是TryParseInt32 / ParseInt32certificate了这一点我认为我会选择C#。

现在,到您的代码。 让我们检查两个function。 一个闻起来,另一个没有:

气味

 public int setupSystem() { doA(); try { doB(); } catch (MyException e) { return ERROR; } doC(); return SUCCESS; } 

这是代码味道 ,因为当您想要设置系统时,您不希望它失败。 未能设置系统意味着您无法在不处理该错误的情况下继续操作。

好的

 public int pingWorkstation() { doA(); try { doB(); } catch (MyException e) { return ERROR; } doC(); return SUCCESS; } 

没关系,因为该方法的目的是测试工作站是否仍可访问。 如果不是,那么这是该方法结果的一部分,而不是需要替代返回路径的特殊情况。

在doB()失败时执行doC()真的很重要吗? 如果没有,为什么不简单地让Exception在堆栈中向上传播到可以有效处理的位置。 就个人而言,我考虑使用错误代码代码气味。

编辑:在您的评论中,您已经准确地描述了您应该简单声明的情节

 public void doSomething() throws MyException 

我对OP代码的唯一问题是你混合范例 – doB通过抛出exception显示错误,而doSomething通过返回代码显示错误。 理想情况下,你会选择其中一个。 当然,在传统维护中,您可能没有那么奢侈。

如果您选择返回错误代码,那没关系,但我不喜欢它,因为它鼓励您使用辅助通道(如全局变量)在失败时通信状态,而不是将该状态捆绑到exception并让它冒泡到堆栈直到您可以做些什么。

我从来不喜欢使用控制流的exception(在某些语言中,比如CLR,它们很贵)。

如果你可以修改doB() ,最好的办法是改变它以返回一个表示成功或失败的布尔值,所以你的例子看起来像:

 public int doSomething() { doA(); if (!doB()) { return ERROR; } doC(); return SUCCESS; } 

在以下情况下应使用例外:

  • 一个函数无法正常完成,并且
  • 当没有可用于指示失败的返回值时,或
  • 当抛出的exception传达的信息多于return FAILURE;时的信息return FAILURE; 例如,可以嵌套exception等。

请记住,除了返回值或方法参数之外,所有exception都只是在代码的不同部分之间发送的消息。 努力优化通过这些方法传达的信息与API的简单性之间的平衡。 当只需要一个简单的SUCCESS / FAILURE标志(并且该方法不需要返回其他值)时,请使用它。 如果方法已经必须返回一个值,那么通常需要使用一个exception(这是一种查看它的方法,只是方法中的“exception”返回值)。 如果必须传递的故障信息太丰富而无法在返回值中传递(例如,I / O故障的原因),请使用exception。

最后,error handling是一个设计决策,并且没有一套适合所有情况的规则。

使用exception来控制流量 ,绝对是不好的。 只有在特殊情况下才应抛出例外情况。

但是,使用一种简单地抛出exception或什么都不做的实用方法并不是特别糟糕。 例如,您可能希望validation进入方法的所有参数,或检查内部状态在复杂计算之间是否仍然有效(由于某种原因,您无法在内部强制执行,也许您正在接受闭包并执行它们)。 在这些情况下,将一致性检查提取到自己的方法中,以提高“实际”方法实现的可读性并不是一个坏主意。

分界线实际上是当遇到超出正常操作参数的东西时应该抛出exception,并且事情是如此糟糕以至于该方法没有真正的方法来继续它的工作。

作为不该做的一个例子,这将使用流控制的exception:

 public int calculateArraySize(Object[] array) { int i = 0; try { array[i++]; } catch (ArrayIndexOutOfBoundsException ignore) {} return i; } 

我相信它会返回正确的结果,但它会非常缓慢和低效,并且对于习惯于正常使用exception的人来说也很难阅读。 😉

另一方面,我认为这样的事情会好的:

 public void myMethod(int count, Foobar foo) throws MyPackageException { validateArgs(count, foo); // Stuff } private void validateArgs(int count, Foobar foo) throws MyPackageException { if (m_isClosed) { throw new IllegalStateException("Cannot call methods on a closed widget"); } if (count < 0) { throw new IllegalArgumentException("count must be greater than zero"); } foo.normalise(); // throws MyPackageException if foo is not fully initialised at this point } 

尽管所有第二种方法都可能引发exception,但它并没有这样做来控制程序流程,而是为了响应特殊情况而提高它们。

如果出现错误,可以抛出exception,就像在doB()中一样。 但问题是函数doSomething()。

您不应该使用return语句指示成功或失败。 你应该做:

 try { doB(); } catch (MyException e) { throw MyException2(e); } 

不应仅使用例外来控制流量。 应该使用经过检查的exception来告诉调用代码不符合API合同的某些条件。 我不会设计doSomething来处理通常使用try/catch块调用doB失败的情况。 如果经常出现故障需要处理,我会设计doB来返回成功或失败的boolean以指示其调用方法是否继续使用自己的doC类型方法:

 public int doSomething() { doA(); if ( doB() ) doC(); return SUCCESS; } else { return ERROR; } } 

根据doB的逻辑,你可以有一些返回值指向它是否正常,然后doSomething可以使用返回的值以适当的方式处理这种情况。

例外情况是非标准行为。 所以可以使用它们来控制流量。

正如Sun所说:

一般来说,不要抛出RuntimeException或创建RuntimeException的子类,因为您不希望因为指定方法可以抛出的exception而烦恼。

这是底线指南:如果可以合理地期望客户端从exception中恢复,则将其作为已检查的exception。 如果客户端无法执行任何操作以从exception中恢复,请将其设置为未经检查的exception。

只记得不要将RuntimeException子类化,而将Exception子类化为更快。

我认为使用exception非常简单,简单明了。 我会创建返回正常值的常规函数​​,并为它们的设计保留exception。

正如其他人所指出的,它实际上取决于这里涉及的方法的预期function。 如果doSomething()的作用是检查某些东西,返回一个允许正常执行其调用方法的代码继续(虽然在不同的路径上取决于返回的代码)然后让它返回该int可能没问题。 但是,在这种情况下, doB()应该是一致的,并返回一个布尔值或doSomething()可以检查的东西,而不是抛出一个不会真正做任何其他事情的exception。

如果doB()是另一个类的公共方法,可能在doSomething()之外有其他用途(而不是你指定的同一个类中的私有方法),它会抛出exception,你想在doSomething()使用它doSomething() (具有上述相同的作用),那么你的例子就可以了。

事实上,当doB()抛出exception时返回的代码称为ERROR ,但是,表明它可能是一个失败,它会阻止doSomething()完成的任何操作。 在这种情况下,您可能不应该捕获doB()抛出的exception,只是让它被抛出堆栈,直到它到达可以处理它的点,或者直到它到达可以报告/记录它的一般error handling程序或者随你。

在C#中,抛出exception相对昂贵,性能方面 – 因此避免将其用于流控制的另一个原因。 这是Java的情况吗? 是的,有一个争论是要提前不要过度分析性能。 但是,如果你知道某些东西需要额外的时间,而且很容易避免,你不应该这样做吗? 除非你真的不在乎!

请参阅此问题的最佳答案:

Javaexception有多慢?

事实certificate它们至少比普通代码流慢50倍。 所以我会说在通常的代码运行中使用exception绝对是个坏主意。

以下是我将如何处理它:

 public void doSomething() throws MyException{ doA(); try { doB(); } finally { doC(); } } 

如果在doB()中发生错误,则exception将在堆栈中向上传播。

你们这里杀了我 你永远不想使用exception来控制流量。 与在任何地方使用goto语句没有什么不同!

例外是例外,意味着发生了一些未预料到的事情。 这么多东西,所以我们停止了应用程序的执行。 任何曾经在应用程序上工作的人都是这种类型的exception流程控制,它会想要追捕那些做这件事的开发人员并限制他(或她)。

使用exception作为流控制使维护和新function开发成为一场噩梦!