好的代码只有一个return
英文原文:http://www.theserverside.com/tip/A-return-to-Good-Code
中文译文:http://www.aqee.net/a-return-to-good-code/
文章作者认为,函数应该是单出口的,即只在结束时return;读书期间,我写代码的风格是多return的,使代码尽量简短。在百度和淘宝工作期间,我们的代码规范都是要求函数单出口,刚开始写很不习惯,一层层的if、else看着着实不爽,但慢慢也发现,按单出口的规范写代码会使得写代码的思路非常清晰,不容易出现内存泄露等问题,我建议在大型项目中还是应该尽量保持函数单出口。这里总结了几种减少缩进层次的方法,可以使得写单出口函数更加清晰明了; 当然最关键的还是合理设计函数的功能,保持函数尽量简短易理解。
1. 使用goto
linux内核中大量使用这种方式,当出错是,使用goto跳到错误处理部分
ret = do_work_1();
if (OK != ret)
{
goto ERR;
}
ret = do_work_2();
if (OK != ret)
{
goto ERR;
}
....
ERR:
// error process code here
2. 重复检查
当代码分成几个逻辑块,每个逻辑块开始前,检查当前状态
ret = do_work_1();
if (OK == ret)
{
ret = do_work_2();
}
// a new logic code block
if (OK == ret)
{
ret = do_work_3();
if (success)
{
ret = do_work_4();
}
....
}
if (OK == ret)
{
ret = do_else_work();
}
3.do while(0)
将可能出现多层if else的功能块代码放到一个do while中,当失败时break。
do
{
ret = do_work_1();
if (OK != ret)
{
break;
}
ret = do_work_2();
if (OK != ret)
{
break;
}
....
} while(0);
其他一些精彩评论
尽量如此,但不必拘泥。
唯一的入口可以大大提高程序的可读性和可维护性。比较安全可靠。
但是僵化的就是八股了,总不成为了这么一点在有时候满篇飞的goto;再不成、设计出无数个小而繁的函数,加大call stack的深度,频繁的出入栈。
----------------------------------------------------------------------------------
很诧异居然有人不认同主贴的说法。
函数不管是在数学领域还是计算机科学领域都是单返回值的。即使是python这种似乎可以返回多个值的,其本质也是将多个值包装在一个容器里而已。
对于效率,有人认为C/C++里这样做,效率很低。对主贴的例子,要提升效率,首选的做法不是应该声明为内联?而对超过10行的代码,清晰的结构难道不够抵消几条显而易见会被局部化到CPU Cache里的mov指令的性能负担?另外拿汇编说事的,不知道现在还有多少人真正写过汇编代码。我本人做过PC104的开发,用汇编写过几乎所有DOS ISR的实现和XMS驱动的实现。做这些工作的时候,实事求是的说,反汇编了很多MSDOS或者Intel的实现。所有这些代码中的过程的IRET/RET指令,无一例外是放在过程最后的:当过程的某个逻辑路径执行结束了,就往AX/EAX装返回值,然后跳转到最后的IRET/RET处,这和主贴的推荐做法是一致的。
在我经历过的所有公司,都要求按照主贴里推荐的做法来写返回值。这是正规的软件工程的要求。然而即使是在这样的环境里,我审查过的代码中,仍然有大量案例按照不推荐的代码来写;也正是这些代码,在后续开发使函数体变得冗长的同时,也经常出现忘记设置或者检查合适的返回值,或者将本来应该执行的代码放到return之后的情况,导致程序不可能执行到该有的业务逻辑的位置。
我知道现在很多程序员是在智能化IDE和密集检查的编译器出现之后才开始程序开发工作的,工具为程序员带来了太多的保障,以至于像主贴展示的那种不好的做法现在已经不太可能再引入大规模的bug(十数年前编译器不会完全检查执行路径,没返回值的路径将得到一个遗留在AX/EAX里的返回值),但我坚持认为这种保护性编程的手法是有益的。
另外if/else,有大量开源代码里if没有对应的else,这些代码可能精巧而有效,但在企业开发中一般是不被允许的。总结起来,无非三类反驳观点。一是效率,一是函数的单出的理解,一是到底哪种写法更可读。
----------------------------------------------------------------------------------
说到效率,以前IBM一个master inventor调侃我们说,每当程序员开始拿效率说事,就意味着他事情搞砸了,开始找借口了。就俺的经验来看,一个普通程序员,除开专门搞算法的,可能一年写的代码中,真正有效率第一的需求的不会超过50行。大量以效率作为第一前提的代码,要么其业务逻辑不是真正需要那么高的效率,要么其执行路径在整个软件执行期内的百分比地道可以忽略。
----------------------------------------------------------------------------------
函数单出的理解。这个只能说见仁见智。在我看来,只要函数还通过预定义的栈帧和寄存器来传递返回值,都应该是单出的。至于异常,这本来就是正常业务逻辑之外的不是么?我确实看到过有很多代码,用异常的throw/catch来实现正常的业务逻辑跳转,说实在的,要是我的组谁写出这种代码,直接让他去学习两周再说。
最后是可读性。很多同学似乎认为更好的可读性就是更符合自己阅读习惯的代码风格。这是不对的。更好的可读性是更不容易给别人造成歧义、误导,同时更易于理解的代码风格。可读性要求代码尽量以简单、直接
一个函数中的一个功能块不应该有return的,我会以do{}while(0)来区隔一个小的模块,如果多个功能块我会以do{do{}while(0);break;....}while(0)这种嵌套的风格来处理,这样基本可以保证在函数的可读性很高的同时,也有单进单出的一致性风格;至少我的组员我会这样提醒,要求他们!
原因:
维护性,可读性,这个在大型代码中个人觉得比什么都要紧,性能问题只要你不做什么蠢事,都不至于引起问题,因为一般这些代码不会出现调用频率高到需要考虑的地步,(做单片机开发的除外);
----------------------------------------------------------------------------------
我有尽量把一个函数的return减少到更少或是只有一个,但有时却发现这样并不太容易。笔者的说法我觉得应该再搭配一个理念,保持一个函数功能简洁、简单易懂。但在某些时候,一个函数似乎不太可能避免过长,如果为了只有一个RETURN反而让逻辑变得复杂,是否值得呢。
以前有个程序相当喜欢写if....else,于是缩进最深的代码甚至有被缩进了10次之多,代码阅读相当困难,而且你还必须往右边拖滚动条才能看清楚。当然这样的代码本身设计就有问题,但有时确实难免(或者经验不足)。个人认为不应该过于偏激,而且这两个例子太过简短或片面,说服力不足啊。