何为“子程序(routine)”?子程序是为实现一个特定的目的而编写的一个可被调用的方法(method)或过程(procedure)。如果是在 iOS 开发中,我们可以简单地将其视为方法。

抛开计算机本身而言,子程序算得上是计算机科学中的一项最为重大的发明了(另一项重要发明是类,在第六章有讲到)。子程序可以让程序变得更加易读,更易于理解,比任何编程语言的任何功能特性都更容易。

创建子程序的理由

关于这点,书上列举了很多点创建子程序的理由。

而其中最重要的两点就是:

  • 降低复杂度
  • 引入中间的、易懂的抽象

有时候可能会觉得没有必要对一个只有两三行的代码写一个子程序,但是只要这个子程序让代码更易懂,并且提供了更高一层的抽象,就应该进行将它写成一个子程序。实际中,在《Clean Code》当中,作者推崇的就是短小的函数。

子程序的内聚性

对于程序而言,内聚性是指子程序中各操作之间联系的紧密程序。目标就是让每一个子程序只把一件事做好,不再做任何其它的事情。

最强也是最好的一种内聚性是功能的内聚性,这代码这个子程序仅执行一项操作。

其他一些不够理想内聚性:

  • 顺序上的内聚性:指在子程序内部包含有需要特定顺序执行的操作,这些步骤需要共享数据,而且只有全部执行完毕后才完成了一项完整的功能。
  • 通信上的内聚性:指一个子程序中的不同操作使用了同样的数据,但是不存在其它任何联系。
  • 临时的内聚性:指含有一些因为需要同时执行才放到一起的操作的子程序。

不可取的内聚性:

  • 过程上的内聚性:指一个子程序中的操作是按特定的顺序执行的。
  • 逻辑上的内聚性:指若干操作被放入同一个子程序中,通过传入的控制标志选择执行其中的一项操作。
  • 巧合的内聚性:指子程序中的各个操作之间没有任何可以看到的关联。

子程序的命名

这里的命名只针对子程序,关于变量的命名在第十一章。

子程序命名的指导原则:

  • 描述子程序所做的所有事情
  • 避免使用无意义的、模糊或表述不清的动词
  • 不要仅通过数字来形成不同的子程序名字
  • 根据需要确定子程序名字的长度
  • 给函数命名时要对返回值有所描述
  • 给过程起名时使用语气强烈的动词加宾语形式(在面向对象语言中,不用在过程名中加入对象的名字,因为对象本身已经包含在调用语句中了)
  • 准确使用对仗词
  • 为常用操作确定命名规则

子程序的长度

不需要对子程序的长度强加限制,可以让如下因素来决定子程序的长度:内聚性、嵌套的层次、变量的数量、决策点(decision points)的数量、解释子程序用意所需的注释数量以及其他一些跟复杂度相关的考虑事项等。

而 Bob 大叔推崇的是短小的函数,他在《Clean Code》里面不止一次强调,函数需要短小。

子程序的参数

一些减少接口错误的指导原则:

  • 按照输入-修改-输出的顺序排列参数(在 iOS 开发中,最常见的输出参数应该就是 NSError 参数了,它一般也是放在最后一个)
  • 如果几个子程序都用了类似的一些参数,应该让这些参数的排列顺序保持一致
  • 使用所有的参数
  • 把状态或出错变量放在最后
  • 不要把子程序的参数用做工作变量
  • 在接口中对参数的假定加以说明(对于强制要求参数符合某种条件的情况下,可以使用断言)
  • 把子程序的参数个数限制在大约 7 个以内
  • 为子程序传递用以维持其接口抽象的变量或对象
  • 使用具名参数(在 Objective-C 或 Swift 当中都有的外部参数)

关于倒数第二点的补充,假设有一个子程序需要一个对象中的三个属性进行操作,这种情况下,我们应该分别传入三个参数,还是直接传入整个对象呢?

这个问题其实也困扰过我好多次了,作者认为两种情况都过于片面,我们需要的是认准问题的要害:子程序的接口要表达何种抽象?

如果要表达的抽象是子程序期望 3 项特定的数据,但这 3 项数据是碰巧由同一个对象所提供的,那就应该单独传递这 3 项数据。然而,如果子程序接口要表达的抽象是想一直拥有某个特定对象,且该子程序要对这一对象执行各种操作,就需要传递整个对象。

使用函数时要特别考虑的问题

  • 什么时候使用函数,什么时候使用过程
    如果一个子程序的主要用途就是返回由其名字所指明的返回值,那么应该使用函数,否则就应该使用过程。
  • 设置函数的返回值
    • 检查所有可能的返回路径(可以在函数开头用一个默认值来初始化返回值)
    • 不要返回指向局部数据的引用或指针

宏子程序和内联子程序

在 Objective-C 开发过程中,还是经常得使用到宏,所以有必要特别关注下:

  • 把宏表达式整个包含在括号内
  • 把含有多条语句的宏用大括号括起来
  • 用给子程序命名的方法来给展开后代码形同子程序的宏全名,以便在需要时可以用子程序来替换宏(除非万不得已,还是不要使用宏来代替子程序)

要点

书上提供的本章要点:

  • 创建子程序最主要的目的是提高程序的可管理性,当然也有其它一些好的理由。其中,节省代码空间只是一个次要原因:提高可读性、可靠性和可修改性等原因都更重要一些。
  • 有时候,把一些简单的操作写成独立的子程序也非常有价值。
  • 子程序可以按照其内聚性分为很多类,而你应该让大多数子程序具有功能上的内聚性,这是最佳的一种内聚性。
  • 子程序的名字是它的质量的指示器。如果名字糟糕但恰如其分,那就说明这个子程序设计得很差劲。如果名字糟糕而且不准确,那么它就反映不出程序是干什么的。不管怎样,糟糕的名字都意味着程序需要修改。
  • 只有在某个子程序的主要目的是返回由其名字所描述的特定结果时,才应该使用函数。
  • 细心的程序员会非常谨慎地使用宏,而且只在万不得已时才用。