考虑这段代码(特别是Java):
public int doSomething()
{
doA();
try {
doB();
} catch (MyException e) {
return ERROR;
}
doC();
return SUCCESS;
}
doB()
定义为:
private void doB() throws MyException
基本上,MyException
仅在doB()
满足某些条件(这不是灾难性的,但确实需要以某种方式提高此条件)的情况下存在,以便doSomething()
知道退出时错误。
您是否发现使用异常,在这种情况下控制流量,是否可以接受?或者这是代码味道?如果是这样,你会如何重构这个?
答案 0 :(得分:16)
当doB()失败时执行doC()真的很重要吗?如果没有,为什么不简单地让Exception在堆栈中向上传播到可以有效处理的位置。就个人而言,我考虑使用错误代码代码味道。
编辑:在评论中,您已经准确描述了应该简单声明的情节
public void doSomething() throws MyException
答案 1 :(得分:11)
这完全取决于错误条件是什么,以及方法的工作是什么。如果返回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;
}
这种例外有一个特殊的名字,由我最近阅读的博客创造。但遗憾的是,我忘记了它的名字。如果你知道的话请评论。最后但并非最不重要的是,上面是伪代码。我不是c#开发人员,所以上面的代码不能编译,我敢肯定,但TryParseInt32 / ParseInt32证明了这一点,我认为我会选择C#。
现在,到你的代码。我们来检查两个功能。一个闻起来,另一个没有:
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;
}
没关系,因为该方法的目的是测试工作站是否仍然可以访问。如果不是,那么这是该方法结果的一部分,而不是需要替代返回路径的特殊情况。
答案 2 :(得分:9)
我对OP代码的唯一问题是你混合范式 - doB通过抛出异常显示错误,而doSomething通过返回代码显示错误。理想情况下,你会选择其中一个。当然,在传统维护中,您可能没有那么奢侈。
如果您选择返回错误代码,那没关系,但我不喜欢它,因为它鼓励您使用辅助通道(如全局变量)在失败时通信状态,而不是将该状态捆绑到异常中并让它冒泡堆叠,直到你可以做些什么为止。
答案 3 :(得分:6)
我从来不喜欢在控制流中使用异常(在某些语言中,比如CLR,它们很贵)。
如果您可以修改 doB(),最好的办法是更改它以返回表示成功或失败的布尔值,因此您的示例如下所示:
public int doSomething()
{
doA();
if (!doB()) {
return ERROR;
}
doC();
return SUCCESS;
}
答案 4 :(得分:5)
在以下情况下应使用例外:
return FAILURE;
时,例如,嵌套异常等。首先要记住,异常(如返回值或方法参数)只是在代码的不同部分之间发送的消息。努力优化通过这些方法传达的信息与API的简单性之间的平衡。当只需要一个简单的SUCCESS / FAILURE标志(并且该方法不需要返回其他值)时,请使用它。如果方法已经必须返回一个值,那么通常需要使用一个异常(这是一种查看它的方法,只是方法中的“异常”返回值)。如果必须传递的故障信息太丰富而无法在返回值中传递(例如,I / O故障的原因),请使用异常。
最后,错误处理是一项设计决策,并没有一套适合所有情况的规则。
答案 5 :(得分:4)
使用控制流的例外情况绝对是不好的。只有在特殊条件下才会抛出异常。
但是,使用实用方法只是抛出异常或什么也不做也不是特别糟糕。例如,您可能希望验证关于方法的条目的所有参数,或者检查内部状态在复杂计算之间是否仍然有效(由于某种原因,您无法在内部强制执行,也许您正在接受闭包并执行它们)。在这些情况下,将一致性检查提取到自己的方法中并不是一个坏主意,以提高“实际”方法实现的可读性。
分界线实际上是在遇到正常操作参数之外的事情时应该抛出异常,而事情是如此糟糕以至于该方法没有真正的方法来继续它的工作。
作为不的例子,这将使用流控制的例外:
public int calculateArraySize(Object[] array)
{
int i = 0;
try
{
array[i++];
}
catch (ArrayIndexOutOfBoundsException ignore) {}
return i;
}
我相信它会返回正确的结果,但是对于那些习惯于正常使用异常的人来说,它会非常缓慢和低效,并且难以阅读。 ; - )
另一方面,在我看来,这样的事情就可以了:
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
}
尽管所有第二种方法都可能引发异常,但它并没有这样做来控制程序流程,而是为了响应异常情况而提高它们。
答案 6 :(得分:2)
如果出现错误,可以抛出异常,就像在doB()中一样。 但问题是函数doSomething()。
您不应该使用return语句指示成功或失败。你应该这样做:
try {
doB();
} catch (MyException e) {
throw MyException2(e);
}
答案 7 :(得分:2)
异常不应仅用于控制流量。应使用经过检查的异常来告知调用代码未达到API合同的某些条件。我不会设计doSomething
来处理doB
的调用经常使用try/catch
块失败的情况。如果您需要处理频繁失败的情况,我会设计doB
以返回成功或失败boolean
,以指示其调用方法是否继续使用自己的doC
- 类型方法:
public int doSomething() {
doA();
if ( doB() )
doC();
return SUCCESS;
} else {
return ERROR;
}
}
答案 8 :(得分:1)
根据doB的逻辑,你可以指出一些返回值是否正常,然后doSomething可以使用返回的值以适当的方式处理这种情况。
答案 9 :(得分:1)
例外情况是非标准行为。所以是可以用它们来控制流量。
正如Sun所说:
一般来说,不要抛出RuntimeException或创建RuntimeException的子类,因为您不希望因指定方法可以抛出的异常而烦恼。
以下是底线指南:如果可以合理地期望客户端从异常中恢复,请将其作为已检查的异常。如果客户端无法执行任何操作以从异常中恢复,请将其设置为未经检查的异常。
请记住不要将RuntimeException子类化,而将Exception子类化为更快。
答案 10 :(得分:0)
我认为使用异常非常简单,简单明了。我会创建返回正常值的常规函数,并为它们的设计保留异常。
答案 11 :(得分:0)
正如其他人所指出的,它实际上取决于此处涉及的方法的预期功能。如果doSomething()
的作用是检查某些东西,返回一个允许正常执行其调用方法的代码继续(虽然根据返回的代码在不同的路径上)然后让它返回该int可能没问题。但是,在这种情况下,doB()
可能应该是一致的,并返回一个布尔值或doSomething()
可以检查的内容,而不是抛出一个不会发生任何其他事情的异常。
如果doB()
是另一个类的公共方法,可能在doSomething()
之外有其他用途(而不是您指定的同一个类中的私有方法),它会抛出异常并且你想在doSomething()
(它具有上面指出的相同角色)中使用它,那么你的例子就可以了。
doB()
抛出异常时返回的代码被称为ERROR
这一事实,表明它可能是一个失败,会阻止任何操作doSomething()
成为其中的一部分完成。在这种情况下,您可能不应该捕获doB()
引发的异常,只是让它被抛出堆栈,直到它到达可以处理它的位置,或者直到它到达可以报告/记录的一般错误处理程序它或其他什么。
答案 12 :(得分:0)
在C#中,抛出异常是相对昂贵的,性能方面的 - 因此避免将其用于流量控制的另一个原因。这是Java的情况吗?是的,有一个论点是不提前过度分析性能。但是,如果你知道某些东西会花费额外的时间,而且很容易避免,你不应该这样做吗?除非你真的不在乎!
答案 13 :(得分:0)
答案 14 :(得分:-1)
以下是我将如何处理它:
public void doSomething()
throws MyException{
doA();
try {
doB();
} finally {
doC();
}
}
如果在doB()中发生错误,则异常将在堆栈中向上传播。
答案 15 :(得分:-2)
异常是例外,意味着发生了一些未预料到的事情。这么多,所以我们停止执行应用程序。任何曾经在应用程序上工作过的人都是这种类型的异常流程控制,它会想要追捕那些做了这个并扼杀他(或她)的开发人员。
使用异常作为流控制使维护和新功能开发成为一场噩梦!