问题
我在函数中用到多处 return,老师说将来招聘的HR(人力资源)看到这种用法,会直接说“再见”?
提问者给了一个例子。他的写法是:
bool foo() {
if (x == x) return true;
else if (x == y) return true;
return false;
}
按老师(估计是大学里的老师)的说法,似乎应该通过引入一个新的变量,以确保函数仅在末尾处有一个 return :
bool foo() {
bool flag = false;
if(x == x) flag = true;
else if (x== y)flag = true;
return flag;
}
南老师的回答
“不要多个return”,这种说法确实曾经有过,但年代相当久远(60年前吧?我1995年时上的《软件工程》方面的大学教材,都已经只是提到,但不刻意强调了。
举个例子:通常一个函数就是要做一件事,但进入函数之后,在正式做事之前,很多时候,不得不先检查一些必要条件,称为“前置条件检查”;并且,经常在条件不成立时,除了打打日志之外,其它事情都不做,只能返回——这时推荐的做法,就是多个 return。
比如,有这么一个需求:
假设要写一个函数,入参是一个文件名 。
干的活就是打开这个文件,然后检查这个文件是不是一张图片,
如果是一张图片,再检测它是不是一张黄图。
重点:
说的时候,我们习惯按“美好路径”说下去,但其实做的时候,就得考虑各种“倒霉路径”。
- ① 入参是一个文件名 —— 这个文件名会不会是空的?
- ② “活”就是打开这个文件……——文件实际存在吗?就算存在,当前程序有打开权限吗?
- ③ 检查这个文件是不是一张图片,如果是……——如果不是呢?
- ④ 再检测它是不是一张黄图——到这里,我们才开始要做真正的业务。
这些判断如果落空,就是一堆的 return :
// 示例伪代码
结果 检测黄图 (string 文件名)
{
assert (文件名 != 空); // 文件名为空,通常这是程序员的错,直接断言
if (!文件存在(文件名))
{
打印日志(“玩我呢?文件都不存在”);
return 文件不存在的结果;
}
文件 f = 打开(文件名) ;
if ( f 不 ok )
{
打印日志(“文件打不开啊,那个爱看图的家伙,是不是又在线占着这图不放?”);
return 文件不OK的结果;
}
数据 d = 读出全部数据(f);
if ( d 不 OK)
{
打印日志(“读不出数据啊,我是不配有权限是吗?”);
return 文件权限不OK的结果;
}
格式 fmt = 格式判断(d);
if (fmt 不是 图片啊!)
{
打印日志(“你们又玩我,我这鉴黄半天,结果这不是一张图”);
return 文件格式不是图片的结果;
}
// 真正干活的留在最后
return 黄(d, AI)? 黄图的结果 : 其它颜色的结果;
}
这样写的最大好处,让代表结构更扁平,代码更容易阅读,加上集中处理错误,代码逻辑更清晰。其关键点在于:条件一个个判断,任意一个不成立,就直接 返回,如此当程序 “过五关斩六将”,终于到达所有条件都成立的时候,代码仍然在函数体内的最顶层(没有缩进)。如果走反过来的逻辑:判断所有条件成立,然后做正事,这时候就不是 “一个一个”条件判断,而是“一级一级”条件判断,形如:
// 反例:
结果 检测黄图 (string 文件名)
{
结果 r = "一切正常";
assert (文件名 != 空);
if (文件存在)
{
文件 f = 打开(文件名) ;
if ( f 打开成功了 )
{
数据 d = 读出全部数据(f);
if ( d 读得很OK )
{
格式 fmt = 格式判断(d);
if (fmt 是 图片)
{
// 真正干活在这里:
r = 黄(d, AI)? 黄图的结果 : 其它颜色的结果
}
else
{
打印日志(“你们又玩我,我这鉴黄半天,结果这不是一张图”);
r = 格式不对的错误;
}
}
else
{
打印日志(“读不出数据啊,我是不配有权限是吗?”);
r = 读不出数据的错误;
}
}
else
{
打印日志(“文件打不开啊,那个爱看图的家伙,是不是又在线占着这图不放?”);
r = 打不开文件的错误;
}
}
else
{
打印日志(“玩我呢?文件都不存在”);
r = 文件不存在的错误;
}
return r; // 满足只一处 return 的要求,但不仅代价大,而且意义不大
}
附加知识:为什么以前的软件工程会要求函数仅一处 return ?
因为很早很早以前的软件设计,属于重“工程”,即周期长,因此有大量的时间来做编码前的设计;并且依赖于大量手绘流程图——问题就出在这个流程图上……
当一个函数有多处 return,就意味着这函数在流程图上表达时,会有多个出口,这时候流程图就会不太好画,画出来也不好看……于是,大家就提倡“单进单出”,最终就是一个函数,最好只有,并且是仅在函数结束处,才 return 。
给个例图:
看左边的传统流程图,一个“开始”一个“停止”,非常简洁。而右边的盒式流程图(N-S图),我特加描红的底边,就是流程的结束,这是它的最大优点或特点:盒子的底边,就是一切流程分支结束的位置。