代码整洁之道-第3章-函数-读书笔记

第 3 章 函数

3.1 短小

  函数的第一规则是要短小。第二规则是还要更短小。
代码块和缩进
  if 语句、 else 语句、 while 语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。
  这也意味着函数不应该大到足以容纳嵌套结构。所以,函数的缩进层次级不该多于一层或两层。当然,这样的函数易于阅读和理解。

3.2 只做一件事

  函数应该做一件事。做好这件事。只做这一件事。
  如果函数只是做了该函数名下同一抽象层上的步骤,则函数还是只做了一件事。编写函数毕竟是为了把大一些的概念(换言之,函数的名称)拆分为另一抽象层上的一系列步骤。
  要判断函数是否不止做了一件事,还有一个方法,就是看是否能再拆出一个函数,该函数不仅只是单纯地重新诠释其实现。
函数中的区段
  只做一件事的函数无法被合理地切分为多个区段。

3.3 每个函数一个抽象层级

自顶向下读代码:向下规则
  我们想要让代码拥有自顶向下的阅读顺序。我们想要让每个函数后面都跟着位于下一个抽象层级的函数,这样一来,在查看函数列表时,就能循抽象层级向下阅读了。我把这叫做向下规则。
  换一种说法。我们想要这样读程序:程序就像是一系列 TO 起头的段落,每一段都描述当前抽象层级,并引用位于下一个抽象层级的后续 TO 起头段落。
  这是保持函数短小、确保只做一件事的要诀。让代码读起来像是一系列自顶向下的 TO 起头段落是保持抽象层级协调一致的有效技巧。

3.4 switch 语句

  对于 switch 语句,我的规矩是如果只出现一次,用于创建多态对象,而且隐藏在某个继承关系中,在系统其它部分看不到,就还能忍受。当然也要就事论事,有事我也会部分或全部违反这条规矩。

3.5 使用描述性的名称

  沃德原则:“如果每个例程都让你感到深合己意,那就是整洁代码。”要遵循这一原则,泰半工作都在于为只做一件事的小函数取个好名字。函数越短小、功能越集中,就越便于取个好名字。
  别害怕长名称。长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称。
  选择描述性的名称能理清你关于模块的设计思路,并帮你改进之。追索好名称,往往导致对代码的改善重构。
  命名方式要保持一致。使用与模块名一脉相承的短语、名词和动词给函数命名。

3.6 函数参数

  最理想的参数数量是零(零参数函数),其次是一(单参数函数),再次是二(双参数函数),应尽量避免三(三参数函数)。有足够特殊的理由才能用三个以上参数(多参数函数)—所以无论如何也不要这么做。

3.6.1 一元函数的普通形式

  向函数传入单个参数有两种极普遍的理由。一种是得到参数的属性,返回参数的状态,如 boolean fileExists("MyFile")。也可能是操作该参数,将其转换为其他什么东西,再输出之,如InputStream fileOpen("MyFile")。应当选用较能区分这两种理由的名称,而且总在一致的上下文中使用这两种形式。
  还有一种虽不那么普遍但仍极有用的单参数函数形式,那就是事件(event)。在这种形式中,有输入参数而无输出参数。程序将函数看作是一个事件,使用该参数修改系统状态。小心使用这种形式。应该让读者很清楚地了解它是个事件。谨慎地选用名称和上下文语境。
  尽量避免编写不遵循这些形式的一元函数。
  对于转换,使用输出参数而非返回值令人迷惑。如果函数要对输入参数进行转换操作,转换结果就该体现为返回值。

3.6.2 标识参数

  标识参数丑陋不堪。向函数出入布尔值简直就是骇人听闻的做法。这样做,方法签名立刻变得复杂起来,大声宣布本函数不止做一件事。
  应该把 render(Boolean isSuite) 函数一分为二: reanderForSuite()renderForSingleTest()

3.6.3 二元函数

  有两个参数的函数要比一元函数难懂。
  有些时候两个参数正好。但是两个参数是单个值的有序组成部分。

3.6.4 三元函数

  有三个参数的函数要比二元函数难懂得多。排序、琢磨、忽略的问题都会加倍体现。

3.6.5 参数对象

  如果函数看来需要两个、三个或三个以上参数,就说明其中一些参数应该封装为类了。

3.6.6 参数列表

  有可变参数的函数可能是一元、二元甚至三元。超过这个数量就可能要犯错。

3.6.7 动词与关键字

  给函数取个好名字,能较好地解释函数的意图,以及函数的顺序和意愿。对于一元函数,函数和参数应当形成一种非常良好的动词/名词对形式。

3.7 无副作用

  副作用是一种谎言。函数承诺只做一件事,但还是会做其他被藏起来的事。有时,它会对自己类中的变量做出未能预期的改动。有时,它会把变量搞成向函数传递的参数或是系统全局变量。无论哪种情况,都是具有破坏性的,会导致古怪的时序性耦合及顺序依赖。

输出参数
  参数多数会被自然而然地看作是函数的输入。
  普通而言,应避免使用输出参数。如果函数必须要修改某种状态,就修改所属对象的状态吧。

3.8 分隔指令与询问

  函数要么做什么事,要么回答什么事,但二者不可得兼。函数应该修改某对象的状态,或是返回该对象的有关信息。两样都干常会导致混乱。

3.9 使用异常替代返回错误码

  从指令式函数返回错误码轻微违反了指令与询问分隔的规则。它鼓励了在 if 语句判断中把指令当做表达式使用。这不会引起动词/形容词混淆,但却导致更深层次的嵌套结构。当返回错误码时,就是在要求调用者立刻处理错误。
  如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来,得到简化。

3.9.1 抽离 Try/Catch 代码块

  Try/Catch 代码块丑陋不堪。它们搞乱了代码结构,把错误处理和正常流程混为一谈。最好把 try 和 catch 代码块的主体部分抽离出来,另外形成函数。

3.9.2 错误处理就是一件事

  函数应该只做一件事,错误处理就是一件事。因此,处理错误的函数不该做其他事。这意味着如果关键字 try 在某个函数中存在,它就该是这个函数的第一个单词,而且在 catch/finally 代码块后面不该有其他内容。

3.9.3 Error.java 依赖磁铁

  返回错误码通常暗示某处有个类或是枚举,定义了所有错误码。
  这样的类就是一块依赖磁铁(dependency magnet);其他许多类都得导入和使用它。当 Error 枚举修改时,所有这些其他的类都需要重新编译和部署。这对 Error 类造成了负面压力。程序员不愿增加新的错误代码,因为这样他们就得重新构建和部署所有东西。于是他们就复用旧的错误码,而不添加新的。
  使用异常替代错误码,新异常就可以从异常类派生出来,无需重新编译或重新部署。

3.10 别重复自己

  重复可能是软件中一切邪恶的根源。许多原则与实践规则都是为控制与消除重复而创建的。

3.11 结构化编程

  有些程序员遵循 Edsger Dijkstra 的结构化编程规则。 Dijstra 认为,每个函数、函数中的每个代码块都应该有一个入口、一个出口。遵循这些规则,意味着在每个函数中只该有一个 return 语句,循环中不能有 break 或 continue 语句,二期永永远远不能有任何 goto 语句。
  我们赞成结构化编程的目标和规范,但对于小函数,这个规则助益不大。只有在大函数中,这些规则才会有明显的好处。
  所以,只要函数保持短小,偶尔出现的 return 、 break 或 continue 语句没有坏处,甚至还比单入单出原则更具有表达力。另一方面, goto 只在大函数中才有道理,所以应该尽快避免使用。

3.12 如何写出这样的函数

  写函数时,一开始并不能做到函数符合规则,而是在后期对代码进行打磨,分解函数、修改名称、消除重复等操作之后,才能符合规则。

3.13 小结

  每个系统都是使用某种领域特定语言搭建,而这种语言是程序员设计来描述那个系统的。

3.14 SetupTeardownIncluder 程序

3.15 文献

posted on 2019-01-07 09:23  zhangmiao14  阅读(498)  评论(0编辑  收藏  举报