一,整洁代码
1,什么是整洁代码
这里提到了2个关键词:优雅,高效。
优雅就不说了,现在讨论整洁代码的时候,大家都会提到这个词,代码是一种艺术。
高效,这个观点还挺有意思的,尤其是这句:
性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。
二,有意义的命名
1,名副其实
我理解这个和代码自注释意思差不多,当命名做的好,能描述清楚这个变量、函数是做什么的,就不需要注释。
写代码最难的就是命名,命名最难的就是名副其实。
2,避免误导
这里提到了2个问题:
(1)特殊字母、词汇
特殊字母比如l、O等,用来做变量名简直是神坑。
特殊词汇,比如UNIX的专有词汇aix,又比如编程通用专有词汇List
(2)很长很接近的词
2个很长的词,都是7个单词拼接起来的,只有中间有一个单词不一样,相似度很高,造成阅读障碍。
3,使用读得出来的名称
编程是一种社会活动,代码不仅要给人看,还要给人读(朗读的读)
4,使用可搜索的名称
这相当于一种编程技巧,不要用过短且普遍存在的词做变量名,除非它的作用域非常小。
比如一个小循环,循环变量用i, j是可以接受的,因为作用域仅限这几行。
5,匈牙利标记法
以前有一种标记法,变量的名称前面加前缀,i表示int,u表示unsigned,g表示全局变量等等。
匈牙利标记法风靡一时是因为,以前的编译器不做类型检查。
现在的编译器做智能化程度比较高,成熟的公司还会用脚本扫描代码不符合自己的编码规范的地方,匈牙利标记法慢慢的被遗弃了。
6,每个概念对应一个词
不要用多个相近的词表示同一个概念。
三,函数
在C语言中,最重要的实体就是函数。
1,函数应该尽量短小
if、else、while语句,其中的代码行应该只占一行,一个函数调用语句。
一方面,这个标准很高,并不容易做到,难点在于很容易造成命名困难和过长参数列表,
另一方面,这个标准和我们重构的标准有冲突。我们平常做函数级重构,要把圈复杂度降到7以下,所以拆函数的时候会把代码一段一段的抠出来,而不是纵横交错,把每个if else里面的语句提出来,更不会把while里面的语句提出来。
2,一个函数只做一件事
这也是做设计的时候,多次被提到的概念。
要判断函数是否不止做了一件事,还有一个办法,就是看它是否能再拆出一个函数。
这个标准也挺高的。
3,每个函数都在同一抽象层级
这一点如果没有可以练习的话,也是很难做到的。一般人习惯性的只把较大较复杂代码块提炼出函数,如果语句很简单,只有两三行,就没有提炼,也就造成抽象层级参差不齐。
抽象层级一致对于自顶向下阅读代码很有帮助。
4,函数参数
(1)向函数传入布尔值简直就是骇人听闻的做法,这相当于大声宣布本函数不止做一件事。
(2)利用一些机制减少函数参数数量,比如变成成员函数。
这在C++中比较好实现,在C语言中结构体放函数指针,写法复杂一点。
5,无副作用
这其实也是“一个函数只做一件事”。
函数有副作用,就会造成时序性耦合。
在LLT中,如果要尽可能覆盖所有代码行,前面用例的执行就很容易造成后面的用例失败,因为很多函数都有副作用,全局变量太多了。
在博弈型算法的开发过程中,我也深有体会,尽量让底层的搜索函数、复杂计算函数等大函数无副作用,把修改全局变量的代码都分离出来,集中在离main函数尽可能近的地方,代码的耦合性会小一点,稳定性强一点。
四,注释
注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。
注释存在的时间越长,就会越来越不准确,因为程序员不喜欢维护注释。
1,好注释
(1)对意图的解释
不是解释这句代码是做什么的,而是在写代码的时候,面临必要的选择的时候,作者是怎么想的。
(2)阐释
对一些不好理解的参数或返回值的意义翻译成可读形式。
(3)警示
用于警示其他的程序员。
(4)TODO注释
这个我一般用一长串///这种注释代替,尤其是短期内马上就要修改的地方的标记。
2,坏注释
大多数注释都属此类。
五,格式
1,垂直格式
垂直区隔:相关的内容紧密联系在一起,不同的概念用空行隔开。
2,水平格式
(1)行宽
一行120个字符,是显示器的宽度,便于阅读。无需左右滚动是原则。
(2)水平间隔
紧密联系相关的事物连接在一起,相关性弱的事物用空格隔开。
这里有作者喜欢的风格:
return b*b - 4*a*c;
毫不夸张的说和我个人喜欢的风格完全一样!
不过大部分代码格式化工具都不会做成这样,所以我们的编码规范也不是这样,而是如下:
return b * b - 4 * a * c;
六,对象和数据结构
1,数据抽象
这里纠正了一个我也一直持有的错误的思想:给类的私有数据成员随意添加共有的get和set方法,只要能用到。
之所以有这个想法是觉得,相比于共有数据成员,用get和set的话,就相当于一个统一的接口,如果未来这个数据成员发生了变化,是比较容易修改的。
但实际上,隐藏实现并非只是在变量上放一个函数层那么简单,隐藏实现关乎抽象!
2,对象和结构体
过程式代码难以添加新数据结构,因为必须修改所有函数,面向对象代码难以添加新函数,因为必须修改所有类。
我理解这其实就是数据和函数的矩阵式结构:过程式代码是函数包含数据,面向对象是数据包含函数。
就好像我有人民币纸币和硬币,还有美元纸币和硬币,我还有俩钱包,如果我经常用硬币很少用纸币,那么硬币放一个钱包纸币放一个钱包,如果我经常用人民币很少用美元,那么我人民币放一个钱包美元放一个钱包,币种和面额的关系就类似于数据和函数的关系。
3,demeter律
方法不应调用由任何函数返回的对象的方法。
书中例子:
String outputDir = ctxt.get*().get*().get*()
然后用outputDir拼凑成绝对路径,用来创建临时文件。
优化方案:ctxt类直接定义一个创建临时文件的方法A。
这一块我感觉挺疑惑的,这个例子的意思应该不是在A里面调用get*().get*().get*()吧?
我理解这个例子反映的应该是两个问题,ctxt类直接定义一个创建临时文件的方法A解决了暴露outputDir的问题,而get链的问题应该是通过别的方法解决,比如层层传递,每个类的方法中只能访问它的父类的共有方法。
七,错误处理
1,使用异常而非返回码
C语言没有这个机制,C语言的设计机制就是靠返回码来运行的。
2,别返回NULL值,别传递NULL值
八,边界
1,整洁的边界
边界上的代码需要清晰的分割和定义了期望的测试。
依靠你能控制的东西,好过依赖你控制不了的东西,免得日后受它控制。
九,单元测试
1,TDD三定律
(1)在编写不能通过的单元测试前,不可编写生产代码
(2)只可编写刚好无法通过的单元测试
(3)只可编写刚好足以通过当前失败测试的生产代码。
这3个定律,语法非常接近高等数学中的ε-δ语言,用严谨的语法用一种不直观的表述形式精确的阐述一个概念。
通俗的理解就是,测试代码和生产代码一起写,每一个小周期非常小,书中说的是30秒。
2,保持测试整洁
脏测试等同于没测试(或者更坏)。
测试的可读性甚至比生产代码的可读性还重要
3,每个测试一个概念
一个测试不要做两件事,一个测试其实就是一个函数。
4,FIRST原则
快速、独立、可重复、自足验证、及时
可重复指的是每次运行结果都一样,不依赖于环境,也没有随机行为。
自足指的是结果只有2种,用例success或fail,不需要看其他信息,比如打印。
十,分离构造和使用
1,将系统的构造和使用分开
构造和使用混杂,会违反单一职责原则。比如:
-
Node getNode()
-
{
-
if(node == NULL) return new Node();
-
else return node;
-
}
这样会造成耦合、很难测试。
2,依赖注入/控制反转
依赖注入/控制反转_nameofcsdn的博客-CSDN博客
十一,跌进
1,简单设计四原则
运行所有重复,不可重复,表达了程序员的意图,尽可能减少类和方法的数量
最近参加的演进式设计培训中,简单设计四原则有个差不多的表述:通过所有测试、尽量消除重复、尽可能清晰的表达、没有冗余,重要程度依次降低,主观性依次增加。
十二,味道与启发
这一章对应《重构:改善既有代码的设计》这本书,讲到了很多代码坏味道。由于之前看过这本书,所以这里我再把印象中没有的拎出来:
(1)一个源文件中存在多种语言
(2)不恰当的静态方法
这个应该是只有面向对象代码才涉及的,类的静态方法是用类调用的,而不是用对象调用的,所以不能实现多态。
如果一个方法不在类中,那应该就是看实际作用范围,如果只在本文件中作用,那就用static修饰,防止被随意extern
(3)掩蔽时序耦合
书中的例子是,三个无参的函数依次调用,阅读者看不出来他们之间的时序耦合,改成返回值传递做入参的写法,就一眼看出来了。
不过这一条看的没什么感觉,还没有实际体会到这方面,一般也不敢随意去挪动代码顺序。
(4)在较高层放置可配置数据
评论(0)