本章重点关注那些关乎子程序质量好坏的特征

术语阐明

什么是子程序? 子程序是为了实现一个特定目的而编写的一个可以被调用的方法(method)或者过程(procedure)。对于js而言,就是一个方法。

高质量子程序的一些重要属性

  • 创建的理由
  • 子程序的整体设计原则
  • 子程序的名字质量
  • 子程序的长度
  • 参数质量

什么是高质量的子程序?——这个问题也许不好回答。但是我们可以从反面看,什么东西不是高质量的子程序。
所以,这里举一个低质量的子程序作为例子。
Xnip2020-12-27_17-52-08.jpg
Xnip2020-12-27_18-05-28.jpg

序号 问题 现状 实现难度
1 缺少方法注释 注释写的少 心态转换
2 方法名质量差,看不出任何信息 普遍 心态转换,多思考
3 参数过多 比较少 容易
4 参数顺序混乱 普遍不讲究顺序 心态转换,多考虑和其它方法的关联性
5 传递了参数,却未使用 比较少 容易
6 改变了输入的值 普遍 尽量不要改,不要图方便。可以集中控制变化
7 对全局变量的操作,不够明显 心态转换
8 数据操作不够安全 心态转换
9 一堆神秘数字 普遍 心态转换,花点工费为神秘数字起个名字
10 程序的功能不够单一 普遍 修炼抽象能力和提炼技巧,多思考
11 代码风格不统一,没有从物理层面降低代码理解难度 一般 子程序内的代码也要注意分层

创建子程序的正当理由?

创建子程序,并不仅仅是为了“避免代码重复”。

创建子程序的正当理由

降低复杂度。

可以通过创建子程序,来隐藏一些信息。

引入中间、易懂的抽象。

如下图所示:实现同样的功能,谁更易懂?
image.png

避免代码重复。

这是显而易见的了。不解释。

隐藏一些复杂、难懂的操作。

如C++中的指针操作,js中如读取配置信息或者一些复杂、丑陋的计算过程。

简化复杂的布尔判断。

略。

方便改善性能。

通过使用子程序,你可以只在一个地方优化代码。

简单来讲,创建一个子程序时,要考虑四个方面:

  • 代码复用。
  • 隐藏细节:隐藏实现细节/隐藏复杂的、难懂的实现过程。
  • 降低复杂度:降低复杂度。
  • 控制变化(提高可维护性):形成中央控制点,限制变化带来的影响,提高可维护性。

破除编写子程序的心理障碍

似乎过于简单而没有必要写成子程序的操作。

  • 编写有效的子程序,最重要的是要破除心理障碍(太简单了,不愿写,感觉没必要)

Xnip2020-12-27_19-00-43.jpg
将简单的、重复的代码,写成子程序,有以下几个好处:
第一,代码自我注释,更具可读性。
第二,降低未来的维护成本,如子程序内的逻辑变复杂后,将会更好控制。

在子程序层上设计

我们的目标是让每一个子程序只做一件事。这样做的好处是,可以获得更高的可靠性。
子程序要追求的是“内聚”。

如何让子程序尽可能第内聚?

  • 功能上的内聚性
  • 顺序上的内聚性。
  • 通信上的内聚性。
  • 临时的内聚性。如startup()、shutdown()

    不可取的内聚性

  • 过程上的内聚性。

  • 逻辑上的内聚性。
  • 巧合的内聚性。

—— 本小结收获不多。
唯一收获,就是要让子程序具有功能上的内聚。其它的内聚都是邪教。都是不得已的选择。
怎样评估一个子程序的内聚性做的好不好?
有一个很简单的方法,就是站在第三方的角度审查代码,看看这段代码是否可以被他轻易第重构(重新组织)

好的子程序名字

起好名字的指导原则

  • 描述子程序所做的所有事情。
  • 避免使用无意义的、模糊或者表述不清的动词。如handleCalculation()、dealWithOutput() —> formatAndPrintOutput()

— 当你感觉无法很好地为子程序起名字时,可以反思一下,是不是这个子程序的功能就很模糊,职责单一的原则是不是被挑战了。

  • 不要仅通过数字来形成不同的子程序名称。如:outputUser()、outputUser1
  • 根据需要确定子程序名字的长度。不要怕程序名称太长。代码压缩之后,所有的名字都是一样的。

image.png

  • 方法名最好是动词+宾语的结构。如 printDocument()、 checkOrderInfo()
  • 准确使用对账词。

image.png

  • 为常用操作,确定命名规则。在一个系统里,相似的操作最好有一个统一的命名规则。比如对接口的访问,可以是request*()或者其它规则。总之,这个规则在一个系统里,最好是统一的。

    子程序可以写多长

    尽量不要突破200。200行之后,子程序的可读性将受到巨大的挑战。

    如何使用子程序参数

    使用参数的几项原则:

  • 如果几个子程序都使用了类似的一些参数,应该让这些参数的排列顺序保持一致。

子程序的参数顺序可以产生记忆效应——不一致的顺序会让参数难易记忆。

  • 使用所有的参数。

如果一个参数你不用它,就把它从子程序的接口中删去。

  • 把状态或者出错变量放在最后。如异常信息等。
  • 不要把子程序的参数用做工作变量。

把传入的参数用做工作变量是很危险的,正确的做法是初始化一个局部变量进行操作。
危险性来自,一方面可能会导致意想不到的修改(意外的修改——js中应该尽量使用const 修饰符),另一方面会让其它维护者误会对变量的预期。
image.png
image.png
注意:上述程序中两个变量名都是极其糟糕的。此处使用仅仅为了说明其角色。

  • 在接口中对参数的假定加以注释。

image.png

  • 把子程序的参数个数限制在大约7个以内。

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

    没有什么对js特别指导意义的东西。忽略。

    宏子程序和内联子程序

    没有什么对js有特别的知道意义。忽略。

    核对表:高质量的子程序

    image.png

    本章思维导图

    第7章 高质量的子程序 (1).png
    原图:https://www.processon.com/view/link/5fe89cef63768932a2893e57

    GoodCase

    case1:

    ``javascript // #1 相似功能,使用相同的动词前缀'init' function initProps (Comp) { const props = Comp.options.props // #2 将入参存储为局部变量进行使用,且设置为const for (const key in props) { proxy(Comp.prototype,_props`, key) // #3 将复杂的实现隐藏在’proxy’中 } }

// #1 相似功能,使用相同的动词前缀’init’ function initComputed (Comp) { const computed = Comp.options.computed // #2 将入参存储为局部变量进行使用,且设置为const for (const key in computed) { defineComputed(Comp.prototype, key, computed[key]) // #3 将复杂的实现隐藏在’defineComputed’中 } }

  1. <a name="uxNeo"></a>
  2. ## case2:
  3. ```javascript
  4. // #1 破除创建方法过短的心理障碍
  5. export function initMixin (Vue: GlobalAPI) {
  6. Vue.mixin = function (mixin: Object) {
  7. this.options = mergeOptions(this.options, mixin) // #2 将复杂操作封装进另一个方法中
  8. return this
  9. }
  10. }

case3:

  1. // #1 起一个见名知意的方法名
  2. export function resolveConstructorOptions (Ctor: Class<Component>) {
  3. let options = Ctor.options
  4. if (Ctor.super) {
  5. const superOptions = resolveConstructorOptions(Ctor.super)
  6. const cachedSuperOptions = Ctor.superOptions
  7. if (superOptions !== cachedSuperOptions) {
  8. // #2 关键部分增加注释
  9. // super option changed,
  10. // need to resolve new options.
  11. Ctor.superOptions = superOptions
  12. // check if there are any late-modified/attached options (#4976)
  13. const modifiedOptions = resolveModifiedOptions(Ctor)
  14. // update base extend options
  15. if (modifiedOptions) {
  16. extend(Ctor.extendOptions, modifiedOptions)
  17. }
  18. options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
  19. if (options.name) {
  20. options.components[options.name] = Ctor
  21. }
  22. }
  23. }
  24. return options
  25. }