注 1: 只看了书的前十章
注 2: 原书使用的语言为 Java, 我改成了 JavaScript
第一章 为什么要整洁代码
1, 代码永不消失
代码就是衔接人脑理解需求的含糊性和机器指令的精确性的桥梁. 哪怕未来会有对现在高级编程语言的再一次抽象 -- 但这个抽象规范自身仍旧是代码.
所以既然代码会一直存在下去, 且自己都干了程序员这一行了, 就好好的对待它吧.
2, 读远比写多
当你录下你平时的编码的过程, 回放时, 你会发现读代码所花的时间远比写的多, 甚至超过 10:1. 所以整洁的代码会增加可读性.
3, 对抗拖延症 -- 稍后等于永不
糟糕的代码会导致项目的难以维护. 而当你正在写糟糕的代码的时候, 心里却圣洁的想:"有朝一日再回来整理". 但现实是残酷的, 正如勒布朗 (LeBlanc) 法则: 稍后等于永不(Later equals never)
4, 精益求精
写代码就跟写文章一样, 先自由发挥, 再细节打磨. 追求完美.
大师级程序员把系统当作故事来讲, 而不是当作程序来写.
第二章 命名
1, 名副其实
(*)图表: mapTable
(√)图表: chart
推荐一个根据中文意思帮你起英文变量名的网址: http://unbug.GitHub.io/codelf/
2, 避免误导
(1)不要用专有名词
const var = 0;
(2)避免细微之处有不同
- XYZControllerForEfficientKeepingOfStrings
- XYZControllerForEfficientHoldingOfStrings
(3)避免废话
如果是一个数组类型, 就没必要叫 ProductArray
如果返回值就是数据, 就没必要叫 ProductData
(4)便于搜索
- (*)
- if ( team.length === 3 )
- (√)
- const MAX_NUM_OF_TEAM = 3 ;
- ......
- if ( team.length === MAX_NUM_OF_TEAM )
MAX_NUM_OF_TEAM 比 3 好检索
早期流行一种匈牙利语标记法:
如 arru8NumberList 的前缀 "arru8" 表示变量是一个无符号 8 位整型数组;
3, 避免思维映射
类名, 参数名用名词:
member
leader
方法名用动词:
getTeam
根据 Javabean 的标准, 方法名可以加上 get set is 前缀
4, 每个概念用一个词
get fetch 选一个, 不要混着用
5, 添加有意义的语境
如果你要记录 member 的详细住址, 会设置了如下几个变量:
- (*)
- addrStreet addrHouseNumber addrCity addrState
- (√)
- new Address {
- street,
- houseNumber,
- city,
- state
- }
第三章 函数
尽量短小
函数的缩进层级尽量控制在 1-3 层
例如要依次读取 A,B,C 三个文件:
- (*)
- function test() {
- readFileA(function (err, data) {
- // todo
- readFileB(function (err, data) {
- // todo
- readFileC(function (err, data) {
- // todo
- //......
- });
- });
- });
- }
- (√)
- function test() {
- try {
- let re_a = await readFileA();
- let re_b = await readFileB();
- let re_c = await readFileC();
- } catch (err) {
- }
- }
只做一件事
判断标准: 是否可以再拆出一个函数
- (*)
- function test() {
- //......
- Session.save();
- //......
- }
- (√)
- function test() {
- //......
- saveSession();
- //......
- }
- function saveSession(){
- Session.save();
- }
每个函数即是一个抽象层, 如上面的例子, Session 跟 test 函数不是一个抽象层, 所以抽离出来.
函数名代替注释
长且具有描述性的名字比描述性的长注释好
- (*)
- // 取出所有满员的团
- getSpecialGroup()
- (√)
- getGroupOfFullMember()
参数顺序太多记不住怎么办?
方法一: 体现在函数名上
- (*)assertEqual(expected, actual)
- (√)assertExpectedEqualsActual(expected, actual)
现代 IDE 已经具有鼠标移在调用函数名上可以浮窗显示形参列表了.
方法二: 让参数可以打乱传
- function getMember({isNew = false,isActivate = false}){
- console.log("isNew:"+isNew,", isActivate:"+isActivate)
- }
- getMember({isNew:true,isActivate:false}) //isNew:true , isActivate:false
- // 不会因为传参的顺序记错而出错
- getMember({isActivate:false,isNew:true}) //isNew:true , isActivate:false
- // 也支持默认参数
- getMember({}) //isNew:false , isActivate:false
这里用到了 ES6 的新特性: 解构赋值
- let obj = {
- a:1,b:2
- };
- let {
- a,b
- } = obj; // a = 1, b = 2
方法三: 减少参数
最理想的参数数量 < 3, 最好不用输入参数.
A, 布尔值参数 一拆二
- (*)getMember(isNew)
- (√) getNewMember() getOldMember()
这里的前提是获取新, 老会员的方法体代码不一样, 不然还是共用在一个方法通过布尔值比较好.
B, 二元函数变一元
- function addSuffix(origin,suffix){
- return origin+"+"+suffix;
- }
- console.log(addSuffix("hello","world"));
- console.log("hello".addSuffix("world"));
异常代替返回错误码
见我另一篇 《 如何做好错误处理?(PHP 篇)》 里面有体现
抽离 try / catch 块
见我另一篇 《 如何做好错误处理?(PHP 篇)》 里面有体现
避免重复
否则得修改多处地方
结构化编程
(1)每个函数都应该有一个入口和一个出口
关于 (只能有一个 return 语句, 在结尾处 / 尽快 return, 即有多个 return 语句) 的争论:
《结构化编程》的建议:
- function test(is) {
- let result;
- if(is){
- result = true;
- }else{
- result = false;
- }
- return result;
- }
《重构》的建议:
- function test(is) {
- if(is){
- return true;
- }else{
- return false;
- }
- }
这两者写法现在仍有争议, 详细的讨论可以点击:
(2)循环中尽量避免有 break 或 continue , 而且决不能出现 goto 语句.
第四章 注释
尽量用代码表达, 而不是用注释
就像上文提到的用详尽的函数名代替注释, 或者:
- // 代码过于追求简洁而导致这里要加详细的注释
- if( smodule.getDependSubsystems().contains(subSysMod.getSubSytem()) )
- // 还不如这里做拆分, 取易懂的变量名方便理解, 就可以不用加注释或者少加
- ArrayList moduleDependees = smodule.getDependSubsystems();
- String ourSubSystem = subSysMod.getSubSystem();
- if( moduleDependees.contains(ourSubSystem) )
原因是: 注释存在的越久, 随着代码的不断迭代, 会离代码的距离越来越远, 这个时候好的做法是同时维护代码 + 注释, 而注释越多越复杂, 维护的成本自然就上升了.
注释不能美化糟糕的代码, 尽量去优化代码
好注释
(1)法律信息, 许可证, 版权, 著作权, 外链文档的 url
(2)对意图的解释
(3)警示
(4)TODO 注释
坏注释
(1)只有自己看得懂
(2)多余的注释
a, 不能提供比代码更多的信息
b, 读的时间比直接看代码还久
(3)歧义性
(4)循规蹈矩的注释
例如用第三方工具生成的注释, 很多都是累赘的废话
(5)日志式, 署名式注释
- (*)
- // write by colin
- // fix #201 bug
- (√)
交给 Git 记录
(6)慎用位置标记
//**********************
及时清理不需要的注释
(1)越堆越多
(2)导致以后因看不懂而不敢删
第五章 格式
向报纸学习
(1)从上往下读, 从左往右读
(2)源文件在最顶端给出高层次的概念和算法, 细节往下逐次展开, 直到最底层的函数和细节.
垂直格式
(1)善用空白行: 人会更容易将目光聚焦到空白行之后的那一行
(2)函数: 调用者放在被调用者上面
横向格式
(1)缩进
(2)IDE 中不会出现横向滚动条
第六章 对象和数据结构
数据结构和对象的区别
数据结构暴露其数据, 没有提供有意义的函数.
对象把数据隐藏在抽象之后, 暴露操作数据的函数.
- // 数据结构
- function Point(x,y){
- this.x = x;
- this.y = y;
- }
- // 对象
- class Point {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- }
- getX() {
- return this.x;
- }
- setX(x) {
- this.x = x;
- }
- }
ES6 暂不提供私有变量(虽然有提案), 但可以通过其他办法变相实现: https://juejin.im/entry/572c0b2d2e958a00667a081d
第七章 错误处理
使用异常而非返回码
(上文有述)
定义异常类
如引用了一个第三方库, 它会 throw 自己的几种异常值, 但我们可以定义一个异常类, 封装好它的几种异常值为一种专门异常, 然后再二次 throw 交给上层捕获.
别返回 null 值
null 值会造成很多不必要的 if 判断
- function getCurrentMember(){
- let a = DB.getCurrentMember();
- if (a){
- return a;
- }else{
- return null;
- }
- }
方法一: 抛异常
- function getCurrentMember(){
- let a = DB.getCurrentMember();
- if (a){
- return a;
- }else{
- throw Error("no member")
- }
- }
方法二: 返回特例值
- function getCurrentMember(){
- let a = DB.getCurrentMember();
- if (a){
- return a;
- }else{
- return {};
- //return {name : "default"};
- }
- }
来源: https://www.cnblogs.com/xjnotxj/p/9690028.html