跳转至

代码大全

软件构建的核心就是管理复杂度。 - 《Code Complete》

如果非让我推荐一本软件工程领域的书的话,那一定是这一本板砖书《代码大全》 从野路子到正规军。编程技巧和工程领域的研究成果。

1章:软件创建世界

创建活动主要包括:详细设计、编码、调试和单元测试

2章:利用隐喻对编程进行更深刻的理解

隐喻通过把软件开发和熟知的事情联系在一起,从而使你对其有更深刻的理解。

3章:软件创建的先决条件

求助于逻辑推理、类比、数据 立刻开始编码的程序员往往比先做计划、后编码的程序员花费更多时间。

问题定义的先决条件:你要解决的问题是什么 需求分析先决条件:描述一个软件系统需要解决的问题 结构设计先决条件:典型结构要素: - 程序的组织形式(设计理由对于维护性来说,与设计本身同等重要)。定义主要模块,每个模块做什么应该明确定义,每个模块之间的交界面也应该明确定义,结构设计应该规定可以直接调用那些模块,不能调用哪些模块。结构设计也应该定义模块传送和从其他模块接受的数据。 - 主要的数据结构,结构设计应该给出主要文件、表和数据结构。 - 关键算法。 - 主要对象,每个对象的责任和相互作用 - 通用功能 - 用户界面 - 输入、输出 - 错误处理 - 鲁棒性。冗余设计、断言、容错性 - 性能(速度和内存)

编程约定

4章:建立子程序的步骤

结构设计至少指出以下问题: - 要隐含的信息 - 输入、输出、受影响的全局变量 - 子程序如何处理错误

先写PDL注释,再在注释下填充代码

确定质量标准: - 接口。确认所有输入和输出数据做了解释、并且使用了所有参数 - 设计质量 - 数据 - 控制结构 - 设计(表达式、参数、逻辑结构)

迷信还是理解

5章:高质量子程序的特点

子程序:具有单一功能的可调用的函数或者过程 生成子程序的原因:降低复杂性、避免重复、限制改动带来的影响、隐含顺序、改进型能、集中控制、隐含数据结构、隐含全局变量、隐含指针操作、代码重用、提高可移植性、分隔复杂操作、简化测试

有意义的命名

可取的内聚性: - 功能内聚性: 子程序名称和功能相符 - 顺序内聚性: 子程序包含需要按照特定顺序进行的、逐步分享数据而不形成完成功能的操作,这时候需要重新组织成功能内聚性的 - 通讯内聚性: 一个子程序中、两个操作只是使用相同的操作,没有其他任何联系。这种内聚性可以接受 - 临时内聚性:因为同事执行的原因才被放入同一个子程序里

不可取的内聚性:不要试图修补,重新创建 过程内聚性:当子程序中的操作是按某一特定顺序进行的,和顺序内聚性不同,它使用的是不同的数据 逻辑内聚性:一个子程序同时含有几个操作,其中一个操作又被传进来的控制标志所选择时,这些操作仅仅是因为控制流才被联系在一起的,并没有其他逻辑上的联系 偶然内聚性:操作无任何联系

松耦合:两个子程序中间的紧密程度。内聚性指的是一个子程序的内部各部分之间的联系程度,耦合性指的是子程序之间的联系程度。 耦合标准: 耦合规模、密切行、可见性、灵活性 一个子程序越容易被其他子程序调用,耦合度越低。 耦合层次: - 简单数据耦合:最好的耦合,通过参数传递数据,数据是非结构化的 - 数据结构耦合:结构化数据,通过参数传递 - 控制耦合:一个子程序通过传入另一个子程序的数据通知它做什么,令人不快 - 全局数据耦合:使用同一个全局数据,如果只读还可以忍受 - 不合理耦合:一个子程序使用了另一个子程序中的代码或者改变了其中的局部变量。也叫做内容耦合。

子程序长度:尽量不要超过200行。错误率和可理解性最好。子程序的长度是由功能和逻辑自然决定的,而不是人为标准决定的。

防错性编程:思想核心是成人程序中都会产生问题,都要被改动。逐步设计:写好PDL、进行低层次设计、审查等手段防止错误引入。 - 使用断言。 - 输入垃圾不一定输出垃圾。好的程序从来不会输出乱七八糟的垃圾,不管输入什么 - 异常情况处理。预先设计好异常处理措施来注意意想不到的情况。开发阶段变明显,运行阶段可修复 - 预计改动:预想到的改动隐藏起来 - 发布时去掉调试信息 - 尽早引入调试辅助工具 - 使用防火墙包容错误带来的危害 - 检查返回值 - 使用多少防错性编程

子程序参数: - 确保形参实参匹配 - 按照 输入-修改-输出 的顺序排列参数,是否使用到了所有的输入和输出参数 - 状态和错误变量放在最后 - 不要子程序中的参数当做工作变量 - 说明参数的接口假设

使用函数:(函数是返回值的子程序,过程不返回值的子程序) - 何时使用函数,何时使用过程 - 确保函数的每个执行路径都是有返回值的。

6章:模块化设计

模块化:内聚性与耦合性。目标是使得每一个子程序都成为一个黑盒子。 - 模块内聚性:一个模块应该提供一组相互联系的服务 - 模块耦合:模块应该被设计成可提供一整套功能,一边程序的其他部分与它清楚地相互作用。而不用对其内部数据进行读写操作。尽可能减少使用全局变量。 模块设计最重要的决定之一就是决定哪个子程序需要对模块中的数据直接存取。

信息隐藏: - 保密:决定那些特性应该是对外部公开的,那些应该是作为秘密隐藏的。隐藏细节方便澄清意图(比如用子程序替代代码片段) -从常见需要隐藏的信息: - 容易被改动的区域:硬件依赖的地方、输入和输出、非标准语言特性、难于设计和实现的阈、状态变量(不要使用逻辑变量使用枚举变量;使用子程序而不是直接检查)、数据规模限制、商业规则(法律、规定等)、 预防潜在改动(分析程序中可能会被用户用到的最小子单元) - 复杂的数据:存取子程序 - 复杂的逻辑:改善可读性;把对数据的操作隐藏在子程序中 - 编程语言层次上的操作

- 障碍:信息过度分散;交叉依赖;误把模块数据当成全局数据

简历模块的理由:用户接口、对硬件有依赖的部分、输入和输出、操作系统依赖、数据管理、ADT、可再使用的代码、可能发生变动的相互联系的操作、互相联系的操作

编程语言支持: 模块包括数据、数据类型、数据操作、公共和局部操作的区分

7章:高级结构设计

设计的层次: - 划分成子系统 - 划分成模块 - 划分成子程序 - 子程序的内部设计

结构化设计: 自顶向下分解 自底向上合成

面向对象: 抽象:抽象的单位是对象 封装:可以看到外表但是不能走进去的方法 模块化:内部发生变化的时候,接口不变 继承:共同与不同

面向对象的设计步骤: - 识别对象及其属性,它往往是数据 - 确定每个对象可以做什么 - 确定每一个对象可以对其他对象做什么 - 确定每个对象对其他对象来说可以见的部分:那一部分是开放的,哪一部分是专用的 - 确定每个对象的公共接口

如果数据变动可能性很大,采用面向对象设计比较适合,如果是功能变动可能性较大,使用分解功能的结构化设计比较好。尽可能用信息隐藏。

往返设计: 设计是一个启发的过程。硬算;图示法。不要死抱着一种方法不放,确认你是不屈不挠,而不是顽固不化。看看波利亚的《how to solve it》 1.理解问题,问题是什么?,你必须要理解问题是什么。问题是什么?条件是什么?有可能满足条件吗?已知条件充分吗?是否矛盾和冗余。画一个图,引入恰当的符号,条件的不同部分分解开 2.设计一个方案。已知和未知的联系,如果不能找到直接联系,能不能得出一个辅助问题,但最后你应该找到一个解决方案。 3.执行你的计划,审查每一步工作是否是正确的,你能证明吗? 4.回顾,检查下答案。

受欢迎的设计的特点: - 智力上的可管理性 - 低复杂性 - 维护的方便性 - 最小联系性 - 可扩充性 - 可重复使用性 - 高扇入。对于一个给定的子程序,应该有尽可能多的子程序调用它。表明低层次上充分利用了功能子程序 - 中或低程序扇出。对于一个给定子程序,它所调用的子程序尽可能少(0-2个) - 可移植性 - 简练性,不要有任何多余代码 - 成层设计:尽量分解层次成层,不要让子程序在几个层次上起作用 - 标准化

8章:生成数据

初始化数据的准则: - 检查输入参数的有效性,临近原则,相关操作放在一起。 - 查找需要重新初始化的地方。

9章:数据名称

命名时要考虑的最重要的问题 - 名称是否完全而又准确地描述了变量所代表的实体,最好使用英语描述实体 - 好记的名称通常面向问题而不是解决问题的。一个恰当的名字说明『是什么』而非『怎样』。 - 最佳名称长度:10-16个 - 变量名中的计算值限定词:含有计算值的限定词(sum、avg)等放在后边 - 变量名中的反义词:保持连续性

特定数据类型命名: - 循环变量:避免使用i,j,k等命名,容易混淆 - 状态变量:不要用flag命名,使用精确含义的描述词 - 临时变量:不要用temp命名 - 逻辑变量的命名:使用典型词语:done、error’found’success’,使用 “非真即假”的词语 - 枚举类型命名:使用相同前缀或者后缀表明同一组的 - 常量命名:使用抽象实体

命名约定: 需要建立约定 一些语言无关的约定准则: - 标识全局变量: 使用前缀 - 标识模块变量: 表示模块内部使用的变量 - 标识类型定义:避免与变量名称冲突 - 标识命名常量 - 标识枚举类型: 使用后缀 - 表示输入参数 - 使用下划线等分隔增强可读性

变量名应该包括三个方面的信息: - 变量内容,它代表的是什么 - 变量数据类型(整形、浮点等) - 变量在程序结构中的位置,比如模块、全局等

10章:变量

作用域:影响范围,也成可见性。 - 尽可能减小作用域。同意变量的引用集中放置

持久性:某一个数据使用的寿命。养成初始化的习惯

赋值时间:越晚赋值,灵活性越大

数据结构与控制结构的关系:从可用的数据和输出该是什么样子开始,然后对程序进行定义,使其把输入转化为输出。

变量功能单一性:应使每一个变量只具有一个功能,比如不要用tmp命名然后多次赋值

全局变量:常见问题:疏忽被其他地方改变;别名覆盖;代码重入;妨碍重新使用代码;损害模块性和可管理性 全局变量使用场景:保存全局数值;代替命名常量;方便常用数据的使用;消除穿梭数据。 安全使用全局变量:区分全局和模块变量;一眼可以识别的全局命名约定;注释表;需要加锁吗;不要把数据放入庞大的变量;使用存取子程序代替全局数据

11章:基本数据类型

常数:避免使用幻数;避免除0;确保类型转换是显示的;避免不同类型比较 整数:检查整数相除;避免整数溢出(了解整数范围);检查中间结果是否溢出 浮点数:不要在位数相差太多的数字运算;不要对浮点数做==比较;防止舍入误差 字符和字符串:避免常量字符串;注意c语言的字符串边界错误; 逻辑变量:用逻辑变量说明程序;避免复杂的多重逻辑判断,代之以有意义的逻辑变量; 枚举类型:可靠已修改;在case语句中刻意代替逻辑变量;第一个入口是否保留为无效值 数组:确保下标没有越界; 指针:内存存储单元及对这个存储单元的解释; 使用显示指针类型;避免强制类型转换(重新设计)

12章:复杂数据类型

记录与结构:相关数据组织到一个结构中;简化参数表;

表驱动法:可以在表中查找信息而使用 if 或者 case 的方法。 - 直接存取:直接查表,代替复杂的逻辑控制,可读性更强容易修改;使用转换函数简历存取标志 - 变址存取: - 阶梯存取:节省空间,入口对数据范围有效。注意边界(< or <=);

抽象数据类型:数据及对数据的操作,解决客观世界实体问题。尽可能从高水平的抽象处理问题。

13章:顺序程序语句

必须有明确顺序的语句:组织使它们之间的依赖关系明显;子程序名称应当清晰表明依赖关系;使用子程序参数使得依赖关系明显;注明不明确的依赖关系 与顺序无关的程序语句:接近原则,相关操作组织在一起; 使代码能从上读到下;同一变量局部化(跨度);使变量存活时间尽量短(清楚变量使用范围); 相关语句组织在一起(方法:用框框把相关语句括起来,框与框之间没有交叉)

14章:条件语句

if 语句:先按照正常情况路径编写,然后写异常情况。使得正常情况的路径在代码中显清晰;把正常情况放在 if 后边而不是 else 后边;确保 if 和 else 语句的正确性 if then else: 用布尔函数调用简化程序;;最常见的情况放在开始;保证覆盖全部情况。 case: 按照字母或者数字顺序排列;正常情况放在最开始;按照出现频率组织。

15章:循环语句

循环的内务处理工作放在循环的开头或者结尾(控制循环);循环中不要强制改变控制循环的变量而达到提前终止循环的目的;使得推出循环的代码明显; 检查循环边界:初始情况、中间、结束情况 从里到外写

16章:少见的控制结构

goto 语句:少用或不用 return 语句:减少 return 语句。

递归:递归至少要有终止条件;设置安全计数器防止无限递归;把递归调用限制在子程序里;注意堆栈是否会溢出;使用循环还是递归,慎用递归

17章:常见的控制问题

布尔表达式:用True和False不要用1和0; 编写肯定形式的布尔表达式,人们不善于理解否定句;利用括号使得表达式清晰;利用懒惰求值(警惕懒惰求值后的语句有错误); 按照数轴上的数字顺序编写数字算式

减少深层嵌套: - 重新组织测试条件减少嵌套 - 使用 if-then-els 替代嵌套 - 把深层嵌套的代码写成子程序 - 重新设计

结构化编程:程序总是单入单初的结构,即程序只能从一个地方开始且只能从一个地方退出的代码块。三个方面: - 顺序编程:一组顺序执行的语句,典型的包括赋值和子程序调用 - 选择:只有一个被执行 - 重复:一组语句被执行多次

降低程序复杂性 Mc Cabe,通过计算程序中的决定点的数目度量复杂性 1.从1 开始往下通过程序。 2.遇到下列关键词或同类词语 +1, if while repeat for and or 3 case 语句每一种情况都 +1,如果case 没有缺省情况再 +1

0-5表示程可能很好;6-10 得想办法简化;10以上需要把部分代码写成子程并在源程序里调用

18章: 布局和风格

布局技巧:用空格提高可读性;分组;对齐;缩进;括号;

准确性、连续性、可读性、易维护性

19章:文档

外部文档:综合资料;详细设计文档 注释分5类: 代码的重复:用不同的词语重申代码的内容 代码的解释: 解释复杂的有效的和灵敏的代码,通常有用但是尽可能修改代码使得代码本身更清晰 代码中标记: TODO 等 代码意图的描述:解释代码的目的。意图注释在问题一级上,而不是在答案一级,是一句利用答案的总结描述。

20章:编程工具

设计工具:图形设计工具 源代码工具:编辑程序;文件比较工具;源码美化工具;模板;浏览程序;多文件字符串搜寻;互相引用参照工具;调用结构生成工具;代码质量分析工具;质量报告工具;重构程序;代码翻译工具;版本控制 执行代码工具:代码生成 环境:unix;case;批处理;拓扑观察

21章:项目大小如何影响构建

项目大小:简化交流方法; 随着项目增大,创建活动减弱 大项目的生产率比小项目要低一些,每行错误数更多

22章:创建管理

使用好的代码 配置管理:SCM。软件设计修改;软件代码改变; 评估创建计划:《软件工程经济》。设计、编码、调试和单元测试。 度量:错误率;行数等 程序员生产力:不同程序员效率差异会很大;工作环境影响生产力 如何对待上司:《怎样赢得朋友和影响他人》

23章:软件质量概述

软件质量特点: - 外部特征:正确性、可用性、效率、可靠性、完整性、适应性、精确性、坚固性 - 内部特征:可维护性、灵活性、可移植性、可重用性、可读性、可测试性、可理解性

提高软件质量的方法:最好的方法是控制开发过程。质量管理目标;确定质量保证活动(要让程序员明白质量是第一位的);测试策略;可靠性评估;软件工程准则(设计、编码、调试、集成); 非正式技术审查;正规技术审查;外部检查;开发过程;修改控制过程;结果的定量;结果的定量;原型;设置目标

各种方法效果:组合使用,任何使用单一测试方法效果不明显;通过阅读代码能发现较多的控制错误(相比直接调试);

何时应作质量保证:贯穿整个项目计划

软件质量的一般原则:提高质量并减少花费。提高效率和质量的最好方法是减少代码再加工的时间,不论再加工是由于需求变更、设计修改或调试。软件产品的工业生产率仅为每人每天8-20行代码,大部分时间在调试。缩短软件开发时间最为明显的方法是提高产品质量,减少调试和再开发软件所需时间。

24章:评审

评审在软件质量保证中的地位:基本思想:开发者对其工作中的一些故障点是一无所知的,而另外一些人则不存在这个盲区,所以让别人来检查你的工作是有意义的。评审是检查、初查、代码阅读及别人查看你的工作的总称。单元测试的检错比仅为25%,功能测试为35%,而集成测试为45%。相比之下,设计和代码检查的检错比平均为55%、60%。(这个结果有点出乎笔者意料)

检查:设计和代码检查的联合使用能去除产品中60%-90%的错误。过程:计划;总览;准备;检查会议;检查报告;再工作;执行;建立检查表(错误类型表),让程序员有针对性地进行训练和得到帮助

其他评审方法:普查;代码阅读;软件演示。

25章:单元测试

单元测试在软件质量中的作用:仅用测试不能提高质量;测试需要你假定能在代码中找到错误

单元测试的一般方法:对每个需求进行测试;对和设计有关的陈旭进行测试; 测试技巧:基于结构的测试(路径覆盖);数据流测试;等效类划分;错误猜测(建立错误表积累经验和直觉、边界分析、检查表) 典型错误:错误排序。三个最常见错误源:贫乏的应用控制知识、需求的冲突和缺乏交流和协调。大多数实现错误来自程序员。拼写错误很常见,还有赋值和边界错误。 测试支持工具:建立脚手架;覆盖率检测;调试工具;系统测试;错误数据库 提高测试质量:计划测试;回归测试

26章:调试

软件质量的基本原则:提高开发质量可以降低开发消耗。 使你有所收获的错误: - 了解错误类型。 - 从别人的角度了解代码质量 - 了解解决问题的方法。 - 了解如何改正:修改问题本身而不是补偿 调试的误区:靠猜测发现错误;不花费时间理解问题; 用最为明显的方式改正错误(你应该改正所发现的特定问题,而不是一些大的模糊不清的修改

找错:发现并理解错误将耗费90%的时间。 科学的调试步骤:1.通过重复试验收集数据。2.建立假设以解释尽可能多的相关数据。3.设计实验正式或者否定假设。4.正式或者否定假设。5.按要求重复以上步骤。 发现错误的有效方法:1.固定错误 2.确定错误源 3.改正错误 4.测试修改 5.寻找类似错误 发现错误的诀窍:使用所有可能数据进行假设;精简错误测试用例;通过不同的方式再生错误;生成更多数据验证假设;使用否定测试的结果;提出尽可能多假设;缩小可以代码区域;细分和诊断整个程序;检查最近修改过的子程序; 逐步集成;耐心检查;为迅速调试设立最大时间(超过时间换个方法);检查一般错误(检查表);交谈调试(阐述问题和寻求帮助);暂时终止对问题的考虑,焦虑的出现是应该暂停一下的信号

修改错误:改正问题之前真正了解其实质;理解整个程序而不知识了解某个问题;确诊错误;放松自己;修改错误问题而不是症状;检查你的修改;寻找相似错误;

调试心理因素:心理因素的两个影响:良好编程习惯的重要性;当初先错误时对部分程序的挑选上。比如经常混淆两个相近拼写的词语,尽量使用差别较大的单词命名。

调试工具:源码diff工具;编译警告;扩展语法和逻辑检查(pylint);执行剖析程序;调试程序;调试和思考联合使用,都不可替代

27章:系统集成

集成非常重要但常常被忽视 多数情况下,递增集成比分段集成更好 现在 devops 比较火,感觉这点内容稍微陈旧

28章:代码调整策略

代码调整:有效的代码不一定是好的代码;代码必须通过测量来判断,不要轻易相信直觉 Pareto原理:80/20 定律 低效率的情况:输入输出操作(IO);浮点操作;分页;系统调用

29章:代码调试技术

循环:避免开关(移出循环);合并循环体相同语句; 逻辑: 短路求值;根据频率确定逻辑表达式顺序;查表法替代逻辑判断; 数据转换:减少数组访问;索引结构;高速缓存 表达式:利用德摩根律简化逻辑;编译期间初始化;预先计算结果 程序:子程序;

30章:软件优化

软件进化种类:根据质量判断进化方向 软件优化指南: - 多设计子程序,模块化 - 减少全局变量 - 改进编程风格 - 改变管理 - 重审修订后的程序 - 重测试:使用测试验证修改效果

编写新程序:小的子程序要比加上注释的大程序好

31章:个人性格

个人性格:一定程度可以改变性格;在成为高级程序员的过程中,性格更具有决定意义 聪明和谦虚:许多良好的编程风格的目的是减少你大脑的负担。 好奇心:建立自我意识;实验,快速试错;阅读解决问题的有关方法;行动之前进行分析和计划;学习成功项目的开发经验,向优秀程序员学习;阅读手册;阅读有关书籍和期刊 诚实:乐于承认自己的错误 交流合作:代码是与人交流的 创造力和纪律:一个杰出的程序员需要遵守许多规则 懒惰:用工具解放自己 不是你想象中那样起作用的性格:软件开发中坚持往往是顽固的意思;经验的价值比其他领域要小(软件领域变化太快,知识更新使得『经验』处在一个奇怪的位置上;计算机谜(警惕没日没夜工作的程序员的工作质量) 习惯:好的程序员一开始就养成好习惯;你一开始雪某件事的时候就应该按照正确的方式学好它;

32章:软件开发方法的有关问题

  • 克服复杂性:分解和模块化;良好的设计;编码约定;划分层次;抽象(类和子程序)
  • 精选开发过程:对于多个程序员的项目组织特征比单个程序员技能更起作用。
  • 首先为人编写程序,其次才是计算机。
  • 注意约定的使用:用约定传递信息(比如变量名);防范已知的危害;提高可读性;弥补语言不足
  • 根据问题范围编程:处理复杂性的一个特定的方法是在高抽象级别上工作,根据问题而不是根据计算机编程。将问题分解成不同的抽象级别(计算机科学范围和问题范围):高级语言结构;计算机科学结构;低级问题领域;高级问题领域
  • 当心飞来横祸:程序编制是一门技艺,需要大量个人判断。警惕代码坏味道和警告信息
  • 重复:逐步修改和提高
  • 不要固执己见:不幸的是一些优秀人员容易偏执。选择和实验。保持开放的思想。构造个人工具箱

波利亚怎样解题 http://mindhacks.cn/2008/04/18/learning-from-polya/