《代码大全》 读书笔记1

发布时间 2023-03-25 10:25:57作者: 南北啊

在王建民老师的推荐下,最近拜读了 Code complete《代码大全》,这部大块头确实经典,涉及到了软件开发的方方面面。有点后悔没有早些阅读,值得推荐给还没读过的朋友。它并不是针对某种语言的武林秘籍,应该可以看作是基础内功修炼吧。笔记比较简略,只供简单查阅~

一、开发前期准备相关
1.需求阶段:

发现错误的时间要尽量接近进入该错误的时间。由于需求是首先进行的活动,所以需求阶段引入的缺陷可能在系统中潜伏的时间最长,代价也更昂贵。
如果没有一个良好的问题定义,你努力解决的可能是一个错误的问题。
明确的需求有助于确保用户驾驭系统的能力;
明确的需求有助于避免争论。
重视需求有助于减少开始编程开发之后的系统变更情况。
一个形象的比喻:需求像水。如果冻结了,就容易在上面开展建设。
开发过程能够帮助客户更好的理解自己的需求,这往往是需求变更的主要来源。
2.架构

架构应详细定义所需的主要的类。瞄准80/20准则:对构成系统的80%功能的20%的类进行详细说明。
架构应建立一套有关错误消息的约定。
架构的总体质量:
《人月神话》的中心论题,说的就是大型系统的本质问题是维持其“概念完整性”。
好的架构设计应该与待解决的问题和谐一致。
架构应该描述所有主要决策的动机。
优秀的软件架构很大程度上是与编程语言无关的。
3.构建决策

深入一种语言去编程
确定你在技术浪潮中的位置,并相应调整计划和预期目标。
二、编写高质量代码
1.类

软件的首要技术使命就是管理复杂度。可以通过把整个复杂系统分解为多个子系统降低问题的复杂度。

关于封装:类很像是冰山,八分之七都在水面以下,你只能看到水面以上的八分之一。

抽象数据类型(ADT)是指一些数据以及在这些数据上所能进行的操作的集合。

考虑类的一种方式,就是把它看作抽象数据类型,再加上继承和多态两个概念。

警惕有超过7个数据成员的类。

尽量使用多态,避免的大量的类型检查。

构造函数:

如果可能,应该在所有的构造函数中初始化所有的数据成员。
用私有构造函数实现单件属性。
2.子程序

子程序是为实现特定的目的而编写的一个可被调用的方法或过程。函数是有返回值的子程序;过程是没有返回值的子程序。
合理的参数个数,上线大概在7个左右。
3.防御式编程

主要思想:子程序不应传入错误数据而被破坏,哪怕是其他子程序产生的错误数据。
在代码中保留多少防御式代码?
保留那些检查重要错误的代码;
去掉检查细微错误的代码;
为技术支持人员记录错误信息;
确保留在代码中的错误信息是友好的。
4.伪代码创建子过程

用类似英语的语句描述特定的具体操作;
避免使用目标编程语言中的语法元素;
在意图的层面编写伪代码;
在一个足够低的层次上编写伪代码,以便于近乎自动地从他生成代码,然后把它编程代码中的注释。

三、变量
1.使用变量的一般事项

2.变量命名

为变量命名时要考虑的重要事项是,该名字要完全、准确地描述出该变量所代表的事物。
较长的名字适用于较少使用的变量或者全局变量;较短的名字适用于局部变量或者循环变量。
代码阅读的次数要远远大于编写的次数。要确保你所取的名字更侧重于阅读方便而不是编写方便。
3.特定数据类型命名

①.限定词命名

如果变量名中含有Total,Average,Sum,Max等限定词,请记住把限定词加在变量名的最后。
用Count和Index代替Num
关于Num限定词容易产生歧义,例如:numStudents表示学生数,studentNum表示学生序号。为了避免这种歧义,可以用Count和Index来代替,比如用studentCount表示学生总数,而用studentIndex表示学生序号。

②.为循环索引命名

如果一个变量要在循环之外使用,那么就要使用一个i,j,k之外更有意义的名字。
如果循环不是只有几行,那么读者很容易忘记i本来的含义,因此最好给循环下标取一个比i更有意义的名字。
③.为状态变量命名

为状态变量取一个比flag更好的名字。标记一般使用枚举类型、具名常量或者用作具名常量的全局变量来对其赋值。

④.为布尔变量命名

谨记典型的布尔变量名:found,error,success,ok。
给布尔变量赋予隐含“真/假”含义的名字,而且可以省略变量名开头的Is前缀。
使用肯定的变量名。
⑤.为具名常量命名

应该根据该常量所表示的含义,而不是该常量所具有的数值来命名。

四、表驱动法
表驱动法是一种编程模式,从表里面查找信息而不使用逻辑语句(if和case)。
表提供了一种复杂逻辑和继承结构的替代方案。
五、一般控制方法
1.布尔表达式

拆分复杂的判断而引入新的变量;
把复杂的表达式做成布尔函数;
用决策表代替复杂的条件。
2.按照数轴的顺序编写数值表达式;

3.将复杂度降低到最低是编写高质量代码的关键。

六、代码改善
1.软件质量的普遍原理就是改善质量以降低开发成本。

2.提高生产效率和改善质量的最佳途径就是减少花在代码返工上的时间,无论返工是由需求、设计改变还是调试引起的。

3.结对编程,通过复查可以快速地将所有开发者的水平提高到最高优秀的开发者的高度。

七.开发者测试
1.白盒测试指的是测试者清楚对象内部工作机制的测试,测试自己开发的程序应该使用这种测试方式。

2 测试的特性:

测试的目标与其他测试活动背道而驰,测试的目的是找出错误。
测试永远不可能证明程序中彻底没有错误。
测试的结果是软件质量的一个指示器,但是测试本身并不能改善软件质量,这种妄想就像天天通过称体重来减肥一样。假如希望改进你的软件质量,仅用更多的测试是没用的,你需要的是更高质量的开发。
3 推荐开发者的测试方式

对每一项相关的需求进行测试,以确保需求都已经被实现;
基础测试和数据流测试;
使用一个检查表,记录你在本项目中迄今为止所犯的,以及在过去的项目中中所犯的错误类型,这有助于“猜测错误”的准确性;
推荐测试先行。
4 测试技巧锦囊

结构化的基础测试
需要去测试程序中的每行代码至少一次。
所需基础测试最少用例数量的计算方式:
对通过子程序的直路,开始记为1;

遇到以下关键字时,加1,比如:if,and,or,while,repeat,for;

遇到每个case语句,加1,如果case语句没有默认语句,再加1.

5.测试数据生成器

为了系统的对程序的某些部分进行测试,你可能会写一些代码。
正确设计的测试数据生成器能产生意想不到的、不寻常的测试用例;
比起手工构造测试数据,数据生成器能够更加彻底地对程序进行测试。
八、调试
①关于调试

理解你正在编写的程序;
明确你犯了那种类型的错误;
从代码阅读者的角度分析代码质量;
审视自己解决问题的方法,花点时间来分析并改善你的调试方法,可能就是减少程序开发时间的最好方法;
审视自己修改正缺陷的方法。
②寻找缺陷步骤

把错误状态稳定下来,也就是能让缺陷稳定的重现,这几乎是最有挑战的工作之一;
确定错误的来源;
修补缺陷;
对修补的缺陷进行测试;
查找是否还有类似的错误。
③修复缺陷

在动手之前先要理解问题,知道你能真正理解问题,每次都能正确地预测结果为止;
理解程序本身,而不仅仅是问题;
验证对错误的分析;
放松一下;
保存最初的源代码,至少你能对新旧代码进行比较,看到底改了哪些地方;
治本,而不是治标;
修改代码时一定要有恰当的理由;
一次只做一个改动;
检查自己的改动;
增加暴露问题的单元测试;
搜索类似的缺陷,如果你想不出如何查找类似缺陷,这就意味着你还没有完全理解问题。
④调试工具

源代码比较工具;
编译器的警告信息,把编译器的警告级别设置为最严格;
增强的语法检查和逻辑检查;
执行性能剖测器;
测试框架;
调试器;
九、重构
1.软件演化类型

在修改中软件的质量要么提高,要么恶化。
软件演化的基本原则就是,演化应当提高程序的内在质量。
2.重构的理由

代码重复:DRY,Do not Repeat Yourself;
冗长的子程序:很少会用到长度超过一个屏幕的子程序。改善方法是提高其模块性-增加定义完善、命名准确的子程序,让他们各自集中力量做好一件事;
循环过长或嵌套过深;
内聚性太差的类;
拥有太多参数的参数列表;
变化导致多个类的相同修改;
同时使用的相关数据并未以类的形式组织;
过多使用基本数据类型;
某个类无所事事;
中间人对象无事可做;
子程序明明不恰当。只要看到某个子程序命名有问题,就应该立即着手修改。
注释被用于解释难懂的代码。不要为拙劣的代码编写文档——应当重写代码;
程序中的一些代码似乎是在将来的某个时候才会用到。对未来需求有所准备的代码并不是编写大量空中楼阁式的代码,而是尽可能将满足当前需求的代码清晰明白的表现出来,使未来的程序员理解这些代码到底完成了什么功能,没完成什么功能,以便根据他们的需求进行调整。
3. 数据级的重构

用具名常量代替神秘数值;
使变量的名字更加清晰且传递更多信息;
用函数代替表达式;
将基础类型转化为类。如果一个基础类型需要额外的功能或者额外的数据,那么就应该把基础类型转化为类,并添加你所需要的行为;
将一组类型码转换为类或者枚举类型;
4.语句级的重构

分解布尔表达式
将复杂布尔表达式转换为命名准确的布尔函数;
在嵌套的if-then-else语句中应该一知道答案就返回,而不是赋值给一个返回值
5.子程序级重构

提取子程序或者方法
将冗长的子程序转换为类
合并相似的子程序,通过参数区分他们的功能
6 类接口的重构

将成员函数放到另一个类中
将一个类变为两个类。如果一个类中的成员函数完成两种截然不同的功能,将这样的类转换为两个类
对于不能修改的类成员去掉其set函数。如果一个成员在对象被创建时设置,之后遍不再允许修改,那么删除其set函数,而是在构造函数中进行初始化。
7 系统级重构

使用工厂函数而不是简单的构造函数;
用异常代替错误代码,或者反其道而行之,取决于你的错误处理策略,请确保代码使用了标准的错误处理方法。
8. 安全的重构

保存初始代码。要保证你还能回到代码的初始状态。
重构的步伐请小些。这样才能理解所做修改对程序的全部影响。
同一时间只做一项重构。在进入到下一项重构之前,对代码进行重新编译和测试。
把要做的事情一步步列出来。写出一份重构列表能够让你在修改时保持思路连贯。
设置一个停车场。把你需要在未来进行而现在可以暂且放在一遍的修改工作记录下来。
利用编译器警告信息。把编译器的警告级别设置为最严格。
重新测试。应该把重新测试作为修改代码工作的补充。
检查对代码的修改。如果说第一次运行程序时检查代码是必要的,那么接下来修改代码的过程中,时刻检查代码则更为必要。另外,应该把简单的修改当作复杂的修改对待。
根据风险级别来调整重构方法。尤其是对于有一定风险的重构,谨慎才能避免出错。
9.重构策略

在增加子程序的时候重构
在添加类的时候重构
在修补缺陷的时候重构
关注于易出错的模块
关注高度复杂的模块
在维护环境下,改善你手中正在处理的代码。如果你正在维护某部分代码,请确保代码在离开你的时候比来之前更健康
开发阶段的重构是提高程序质量的最佳时机。
十、代码调整技术(性能)
1.逻辑

在知道答案后停止判断;
按照出现频率调整判断顺序,让运行最快和判断为真可能性的判断首先执行;
2.循环

将判断外提。如果在循环运行时某个判断结果不会改变,你就可以把这个判断提到循环的外面。
合并。就是把两个对同一组元素进行循环的操作合并在一起,减少循环开销。
尽可能减少在循环内部做的事情。如果可以在循环外面计算某些语句,而只在循环内部使用计算结果,那么就把该部分语句放在循环外面。
十一、管理构建
1.鼓励良好的编码实践几种技术

给项目的每一部分分派两个人
逐行复查代码
要求高级技术人员给代码签名
安排一些好的代码示例给别人看
强调代码是公共财产
奖励好代码
一份简单的标准:“我必须能理解并阅读这个项目里的所有代码”
2.编程工具

①源代码工具

IDE
diff比较器
merge工具
源代码美化器
代码模板
②重构源代码

重构器

程序库:

代码生成器

打造自己的编程工具

十二、源代码布局与风格
1.基本原则

好布局方案的关键是能使程序的外观与逻辑结构一致,也就是让人和计算机有同样的理解。
编程工作量的一小部分是写让计算机能看懂的程序,一大部分是让其他人能看懂程序。
外表悦目比其他指标是最不重要的。
2.良好布局的目标

程序表现代码的逻辑结构
始终如一的表现代码的逻辑结构
改善可读性
经得起修改
3.布局技术

空白。能提高可读性,如空格、制表符、换行、空行。
分组。确保相关的语句成组放在一起。
空行。将不相关的语句分隔开。
缩进。用缩进的形式显示程序的逻辑结构。
括号。对包含两个以上的项的表达式,应该用括号去澄清。
4.控制结构的布局

不要用未缩进的begin-end对
段落之间要使用空行
单语句代码段的格式要前后统一
对于复杂的表达式,将条件分隔放在几行上
5.单行语句的布局

每行只放一个语句
每个语句只进行一个操作
数据声明的布局:
每行只声明一个变量
变量声明应尽量接近其首次使用的位置
6.注释的布局

注释的缩进要与相应代码一致
每行注释至少用一个空行分开
7.子程序的布局

用空行分隔子程序的各部分
将子程序参数按照标准缩进
8.类的布局

一个文件应只有一个类
文件的命名应与类名有关
在文件中清晰的分隔子程序,至少使用两个空行
十三、自说明代码
1.编程风格做文档

在代码层文档中起作用的因素并不是注释,而是好的编程风格。对于精心编写的代码而言,注释只是精美外衣的小饰物而已。

代码易读性的最高水平:自说明代码。

2.高效注释之关键

采用不会打断或抑制修改的注释风格
用高层次的伪代码减少注释时间
将注释集成到你的开发风格中
3.注释技术

①行尾注释

不要对单行代码做行尾注释
行尾注释可用于数据声明
②代码块注释

注释应表达代码的意图
注释代码时应注重 为何做(why) 而不是 怎么做(how)
用注释为后面的内容做铺垫
说明非常规做法。
将主次注释区分开。在次要注释前加省略号,主要注释以正常方式编排
③注释声明数据

对数值声明的注释应标明其单位;

十四、软件工艺的话题
1.征服复杂度:

软件设计与构建的 主要目标就是征服复杂性。

2.代码可读性

首先是为人写程序,其次才是为机器

3.深入一门语言去编程,而不是浮于表面

4.借助规范集中注意力

规范是一套用于管理复杂度的智力工具。

5.基于问题域编程

尽可能工作于最高的抽象层次。
将程序划分为不同的抽象层次。