是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?
或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?
是否有充分的理由说明在函数中只有一个return语句是一个更好的实践?
或者在逻辑上正确的情况下,可以从函数中返回,这意味着函数中可能有许多返回语句?
当前回答
在一个没有副作用的函数中,没有理由有多个返回值,你应该用函数式的风格来编写它们。在有副作用的方法中,事情更有顺序(时间索引),因此您使用命令式风格编写,使用return语句作为停止执行的命令。
换句话说,如果可能的话,尽量采用这种风格
return a > 0 ?
positively(a):
negatively(a);
在这
if (a > 0)
return positively(a);
else
return negatively(a);
如果您发现自己编写了几层嵌套的条件,可能有一种方法可以重构它,例如使用谓词列表。如果您发现If和else在语法上相差很大,您可能希望将其分解为更小的函数。跨越超过一屏文本的条件块很难阅读。
没有适用于每一种语言的严格规则。像只有一个return语句这样的东西不会使你的代码好。但是好的代码将允许您以这种方式编写函数。
其他回答
我相信多次返回通常是好的(在我用c#编写的代码中)。单返回样式是从C语言中保留下来的,但是您可能没有用C语言编写代码。
在所有编程语言中,没有法律要求一个方法只有一个退出点。有些人坚持这种风格的优越性,有时他们把它提升为“规则”或“法律”,但这种信念没有任何证据或研究支持。
在C代码中,多个返回样式可能是一个坏习惯,资源必须显式地去分配,但Java、c#、Python或JavaScript等语言具有自动垃圾收集和try. finally块(以及在c#中使用块)等构造,并且这个参数不适用-在这些语言中,需要集中的手动资源回收是非常罕见的。
在某些情况下,单个返回值可读性更好,而在某些情况下则不是这样。看看它是否减少了代码行数,使逻辑更清晰,或减少了大括号、缩进或临时变量的数量。
因此,根据你的艺术感受使用尽可能多的返回,因为这是一个布局和可读性问题,而不是技术问题。
我在我的博客上详细地讨论过这个问题。
在其他条件相同的情况下,单一出口点可以显著提高代码的可读性。 但有一个问题:流行的结构
resulttype res;
if if if...
return res;
是假的,“res=”不比“return”好多少。它只有一个return语句,但函数实际结束的地方有多个点。
如果你的函数有多个返回值(或“res=”s),最好将它分解成几个较小的函数,每个函数都有一个退出点。
对于足够小的函数来说,多个出口点很好——也就是说,可以在一个屏幕长度上完整地查看函数。如果一个冗长的函数同样包含多个出口点,则表明该函数可以进一步分割。
也就是说,除非绝对必要,否则我避免使用多出口功能。在更复杂的函数中,由于一些模糊的行中出现的一些错误返回,我感到了错误的痛苦。
我想说的是,你应该有尽可能多的需要,或者任何使代码更干净的(如保护子句)。
我个人从来没有听过/见过任何“最佳实践”说你应该只有一个返回语句。
在大多数情况下,我倾向于根据逻辑路径尽快退出函数(保护子句就是一个很好的例子)。
有时出于性能考虑,这是必要的(我不想获取不同的缓存线,就像继续一样;有时)。
如果你不使用RAII分配资源(内存、文件描述符、锁等),那么多次返回很容易出错,而且肯定是重复的,因为释放需要手动执行多次,你必须仔细跟踪。
在这个例子中:
function()
{
HRESULT error = S_OK;
if(SUCCEEDED(Operation1()))
{
if(SUCCEEDED(Operation2()))
{
if(SUCCEEDED(Operation3()))
{
if(SUCCEEDED(Operation4()))
{
}
else
{
error = OPERATION4FAILED;
}
}
else
{
error = OPERATION3FAILED;
}
}
else
{
error = OPERATION2FAILED;
}
}
else
{
error = OPERATION1FAILED;
}
return error;
}
我会把它写成:
function() {
HRESULT error = OPERATION1FAILED;//assume failure
if(SUCCEEDED(Operation1())) {
error = OPERATION2FAILED;//assume failure
if(SUCCEEDED(Operation3())) {
error = OPERATION3FAILED;//assume failure
if(SUCCEEDED(Operation3())) {
error = OPERATION4FAILED; //assume failure
if(SUCCEEDED(Operation4())) {
error = S_OK;
}
}
}
}
return error;
}
这当然看起来更好。
这在手动资源释放的情况下尤其有用,因为在哪里和哪些释放是必要的是相当直接的。如下例所示:
function() {
HRESULT error = OPERATION1FAILED;//assume failure
if(SUCCEEDED(Operation1())) {
//allocate resource for op2;
char* const p2 = new char[1024];
error = OPERATION2FAILED;//assume failure
if(SUCCEEDED(Operation2(p2))) {
//allocate resource for op3;
char* const p3 = new char[1024];
error = OPERATION3FAILED;//assume failure
if(SUCCEEDED(Operation3(p3))) {
error = OPERATION4FAILED; //assume failure
if(SUCCEEDED(Operation4(p2,p3))) {
error = S_OK;
}
}
//free resource for op3;
delete [] p3;
}
//free resource for op2;
delete [] p2;
}
return error;
}
如果在没有RAII(忘记异常问题!)的情况下使用多个出口编写这段代码,则必须多次写入删除。如果你用}else{then 这有点难看。
但是RAII使得多个出口资源问题变得毫无意义。