当函数中有多个返回语句时,这称为“提前返回”。如果您为“提前返回”执行Google search,您会发现一个接一个的链接表明它很糟糕。
我说的是废话。
人们声称早退是不好的,有两个主要原因和一个次要原因。我将通过它们并按顺序给出我的反驳。请记住,这是我的全部意见,最终您必须自己决定。
1) 原因:提前退货造成清理困难。
反驳:这就是RAII 的用途。一个设计良好的程序不会以这样一种方式分配资源,即如果执行提前离开范围,这些资源就会泄漏。而不是这样做:
...
int foo()
{
MyComplexDevice* my_device = new MyComplexDevice;
// ...
if( something_bad_hapened )
return 0;
// ...
delete my_device;
return 42;
}
你这样做:
int foo()
{
std::auto_ptr<MyComplexDevice> my_device(new MyComplexDevice);
if( something_bad_hapened )
return 0;
// ...
return 42;
}
并且提前返回不会导致资源泄漏。在大多数情况下,您甚至不需要使用auto_ptr,因为您将创建数组或字符串,在这种情况下,您将使用vector、string 或类似的东西。
您应该像这样设计您的代码以保证稳健性,因为可能会出现异常。异常是一种提前返回的形式,例如显式的return 语句,您需要准备好处理它们。 foo() 中的异常您可能无法处理,但 foo() 无论如何都不应泄漏。
2) 原因:提前返回使代码更复杂。
反驳:早期回报实际上使代码更简单。
函数应该有一个责任是一种普遍的哲学。我同意这一点。但是人们对此太过分了,并得出结论,如果一个函数有多个返回,它必须有多个责任。 (他们通过说函数的长度不应超过 50 行或其他任意数字来扩展这一点。)我说不。仅仅因为一个函数只有一个职责,并不意味着它不需要做很多事情来履行这个职责。
以打开数据库为例。这是一项责任,但它由许多步骤组成,每个步骤都可能出错。打开连接。登录。获取一个连接对象并返回它。 3 个步骤,每个步骤都可能失败。您可以将其分解为 3 个子步骤,但不要使用这样的代码:
int foo()
{
DatabaseObject db = OpenDatabase(...);
}
你最终会拥有:
int foo()
{
Connection conn = Connect(...);
bool login = Login(...);
DBObj db = GetDBObj(conn);
}
因此,您实际上只是将假定的多个职责移到调用堆栈中的更高位置。
3) 原因:多个返回点不是面向对象的。
反驳:这实际上只是另一种说法“每个人都说多次退货不好,尽管我真的不知道为什么。”
换一种说法,这实际上只是试图将所有东西都塞进一个物体形状的盒子里,即使它不属于那里。当然,也许连接是一个对象。但是是登录吗?登录尝试不是(IMO)对象。它是一个操作。或者算法。试图采用这种算法并将其塞进一个对象形状的盒子中是对 OOP 的无端尝试,只会导致代码更复杂、更难维护,甚至可能效率更低。