代码整洁之道
整洁代码
细节之于架构,维护之于重构,可维护代码之于可执行代码。
无论时架构还是代码都不强求完美,只求竭诚尽力。
“代码感”,“整洁感”
稍后等于永不 Later Equals Never
除非有一个最小单位模块可能会进行重构,更多的不起眼的细节处要力求在第一次做到最好,因为没有时间留给你再发现和修改,举个例子,那就是某些特殊的页面元素的CSS样式:圆角、阴影等,在你看来差一点无所谓,这些是产品经理和UI设计师关心的细节。所谓的最小单位模块,比如一个轮播图、一个对话框。
比如硬编码、可以做成公共库的代码却在页面重复地添加。
整洁代码的手段
书中作者讲道并不从一开始就按照规则写函数(代码)
- 消除重复
- 关注点分离(只做一件事)
- 统一抽象层次
- 提高表达力(有意义的命名)
- 小规模抽象:从直接的实现手段不断过渡为抽象封装
- 小块代码:类、对象、方法功能不要太多,这是从代码组织上做的切分,与关注点分离类似
- 测试驱动
命名
- 虽然命名变长,但描述性的名称对于理解其含义、编辑器的搜索都大有裨益。从命名就能大抵看出其要做什么,不用过多地去关注其它,能大大提升修改代码、维护代码的效率,降低出错率。
- 同一模块下使用类似的措辞:
getBooks
,updateBooks
,deleteBooks
,addBooks
每个概念对应一个词,没有本质上的区别,get是美式英语,fetch是英式英语,只要统一一种即可。getUser
, getItems
, getShops
- 使用解决方案领域名称和使用源自所涉问题领域的名称
前者即计算机科学所涉领域,如component、service等,后者为具体业务领域,如我们的商品、货源、市场等 术语。
函数
核心
函数参数
原则:尽量不超过两个参数。
参数过多的坏处:声明处和调用处,参数的数量、顺序难以保证对应,对于弱类型的JavaScript,类型对应不正确,导致函数执行时出错。
输出参数
对输出参数的转/改变发生隐蔽,容易忽略。而使用输入参数用函数返回值体系,就很明朗。
尽量使用输入参数,避免输出参数。
如何减少/理清参数
某个参数是一个模块或者可以作为一个模块,则可以把它从作为函数的调用者,就像给对象的prototype进行扩展。
function reviseJourney(journey, item, time) {
journey[item].time = time;
}
// 修改后
// 函数
function reviseJourney(item, time) {
this[item].time = time;
}
reviseJourney.call(journey, '成都', '2019-10-01');
// 对象原型
Journey.prototype.revise = function () {
this[item].time = time;
}
journey.revise('成都', '2019-10-01');
向下过程中多次出现的参数,作为当前类的成员变量
class testPromise {
resolve;
reject;
authorize() {
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
someAsync() {
this.$axios(url).then(res => {
invoke();
});
}
});
invoke() {
// ...
this.resolve();
}
}
}
两个参数的场景
// 单个值的有序组成部分,如对象的key, value, 笛卡尔坐标Point p = new Point(0,0)
updateFilterResults(key, value) {
if (value === '' && typeof this.filterResults[key] === 'undefined') {
return false;
}
this.filterResults[key] = value;
},
// 有容易理解的自然顺序或层级关系: 筛选器的第几个picker,该picker的选项
changeFilterStatus(filterIdx, pickedIdx) {
let filterItem = this.filterPickerGroups[filterIdx];
filterItem.selectIndex = pickedIdx;
this.$set(this._filterPickerGroups, filterIdx, filterItem);
},
把一些参数封装为类,例如(x, y)封装为Center类
// http请求中,unionId作为一个参数,公司信息封装为一个对象
saveCompany(unionId, companyDetail) {
let url = '/market/user/company/save';
let params = {
unionId,
...companyDetail
}
return this.httpReq.post(url, params);
}
函数名和参数的强烈结合,可帮助减小参数名的长度,帮助记忆参数(顺序)。如下,使用第一种即可。
function changePassword(pre, cur) {}
function changePassword(prePsw, curPsw) {}
论文
类
把相近的变量隶属到一个概念,一组变量摸不清语境,则把这组变量作为一个类的成员字段。
关注点分离(只做一件事)
个人理解,这里的一件事,是指真正地用一段代码去描述怎么完成这件事。
而调用下一抽象层级的函数、一个完整的语句(条件、循环),不算做一件事,只算作一个步骤。
- 如果函数只是执行了该函数名下同一 抽象层 上的步骤,则函数只做了一件事。
- 不能再拆出另一个函数。
- 能用一小段话描述出一个连贯的动作。
- 如果函数主要是由多个区段组成,把它们拆出为下一抽象层的函数,连续调用。
例如以下cancelPick函数,changeFilterStatus改变筛选状态,resetRelatePicker重置与该筛选项的其它筛选项, updateFilterResults更新筛选结果,看上去做了“几件事”,但都是向下的调用。
前提:无副作用, 兼顾时序性耦合
(代码整洁之道40页)
cancelPick(filterIdx) {
let filterItem = this.filterPickerGroups[Number(filterIdx)];
this.changeFilterStatus(filterIdx, -1);
this.resetRelatePicker(filterIdx);
this.updateFilterResults(filterItem.name, '');
},
抽象层级
抽象层级的理解
抽象层,大多还是靠“代码感”确定的。如若真要描述,可以把抽象层级理解为一个职位。
在其位,谋其政, 任其职,尽其责;不在其位,不谋其政
- 在其位:抽象层级
- 谋其政,任其职:这一件事(当前函数所要做的事)
- 尽其责:这件事的实现(代码段)
- 不在其位,不谋其政:不在职位位置上,就不去考虑那个职位上的事。可以安排下属去做(调用下一抽象层级的函数),如果需要,听取汇报结果(函数返回结果)。
高中低抽象层级
- 高:例如前端与视图层有关的操作。
- 中:除了高、低之外的。
- 低:编程语言层的操作,如对字符串的处理,对象数组的转换。
小结
- 多个参数封装为类
2. 标记参数,传入和不传入分成两个方法
3. 不用一股脑加set方法,业务需要的时候写方法