代码大全——检查表
1. 欢迎进入软件创建世界
1.1. l.3 小 结
● 创建活动是总体设计和系统测试之间承上启下的工作。
● 创建活动主要包括:详细设计、编码、调试和单元测试。
● 关于创建活动的其它称谓有:实现、编程等。
● 创建活动质量对软件质量有潜在影响。
2. 利用隐喻对编程进行更深刻的理解
2.1. 2.4 小 结
● 隐喻仅仅是启发,而不是公式,因此,它们更倾向于比较随便,无拘无束。
● 隐喻通过把软件开发与你所熟知的事情联系在一起,从而使你对其有更深刻的理解。 ● 一些隐喻要好于其它隐喻。
● 把软件创建与建造建筑物类比,表明开发软件前要精心准备,并表明了大规模项目与
小规模项目之间的差别。
● 认为软件开发实践是智能工具箱中的工具进一步表明,每个程序员都有许多自己的工
具,没有任何一种工具是万能的。为每件工作选择合适的工具,是成为一个优秀程序员的首要素质之一。
3. 软件创建的先决条件
3.1. 需求
3.1.1. 需求内容
● 系统的所有输入都定义了吗?包括它们的来源、精度、取值范围和频率?
● 系统所有的输出都定义了吗?包括它们的目标、精度、取值范围、频率和格式? ● 所有的报告格式都定义了吗?
● 所有的硬件与软件接口都定义了吗?
● 所有的通信交界面都定义了吗?包括握手、错误检查以及通信约定?
● 是否从用户的观点出发,定义了所有必要操作的反应时间?
● 是否定义了时间问题,如处理时间、数据传输率以及系统吞吐能力?
● 是否对用户所要求完成的任务部作出了规定?
● 每项任务所需用到和产生的数据都规定了吗?
● 规定保密级别了吗?
● 规定可靠性了吗?包括软件出错的后果、在出错时要保护的至关重要的信息、以及错
误测试和恢复策略。
● 规定所需最大内存了吗?
● 所需最大存储容量规定了吗?
● 对系统的维护性是否作出了规定?包括系统对运行环境、精度、性能以其与其它软件
的接口等方面变化的适应能力规定了吗?
● 是否规定了相互冲突的设计之间的折衷原则,例如,在坚固性与准确性之间如何进行
折衷?
● 是否制定了系统成败的标准?
3.1.2. 关于需求的完善性
● 在开发开始前暂时得不到的信息是什么?是否规定了不够完善的区域?
● 需求定义是否已经完善到了可以成为软件标准的地步?
● 需求中是否有哪一部分令你感到不安?有没有根本不可能实现,而仅仅为了取悦老板
和用户才加进来的内容?
3.1.3. 关于需求的质量
● 需求是否是用户的语言制定的?用户也这样认为吗?
● 需求中是否每一条之间都尽量避免冲突?
● 需求中是否注意了避免规定设计工作?
● 需求在详细程度方面是否保持了一致性;有没有应该更详细些的要求?有没有应该更
简略些的?
● 需求是否明确得可以分为一些独立的可执行部分,而每一部分又都很明了? ● 是否每一条都与问题和答案相关?是否每一条都可以追溯到产生它的环境中?
● 是否每一条需求都可以作为测试依据?是否可以针对每一条进行独立测试以确定是否
满足需求?
● 是否对可能的改动作出了规定?包括每一改动的可能性?
3.2. 结构设计
● 一个好的结构设计应该阐明所有问题。这个表并不是用于指导结构设计的,而只是想
提供一种方法,通过它,你可以估计处于软件食物链顶层的程序员可以从食物中获得多少营养。它可以作为建立自己的检查表的起点。同要求定义检查表的使用一样,如果你正在从事一个非正式的项目,那么其中有些条款是不必考虑的。但如果你正在开
发一个较大的系统,那绝大部分内容都是非常有用的。
● 软件的总体组织形式是否清晰明了?包括对于结构设计的总体评论与描述。 ● 模块定义是否清楚?包括它们的功能及其与其它模块的接口。
● 要求定义中所提出的所有功能,是否有恰当数量的模块覆盖?
● 结构设计是否考虑了可能的更改?
● 是否包括了必要的购买?
● 是否阐明了如何改进重新启用的代码来满足现在的结构设计要求?
● 是否描述并验证了所有主要的数据结构?
● 主要数据结构是否隐含在存取子程序中?
● 规定数据库组织形式和其它内容了吗?
● 是否说明并验证所有关键算法?
● 是否说明验证所有主要目标?
● 说明处理用户输入的策略了吗?
● 说明并验证处理输入/输出的策略了吗?
● 是否定义了用户界面的关键方面?
● 用户界面是否进行了模块化,以使对它所作的改动不会影响程序其它部分
● 是否描述并验证了内存使用估算和内存管理?
● 是否对每一模块给出了存储空间和速度限制?
● 是否说明了字符串处理策略?是否提供了对字符串占用空间的估计?
● 所提供的错误处理策略是不是一致的?
● 是否对错误信息进行了成套化管理以提供一个整洁的用户界面?
● 是否指定了坚固性级别?
● 有没有哪一部分结构设计被过分定义或缺少定义了?它是否明确说明了;
● 是否明确提出了系统目标?
● 整个结构在概念上是否是一致的?
● 机器和使用实现的语言是否顶层设计依赖?
● 给出做出每个重要决定的动机了吗?
● 你作为系统实现者的程序员,对结构设计满意吗?
3.3. 3.9 小 结
● 如果想开发一个高质量的软件,必须自始至终重视质量问题。在开始阶段强调质量往
往比在最后强调质量更为有效。
● 程序员的份内工作之一便是向老板和同事宣传软件的开发过程,包括在编程开始前从
事先决条件准备工作的重要性。
● 如果问题定义工作做得不好,那么在创建阶段,所解决的问题可能并不是用户真正要
解决的问题。
● 如果需求分析工作做得不好,很可能因此而漏掉要解决问题中的重要细节。在创建工
作后更改要求,要比在需求分析阶段进行更改的成本高20到100倍。所以,在开始编程前一定要确认要求定义工作一切正常。
● 在编程前规定好约定,在创建工作结束后再改变代码来满足约定几乎是不可能的。 ● 在创建活动开始之前如果无法完成准备工作,可以尝试在不太稳固的基础上进行创建
活动。
4. 建立子程序的步骤
4.1. 创建子程序
● 是否检查过先决条件已经满足了?
● 定义子程序将要解决的问题了吗?
● 结构设计是否足够清楚,使得你可以给子程序起个好名字?
● 考虑过如何测试子程序了吗?
● 是否从模块化水平或者满足时间和内存要求角度考虑过效率问题?
● 是否查阅过参考书;以寻找有帮助的算法?
● 是否用详尽的 PDL 设计子程序?
● 在必要时,是否在逻辑设计步骤前考虑了数据?
● 是否检查过PDL ,它很容易理解吗?
● 是否注意到了足以使你返回到结构设计阶段的警告(使用了全局数据,更适合其它子
程序的操作,等等)。
● 是否使用了PDL 到代码流程,是否把PDL 作为编码基础并把原有的PDL 转为注释? ● 是否精确地把PDL 翻译成了代码?
● 在作出假设时,验证它们了吗?
● 是从几个设计方案中选择了最好的,还是随意选择了一个方案?
● 是否彻底理解你的代码?它容易理解吗?
4.2. 4.6 小 结
● 要想写好PDL ,首先要用易懂的自然语言,避免拘泥于某种程序语言,其次要在意向
层次上写PDL ,描述设计作什么而不是如何作。
● PDL 到代码流程方法是详细设计的有力工具,而且使得编码非常容易。可以把PDL 直
接翻译成注释,但要注意保证注释是精确而有用的。
● 应该在工作的每一步中都检查子程序,并鼓励同事们检查。这样,可以在投入的资金
和工作努力最少时便发现错误,从而极大降低改错成本。
5. 高质量子程序特点
5.1. 总体问题
● 创建子程序的理由充分吗?
● 如果把一个子程序中的某些部分独立成另一个子程序会更好的话,你这样做了吗? ● 是否用了明显而清楚的动宾词组对过程进行命名?是否是用返回值的描述来命名函
数?
● 子程序的名称是否描述了它做的所有工作?
● 子程序的内聚性是不是很强的功能内聚性?它只做一件工作并做得很好吗?
● 子程序的耦合是不是松散的?两个子程序之间的联系是不是小规模、密切、可见和灵
活的?
● 子程序的长度是不是它的功能和逻辑自然地决定的:而不是由人为标准决定的?
5.2. 防错性编程
● 断言是否用于验证假设?
● 子程序对于非法输入数据进行防护了吗?
● 子程序是否能很好地进行程序终止?
● 子程序是否能很好地处理修改情况?
● 是否不用很麻烦地启用或去掉调试帮助?
● 是否信息隐蔽、松散耦合,以及使用“防火墙”数据检查,以使得它不影响子程序之 ● 外的代码?
● 子程序是否检查返回值?
● 产品代码中的防错性代码是否帮助用户,而不是程序员?
5.3. 参数传递问题
● 形式参数与实际参数匹配吗?
● 子程序中参数的排列合理吗?与相似子程序中的参数排列顺序匹配吗?
● 接口假设说明了吗?
● 子程序中参数个数是不是7个或者更少,
● 是否只传递了结构化变量中另一个子程序用得到的部分?
● 是否用到了每一个输入参数?
● 是否用到了每一个输出参数?
● 如果子程序是一函数,是否在所有情况下它都会返回一个值?
5.4. 5.10 小 结
● 建立子程序的最重要原因是加强可管理性(即降低复杂性),其它原因还有节省空间、 ● 改进正确性、可靠性、可修改性等等。
● 强调强内聚性和松散耦合的首要原因是它们提供了较高层次的抽象性,你可以认为一
个具备这种特性的子程序运行是独立的,这可以使你集中精力完成其它任务。
● 有些情况下,放入子程序而带来巨大收益的操作可能是非常简单的。
● 子程序的名称表明了它的质量,如果名称不好但却是精确的,那么说明它的设计也是
非常令人遗憾的。如果一个子程序的名称既不好又不精确,那它根本就无法告诉你程序作了些什么。无论哪种情况,都说明程序需要改进。
● 防错性编程可以使错误更容易被发现和修复,对最终软件的危害性显著减小。
6. 模块化设计
6.1. 模块的质量
● 模块是否有一个中心目的?
● 模块是否是围绕着一组公用数据进行组织的?
● 模块是否提供了一套相互联系的功能?
● 模块功能是否足够完备,从而使得其它模块不必干预其内部数据?
● 一个模块相对其它模块是否是独立的?它们之间是松散耦合的吗?
● 一个模块的实现细节,对其它模块来说,是隐含的吗?
● 模块的接口是否抽象到了不必关心其功能实现方式的地步?它是作为一个黑盒子来设
计的吗?
● 是否考虑过把模块再划分为单元模块?是否对其进行了充分的再划分工作?
● 如果用不完全支持模块的语言编程,你是否制定了编程约定以使这种语言支持模块?
6.2. 6.5 小 结
● 不管调用哪一个,子程序与模块的不同是很重要的,要认真考虑子程序与模块的设计。 ● 从模块数据是被几个子程序使用的这一角度来说,它与全局数据是相同的,但从可以
使用它的子程序是有限的,而且清楚地知道是哪些子程序可以使用它这一角度来说,模块数据与全局数据又是不同的。因此,可以使用模块数据而没有全局数据的危险。
7. 高级结构设计
7.1. 高层次设计
● 本表给出了在评估设计质量时,通常要考虑一些问题。本表是3.4 节中结构设计检查
表的补充,这个表所考虑的主要是设计质量。3.4 节中的检查表则侧重于结构设计和设计内容。这个表中的某些内容是相互重合的。
● 是否使用了往返设计方法,应从几个方案中选择最好的,而不是首次尝试就确定方案。 ● 每个子程序的设计是否都和与其相关的子程序设计一致?
● 设计中是否对在结构设计层次上发现但并未解决的问题作了充分说明?
● 是否对把程序分解成目标或模块的方法感到满意?
● 是否对把模块分解成子程序的方法感到满意?
● 是否明确定义了子程序的边界?
● 是否是按照相互作用最小的原则定义子程序的?
● 设计是否充分利用了自顶向下和自底向上法?
● 设计是否对问题域要素、用户接口要素、任务管理要素和数据管理要素进行了区分? ● 设计是智力上可管理的吗?
● 设计是低复杂性吗?
● 程序是很容易维护的吗?
● 设计是否将子程序之间的联系保持在最低限度?
● 设计是否为将来程序可能扩展作了准备?
● 子程序是否是设计成可以在其它系统中再使用的?
● 低层次子程序是高扇入的吗?
● 是否绝大多数子程序都是低或中等程度扇出的?
● 设计易于移植到其它环境吗?
● 设计是简练的吗?是不是所有部分都是必要的?
● 设计是成层的吗?
● 设计中是否尽量采用了标准化技术以避免奇特的、难以理解的要素?
7.2. 7.6 小结
● 设计是一个启发的过程。固执地坚持某一种方法只会抑制创造力,从而产生低质量的
程。 坚持设计方法上有一些不屈不挠的精神是有益的,因为这可以迫使你对这种方法进行充 分理解。但是,一定要确信你是在不屈不挠而不是顽固不化。 ● 好的设计是通过迭代逼近得到的:你尝试过的设计方案越多,你最终所确定的设计方
案也越好。
● 结构化设计比较适合于小规模的子程序组合,同时,它对于功能变化可能性比数据大
的问题也是较适用的。
● 面向对象设计更适于子程序与数据的组合,通常在比结构化设计抽象程度更高些的层
次上适用。它尤其适合于数据变动可能性大于功能变动可能性的问题。
● 设计方法仅是一种工具,你对工具运用得好坏决定了你所设计的程序的质量。利用不
好的设计方法,也可能设计出高质量的程序。而即使是好的方法,如果运用不当的话,也只能设计出拙劣的程序。但不管怎样,选择正确的工具更容易设计出高质量的软件。 ● 许多关于设计的丰富而有用的信息都是在本书之外的。在这里所论述的,不过是冰山
的一角而已。
8. 数据生成
8.1. 生成类型
● 程序中是否对每种可能变动的数据都使用了不同的类型?
● 类型名称是面向客观世界中的实体类型而不是面向程序语言中的类型吗?
● 类型名称是否有足够的表现力来帮助说明数据?
● 避免重新定义已定义的类型了吗?
8.2. 说明数据
● 是否使用了模框为简化数据说明工作?并用其来统一说明形式?
● 如果所用的语言支持隐式说明,是否采取了补救措施?
8.3. 初始化
● 是否每个子程序都对输入参数进行了检查以保证其有效性?
● 是否在使用变量的位置附近对其进行初始化的?
● 是否恰当地对计数器和指针进行了初始化?是否在必要时对其进行了重新初始化? ● 在反复执行的代码段中,是否对变量进行了恰当地重新初始化?
● 用编译程序编译代码时是否是无警告的
8.4. 8.6 小结
● 在你的工具箱中需要一张全部数据结构的清单,以便用最合适的方法处理每一种问题。 ● 建立自己的数据类型,以增加程序的可变动性,并使其成为自说明的。
● 数据初始化很容易产生错误,因此应采用本章推荐的技术来避免由意外初始值所产生
的错误。
9. 数据名称
9.1. 通用命名约定
● 变量名称是否完全准确地描述了变量代表的是什么?
● 变量名是否指向是客观世界中的问题,而不是关于这问题的用程序语言表达解决方案? ● 变量名称是否是够长,使得你不必破译它?
● 变量名中如果含有计算限定词的话,是否将其放在最后?
● 是否在名称中用Count 或Index 来代替了Num ?
● 对特殊类型数据的命名
● 循环变量的名称是有意义的吗?(如果循环体较长是嵌套循环的话,应用有含义的名
称来代替i 、j 、k 之类的名称)
● 是否用更富于含义的名称来代替了被叫作"tempotarg" 的临时变量?
● 当逻辑变量的值是"True" 时,它的名称是否充分表达了其含义?
● 是否用前缀或后缀来表明了某些枚举类型是一类的?如用Color 来作ColorRed ,
ColorGreen ,ColorBlue 等枚举类型的前缀。
● 命名常量的名称是否是指向它们代表的实体而不是它们所代表的数值的?
9.2. 命名约定
● 命名约定是否区分了局部、模块和全局数据?
● 命名约定是否对类型名称、命名常量、枚举类型和变量进行了区分?
● 在不支持强化仅供子程序输入参数的语言中,命名约定是否对这类参数进行了标识? ● 命名约定是不是与程序语言的标准约定尽可能地相容?
● 对于语言中没有强制的子程序中仅做输入的参数,是否约定将它标识了?
● 是否对名称进行了格式化以增强程序的可读性?
9.3. 短名称
● 代码是否使用了长名称?(除非有必要使用短名称)
● 是否避免了只省略一个字母的缩写?
● 所有单词保持缩写的连续性了吗?
● 所有的名称都是容易发音的吗?
● 是否避免了会引起错误发音的名称?
● 是否在注释表中对短变量名进行了注释?
● 避免如下这些常见的命名错误了吗
⏹ 易引起误会的名称
⏹ 含义相似的名称
⏹ 仅有一或两个字母不同的名称
⏹ 发音相似的名称
⏹ 使用数字的名称
⏹ 对单词作改写以使其比较短的名称
⏹ 英语中常拼写错的名称
⏹ 标准库子程序或已定义的变量名又定义了
⏹ 完全是随意的名称
⏹ 含有难以辨识字母的名称
⏹ 使用数据时通常要考虑的一些问题
9.4. 9.8 小结
● 恰当的变量名是可读性好的必要条件之一。特殊的变量如循环变量和状态变量要予以
特殊考虑。
● 命名约定可以区分局部、模块和全局变量。同时它还可以区分类型名称,比如可以对
命名常量、枚举类型和变量加以区分。
● 不管你从事的是哪种项目,都应该采用命名约定。所采用的命名约定取决于程序的规 ● 模和从事这一程序的程序员的人数。
● 匈牙利约定是一种非常有效的命名约定,比较适于大规模项目和程序。
● 在现代编程语言中几乎不需要采用缩写技术。
10. 变 量
10.1. 一般数据
● 是否已经使变量的作用域尽可能地小?
● 是否把对变量的使用集中到了一起?
● 控制结构与数据结构是相对应的吗?
● 每个变量是否有且仅有一个功能?
● 每个变量的含义都是明确的吗?是否保证了每个变量都没有隐含的意义?
● 每一个说明过的变量都被用到了吗?
10.2. 全局变量
● 是否是在迫不得已的情况下,才使某些变量成为全局的?
● 命名约定是否对局部、模块和全局变量进行了区分?
● 是否说明了所有全局变量?
● 程序中是否不含有伪全局变量——传往各个子程序的庞大而臃肿的数据结构? ● 是否用存取子程序来代替了全局数据?
● 是把存取子程序和数据组织成模块而不是随意归成一堆的吗?
● 存取子程序的抽象层次是否超过了程序语言实现细节?
● 所有相互有联系的存取子程序,其抽象程度都是一致的吗?
10.3. 10.7 小结
● 尽量减小变量的作用域。把对变量引用集中到一起,应尽量使变量成为局部或模块的,
避免使用全局变量。
● 使每个变量有且仅有一个功能。
● 并不是因为全局数据危险才避免使用它们,之所以避免用它是因为可以用更好的技术
来代替它。
● 如果全局数据确实不可避免的话,应通过存取子程序来对其进行存取操作。存取子程
序不仅具备全局变量和全部功能,而且可以提供更多的功能。
11. 基本数据类型
11.1.1. 常数
● 代码中是否避免了? 奇异数?(常数?)
● 程序中是否采取了措施来防止出现被“0”除错误?
● 类型转换是显式进行的吗?
● 如果在同一个表达式中出现了两种不同类型的变量,是否对表达式按照你的意愿进行
求值?
● 程序中是否避免了混合类型比较?
● 在编译过程中是没有警告的吗?
11.1.2. 整型数
● 使用整型数相除表达式的结果是否与预期的一致?
● 整型数表达式中是否避免了整型数溢出问题?
11.1.3. 浮点数
● 是否避免了数量级相差过大的数之间加减运算?
● 程序中是否系统地采取措施来防止舍入误差问题?
● 程序中是否避免了对浮点数进行相等比较?
11.1.4. 字符和字符串
● 程序中是否避免了常数型字符和字符串?
● 对字符串的引用是否避免了边界错误?
● 若是用c 写成的程序,是否是把字符数组和字符串指针区别对待的?
● 若程序是用C 写成的,是否遵守了把字符串长度说明为CONSTANT+1这一约定? ● 是否用字符数组代替指针?
● 在C 语言程序中,是否把字符由初始化成了NULL 以避免出现无限长字符串? ● 在C 语言程序中,是否用strncpy()代替了strcpy()?并且用了strncat()和strncmp()? 11.1.5. 逻辑变量
● 程序中是否使用了附加的逻辑变量来说明条件判断?
● 程序中是否使用了附加的逻辑变量来简化条件判断?
11.1.6. 枚举类型
● 程序中是否用枚举类型代替了命名常量来改善可读性、可靠性和易改动性? ● 是否用了枚举类型代替逻辑变量以改进可读性和灵活性?
● 在使用了枚举类型的判断中是否检查了无效值?
● 枚举类型的第一个入口是否是保留为无效的?
11.1.7. 命名常量
● 在数据说明中使用的是命名常量吗?
● 是否一致地使用了命名常量,而不是一会儿使用命名常量,一会儿使用数值? 11.1.8. 数组
● 是否所有的下标都在数组界限之内?
● 是否对数组所有的引用都没有发生越界错误?
● 多维数组的下标排列顺序正确吗?
● 在嵌套循环中,作为循环变量的数组下标是正确的吗?是否出现了交叉错误? 11.1.9. 指针
● 是否把指针操作独立在函数中?
● 指针引用是有效的吗?是否误用了悬挂指针?
● 程序中在使用指针之前,是否对它进行了检查;
● 在使用由指针指向的变量之前,是否对其有效性进行了检查?
● 在释放指针之后,是否把它们的值赋成了NULL 或NIL ?
● 为了提高可读性,程序中是否使用了所有需要用的指针变量?
● 链表中的指针是否是按正确的顺序释放的?
● 程序中是否分配了备用内存空间以作为内存溢出时拯救数据和工作努力的降落伞? ● 是否是在万不得已时才使用指针的?
11.2. 11.10 小 结
● 使用各种特定的数据类型意味着需要记住许多种规则。因此要用上面的检查表来确认
你已考虑过了所有常见问题
12. 复杂数据类型
12.1. 12.4 小 结
● 恰当地对数据进行结构化,可以使程序更简单、更容易理解也更容易维护。
● 可以用表来代替复杂的逻辑结构。当你被程序的复杂逻辑迷惑时,应考虑是否可用
查寻表来简化程序。
● 抽象数据类型是降低复杂性的有力武器。它使你可以分层编写程序,而且是从问题
域而不是程序语言细节来编写顶层的程序。
13. 顺序程序语句
13.1. 组织顺序式程序代码
● 把语句间的依赖关系表示得很清楚吗?
● 子程序名是否把依赖关系表示得很清楚?
● 子程序的参数是否把依赖关系表示得很清楚?
● 若代码的依赖关系不清楚,用注释注明了吗?
● 代码能从上读到下吗?
● 变量的出现靠得很近吗? ——从跨度和存活时间来考虑。
● 是否把相关语句组织在一起?
● 是否把相对独立的各相关语句写成子程序了?
13.2. 13.3 小 结
● 组织顺序式代码最好的原则是整理出依赖关系。
● 用合适的子程序名、参数表、注释来标明依赖关系。
● 如果代码没有明显依赖关系,把相关语句组织在一起,特别是使用同一参数的那些语
句。
14. 条件语句
14.1. if-then 语句
● 正常情况路径在代码中流向是否很分明?
● if-then 语句在出现等号时流向是否正确?
● else 语句是否有必要?
● else 语句正确吗?
● if 语句和else 语句正确吗?它们是否弄反了?
● 正常情况是否跟在if 后而非else 后?
● if-then-else
● 复杂的条件是否封装成布尔函数调用了?
● 最常见情况放在前面吗?
● 全部情况都覆盖住了吗?
● if-then-else 语句是最好的选择吗?——用case 语句代替是否更好?
14.2. case 语句
● 各情况的安排次序有含义吗?
● 每种情况对应的操作简单吗?——如需要调用别的子程序。
● case 语句中的变量有实际意义吗?它是为了用case 语句而单纯地定义出来的伪变量
吗?
● 缺省语句的用法是否合法(规范)?
● 用缺省语句检查和报告异常情况吗?
● 在C 语言中,每一情况的结尾用了break 了吗?
14.3. 14.3 小 结
● 注意if 和else 的顺序,特别是在处理好多异常情况时,务必使正常情况流向清晰。 ● 组织好if-then-else 和case 语句中的几种情况,使可读性最好。
● 在case 语句中用缺省值,在if-then-else 中的最后一个else 中获取意外错误。 ● 各种控制结构并不都同样有用,在编码时选用最合适的控制结构。
15. 循环语句
15.1. 15.5 小 结
● 循环很复杂,使其简化有利于阅读。
● 简化循环的技巧有:避免使用怪样子循环、使循环次数最小、使进出口清楚、把内务
代码放在一个地方。
● 循环控制变量不可滥用,应给它起一个有含义的名字并让它只起一个用途。 ● 仔细考虑一下整个循环,保证循环在各种情况和终止条件下都能照常运行。
16. 少见的控制结构
16.1. goto
● goto 是最后的选择吗?用goto 使程序更好读更好维护吗?
● 用goto 是为效率的目的吗?用goto 达到此目的了吗?
● 一个程序是否只用一个goto 呢?
● Goto 只转向前面的程序段而不是转向其后面的程序段吗?(后面指已执行过程序) ● Goto 所转向的标号都有了吗?
16.2. return
● 每个子程序的return 数目是否最少?
● Return 增强了可读性了吗?
16.3. 递归调用
● 用递归调用的代码含使递归结束的语句吗?
● 程序设置了安全计数器来保证递归调用终止了吗?
● 是否只在一个程序中用递归调用?
● 递归调用的深度是否限制在程序堆栈容量可满足的条件下。
● 递归调用是实现程序的最优途径吗?它比循环更简单吗?
16.4. 16.4 小 结
● 有些情况下,goto 是编出易读易维护程序的最好方法。
● 多重return 有时增强了程序的可读性与可维护性,并且防止多重嵌套逻辑,但没必要
只想到怎样用好return 。
● 在问题较简单时,递归调用能把问题很巧妙解决。要慎用递归调用。
17. 常见的控制问题
17.1. 控制结构方面
● 表达式用True 和False 而非1 和0?
● 布尔型表达式的值是否隐含地与False 比较?
● 是否通过定义中间布尔型变量和布尔型函数及用决策表的方法来简化表达式? ● 布尔型表达式是用肯定形式写出来的吗?
● 在C 中,数值、字符,指针是显式与0比较的吗?
● begin 和end 能保持平衡吗?
● 为了使程序看起清楚,需要的地方用begin 和end 对标明了吗?
● 空语句看起来清楚吗?
● 通过诸如重新组合测试条件、转化为if-then -else 或case 语句或把嵌套内代码写成子
程序的方法来简化嵌套语句了吗?
● 如果程序的决定点数超过10,有什么正常理由不重新设计它吗?
17.2. 17.8 小 结
● 使布尔型表达式简单可读性高对代码的质量很有好处。
● 深层嵌套使程序难懂,不过可用相对简单方法避免这样做。
● 结构化编程是一个简化程序的思想,用顺序编程、选择或循环中的一种或几种方法的
组合可编出任何程序。
● 作这种简化程序的思想可提高程序的产量和质量。
● 如果所用语言不支持结构化结构,你能模仿它们。你应该把程序编成某种语言的程序
而不是用某种语言编程的。
● 降低复杂性是编写高质量的代码的关键。
18. 布局和风格
18.1. 简述
● 格式化的本意是要显示代码的逻辑结构吗?
● 格式化的形式能始终一致吗?
● 格式化后使代码易于维护吗?
● 格式化后改进了可读性吗?
18.2. 控制结构
● begin -end 对中代码避免再次缩排了吗?
● 一系列的块结构用空行相互分隔了吗?
● 复杂的表达式格式化后可读性增强了吗?
● 单条语句块始终一致地格式化了吗?
● Case 语句的格式化与其它控制结构格式化相协调吗?
● goto 语句格式化后自己显得更清楚了吗?
18.3. 单条语句
● 把单条语句分成几行,每行都明显地不能作独立行看待了吗?
● 续行有意识地退格了吗?
● 相关语句组对齐了吗?
● 不相关语句组不应对齐,你是这样的吗?
● 每行至多含一条语句吗?
● 每个语句避免副作用了吗?
● 数据定义时相应项对齐了吗?
● 每行至多定义一个数据,是吗?
18.4. 注释
● 注释行与它所对应的代码退同样格数了吗?
● 注释行的形式易修改吗?
18.5. 子程序
● 子程序的参量格式化后各参数易读、易修改、易加注释吗?
● 在C 中是否用新子程序定义方法呢?
● Fortran 中,参数定义是否和局部变量定义分开?
● 文件、模块和程序
● 若语言允许有多个源文件,每个源文件仅含一个模块是吗?
● 一个文件内的各子程序是否用空行清楚隔开?
● 如果一个文件含几个模块,那么每个模块中的子程序是否被组织到被清楚隔开? ● 各子程序是否按字母顺序排列?
18.6. 18.9 小 结
● 布局首先考虑的是去显示程序的逻辑结构。评价这种考虑是否达到目的标准有:准确
性、连续性、可读性、易维护性。好看是第二条标准——比较弱的标准。如果以上几条标准都达到了而且程序也较好看,那么布局一般就成功了。
● 在C 、Pascal 、Basic 中用纯块结构模仿及begin -end 作块边界,这两种布局形式行之
有效。在Ada 中用纯块结构。
● 结构化代码是有其自身目的的,你最好还是用一些约定俗成的布局形式而少来创新,
以保持与别人协调一致。若你的布局形式与约定的不一样,那么很有可能影响你程序
19. 文 档
19.1. 19.2 编程风格作文档
19.1.1. 子程序
● 每一个子程序名都确切地描述了要做什么事吗?
● 每一个子程序详细定义任务吗?
● 程序会从它们的子程序中获益吗?
● 每个子程序的接口处明确吗?
19.1.2. 数据名称
● 类型名的描述足以帮助文件数据说明吗?
● 变量名好吗?
● 变量仅用于命名这个目的吗?
● 循环计算变量能给出更多的信息吗?
● 用枚举类型变量来代替标记或逻辑变量了吗?
● 命名常量没有用来代替数字或字串吗?
● 类型名、枚举类型名、命名常量、局部变量、模块变量和全局变量中的命名规则不同
吗?
19.1.3. 数据组织
● 附加变量在需要时要清零吗?
● 变量的引用彼此间很接近吗?
● 数据结构简化会导致降低其灵活性吗?
● 复杂的数据存取是通过子程序来完成的吗?
19.1.4. 控制
● 正常编码路径清晰吗?
● 相关语句分成一组了吗?
● 相对独立的语句都组成子程序了吗?
● 正常情况跟在IF 后,而不是ELSE 后吗?
● 控制结构简化会降低灵活性吗?
● 像一个定义完好的子程序那样,每个循环执行一个且仅一个功能吗?
● 嵌套层次是最少吗?
● 逻辑表达式用附加的逻辑变量、逻辑函数和功能表简化了吗?
19.1.5. 布局
● 程序布局显示出它的逻辑结构吗?
19.1.6. 设计
● 代码直观吗?它的编写巧妙吗?
● 实现细节可能隐去了吗?
● 程序编写是立足于问题域而不是计算机科学或语言结构域吗?
19.2. 有效的注释技术
19.2.1. 通用部分
● 代码中是否包含了关于程序的大部分信息?
● 是否可以做到随意拿出一段代码便可以立刻理解它的意思?
● 注释是否注释了程序的意图或总结了程序的功用而不是简单地重复代码? ● 是否使用了PDL ——代码流程以减少注释时间?
● 对使人困惑的代码是否进行了重写而不是注释?
● 注释是否已经过时了?
● 注释是清楚正确的吗?
● 注释风格是否使得注释很容易修改?
19.2.2. 语句和段落
● 是否避免了结束行注释?
● 注释的重点是“为什么”而不是“是什么”吗?
● 注释是否提示了后续代码?
● 每个注释都是合理的吗?是否删掉或改进了冗余、自相矛盾的注释?
● 是否注释了令人惊异的代码?
● 是否避免了缩写?
● 主要和次要注释间的区别明显吗?
● 用于错误处理或未说明功能的代码注释了吗?
19.2.3. 数据说明
● 数据说明单元注释了吗?
● 数值数据的取值范围注释了吗?
● 是否注释了代码的含义?
● 对输入数据的限制注释了吗?
● 是否在位层次上对标志进行了注释?
● 是否在说明全局数据的地方对其进行了注释?
● 常数值是否被注释了?或者被用命名常量代替了吗?
19.2.4. 控制结构
● 每一个控制语句都进行注释了吗?
● 冗长或复杂的控制结构进行注释了吗?
19.3. 19.6 小 结
● 是否注释就像是立法。注释得好,是非常值得的,注释得不好,则是浪费时间而且有
害。
● 源代码中应含有关于程序的绝大部分重要信息。只要程序还在运行,那么代码中的注
释便不会丢失或被丢弃。把重要信息加入代码是非常重要的。
● 好的注释是在意愿层次上进行的,它们解释的是“为什么”而不是“是什么”。
● 注释应表达出代码本身表达不了的意思。好的代码应是自说明的。当你对代码进行注
释时,应问一下自己“如何改进代码以使得对其注释是多余的?”,改进代码再加注释以使它更清楚。
20. 编程工具
20.1. 20.7 小 结
● 好的工具使编程更容易。
● 你可以编写大多数你需要的工具。
● 用今天的技术水平能编出高水平的编程工具。
21. 项目大小如何影响创建
21.1. 21.5 小 结
● 在一些小项目中的活动并不能想当然地用于大项目中,你应仔细计划它们。随着项目
增大,创建作用减弱。
● 随着项目的增大,交流方法应简化。各种方法的使用应因时而异。
● 在相同条件下,大项目的生产率比小项目低一些。
● 在相同条件下,大项目的每行错误数比小项目的每行错误数多。
22. 创建管理
22.1. 配置管理
22.1.1. 一般
● 你的软件配置管理计划是否用于帮助程序员,并能将额外开销减至最少?
● 你使用SCM 手段能否避免对项目失控?
● 你所在组是否有修改请求?请示控制可以是非正式方式或正式的方式。
● 你是否能比较正确地估计每次修改的影响?
● 你是否将重要修改视为需求分析不充分的警告?
22.1.2. 工具
● 你是否使用版本控制软件以便配置管理?
● 你是否使用版本控制软件以减少开发时的协调问题?
● 你是否使用制作或其它控制依赖软件使编程更为有效和可靠?
22.1.3. 备份
● 你是否将所有项目材料定期备份?
● 你是否定期将所有项目备份移到安全地点存放?
● 包括源代码、文档、图形和重要注释在内的所有材料都备份了吗?
● 你是否对备份程序进行了测试?
22.2. 22.7 小 结
● 配置管理,适当应用时,可使程序的工作变得更容易进行。
● 你能找到某种方法度量项目的某一方面,这样比根本不度量好。准确的度量是完善的
计划、质量控制和提高开发速度的关键。
● 程序员和管理者都是普通的人,当他们受到礼待时往往干得更好。
● 合适的软件工程评估是软件开发管理最富挑战性的方面,你不妨尝试几种方法,看看
评估的差别,以加深你对项目的认识。
23. 软件质量概述
23.1. 质量保证步骤
● 你是否有项目较为重要的特定质量描述?
● 你是否让其它人明白项目的质量目标?
● 你是否要求不同的外部和内部恃征?
● 你是否考虑过某些特性可能和其它相互矛盾或采用共同促进的方式?
● 你的项目是否需要几种不同的错误检查方法,以便能发现几种类型的错误?
● 你的项目是否包括这样一个计划,以便在软件开发过程中采取措施确保软件质量? ● 你的软件质量是否能按某种方式度量,以确定软件质量是提高或下降了?
● 你在管理中是否知道质量确保在刚开始可能增加费用,但今后的花费能节省? 23.2. 23.6 小 结
● 并不是所有质量保证目标都能实现。确定你想实现的目标,并将其让你所在组的每一
个人知道。
● 各种错误检查方法就其自身来说并不十分有效。仅用一种方法消除错误并非有效。成
功的质量确保计划使用几种不同的方法,以确定不同类型的错误。
● 你可在创建之前或创建过程中使用几种有效的方法以发现错误。你发现错误越早,其
所引起的损失也越小。
● 软件开发领域中的质量保证是面向过程的。软件开发并不包括影响最终产品的重复开
发阶段,所以产品开发过程控制着产品的质量。
● 质量最终是主动的,它要求对系统的资源进行再分配,以便能预防错误而不是花费较
大地去提高质量。
24. 评 审
24.1. 有效检查法
● 你的检查表是否着重将检查员的注意力引向过去常发生错误的地方?
● 是否侧重于缺陷检查而不是纠错?
● 在检查会议之前检查员是否有足够的准备时间?每一位检查员都作好了准备吗? ● 每一位参与者是否都扮演不同的角色?
● 会议是否开得富有成果?
● 会议是否限制在2 小时之内?
● 协调者在指导检查方面接受过特殊的训练吗?
● 在每次检查中,错误类型数据是否都作了收集,以便于你今后制作检查表? ● 是否收集了准备和检查率,以便可以优化将来的准备和检查?
● 每次检查所指定的条款是否都落实了?是由协调员本人还是重新作了检查? ● 管理员是否明白为什么他不参加检查会议?
24.2. 24.4 小 结
● 总的来说,评审在发现错误方面较测试要好。
● 评审侧重于错误发现而不是纠错。
● 评审往往比测试要能发现更多种不同的错误,意味着你应使用评审或调试方法以确保
软件的质量。
● 使用检查表、准备良好的分工、连续的处理以便最大限制地提高检错能力,检查往往
较普查能发现更多的错误。
● 普查和代码阅读是检查的候补方法。代码阅读可有效地利用每个人的时间。
25. 单元测试
25.1. 测试用例
● 每个子程序的要求是否有自己的测试用例?
● 子程序结构的每个部分是否都有自己的测试用例?
● 程序中每一行代码都是否至少被一个测试用例所测试过?这是否是由通过计算测试 ● 每一行代码所需的最少用例来确定的?
● 所有定义——使用数据流路径是否被至少一个测试用例所测试过?
● 代码是否被看起来不大正确的数据流模式所检查过?比如定义一定义,定义一退出,
和定义一失效?
● 是否使用常见错误表以便编写测试用例来发现过去常出现的错误?
● 是否所有的简单边界都得到了测试:最大、最小或易混淆边界?
● 是否所有复合边界都得到了测试?
● 是否对各种错误类型的数据都进行了测试?
● 是否所有典型的中间数都得到了测试?
● 是否对最小正常配置进行了测试?
● 是否对最大正常配置进行了测试?
● 是否测试了和旧数据的兼容性?是否所有保留下来的硬件、操作系统旧的版本,以及
其它软件旧版本的接口都得到了测试?
● 测试用例是否便于手工检查?
25.2. 25.8 小 结
● 开发者使用测试是完成测试策略的一个重要组成部分。模块和系统测试也是重要的,
但它们不在本书的讨论之内。
● 各种测试方法的混合使用也是搞好软件质量程序的一个方面。高质量开发方法,包括
有条理地对需求和设计进行测试。检查和普查在发现错误方面也至少同调试方法一样有效,并且可发现各种类型的错误。
● 通过采用基本测试、数据流分析和错误猜测你可生成许多测试用例。
● 错误倾向于集中在少数几个子程序中。找到这几个子程序,重新设计和重写它们。 ● 测试数据往往比受测试的代码含有更多的错误。这种找错方法只会浪费时间而不会 ● 提高代码质量,测试数据错误往往比编程错误更令人讨厌,这可通过仔细地进行测试
来避免。
● 最终提高测试质量的最好方法是,有条理的度量它,利用你的知识提高测试质量。
26. 调 试
26.1. 调式
● 发现错误的方法:
⏹
⏹
⏹
⏹
⏹
⏹
⏹
⏹
⏹ 使用所有数据建立假设 求精产生错误的测试用例 通过不同的方法再生错误 产生更多的数据以生成更多的假设 使用否定测试结果 提出尽可能多的假设 缩小可疑代码区 检查最近作过修改的代码 扩展可疑代码区
⏹ 逐步集成
⏹ 怀疑以前出过错的子程序
⏹ 耐心检查
⏹ 为迅速的草率的调试设定最大时间
⏹ 检查一般错误
⏹ 使用交谈调试法
⏹ 中断对问题的思考
● 改正错误的方法:
⏹ 理解问题的实质
⏹ 理解整个程序
⏹ 确诊错误
⏹ 放松情绪
⏹ 保存初始源代码
⏹ 修改错误而不是修改症状
⏹ 仅为某种原因修改代码
⏹ 一次作一个修改
⏹ 检查你的工作,验证修改
⏹ 寻找相似错误
● 调试的一般方法
⏹ 你是否将调试作为学习有关程序、错误、代码质量及解决问题方法的一次机会? ⏹ 你是否避免使用试错法,或避免采用迷信的方法?
⏹ 你是否认为错误是由于你的过错?
⏹ 你是否使用科学方法以固定间歇性错误?
⏹ 你是否使用科学方法发现错误?
⏹ 你是否每次使用不同的方法,而不是只用一种方法发现错误?
⏹ 你是否验证了修改信息是正确的?
⏹ 你是否利用了警告信息、执行剖析程序、建立脚手架方法、交互式调试等? 26.2. 26.6 小 结
● 调试是软件开发中的问题。最好的方法是使用本书的其他方法避免一开始就发生错误。 ● 你仍需花费时间提高你的调试技能——因为最好和最差的调试效率差别为10:1,甚至
更大。
● 注意你的编译警告信息,并及时改正编译所提示的错误。如果你忽略了明显错误的话,
你就难以改正微妙的错误。
● 发现和改正某错误的关键是,有一个有条理的方法,注重你的测试,这样每次你都能
向前迈进一步。
● 在你改正一个问题之前应理解问题的本质,对错误源的任意猜测和修改只会使你的程
序更为糟糕。
● 调试是软件开发的有力工具。你应能利用调试工具。记住你还应好好动脑筋。
27. 系统集成
27.1. 递增集成法
27.1.1. 递增集成策略
● 这种方法是否能辨别出程序或模块的最佳集成顺序?
● 集成次序是否和结构次序相同,以致于结果程序只能在适当的时候被集成? ● 这种方法易于故障诊断吗?
● 这种方法是否需要最少的搭架程序?
● 这种策略确实比其它方法好吗?
● 各部分间的接口是否已被指定好了?
● (指定接口不是集成的任务,但证明是否已这样做是集成的任务)
27.2. 改进的公布法
● 在工程完成前你已经有几个版本了吗?最后的功能将能否实现?
● 第一次释放是否包括了程序的核心,其它程序是否可以从这上面发展?
● 第一次释放是否能尽快地扩大?
● 第一次释放是否可用,至少在最低限度上可用?
● 在工程目标并不十分明确时,你是否尽量努力确定每个改进阶段的内容? ● 每一个释放都要加进重要内容吗?
● 执行过程是否有灵活性以响应用户反馈,如果没有灵活性,这种不灵活性是有意安排
的吗?
● 每一个释放是否被看成一个小工程,有自己的编码和测试,在某些情况下,还有自己
的分析和设计。
● 结构是否足够开放以支持几次释放后的许多变化。
● 你是否考虑以对现有程序进行修改为基础的改进的公布过程。
● 你是否要用到每一阶段的结果去改进、估计和计划下一阶段?
27.3. 27.5 小 结
● 集成的计划安排影响程序模块设计、编码和调试的次序,它也影响你是否能顺利地测
试、调试它们。
● 递增集成法有许多形式,除非工程非常琐碎,这些形式中的任一种都比分段集成好。 ● 一个好的递增方法更接近Lewis 和Clark 探险的方法,而不是Amundsen 的方法,此
种(前者)方法假定路程中的一部分没有图标,灵活性是十分重要的,它的计划可以
应付意想不到的情况。
● 改进公布法在工程中可将能工作的软件尽快送到用户手中,而传统方法必须使用户等
到所有东西都送到后才能工作。
● 改进公布法是对双方都有益的一种方法,对于用户,它可以使他们早日确定工程是否
成功,在工程管理上对工程进行有了清楚的了解;对于开发,它可以使人们知道在提高代码质量方面应该做些什么
28. 代码调整策略
28.1. 28.5 小 结
● 性能是软件整体质量的一方面,可能并非最具影响力的一方面。调整代码只是程序性
能的一部分,恐怕亦非最重要的。程序结构、具体设计数据结构和算法选择,对于程序规模和执行速度的影响要比产生代码本身的工作大。
● 优化设计关键要用到定量测量。重要的是找到确实能改善程序性能的段落,在此就要
强调优化的作用。
● 大多程序主要的时间花在小部分代码上。在执行程序及测试前不容易知道是哪部分代
码。
● 为写出优良程序最好是写出清晰易读和易修改的源程序来。
29. 代码调试技术
29.1. 29.8 小 结
● 优化的结果因不同语言、编译器和环境而大不相同。如果不对特定的优化进行测试, ● 没人知道结果会怎样。
● 头一次优化未必是最好的。即使你找到了一个好的,试着寻找更完美的。
● 代码调试有点像原子能,是一个有争议的题目,有些人认为它对可靠性和可维护性是
有害的,根本不该使用。有些人认为只要注意,它是有益的。如果你决定要使用本章描述的技术,千万小心从事。
30. 软件优化
30.1. 修改程序
● 每次改变都是按修改策略的吗?
● 变动是否彻底地检查过了?
● 软件是否作了重测试,确保修改未使性能变坏?
● 修改是否提高了程序的内在质量?
● 是否尽可能将程序拆成子程序从而模块化?
● 是否尽可能地减少了全局变量?
● 是否改进了程序风格?
● 如果为了共享代码,必须作些改动,你是否考虑过共享代码在高层程序上而非在低层
程序上?
● 如此改动后,是否为以后的修改创造便利?
30.2. 30.4 小 结
● 初始开发中,升级是传统开发方法的一个典型事例,并且随着新方法的使用,它可能
变得更为突出。
● 使用升级,软件质量可能是提高或恶化了。软件升级的基本规则其内部质量应随时间
的流逝而提高。虽然在扩充维护阶段,软件退化是能避免的,在创建中软件退化就不妙了。
● 开发对提高你的程序质量是一个最好的机会,如果你想有所收益,你应很好的利用每
一次机会。
● 当你修改程序时创建新的子程序的方法,对程序的质量有很大的影响。创建新的子程
序是你用这种方式:你应用品化结构的粗糙边缘而不要再向其上打洞。
31. 个人性格
31.1. 31.10 小结
● 你的个人性格直接影响你编写计算机程序的能力。
● 最有明显作用的性格为:谦虚、好奇心、诚实、创造性和纪律,还有文明的“懒惰”。 ● 高级程序员的发展和生成与天才并无多大联系,任何事情都和个人的发展有关。 ● 令人吃惊的是,小聪明、经验、坚持和欲望既可帮助你也能妨碍你。
● 许多程序员不主动去吸收新信息和新技术,而是靠偶然地上获得一些新信息,如果你
抽出少量时间学习别人的编程经验,过一段时间后,你将在你的同行中脱颖而出。 ● 好的性格对养成良好习惯有很大影响,为了成为一位高水平的程序员,你应养成良好
的习惯,其余的就会随之而来。
32. 软件开发方法的有关问题
32.1. 32.9 小 结
● 程序编制的一个主要目的复杂性管理。
● 编程过程对最终产品的影响比人们想象中的要大。
● 合作程序开发要求各成员之间进行广泛的交流,其次才是和计算机的交流,而个人则
主要是和你自己而不是和计算机交流。
● 当被乱用时,约定好比是雪上加霜,而使用得当的话,约定可增加开发环境的有用结
构,并有助于管理复杂性和交流。
● 面向问题而不是解答的编程有助于对复杂性的管理。
● 注意警告信息是相当重要的,因为编程几乎是纯智力活动。
● 在开发过程重复越多,产品质量也就越高。
● 武断的方法和高质量软件开发是不相容的。你应知道各种程序编制的方法,并能从中
挑选出适合你工作的方法。
33. 从何处获取更多的信息
代码大全——检查表
1. 欢迎进入软件创建世界
1.1. l.3 小 结
● 创建活动是总体设计和系统测试之间承上启下的工作。
● 创建活动主要包括:详细设计、编码、调试和单元测试。
● 关于创建活动的其它称谓有:实现、编程等。
● 创建活动质量对软件质量有潜在影响。
2. 利用隐喻对编程进行更深刻的理解
2.1. 2.4 小 结
● 隐喻仅仅是启发,而不是公式,因此,它们更倾向于比较随便,无拘无束。
● 隐喻通过把软件开发与你所熟知的事情联系在一起,从而使你对其有更深刻的理解。 ● 一些隐喻要好于其它隐喻。
● 把软件创建与建造建筑物类比,表明开发软件前要精心准备,并表明了大规模项目与
小规模项目之间的差别。
● 认为软件开发实践是智能工具箱中的工具进一步表明,每个程序员都有许多自己的工
具,没有任何一种工具是万能的。为每件工作选择合适的工具,是成为一个优秀程序员的首要素质之一。
3. 软件创建的先决条件
3.1. 需求
3.1.1. 需求内容
● 系统的所有输入都定义了吗?包括它们的来源、精度、取值范围和频率?
● 系统所有的输出都定义了吗?包括它们的目标、精度、取值范围、频率和格式? ● 所有的报告格式都定义了吗?
● 所有的硬件与软件接口都定义了吗?
● 所有的通信交界面都定义了吗?包括握手、错误检查以及通信约定?
● 是否从用户的观点出发,定义了所有必要操作的反应时间?
● 是否定义了时间问题,如处理时间、数据传输率以及系统吞吐能力?
● 是否对用户所要求完成的任务部作出了规定?
● 每项任务所需用到和产生的数据都规定了吗?
● 规定保密级别了吗?
● 规定可靠性了吗?包括软件出错的后果、在出错时要保护的至关重要的信息、以及错
误测试和恢复策略。
● 规定所需最大内存了吗?
● 所需最大存储容量规定了吗?
● 对系统的维护性是否作出了规定?包括系统对运行环境、精度、性能以其与其它软件
的接口等方面变化的适应能力规定了吗?
● 是否规定了相互冲突的设计之间的折衷原则,例如,在坚固性与准确性之间如何进行
折衷?
● 是否制定了系统成败的标准?
3.1.2. 关于需求的完善性
● 在开发开始前暂时得不到的信息是什么?是否规定了不够完善的区域?
● 需求定义是否已经完善到了可以成为软件标准的地步?
● 需求中是否有哪一部分令你感到不安?有没有根本不可能实现,而仅仅为了取悦老板
和用户才加进来的内容?
3.1.3. 关于需求的质量
● 需求是否是用户的语言制定的?用户也这样认为吗?
● 需求中是否每一条之间都尽量避免冲突?
● 需求中是否注意了避免规定设计工作?
● 需求在详细程度方面是否保持了一致性;有没有应该更详细些的要求?有没有应该更
简略些的?
● 需求是否明确得可以分为一些独立的可执行部分,而每一部分又都很明了? ● 是否每一条都与问题和答案相关?是否每一条都可以追溯到产生它的环境中?
● 是否每一条需求都可以作为测试依据?是否可以针对每一条进行独立测试以确定是否
满足需求?
● 是否对可能的改动作出了规定?包括每一改动的可能性?
3.2. 结构设计
● 一个好的结构设计应该阐明所有问题。这个表并不是用于指导结构设计的,而只是想
提供一种方法,通过它,你可以估计处于软件食物链顶层的程序员可以从食物中获得多少营养。它可以作为建立自己的检查表的起点。同要求定义检查表的使用一样,如果你正在从事一个非正式的项目,那么其中有些条款是不必考虑的。但如果你正在开
发一个较大的系统,那绝大部分内容都是非常有用的。
● 软件的总体组织形式是否清晰明了?包括对于结构设计的总体评论与描述。 ● 模块定义是否清楚?包括它们的功能及其与其它模块的接口。
● 要求定义中所提出的所有功能,是否有恰当数量的模块覆盖?
● 结构设计是否考虑了可能的更改?
● 是否包括了必要的购买?
● 是否阐明了如何改进重新启用的代码来满足现在的结构设计要求?
● 是否描述并验证了所有主要的数据结构?
● 主要数据结构是否隐含在存取子程序中?
● 规定数据库组织形式和其它内容了吗?
● 是否说明并验证所有关键算法?
● 是否说明验证所有主要目标?
● 说明处理用户输入的策略了吗?
● 说明并验证处理输入/输出的策略了吗?
● 是否定义了用户界面的关键方面?
● 用户界面是否进行了模块化,以使对它所作的改动不会影响程序其它部分
● 是否描述并验证了内存使用估算和内存管理?
● 是否对每一模块给出了存储空间和速度限制?
● 是否说明了字符串处理策略?是否提供了对字符串占用空间的估计?
● 所提供的错误处理策略是不是一致的?
● 是否对错误信息进行了成套化管理以提供一个整洁的用户界面?
● 是否指定了坚固性级别?
● 有没有哪一部分结构设计被过分定义或缺少定义了?它是否明确说明了;
● 是否明确提出了系统目标?
● 整个结构在概念上是否是一致的?
● 机器和使用实现的语言是否顶层设计依赖?
● 给出做出每个重要决定的动机了吗?
● 你作为系统实现者的程序员,对结构设计满意吗?
3.3. 3.9 小 结
● 如果想开发一个高质量的软件,必须自始至终重视质量问题。在开始阶段强调质量往
往比在最后强调质量更为有效。
● 程序员的份内工作之一便是向老板和同事宣传软件的开发过程,包括在编程开始前从
事先决条件准备工作的重要性。
● 如果问题定义工作做得不好,那么在创建阶段,所解决的问题可能并不是用户真正要
解决的问题。
● 如果需求分析工作做得不好,很可能因此而漏掉要解决问题中的重要细节。在创建工
作后更改要求,要比在需求分析阶段进行更改的成本高20到100倍。所以,在开始编程前一定要确认要求定义工作一切正常。
● 在编程前规定好约定,在创建工作结束后再改变代码来满足约定几乎是不可能的。 ● 在创建活动开始之前如果无法完成准备工作,可以尝试在不太稳固的基础上进行创建
活动。
4. 建立子程序的步骤
4.1. 创建子程序
● 是否检查过先决条件已经满足了?
● 定义子程序将要解决的问题了吗?
● 结构设计是否足够清楚,使得你可以给子程序起个好名字?
● 考虑过如何测试子程序了吗?
● 是否从模块化水平或者满足时间和内存要求角度考虑过效率问题?
● 是否查阅过参考书;以寻找有帮助的算法?
● 是否用详尽的 PDL 设计子程序?
● 在必要时,是否在逻辑设计步骤前考虑了数据?
● 是否检查过PDL ,它很容易理解吗?
● 是否注意到了足以使你返回到结构设计阶段的警告(使用了全局数据,更适合其它子
程序的操作,等等)。
● 是否使用了PDL 到代码流程,是否把PDL 作为编码基础并把原有的PDL 转为注释? ● 是否精确地把PDL 翻译成了代码?
● 在作出假设时,验证它们了吗?
● 是从几个设计方案中选择了最好的,还是随意选择了一个方案?
● 是否彻底理解你的代码?它容易理解吗?
4.2. 4.6 小 结
● 要想写好PDL ,首先要用易懂的自然语言,避免拘泥于某种程序语言,其次要在意向
层次上写PDL ,描述设计作什么而不是如何作。
● PDL 到代码流程方法是详细设计的有力工具,而且使得编码非常容易。可以把PDL 直
接翻译成注释,但要注意保证注释是精确而有用的。
● 应该在工作的每一步中都检查子程序,并鼓励同事们检查。这样,可以在投入的资金
和工作努力最少时便发现错误,从而极大降低改错成本。
5. 高质量子程序特点
5.1. 总体问题
● 创建子程序的理由充分吗?
● 如果把一个子程序中的某些部分独立成另一个子程序会更好的话,你这样做了吗? ● 是否用了明显而清楚的动宾词组对过程进行命名?是否是用返回值的描述来命名函
数?
● 子程序的名称是否描述了它做的所有工作?
● 子程序的内聚性是不是很强的功能内聚性?它只做一件工作并做得很好吗?
● 子程序的耦合是不是松散的?两个子程序之间的联系是不是小规模、密切、可见和灵
活的?
● 子程序的长度是不是它的功能和逻辑自然地决定的:而不是由人为标准决定的?
5.2. 防错性编程
● 断言是否用于验证假设?
● 子程序对于非法输入数据进行防护了吗?
● 子程序是否能很好地进行程序终止?
● 子程序是否能很好地处理修改情况?
● 是否不用很麻烦地启用或去掉调试帮助?
● 是否信息隐蔽、松散耦合,以及使用“防火墙”数据检查,以使得它不影响子程序之 ● 外的代码?
● 子程序是否检查返回值?
● 产品代码中的防错性代码是否帮助用户,而不是程序员?
5.3. 参数传递问题
● 形式参数与实际参数匹配吗?
● 子程序中参数的排列合理吗?与相似子程序中的参数排列顺序匹配吗?
● 接口假设说明了吗?
● 子程序中参数个数是不是7个或者更少,
● 是否只传递了结构化变量中另一个子程序用得到的部分?
● 是否用到了每一个输入参数?
● 是否用到了每一个输出参数?
● 如果子程序是一函数,是否在所有情况下它都会返回一个值?
5.4. 5.10 小 结
● 建立子程序的最重要原因是加强可管理性(即降低复杂性),其它原因还有节省空间、 ● 改进正确性、可靠性、可修改性等等。
● 强调强内聚性和松散耦合的首要原因是它们提供了较高层次的抽象性,你可以认为一
个具备这种特性的子程序运行是独立的,这可以使你集中精力完成其它任务。
● 有些情况下,放入子程序而带来巨大收益的操作可能是非常简单的。
● 子程序的名称表明了它的质量,如果名称不好但却是精确的,那么说明它的设计也是
非常令人遗憾的。如果一个子程序的名称既不好又不精确,那它根本就无法告诉你程序作了些什么。无论哪种情况,都说明程序需要改进。
● 防错性编程可以使错误更容易被发现和修复,对最终软件的危害性显著减小。
6. 模块化设计
6.1. 模块的质量
● 模块是否有一个中心目的?
● 模块是否是围绕着一组公用数据进行组织的?
● 模块是否提供了一套相互联系的功能?
● 模块功能是否足够完备,从而使得其它模块不必干预其内部数据?
● 一个模块相对其它模块是否是独立的?它们之间是松散耦合的吗?
● 一个模块的实现细节,对其它模块来说,是隐含的吗?
● 模块的接口是否抽象到了不必关心其功能实现方式的地步?它是作为一个黑盒子来设
计的吗?
● 是否考虑过把模块再划分为单元模块?是否对其进行了充分的再划分工作?
● 如果用不完全支持模块的语言编程,你是否制定了编程约定以使这种语言支持模块?
6.2. 6.5 小 结
● 不管调用哪一个,子程序与模块的不同是很重要的,要认真考虑子程序与模块的设计。 ● 从模块数据是被几个子程序使用的这一角度来说,它与全局数据是相同的,但从可以
使用它的子程序是有限的,而且清楚地知道是哪些子程序可以使用它这一角度来说,模块数据与全局数据又是不同的。因此,可以使用模块数据而没有全局数据的危险。
7. 高级结构设计
7.1. 高层次设计
● 本表给出了在评估设计质量时,通常要考虑一些问题。本表是3.4 节中结构设计检查
表的补充,这个表所考虑的主要是设计质量。3.4 节中的检查表则侧重于结构设计和设计内容。这个表中的某些内容是相互重合的。
● 是否使用了往返设计方法,应从几个方案中选择最好的,而不是首次尝试就确定方案。 ● 每个子程序的设计是否都和与其相关的子程序设计一致?
● 设计中是否对在结构设计层次上发现但并未解决的问题作了充分说明?
● 是否对把程序分解成目标或模块的方法感到满意?
● 是否对把模块分解成子程序的方法感到满意?
● 是否明确定义了子程序的边界?
● 是否是按照相互作用最小的原则定义子程序的?
● 设计是否充分利用了自顶向下和自底向上法?
● 设计是否对问题域要素、用户接口要素、任务管理要素和数据管理要素进行了区分? ● 设计是智力上可管理的吗?
● 设计是低复杂性吗?
● 程序是很容易维护的吗?
● 设计是否将子程序之间的联系保持在最低限度?
● 设计是否为将来程序可能扩展作了准备?
● 子程序是否是设计成可以在其它系统中再使用的?
● 低层次子程序是高扇入的吗?
● 是否绝大多数子程序都是低或中等程度扇出的?
● 设计易于移植到其它环境吗?
● 设计是简练的吗?是不是所有部分都是必要的?
● 设计是成层的吗?
● 设计中是否尽量采用了标准化技术以避免奇特的、难以理解的要素?
7.2. 7.6 小结
● 设计是一个启发的过程。固执地坚持某一种方法只会抑制创造力,从而产生低质量的
程。 坚持设计方法上有一些不屈不挠的精神是有益的,因为这可以迫使你对这种方法进行充 分理解。但是,一定要确信你是在不屈不挠而不是顽固不化。 ● 好的设计是通过迭代逼近得到的:你尝试过的设计方案越多,你最终所确定的设计方
案也越好。
● 结构化设计比较适合于小规模的子程序组合,同时,它对于功能变化可能性比数据大
的问题也是较适用的。
● 面向对象设计更适于子程序与数据的组合,通常在比结构化设计抽象程度更高些的层
次上适用。它尤其适合于数据变动可能性大于功能变动可能性的问题。
● 设计方法仅是一种工具,你对工具运用得好坏决定了你所设计的程序的质量。利用不
好的设计方法,也可能设计出高质量的程序。而即使是好的方法,如果运用不当的话,也只能设计出拙劣的程序。但不管怎样,选择正确的工具更容易设计出高质量的软件。 ● 许多关于设计的丰富而有用的信息都是在本书之外的。在这里所论述的,不过是冰山
的一角而已。
8. 数据生成
8.1. 生成类型
● 程序中是否对每种可能变动的数据都使用了不同的类型?
● 类型名称是面向客观世界中的实体类型而不是面向程序语言中的类型吗?
● 类型名称是否有足够的表现力来帮助说明数据?
● 避免重新定义已定义的类型了吗?
8.2. 说明数据
● 是否使用了模框为简化数据说明工作?并用其来统一说明形式?
● 如果所用的语言支持隐式说明,是否采取了补救措施?
8.3. 初始化
● 是否每个子程序都对输入参数进行了检查以保证其有效性?
● 是否在使用变量的位置附近对其进行初始化的?
● 是否恰当地对计数器和指针进行了初始化?是否在必要时对其进行了重新初始化? ● 在反复执行的代码段中,是否对变量进行了恰当地重新初始化?
● 用编译程序编译代码时是否是无警告的
8.4. 8.6 小结
● 在你的工具箱中需要一张全部数据结构的清单,以便用最合适的方法处理每一种问题。 ● 建立自己的数据类型,以增加程序的可变动性,并使其成为自说明的。
● 数据初始化很容易产生错误,因此应采用本章推荐的技术来避免由意外初始值所产生
的错误。
9. 数据名称
9.1. 通用命名约定
● 变量名称是否完全准确地描述了变量代表的是什么?
● 变量名是否指向是客观世界中的问题,而不是关于这问题的用程序语言表达解决方案? ● 变量名称是否是够长,使得你不必破译它?
● 变量名中如果含有计算限定词的话,是否将其放在最后?
● 是否在名称中用Count 或Index 来代替了Num ?
● 对特殊类型数据的命名
● 循环变量的名称是有意义的吗?(如果循环体较长是嵌套循环的话,应用有含义的名
称来代替i 、j 、k 之类的名称)
● 是否用更富于含义的名称来代替了被叫作"tempotarg" 的临时变量?
● 当逻辑变量的值是"True" 时,它的名称是否充分表达了其含义?
● 是否用前缀或后缀来表明了某些枚举类型是一类的?如用Color 来作ColorRed ,
ColorGreen ,ColorBlue 等枚举类型的前缀。
● 命名常量的名称是否是指向它们代表的实体而不是它们所代表的数值的?
9.2. 命名约定
● 命名约定是否区分了局部、模块和全局数据?
● 命名约定是否对类型名称、命名常量、枚举类型和变量进行了区分?
● 在不支持强化仅供子程序输入参数的语言中,命名约定是否对这类参数进行了标识? ● 命名约定是不是与程序语言的标准约定尽可能地相容?
● 对于语言中没有强制的子程序中仅做输入的参数,是否约定将它标识了?
● 是否对名称进行了格式化以增强程序的可读性?
9.3. 短名称
● 代码是否使用了长名称?(除非有必要使用短名称)
● 是否避免了只省略一个字母的缩写?
● 所有单词保持缩写的连续性了吗?
● 所有的名称都是容易发音的吗?
● 是否避免了会引起错误发音的名称?
● 是否在注释表中对短变量名进行了注释?
● 避免如下这些常见的命名错误了吗
⏹ 易引起误会的名称
⏹ 含义相似的名称
⏹ 仅有一或两个字母不同的名称
⏹ 发音相似的名称
⏹ 使用数字的名称
⏹ 对单词作改写以使其比较短的名称
⏹ 英语中常拼写错的名称
⏹ 标准库子程序或已定义的变量名又定义了
⏹ 完全是随意的名称
⏹ 含有难以辨识字母的名称
⏹ 使用数据时通常要考虑的一些问题
9.4. 9.8 小结
● 恰当的变量名是可读性好的必要条件之一。特殊的变量如循环变量和状态变量要予以
特殊考虑。
● 命名约定可以区分局部、模块和全局变量。同时它还可以区分类型名称,比如可以对
命名常量、枚举类型和变量加以区分。
● 不管你从事的是哪种项目,都应该采用命名约定。所采用的命名约定取决于程序的规 ● 模和从事这一程序的程序员的人数。
● 匈牙利约定是一种非常有效的命名约定,比较适于大规模项目和程序。
● 在现代编程语言中几乎不需要采用缩写技术。
10. 变 量
10.1. 一般数据
● 是否已经使变量的作用域尽可能地小?
● 是否把对变量的使用集中到了一起?
● 控制结构与数据结构是相对应的吗?
● 每个变量是否有且仅有一个功能?
● 每个变量的含义都是明确的吗?是否保证了每个变量都没有隐含的意义?
● 每一个说明过的变量都被用到了吗?
10.2. 全局变量
● 是否是在迫不得已的情况下,才使某些变量成为全局的?
● 命名约定是否对局部、模块和全局变量进行了区分?
● 是否说明了所有全局变量?
● 程序中是否不含有伪全局变量——传往各个子程序的庞大而臃肿的数据结构? ● 是否用存取子程序来代替了全局数据?
● 是把存取子程序和数据组织成模块而不是随意归成一堆的吗?
● 存取子程序的抽象层次是否超过了程序语言实现细节?
● 所有相互有联系的存取子程序,其抽象程度都是一致的吗?
10.3. 10.7 小结
● 尽量减小变量的作用域。把对变量引用集中到一起,应尽量使变量成为局部或模块的,
避免使用全局变量。
● 使每个变量有且仅有一个功能。
● 并不是因为全局数据危险才避免使用它们,之所以避免用它是因为可以用更好的技术
来代替它。
● 如果全局数据确实不可避免的话,应通过存取子程序来对其进行存取操作。存取子程
序不仅具备全局变量和全部功能,而且可以提供更多的功能。
11. 基本数据类型
11.1.1. 常数
● 代码中是否避免了? 奇异数?(常数?)
● 程序中是否采取了措施来防止出现被“0”除错误?
● 类型转换是显式进行的吗?
● 如果在同一个表达式中出现了两种不同类型的变量,是否对表达式按照你的意愿进行
求值?
● 程序中是否避免了混合类型比较?
● 在编译过程中是没有警告的吗?
11.1.2. 整型数
● 使用整型数相除表达式的结果是否与预期的一致?
● 整型数表达式中是否避免了整型数溢出问题?
11.1.3. 浮点数
● 是否避免了数量级相差过大的数之间加减运算?
● 程序中是否系统地采取措施来防止舍入误差问题?
● 程序中是否避免了对浮点数进行相等比较?
11.1.4. 字符和字符串
● 程序中是否避免了常数型字符和字符串?
● 对字符串的引用是否避免了边界错误?
● 若是用c 写成的程序,是否是把字符数组和字符串指针区别对待的?
● 若程序是用C 写成的,是否遵守了把字符串长度说明为CONSTANT+1这一约定? ● 是否用字符数组代替指针?
● 在C 语言程序中,是否把字符由初始化成了NULL 以避免出现无限长字符串? ● 在C 语言程序中,是否用strncpy()代替了strcpy()?并且用了strncat()和strncmp()? 11.1.5. 逻辑变量
● 程序中是否使用了附加的逻辑变量来说明条件判断?
● 程序中是否使用了附加的逻辑变量来简化条件判断?
11.1.6. 枚举类型
● 程序中是否用枚举类型代替了命名常量来改善可读性、可靠性和易改动性? ● 是否用了枚举类型代替逻辑变量以改进可读性和灵活性?
● 在使用了枚举类型的判断中是否检查了无效值?
● 枚举类型的第一个入口是否是保留为无效的?
11.1.7. 命名常量
● 在数据说明中使用的是命名常量吗?
● 是否一致地使用了命名常量,而不是一会儿使用命名常量,一会儿使用数值? 11.1.8. 数组
● 是否所有的下标都在数组界限之内?
● 是否对数组所有的引用都没有发生越界错误?
● 多维数组的下标排列顺序正确吗?
● 在嵌套循环中,作为循环变量的数组下标是正确的吗?是否出现了交叉错误? 11.1.9. 指针
● 是否把指针操作独立在函数中?
● 指针引用是有效的吗?是否误用了悬挂指针?
● 程序中在使用指针之前,是否对它进行了检查;
● 在使用由指针指向的变量之前,是否对其有效性进行了检查?
● 在释放指针之后,是否把它们的值赋成了NULL 或NIL ?
● 为了提高可读性,程序中是否使用了所有需要用的指针变量?
● 链表中的指针是否是按正确的顺序释放的?
● 程序中是否分配了备用内存空间以作为内存溢出时拯救数据和工作努力的降落伞? ● 是否是在万不得已时才使用指针的?
11.2. 11.10 小 结
● 使用各种特定的数据类型意味着需要记住许多种规则。因此要用上面的检查表来确认
你已考虑过了所有常见问题
12. 复杂数据类型
12.1. 12.4 小 结
● 恰当地对数据进行结构化,可以使程序更简单、更容易理解也更容易维护。
● 可以用表来代替复杂的逻辑结构。当你被程序的复杂逻辑迷惑时,应考虑是否可用
查寻表来简化程序。
● 抽象数据类型是降低复杂性的有力武器。它使你可以分层编写程序,而且是从问题
域而不是程序语言细节来编写顶层的程序。
13. 顺序程序语句
13.1. 组织顺序式程序代码
● 把语句间的依赖关系表示得很清楚吗?
● 子程序名是否把依赖关系表示得很清楚?
● 子程序的参数是否把依赖关系表示得很清楚?
● 若代码的依赖关系不清楚,用注释注明了吗?
● 代码能从上读到下吗?
● 变量的出现靠得很近吗? ——从跨度和存活时间来考虑。
● 是否把相关语句组织在一起?
● 是否把相对独立的各相关语句写成子程序了?
13.2. 13.3 小 结
● 组织顺序式代码最好的原则是整理出依赖关系。
● 用合适的子程序名、参数表、注释来标明依赖关系。
● 如果代码没有明显依赖关系,把相关语句组织在一起,特别是使用同一参数的那些语
句。
14. 条件语句
14.1. if-then 语句
● 正常情况路径在代码中流向是否很分明?
● if-then 语句在出现等号时流向是否正确?
● else 语句是否有必要?
● else 语句正确吗?
● if 语句和else 语句正确吗?它们是否弄反了?
● 正常情况是否跟在if 后而非else 后?
● if-then-else
● 复杂的条件是否封装成布尔函数调用了?
● 最常见情况放在前面吗?
● 全部情况都覆盖住了吗?
● if-then-else 语句是最好的选择吗?——用case 语句代替是否更好?
14.2. case 语句
● 各情况的安排次序有含义吗?
● 每种情况对应的操作简单吗?——如需要调用别的子程序。
● case 语句中的变量有实际意义吗?它是为了用case 语句而单纯地定义出来的伪变量
吗?
● 缺省语句的用法是否合法(规范)?
● 用缺省语句检查和报告异常情况吗?
● 在C 语言中,每一情况的结尾用了break 了吗?
14.3. 14.3 小 结
● 注意if 和else 的顺序,特别是在处理好多异常情况时,务必使正常情况流向清晰。 ● 组织好if-then-else 和case 语句中的几种情况,使可读性最好。
● 在case 语句中用缺省值,在if-then-else 中的最后一个else 中获取意外错误。 ● 各种控制结构并不都同样有用,在编码时选用最合适的控制结构。
15. 循环语句
15.1. 15.5 小 结
● 循环很复杂,使其简化有利于阅读。
● 简化循环的技巧有:避免使用怪样子循环、使循环次数最小、使进出口清楚、把内务
代码放在一个地方。
● 循环控制变量不可滥用,应给它起一个有含义的名字并让它只起一个用途。 ● 仔细考虑一下整个循环,保证循环在各种情况和终止条件下都能照常运行。
16. 少见的控制结构
16.1. goto
● goto 是最后的选择吗?用goto 使程序更好读更好维护吗?
● 用goto 是为效率的目的吗?用goto 达到此目的了吗?
● 一个程序是否只用一个goto 呢?
● Goto 只转向前面的程序段而不是转向其后面的程序段吗?(后面指已执行过程序) ● Goto 所转向的标号都有了吗?
16.2. return
● 每个子程序的return 数目是否最少?
● Return 增强了可读性了吗?
16.3. 递归调用
● 用递归调用的代码含使递归结束的语句吗?
● 程序设置了安全计数器来保证递归调用终止了吗?
● 是否只在一个程序中用递归调用?
● 递归调用的深度是否限制在程序堆栈容量可满足的条件下。
● 递归调用是实现程序的最优途径吗?它比循环更简单吗?
16.4. 16.4 小 结
● 有些情况下,goto 是编出易读易维护程序的最好方法。
● 多重return 有时增强了程序的可读性与可维护性,并且防止多重嵌套逻辑,但没必要
只想到怎样用好return 。
● 在问题较简单时,递归调用能把问题很巧妙解决。要慎用递归调用。
17. 常见的控制问题
17.1. 控制结构方面
● 表达式用True 和False 而非1 和0?
● 布尔型表达式的值是否隐含地与False 比较?
● 是否通过定义中间布尔型变量和布尔型函数及用决策表的方法来简化表达式? ● 布尔型表达式是用肯定形式写出来的吗?
● 在C 中,数值、字符,指针是显式与0比较的吗?
● begin 和end 能保持平衡吗?
● 为了使程序看起清楚,需要的地方用begin 和end 对标明了吗?
● 空语句看起来清楚吗?
● 通过诸如重新组合测试条件、转化为if-then -else 或case 语句或把嵌套内代码写成子
程序的方法来简化嵌套语句了吗?
● 如果程序的决定点数超过10,有什么正常理由不重新设计它吗?
17.2. 17.8 小 结
● 使布尔型表达式简单可读性高对代码的质量很有好处。
● 深层嵌套使程序难懂,不过可用相对简单方法避免这样做。
● 结构化编程是一个简化程序的思想,用顺序编程、选择或循环中的一种或几种方法的
组合可编出任何程序。
● 作这种简化程序的思想可提高程序的产量和质量。
● 如果所用语言不支持结构化结构,你能模仿它们。你应该把程序编成某种语言的程序
而不是用某种语言编程的。
● 降低复杂性是编写高质量的代码的关键。
18. 布局和风格
18.1. 简述
● 格式化的本意是要显示代码的逻辑结构吗?
● 格式化的形式能始终一致吗?
● 格式化后使代码易于维护吗?
● 格式化后改进了可读性吗?
18.2. 控制结构
● begin -end 对中代码避免再次缩排了吗?
● 一系列的块结构用空行相互分隔了吗?
● 复杂的表达式格式化后可读性增强了吗?
● 单条语句块始终一致地格式化了吗?
● Case 语句的格式化与其它控制结构格式化相协调吗?
● goto 语句格式化后自己显得更清楚了吗?
18.3. 单条语句
● 把单条语句分成几行,每行都明显地不能作独立行看待了吗?
● 续行有意识地退格了吗?
● 相关语句组对齐了吗?
● 不相关语句组不应对齐,你是这样的吗?
● 每行至多含一条语句吗?
● 每个语句避免副作用了吗?
● 数据定义时相应项对齐了吗?
● 每行至多定义一个数据,是吗?
18.4. 注释
● 注释行与它所对应的代码退同样格数了吗?
● 注释行的形式易修改吗?
18.5. 子程序
● 子程序的参量格式化后各参数易读、易修改、易加注释吗?
● 在C 中是否用新子程序定义方法呢?
● Fortran 中,参数定义是否和局部变量定义分开?
● 文件、模块和程序
● 若语言允许有多个源文件,每个源文件仅含一个模块是吗?
● 一个文件内的各子程序是否用空行清楚隔开?
● 如果一个文件含几个模块,那么每个模块中的子程序是否被组织到被清楚隔开? ● 各子程序是否按字母顺序排列?
18.6. 18.9 小 结
● 布局首先考虑的是去显示程序的逻辑结构。评价这种考虑是否达到目的标准有:准确
性、连续性、可读性、易维护性。好看是第二条标准——比较弱的标准。如果以上几条标准都达到了而且程序也较好看,那么布局一般就成功了。
● 在C 、Pascal 、Basic 中用纯块结构模仿及begin -end 作块边界,这两种布局形式行之
有效。在Ada 中用纯块结构。
● 结构化代码是有其自身目的的,你最好还是用一些约定俗成的布局形式而少来创新,
以保持与别人协调一致。若你的布局形式与约定的不一样,那么很有可能影响你程序
19. 文 档
19.1. 19.2 编程风格作文档
19.1.1. 子程序
● 每一个子程序名都确切地描述了要做什么事吗?
● 每一个子程序详细定义任务吗?
● 程序会从它们的子程序中获益吗?
● 每个子程序的接口处明确吗?
19.1.2. 数据名称
● 类型名的描述足以帮助文件数据说明吗?
● 变量名好吗?
● 变量仅用于命名这个目的吗?
● 循环计算变量能给出更多的信息吗?
● 用枚举类型变量来代替标记或逻辑变量了吗?
● 命名常量没有用来代替数字或字串吗?
● 类型名、枚举类型名、命名常量、局部变量、模块变量和全局变量中的命名规则不同
吗?
19.1.3. 数据组织
● 附加变量在需要时要清零吗?
● 变量的引用彼此间很接近吗?
● 数据结构简化会导致降低其灵活性吗?
● 复杂的数据存取是通过子程序来完成的吗?
19.1.4. 控制
● 正常编码路径清晰吗?
● 相关语句分成一组了吗?
● 相对独立的语句都组成子程序了吗?
● 正常情况跟在IF 后,而不是ELSE 后吗?
● 控制结构简化会降低灵活性吗?
● 像一个定义完好的子程序那样,每个循环执行一个且仅一个功能吗?
● 嵌套层次是最少吗?
● 逻辑表达式用附加的逻辑变量、逻辑函数和功能表简化了吗?
19.1.5. 布局
● 程序布局显示出它的逻辑结构吗?
19.1.6. 设计
● 代码直观吗?它的编写巧妙吗?
● 实现细节可能隐去了吗?
● 程序编写是立足于问题域而不是计算机科学或语言结构域吗?
19.2. 有效的注释技术
19.2.1. 通用部分
● 代码中是否包含了关于程序的大部分信息?
● 是否可以做到随意拿出一段代码便可以立刻理解它的意思?
● 注释是否注释了程序的意图或总结了程序的功用而不是简单地重复代码? ● 是否使用了PDL ——代码流程以减少注释时间?
● 对使人困惑的代码是否进行了重写而不是注释?
● 注释是否已经过时了?
● 注释是清楚正确的吗?
● 注释风格是否使得注释很容易修改?
19.2.2. 语句和段落
● 是否避免了结束行注释?
● 注释的重点是“为什么”而不是“是什么”吗?
● 注释是否提示了后续代码?
● 每个注释都是合理的吗?是否删掉或改进了冗余、自相矛盾的注释?
● 是否注释了令人惊异的代码?
● 是否避免了缩写?
● 主要和次要注释间的区别明显吗?
● 用于错误处理或未说明功能的代码注释了吗?
19.2.3. 数据说明
● 数据说明单元注释了吗?
● 数值数据的取值范围注释了吗?
● 是否注释了代码的含义?
● 对输入数据的限制注释了吗?
● 是否在位层次上对标志进行了注释?
● 是否在说明全局数据的地方对其进行了注释?
● 常数值是否被注释了?或者被用命名常量代替了吗?
19.2.4. 控制结构
● 每一个控制语句都进行注释了吗?
● 冗长或复杂的控制结构进行注释了吗?
19.3. 19.6 小 结
● 是否注释就像是立法。注释得好,是非常值得的,注释得不好,则是浪费时间而且有
害。
● 源代码中应含有关于程序的绝大部分重要信息。只要程序还在运行,那么代码中的注
释便不会丢失或被丢弃。把重要信息加入代码是非常重要的。
● 好的注释是在意愿层次上进行的,它们解释的是“为什么”而不是“是什么”。
● 注释应表达出代码本身表达不了的意思。好的代码应是自说明的。当你对代码进行注
释时,应问一下自己“如何改进代码以使得对其注释是多余的?”,改进代码再加注释以使它更清楚。
20. 编程工具
20.1. 20.7 小 结
● 好的工具使编程更容易。
● 你可以编写大多数你需要的工具。
● 用今天的技术水平能编出高水平的编程工具。
21. 项目大小如何影响创建
21.1. 21.5 小 结
● 在一些小项目中的活动并不能想当然地用于大项目中,你应仔细计划它们。随着项目
增大,创建作用减弱。
● 随着项目的增大,交流方法应简化。各种方法的使用应因时而异。
● 在相同条件下,大项目的生产率比小项目低一些。
● 在相同条件下,大项目的每行错误数比小项目的每行错误数多。
22. 创建管理
22.1. 配置管理
22.1.1. 一般
● 你的软件配置管理计划是否用于帮助程序员,并能将额外开销减至最少?
● 你使用SCM 手段能否避免对项目失控?
● 你所在组是否有修改请求?请示控制可以是非正式方式或正式的方式。
● 你是否能比较正确地估计每次修改的影响?
● 你是否将重要修改视为需求分析不充分的警告?
22.1.2. 工具
● 你是否使用版本控制软件以便配置管理?
● 你是否使用版本控制软件以减少开发时的协调问题?
● 你是否使用制作或其它控制依赖软件使编程更为有效和可靠?
22.1.3. 备份
● 你是否将所有项目材料定期备份?
● 你是否定期将所有项目备份移到安全地点存放?
● 包括源代码、文档、图形和重要注释在内的所有材料都备份了吗?
● 你是否对备份程序进行了测试?
22.2. 22.7 小 结
● 配置管理,适当应用时,可使程序的工作变得更容易进行。
● 你能找到某种方法度量项目的某一方面,这样比根本不度量好。准确的度量是完善的
计划、质量控制和提高开发速度的关键。
● 程序员和管理者都是普通的人,当他们受到礼待时往往干得更好。
● 合适的软件工程评估是软件开发管理最富挑战性的方面,你不妨尝试几种方法,看看
评估的差别,以加深你对项目的认识。
23. 软件质量概述
23.1. 质量保证步骤
● 你是否有项目较为重要的特定质量描述?
● 你是否让其它人明白项目的质量目标?
● 你是否要求不同的外部和内部恃征?
● 你是否考虑过某些特性可能和其它相互矛盾或采用共同促进的方式?
● 你的项目是否需要几种不同的错误检查方法,以便能发现几种类型的错误?
● 你的项目是否包括这样一个计划,以便在软件开发过程中采取措施确保软件质量? ● 你的软件质量是否能按某种方式度量,以确定软件质量是提高或下降了?
● 你在管理中是否知道质量确保在刚开始可能增加费用,但今后的花费能节省? 23.2. 23.6 小 结
● 并不是所有质量保证目标都能实现。确定你想实现的目标,并将其让你所在组的每一
个人知道。
● 各种错误检查方法就其自身来说并不十分有效。仅用一种方法消除错误并非有效。成
功的质量确保计划使用几种不同的方法,以确定不同类型的错误。
● 你可在创建之前或创建过程中使用几种有效的方法以发现错误。你发现错误越早,其
所引起的损失也越小。
● 软件开发领域中的质量保证是面向过程的。软件开发并不包括影响最终产品的重复开
发阶段,所以产品开发过程控制着产品的质量。
● 质量最终是主动的,它要求对系统的资源进行再分配,以便能预防错误而不是花费较
大地去提高质量。
24. 评 审
24.1. 有效检查法
● 你的检查表是否着重将检查员的注意力引向过去常发生错误的地方?
● 是否侧重于缺陷检查而不是纠错?
● 在检查会议之前检查员是否有足够的准备时间?每一位检查员都作好了准备吗? ● 每一位参与者是否都扮演不同的角色?
● 会议是否开得富有成果?
● 会议是否限制在2 小时之内?
● 协调者在指导检查方面接受过特殊的训练吗?
● 在每次检查中,错误类型数据是否都作了收集,以便于你今后制作检查表? ● 是否收集了准备和检查率,以便可以优化将来的准备和检查?
● 每次检查所指定的条款是否都落实了?是由协调员本人还是重新作了检查? ● 管理员是否明白为什么他不参加检查会议?
24.2. 24.4 小 结
● 总的来说,评审在发现错误方面较测试要好。
● 评审侧重于错误发现而不是纠错。
● 评审往往比测试要能发现更多种不同的错误,意味着你应使用评审或调试方法以确保
软件的质量。
● 使用检查表、准备良好的分工、连续的处理以便最大限制地提高检错能力,检查往往
较普查能发现更多的错误。
● 普查和代码阅读是检查的候补方法。代码阅读可有效地利用每个人的时间。
25. 单元测试
25.1. 测试用例
● 每个子程序的要求是否有自己的测试用例?
● 子程序结构的每个部分是否都有自己的测试用例?
● 程序中每一行代码都是否至少被一个测试用例所测试过?这是否是由通过计算测试 ● 每一行代码所需的最少用例来确定的?
● 所有定义——使用数据流路径是否被至少一个测试用例所测试过?
● 代码是否被看起来不大正确的数据流模式所检查过?比如定义一定义,定义一退出,
和定义一失效?
● 是否使用常见错误表以便编写测试用例来发现过去常出现的错误?
● 是否所有的简单边界都得到了测试:最大、最小或易混淆边界?
● 是否所有复合边界都得到了测试?
● 是否对各种错误类型的数据都进行了测试?
● 是否所有典型的中间数都得到了测试?
● 是否对最小正常配置进行了测试?
● 是否对最大正常配置进行了测试?
● 是否测试了和旧数据的兼容性?是否所有保留下来的硬件、操作系统旧的版本,以及
其它软件旧版本的接口都得到了测试?
● 测试用例是否便于手工检查?
25.2. 25.8 小 结
● 开发者使用测试是完成测试策略的一个重要组成部分。模块和系统测试也是重要的,
但它们不在本书的讨论之内。
● 各种测试方法的混合使用也是搞好软件质量程序的一个方面。高质量开发方法,包括
有条理地对需求和设计进行测试。检查和普查在发现错误方面也至少同调试方法一样有效,并且可发现各种类型的错误。
● 通过采用基本测试、数据流分析和错误猜测你可生成许多测试用例。
● 错误倾向于集中在少数几个子程序中。找到这几个子程序,重新设计和重写它们。 ● 测试数据往往比受测试的代码含有更多的错误。这种找错方法只会浪费时间而不会 ● 提高代码质量,测试数据错误往往比编程错误更令人讨厌,这可通过仔细地进行测试
来避免。
● 最终提高测试质量的最好方法是,有条理的度量它,利用你的知识提高测试质量。
26. 调 试
26.1. 调式
● 发现错误的方法:
⏹
⏹
⏹
⏹
⏹
⏹
⏹
⏹
⏹ 使用所有数据建立假设 求精产生错误的测试用例 通过不同的方法再生错误 产生更多的数据以生成更多的假设 使用否定测试结果 提出尽可能多的假设 缩小可疑代码区 检查最近作过修改的代码 扩展可疑代码区
⏹ 逐步集成
⏹ 怀疑以前出过错的子程序
⏹ 耐心检查
⏹ 为迅速的草率的调试设定最大时间
⏹ 检查一般错误
⏹ 使用交谈调试法
⏹ 中断对问题的思考
● 改正错误的方法:
⏹ 理解问题的实质
⏹ 理解整个程序
⏹ 确诊错误
⏹ 放松情绪
⏹ 保存初始源代码
⏹ 修改错误而不是修改症状
⏹ 仅为某种原因修改代码
⏹ 一次作一个修改
⏹ 检查你的工作,验证修改
⏹ 寻找相似错误
● 调试的一般方法
⏹ 你是否将调试作为学习有关程序、错误、代码质量及解决问题方法的一次机会? ⏹ 你是否避免使用试错法,或避免采用迷信的方法?
⏹ 你是否认为错误是由于你的过错?
⏹ 你是否使用科学方法以固定间歇性错误?
⏹ 你是否使用科学方法发现错误?
⏹ 你是否每次使用不同的方法,而不是只用一种方法发现错误?
⏹ 你是否验证了修改信息是正确的?
⏹ 你是否利用了警告信息、执行剖析程序、建立脚手架方法、交互式调试等? 26.2. 26.6 小 结
● 调试是软件开发中的问题。最好的方法是使用本书的其他方法避免一开始就发生错误。 ● 你仍需花费时间提高你的调试技能——因为最好和最差的调试效率差别为10:1,甚至
更大。
● 注意你的编译警告信息,并及时改正编译所提示的错误。如果你忽略了明显错误的话,
你就难以改正微妙的错误。
● 发现和改正某错误的关键是,有一个有条理的方法,注重你的测试,这样每次你都能
向前迈进一步。
● 在你改正一个问题之前应理解问题的本质,对错误源的任意猜测和修改只会使你的程
序更为糟糕。
● 调试是软件开发的有力工具。你应能利用调试工具。记住你还应好好动脑筋。
27. 系统集成
27.1. 递增集成法
27.1.1. 递增集成策略
● 这种方法是否能辨别出程序或模块的最佳集成顺序?
● 集成次序是否和结构次序相同,以致于结果程序只能在适当的时候被集成? ● 这种方法易于故障诊断吗?
● 这种方法是否需要最少的搭架程序?
● 这种策略确实比其它方法好吗?
● 各部分间的接口是否已被指定好了?
● (指定接口不是集成的任务,但证明是否已这样做是集成的任务)
27.2. 改进的公布法
● 在工程完成前你已经有几个版本了吗?最后的功能将能否实现?
● 第一次释放是否包括了程序的核心,其它程序是否可以从这上面发展?
● 第一次释放是否能尽快地扩大?
● 第一次释放是否可用,至少在最低限度上可用?
● 在工程目标并不十分明确时,你是否尽量努力确定每个改进阶段的内容? ● 每一个释放都要加进重要内容吗?
● 执行过程是否有灵活性以响应用户反馈,如果没有灵活性,这种不灵活性是有意安排
的吗?
● 每一个释放是否被看成一个小工程,有自己的编码和测试,在某些情况下,还有自己
的分析和设计。
● 结构是否足够开放以支持几次释放后的许多变化。
● 你是否考虑以对现有程序进行修改为基础的改进的公布过程。
● 你是否要用到每一阶段的结果去改进、估计和计划下一阶段?
27.3. 27.5 小 结
● 集成的计划安排影响程序模块设计、编码和调试的次序,它也影响你是否能顺利地测
试、调试它们。
● 递增集成法有许多形式,除非工程非常琐碎,这些形式中的任一种都比分段集成好。 ● 一个好的递增方法更接近Lewis 和Clark 探险的方法,而不是Amundsen 的方法,此
种(前者)方法假定路程中的一部分没有图标,灵活性是十分重要的,它的计划可以
应付意想不到的情况。
● 改进公布法在工程中可将能工作的软件尽快送到用户手中,而传统方法必须使用户等
到所有东西都送到后才能工作。
● 改进公布法是对双方都有益的一种方法,对于用户,它可以使他们早日确定工程是否
成功,在工程管理上对工程进行有了清楚的了解;对于开发,它可以使人们知道在提高代码质量方面应该做些什么
28. 代码调整策略
28.1. 28.5 小 结
● 性能是软件整体质量的一方面,可能并非最具影响力的一方面。调整代码只是程序性
能的一部分,恐怕亦非最重要的。程序结构、具体设计数据结构和算法选择,对于程序规模和执行速度的影响要比产生代码本身的工作大。
● 优化设计关键要用到定量测量。重要的是找到确实能改善程序性能的段落,在此就要
强调优化的作用。
● 大多程序主要的时间花在小部分代码上。在执行程序及测试前不容易知道是哪部分代
码。
● 为写出优良程序最好是写出清晰易读和易修改的源程序来。
29. 代码调试技术
29.1. 29.8 小 结
● 优化的结果因不同语言、编译器和环境而大不相同。如果不对特定的优化进行测试, ● 没人知道结果会怎样。
● 头一次优化未必是最好的。即使你找到了一个好的,试着寻找更完美的。
● 代码调试有点像原子能,是一个有争议的题目,有些人认为它对可靠性和可维护性是
有害的,根本不该使用。有些人认为只要注意,它是有益的。如果你决定要使用本章描述的技术,千万小心从事。
30. 软件优化
30.1. 修改程序
● 每次改变都是按修改策略的吗?
● 变动是否彻底地检查过了?
● 软件是否作了重测试,确保修改未使性能变坏?
● 修改是否提高了程序的内在质量?
● 是否尽可能将程序拆成子程序从而模块化?
● 是否尽可能地减少了全局变量?
● 是否改进了程序风格?
● 如果为了共享代码,必须作些改动,你是否考虑过共享代码在高层程序上而非在低层
程序上?
● 如此改动后,是否为以后的修改创造便利?
30.2. 30.4 小 结
● 初始开发中,升级是传统开发方法的一个典型事例,并且随着新方法的使用,它可能
变得更为突出。
● 使用升级,软件质量可能是提高或恶化了。软件升级的基本规则其内部质量应随时间
的流逝而提高。虽然在扩充维护阶段,软件退化是能避免的,在创建中软件退化就不妙了。
● 开发对提高你的程序质量是一个最好的机会,如果你想有所收益,你应很好的利用每
一次机会。
● 当你修改程序时创建新的子程序的方法,对程序的质量有很大的影响。创建新的子程
序是你用这种方式:你应用品化结构的粗糙边缘而不要再向其上打洞。
31. 个人性格
31.1. 31.10 小结
● 你的个人性格直接影响你编写计算机程序的能力。
● 最有明显作用的性格为:谦虚、好奇心、诚实、创造性和纪律,还有文明的“懒惰”。 ● 高级程序员的发展和生成与天才并无多大联系,任何事情都和个人的发展有关。 ● 令人吃惊的是,小聪明、经验、坚持和欲望既可帮助你也能妨碍你。
● 许多程序员不主动去吸收新信息和新技术,而是靠偶然地上获得一些新信息,如果你
抽出少量时间学习别人的编程经验,过一段时间后,你将在你的同行中脱颖而出。 ● 好的性格对养成良好习惯有很大影响,为了成为一位高水平的程序员,你应养成良好
的习惯,其余的就会随之而来。
32. 软件开发方法的有关问题
32.1. 32.9 小 结
● 程序编制的一个主要目的复杂性管理。
● 编程过程对最终产品的影响比人们想象中的要大。
● 合作程序开发要求各成员之间进行广泛的交流,其次才是和计算机的交流,而个人则
主要是和你自己而不是和计算机交流。
● 当被乱用时,约定好比是雪上加霜,而使用得当的话,约定可增加开发环境的有用结
构,并有助于管理复杂性和交流。
● 面向问题而不是解答的编程有助于对复杂性的管理。
● 注意警告信息是相当重要的,因为编程几乎是纯智力活动。
● 在开发过程重复越多,产品质量也就越高。
● 武断的方法和高质量软件开发是不相容的。你应知道各种程序编制的方法,并能从中
挑选出适合你工作的方法。
33. 从何处获取更多的信息