一、概念
- 什么是子程序?
为实现一个特定的目的编写的一个可被调用的方法(method)或过程(procedure)。 - 什么是高质量的子程序?
正面回答很难,可以从我们厌恶什么开始作排除,先观察一个反例。
分析:
- 有很差劲的名字,HandleStuff未能指明函数的作用,客户无法快速理解其作用
- 没有文档说明(敏捷追求代码即文档,具有自描述性),此处代码没有突出业务概念
- 代码布局不友好,代码的组织没有突出业务逻辑提示
- 入参的命名及是否可被修改存在矛盾,inputRec的类型是引用,表明可被修改,同时命名为input从意义上看只作输入,其值就不应该被修改
- 子程序对全局变量进行了读写,存在副作用,它从全局变量corpExpense中读取数据并将其写入全局变量profit
- 不满足单一职责,作了初始化、写数据库、计算等操作,且它们之间没有明确的联系
- 没有防范除0错误
- 使用了magic number,未能突出数字要表达的业务语义
- 子程序的参数太多,有11个参数,难以理解,一般上限为8个
- 子程序参数混乱且没有注释
- 其他,等等
- 子程序的作用
- 避免重复、易读、易理解
- 节约空间、提高性能
- 易于开发、调试、编档、维护
二、创建子程序的正当理由
- 降低复杂度,子程序可以隐藏客户不关注的信息,如实现细节
- 引入中间、易懂的抽象
对比: - 避免重复代码
- 支持子类化
- 隐藏顺序
- 隐藏指针操作
- 提高可移植性
- 简化复杂的布尔判断
- 改善性能,可以只在一个地方优化代码,二八法则
三、在子程序层上设计
内聚性
☞指子程序中各种操作之间联系的紧密程度。
内聚性的层次
【格言】理解一些概念要比记住一些特定的术语重要。
功能的内聚性 一个子程序只执行一项操作,是最强最好的一种内聚性。
顺序的内聚性 子程序内包含有需要按特定顺序执行的操作,这些步骤需要共享数据,且只有全部执行完成后才完成了一项完整的功能。
通信上的内聚性 一个子程序中的不同操作使用了同样的数据,但不存在其他任何关联。
临时的内聚性 子程序含有一些因需要同时执行才放到一起的操作
过程上的内聚性 一个子程序中的操作是按照特定的顺序执行的
逻辑上的内聚性 若干个操作被放入同一个子程序中,通过传入的控制标志选择执行其中的一项操作
巧合的内聚性 子程序中各个操作没有任何的可以看到的关联
【记住】编写子程序,需要把注意力集中在功能的内聚性上。
四、好的名字
指导原则
- 描述子程序所做的所有事情 包括输出的结果及副作用
- 避免使用无意义的、模糊或表述不清的动词 eg:HandleCaculation没有表明具体做什么,除了计算,那是什么的计算呢?
- 不要仅通过数字来形容不同的子程序名字 eg:OutputUser1,OutputUser2
- 根据需要确定子程序名的长度,最佳长度9-15字符
- 给函数命名时要对返回值有所描述
- 建议使用动宾形式的名字 eg:PrintDocument
- 准确使用对仗词,有助于保持一致性,提供可读性。eg:add/remove, inc/dec, begin/end
- 为常用操作确立命名规则
五、子程序可以写多长
历史研究建议是不要超过200行。
六、如何使用子程序参数
指导规则
- 按照输入-修改-输出的顺序排列参数
- 考虑自己创建in和out关键字,注意不要误解自己创建,应该是利用语言特性规则限制入参的读写修改特性
- 如果几个子程序都用了类似的一些参数,应该让这些参数的排列顺序保持一致
- 使用所有的参数,没有冗余的入参
- 把状态或出错变量放在最后
- 不要把子程序的参数用作工作变量
- 在接口中对参数的假定加以说明
- 把参数个数限制在大约7个以内
- 使用具名参数
七、其它
返回值
- 检查所有可能的返回路径
- 不要返回指向局部数据的引用或指针
宏函数
- 把宏表达式整个包括在括号内
反例:
正例: - 把含有多条语句的宏用大括号括起来
反例:
正例: - 用子程序的命名规则给宏命名,以便在需要的时候用子程序替换
- 谨慎使用宏,除非万不得已才使用宏代替子程序
内联
【记住】节制使用内联子程序
- 违反封装原则
- 会增加整体代码长度
- 对性能上考量,需要对比测试为准