文档章节

单一出口原则

Peter87
 Peter87
发布于 2015/08/09 10:50
字数 1579
阅读 192
收藏 5

最近在读《重构——改善既有代码的设计》这本书,在 9.4 Remove Control Flag(移除控制标记)这一节,作者提到了“单一入口”和“单一出口”这两个原则,并对“单一出口”原则批驳了一番,让我想起了一个遥远的故事。

那是3年前在H3C实习的日子,开发部门对代码规范规定略微严格,并且有代码鉴定小组严格把关进行代码检查。尤其还记得当时对于“单一出口”原则的提倡,比如下面这段代码:

Function
{  
    if (condition)
    {       
        return false;
    }    
 
   return AnotherFunction();
}

会建议为如下的形式:

for loop
{         
    bRet = FALSE;
    if (!condition)
    { 
	bRet = AnotherFunction();
    }

    return bRet ;
}

在当时,我虽然觉得这样有道理,却实际上并没有明白其中的原因。但是这种做法有一个弊端,也就是如果一个函数当中条件较多时会使得该函数的嵌套层次暴增,这个时候代码阅读起来会比较费劲。最近在阅读编程实践相关书籍的时候,恰好碰到了对于该单一出口原则的不同的观点。

来自Boswell与Foucher的反驳

Boswell(Dustin Boswell)和Foucher(Trevor Foucher)是《The Art of Readable Code》一书的两位作者。在该书第7章的“Returning Early from a Function”小节当中有如下一段话:

Some coders believe that functions should never have multiple return statements. This is nonsense. Returning early from a function is perfectly fine—and often desirable.

很显然,作者认为刻板的遵守单一出口原则会影响到代码的可读性。同时,在“Minimize Nesting”一节,作者还举了一个使用“returning early from a function”来优化代码可读性的例子。

if (user_result == SUCCESS)
{
  if (permission_result != SUCCESS)
  {
    reply.WriteErrors("error reading permissions");
    reply.Done();
    return;
  }
  reply.WriteErrors("");
}
else
{
  reply.WriteErrors(user_result);
}
reply.Done();

优化后:

if (user_result != SUCCESS)
{
  reply.WriteErrors(user_result);
  reply.Done();
  return;
}

if (permission_result != SUCCESS)
{
  reply.WriteErrors(permission_result);
  reply.Done();
  return;
}

reply.WriteErrors("");
reply.Done();

这个例子主要提倡“在一些罕见情况发生时提前退出”,并没有直接针对单一出口原则。但也间接地反击了单一出口原则,因为单一出口原则是绝对不会支持提前return的。其实,我们可以将上面的例子简单修改一下,使其符合单一出口原则:

if (user_result == SUCCESS)
{
  if (permission_result != SUCCESS)
  {
    reply.WriteErrors("error reading permissions");
  }
  else // 简单的加上一个else.
  {
    reply.WriteErrors("");
   }
} else
{
  reply.WriteErrors(user_result);
}
reply.Done();

这就是就是对单一出口原则的一次反驳,看起来也不无道理。值得赞赏的是作者这里不仅解释了他们推崇方法的好处,也提到了为什么单一出口原则得以被提出的原因:保持单一出口原则的一个主要原因是在函数的结尾可以做统一的清理工作,特别对于C语言当中更应该如此。但是,对于现代编程语言当中本身已经提供了类似的机制。 || Language || Structured idiom for cleanup code || -------------------------------------------------------- || C++ || destructors || || Java, Python || try finally || || Python || with || || C# || using ||

插个小话题:对于作者提到的这个例子,我觉得还可以使用《重构》一书当中的“Consolidate Duplicate Conditional Fragments”进一步优化,如下:

if (user_result != SUCCESS)
{
  reply.WriteErrors(user_result);
  reply.Done();
  return;
}

if (permission_result != SUCCESS)
{
  reply.WriteErrors("error reading permissions");
}
else
{
  reply.WriteErrors("");
}

reply.Done();
return;

有关“单一出口原则”的更多内容

这里有一篇文章讨论了对该原则有更多讨论,多数人赞同应用该原则的一些例外情况:

  • GuardClause,所谓的“卫语句”,指的是在函数开头用来判断一些明显非法情况并立即退出的语句。比如常见的指针判空操作;
  • 函数当中存在有多种返回情况,比如switch/case语句当中的每一个分支执行之后均直接return;

但对该篇文章对应的另一篇文章却大费笔墨支持单一出口原则,尤其在诸如BASIC、Fortran、C等面向过程的编程语言当中:

I believe a single code block should have one point of entry and one point of exit. A function is a code block, so this holds true for functions. However it also and perhaps more importantly holds true for any other situation where it might be an issue. In languages like BASIC, Fortran, C and Assembly language you can create blocks of code that have multiple points of both entry and exit. Code where this happens is often called 'spaghetti code'. The more points of entry and exit the more difficult it is to debug the code. Eventually, it becomes impossible for any practical purpose.

另外在诸如C++这些现代化编程语言当中也是应该谨慎的:

More modern languages such as C++ offer facilities that insulate programmers from some of the consequences of breaking the single point rule. They do not cause it to stop being a sound rule. They do not turn poor practice into good practice. If you can think of a way that it can go wrong, it will go wrong in the fullness of time. The bizarre rationale offered for dispensing with this rule is born of limited experience and hubris.

这里这里和Overflow上面的讨论1讨论2有更多与此相关的内容,不妨移步去凑凑热闹。

####自己的观点 在写这篇笔记的前后,我的观点是有一些细微的变化的。

从懵懵懂懂继承单一出口原则编程手法之后到不久之前,自己一直如此实践,也觉得这样写是一种自然的写法。当然,这主要得益于没有编写过逻辑太过庞杂的函数以至于让if...else语句嵌套出富丽堂皇的N层楼阁。所以,在阅读到《编写可读代码的艺术》和《重构——改善既有代码的设计》的相关章节之时,突然觉得自己原来一直所坚持的是错误的。这也促使自己决定将此记录下来,备忘之余说不定也能分享给其他人。

在阅读了不少网络上的观点之后,一颗激动的心反而冷静下来了不少。仔细想想,两种做法都具有可取之处,也不一定得非此即彼,一定要占到某个阵营当中去。比如,在笔记开头的那个例子当中可以不用遵守单一出口原则,代码阅读起来会更简洁。另外,不论是在面向过程,还是面向对象的程序设计语言之中,谨慎的遵守单一出口原则如果可以让程序的可读性更好,那就好好使用它。

© 著作权归作者所有

Peter87
粉丝 0
博文 11
码字总数 21993
作品 0
杭州
程序员
私信 提问
Remove Control Flag (移除控制标记)

Summary: 在一系列布尔表达式中,某个变量带有“控制标记”(control flag)的作用。以break语句或return语句取代控制标记。 动机: 在一系列条件表达式中,你常常会看到用以判断何时停止条件...

忆瑶
2014/03/27
104
0
Javascript 设计模式之设计原则与 23 种设计模式

关于设计 按照哪一种思路或标准来实现功能; 功能相同,可以有不同的设计方案来实现; 伴随需求增加,设计作用才体现出来(版本迭代,需求变化); 设计哲学(Unix/Linux) 小即是美; 让每个...

pr
07/21
0
0
Replace Nested Conditional with Guard Clauses

Summary: 函数中的条件逻辑使人难以看清正常的执行路径。使用卫语句表现所有特殊情况。 动机: 条件表达式通常有两种表现形式。第一种形式是:所有分支都属于正常行为。第二种形式则是:条件...

忆瑶
2014/03/28
44
0
php设计模式的六大原则(二):开闭原则

开闭原则应该是六个原则中概念最模糊的一个,例子也是和其他的原则相似的,所以只引用几句比较一针见血的说明: 用抽象构建框架,用实现扩展细节。 单一职责原则告诉我们实现类要职责单一;里...

stone_
2015/11/13
156
0
设计模式六大原则(1):单一职责原则

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。 问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致...

LCZ777
2014/06/29
29
0

没有更多内容

加载失败,请刷新页面

加载更多

OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
56分钟前
4
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
今天
6
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
今天
4
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
今天
6
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部