"为什么要有代码规范?"
看到这个问题,可能行内人就会说"这是问题吗?这需要讨论吗?",因为现在已经是行内人的共同认知了。然而,对于未入行或刚入门的小伙伴们又确实是问题。而且,理清之后还能能帮他们建立更好的观念,让后面的工作走得更顺。
大神唐纳德.克努特在《计算机编程艺术》中,说过"代码是给人看的,顺带着给机器执行"。
"Programs are meant to be ready by humans and only icidentally for computers to execute."
------Donald Ervin Knuth《The Art of Computer Programming》
怎么理解这句话呢?
乍一看,它是违反我们的直觉的,但是细细想来,又是非常合理的------代码在机器的世界里,最后都会转化成0和1,它本来的样子机器根本就不在乎;反而,代码在人类世界里,都是需要被反复阅读理解的,const a = 0
跟 const showDialog = false
,在人类世界完全就是不一样的语句。
所以,代码的的确确是写给人看的。
那既然代码都是写给人看的,我们就要想办法写出好看的代码。怎么个好看法呢?在这里我把它分成悦目和赏心两种。
目标一:悦目,看起来很舒服
悦目,就是字面上的看起来好看,要处理好标点符号、格式、排版的问题。
虽然不至于像政府的红头文件那样具体规范到每个字、每个词、每个标点,也不至于导致要像强迫症那样每敲一个字母都小心翼翼,但是起码要保证层级对齐,空格间隙一致。
而且很幸运的是,现在的代码工具都能解决大部分的格式问题。你只需要点击鼠标右键,进行格式化即可。
除了代码层面要好看,还可以规范到文件夹等项目架构上。比如,以下就是一个 angularjs 写的项目。
文件夹中,事无巨细地规定了各文件夹的功能:
- @core 中, 有专门负责数据处理的 data ,本地数据模拟 mock,和常用的 js 工具库 utils。
- @theme 中,分了组件、指令、布局、管道、样式,
- pages 中,则是各个页面的组件、html、css
文件中,命名也很讲究:
- 用 module 后缀表示模块的代码,如上 core.module.ts
- 用 component 后缀表示组件的代码,如上 chartjs-bar.component.ts
- 用 layout 后缀表示布局的代码,如上 one-column.layout.ts
- 相关功能的命名前缀保持一致,如 chartjs-bar-horizontal.component.ts 和 chartjs-bar.component.ts
这样,整个目录结构阅读下来,就对项目整体有了很好的认识。如果现在让你去修改一个数据,你就能立马想到要去 @core/data
查看代码了。
目标二:赏心,降低心智负担
而赏心,则要让人减少心力消耗,也就是现在常说的降低心智负担。
1. 减少 bug 发生
试想想,你拿到一个旧项目,执行时,控制台输出一堆 warning,出了一身冷汗;然后到 99% 的时候,又来了几个 error,整个项目就彻底起不来,那不是整个人都崩溃了~
比如,容易引起 XSS 攻击的写法、Java 的空指针、前端的eval字符串转函数,这些都会在规范中作规定,指出处理方法。
再比如 vue 规范中的,"组件名为多个单词必要"。官方解释是:这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。不仅考虑了当前的情况,更考虑了未来可能出现的情况。
2. 让人更好理解
如何让人更好理解,在代码规范中占大篇幅的内容。
比如常说的命名问题,可以规定常用的动词,get,set,save,load,delete 等作为常用的方法名前缀;也可以规避拼音和英文混排的情况,因为这样会让人在两个语言中来回切换,还是十分痛苦的;如果是行业软件,提供行业内的英文单词表,对我们中国的开发者也更友好一些。
又比如注释,要把函数的功能,输入输出,甚至例子写得清楚明白。以下是前端工具库 lodash 深拷贝方法的注释,3行代码,却有 10 行的完整的注释:
scss
**
* This method is like `clone` except that it recursively clones `value`.
* Object inheritance is preserved.
*
* @since 1.0.0
* @category Lang
* @param {*} value The value to recursively clone.
* @returns {*} Returns the deep cloned value.
* @see clone
* @example
*
* const objects = [{ 'a': 1 }, { 'b': 2 }]
*
* const deep = cloneDeep(objects)
* console.log(deep[0] === objects[0])
* // => false
*/
function cloneDeep(value) {
return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
}
另外,我们要注意的是:函数是有顺序、有层级的。
将函数拆分成短小精悍的函数,或将函数有规律地整理或者分层,不随便跳转的函数,都能让人快速的理解代码。虽然现在的编程语言已经不像以前的 Basic 那样有 goto 语句,但是还是经常有同学在函数中使用大段的 if else,并随意调用其他函数,这些同样会导致阅读起来费劲。
这些也可以写到我们的规范中。
示例 ,一个理想的前端 vue 代码:
xml
<script>
import '...' from vue;
import '...' from '@utils/';
import '...' from './components/';
export default {
components: {},
props(){},
data(){},
methods: {
// 其他一些被本组件调用的函数
...,
// 最后的入口函数
getData(),
processData(),
bindEvent(),
},
mounted() {
this.getData();
this.processData();
this.bindEvent();
}
}
</script>
<template>
...
</template>
<style>
...
</style>
可以看出其将代码作了一定的顺序排布:
- 遵循先定义后使用的原则,定义变量的 script 放前面,template 后面;
- import 从远及近地引入库。
- 定义组件时,从外部引入的 component、props 先定义,然后才是本地的变量 data,最后是方法 methods 和入口mounted;
- 方法中,越是底层的方法放在越上面,最后才放入口调用的方法。
这样处理之后,如果要阅读整体的代码,只需要从入口文件开始,进行阅读,就能大概理解全貌了;要理解代码细节的时候,再往上阅读即可。
总结
写出赏心悦目的代码,这是一个说大不大说小不小的目标,也是我们写代码第一天起就要去想的问题。
而代码规范需要为我们指明方向,我想这是代码规范存在的意义。