术语阐明
什么是子程序? 子程序是为了实现一个特定目的而编写的一个可以被调用的方法(method)或者过程(procedure)。对于js而言,就是一个方法。
高质量子程序的一些重要属性
- 创建的理由
- 子程序的整体设计原则
- 子程序的名字质量
- 子程序的长度
- 参数质量
什么是高质量的子程序?——这个问题也许不好回答。但是我们可以从反面看,什么东西不是高质量的子程序。
所以,这里举一个低质量的子程序作为例子。
序号 | 问题 | 现状 | 实现难度 |
---|---|---|---|
1 | 缺少方法注释 | 注释写的少 | 心态转换 |
2 | 方法名质量差,看不出任何信息 | 普遍 | 心态转换,多思考 |
3 | 参数过多 | 比较少 | 容易 |
4 | 参数顺序混乱 | 普遍不讲究顺序 | 心态转换,多考虑和其它方法的关联性 |
5 | 传递了参数,却未使用 | 比较少 | 容易 |
6 | 改变了输入的值 | 普遍 | 尽量不要改,不要图方便。可以集中控制变化 |
7 | 对全局变量的操作,不够明显 | 少 | 心态转换 |
8 | 数据操作不够安全 | 少 | 心态转换 |
9 | 一堆神秘数字 | 普遍 | 心态转换,花点工费为神秘数字起个名字 |
10 | 程序的功能不够单一 | 普遍 | 修炼抽象能力和提炼技巧,多思考 |
11 | 代码风格不统一,没有从物理层面降低代码理解难度 | 一般 | 子程序内的代码也要注意分层 |
创建子程序的正当理由?
创建子程序的正当理由
降低复杂度。
引入中间、易懂的抽象。
避免代码重复。
隐藏一些复杂、难懂的操作。
如C++中的指针操作,js中如读取配置信息或者一些复杂、丑陋的计算过程。
简化复杂的布尔判断。
方便改善性能。
通过使用子程序,你可以只在一个地方优化代码。
简单来讲,创建一个子程序时,要考虑四个方面:
- 代码复用。
- 隐藏细节:隐藏实现细节/隐藏复杂的、难懂的实现过程。
- 降低复杂度:降低复杂度。
- 控制变化(提高可维护性):形成中央控制点,限制变化带来的影响,提高可维护性。
破除编写子程序的心理障碍
似乎过于简单而没有必要写成子程序的操作。
- 编写有效的子程序,最重要的是要破除心理障碍(太简单了,不愿写,感觉没必要)。
将简单的、重复的代码,写成子程序,有以下几个好处:
第一,代码自我注释,更具可读性。
第二,降低未来的维护成本,如子程序内的逻辑变复杂后,将会更好控制。
在子程序层上设计
我们的目标是让每一个子程序只做一件事。这样做的好处是,可以获得更高的可靠性。
子程序要追求的是“内聚”。
如何让子程序尽可能第内聚?
—— 本小结收获不多。
唯一收获,就是要让子程序具有功能上的内聚。其它的内聚都是邪教。都是不得已的选择。
怎样评估一个子程序的内聚性做的好不好?
有一个很简单的方法,就是站在第三方的角度审查代码,看看这段代码是否可以被他轻易第重构(重新组织)
好的子程序名字
起好名字的指导原则
- 描述子程序所做的所有事情。
- 避免使用无意义的、模糊或者表述不清的动词。如handleCalculation()、dealWithOutput() —> formatAndPrintOutput()
— 当你感觉无法很好地为子程序起名字时,可以反思一下,是不是这个子程序的功能就很模糊,职责单一的原则是不是被挑战了。
- 不要仅通过数字来形成不同的子程序名称。如:outputUser()、outputUser1
- 根据需要确定子程序名字的长度。不要怕程序名称太长。代码压缩之后,所有的名字都是一样的。
- 方法名最好是动词+宾语的结构。如 printDocument()、 checkOrderInfo()
- 准确使用对账词。
为常用操作,确定命名规则。在一个系统里,相似的操作最好有一个统一的命名规则。比如对接口的访问,可以是request*()或者其它规则。总之,这个规则在一个系统里,最好是统一的。
子程序可以写多长
尽量不要突破200。200行之后,子程序的可读性将受到巨大的挑战。
如何使用子程序参数
使用参数的几项原则:
如果几个子程序都使用了类似的一些参数,应该让这些参数的排列顺序保持一致。
子程序的参数顺序可以产生记忆效应——不一致的顺序会让参数难易记忆。
- 使用所有的参数。
如果一个参数你不用它,就把它从子程序的接口中删去。
- 把状态或者出错变量放在最后。如异常信息等。
- 不要把子程序的参数用做工作变量。
把传入的参数用做工作变量是很危险的,正确的做法是初始化一个局部变量进行操作。
危险性来自,一方面可能会导致意想不到的修改(意外的修改——js中应该尽量使用const 修饰符),另一方面会让其它维护者误会对变量的预期。
注意:上述程序中两个变量名都是极其糟糕的。此处使用仅仅为了说明其角色。
- 在接口中对参数的假定加以注释。
- 把子程序的参数个数限制在大约7个以内。
使用函数时要特别考虑的问题
没有什么对js特别指导意义的东西。忽略。宏子程序和内联子程序
没有什么对js有特别的知道意义。忽略。核对表:高质量的子程序
本章思维导图
原图:https://www.processon.com/view/link/5fe89cef63768932a2893e57GoodCase
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’中 } }
<a name="uxNeo"></a>
## case2:
```javascript
// #1 破除创建方法过短的心理障碍
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin) // #2 将复杂操作封装进另一个方法中
return this
}
}
case3:
// #1 起一个见名知意的方法名
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// #2 关键部分增加注释
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}