核对表1:需求 (p42)
这张需求核对表包含了一系列的问题——问问自己项目的需求工作做得如何。本书并不会告诉你如何做出好的需求分析,所以列表里面也不会有这样的问题。在开始构建之前,用这份列表做一次“心智健全”检查,看看你的地基到底有多坚固——用“需求里氏震级”来衡量。
并不是核对表中所有的问题都适用于你的项目。如果你做的是一个非正式项目,那么你会发现有些东西根本就不需要考虑。你还会发现一些问题你需要考虑,但不需要做出正式的回答。如果你在做一个大型的、正式的项目,你也许就要逐条考虑了。
1.1.针对功能需求
* 是否详细定义了系统的全部输入,包括其来源、精度、取值范围、出现频率等?
* 是否详细定义了系统的全部输出,包括目的地、精度、取值范围、出现频率、格式等?
* 是否详细定义了所有输出格式(Web页面、报表,等等)?
* 是否详细定义了所有硬件及软件的外部接口?
* 是否详细定义了全部外部通信接口,包括握手协议、纠错协议、通信协议等?
* 是否列出了用户想要做的全部事情?
* 是否详细定义了每个任务所用的数据,以及每个任务得到的数据?
1.2.针对非功能需求(质量需求)
* 是否为全部必要的操作,从用户的视角,详细描述了期望响应时间?
* 是否详细描述了其他与计时有关的考虑,例如处理时间、数据传输率、系统吞吐量?
* 是否详细定义了安全级别?
* 是否详细定义了可靠性,包括软件失灵的后果、发生故障时需要保护的至关重要的信息、错误检测与恢复的策略等?
* 是否详细定义了机器内存和剩余磁盘空间的最小值?
* 是否详细定义了系统的可维护性,包括适应特定功能的变更、操作环境的变更、与其他软件的接口的变更能力?
* 是否包含对“成功”的定义?“失败”的定义呢?
1.3.需求的质量
* 需求是用用户的语言书写的吗?用户也这么认为吗?
* 每条需求都不与其他需求冲突吗?
* 是否详细定义了相互竞争的特性之间的权衡—— 例如,健壮性与正确性之 间的权衡?
* 是否避免在需求中规定设计(方案)?
* 需求是否在详细程度上保持相当一致的水平?有些需求应该更详细地描述吗?有些需求应该更粗略地描述吗?
* 需求是否足够清晰,即使转交给一个独立的小组去构建,他们也能理解吗?开发者也这么想吗?
* 每个条款都与待解决的问题及其解决方案相关吗?能从每个条款上溯到它在问题域中对应的根源吗?
* 是否每条需求都是可测试的?是否可能进行独立的测试,以检验满不满足各项需求?
* 是否详细描述了所有可能的对需求的改动,包括各项改动的可能性?
1.4.需求的完备性
* 对于在开始开发之前无法获得的信息,是否详细描述了信息不完全的区域?
* 需求的完备度是否能达到这种程度:如果产品满足所有需求,那么它就是可接受的?
* 你对全部需求都感到很舒服吗?你是否已经去掉了那些不可能实现的需求——那些只是为了安抚客户和老板的东西?
核对表2:架构 (p54)
以下是一份问题列表,优秀的架构应该关注这些问题。这张核对表的意图并非用做一份有关如何做架构的完全指南,而是作为一种实用的评估手段,用来评估软件食物链到了程序员这一头还有多少营养成分。这张核对表可用做你自己的核对表的出发点。就像“需求”的核对表一样,如果你从事的是非止式项目,那么你会发现其中某些条款甚至都不用去想。如果你从事的是更大型的项目,那么大多数条款都会是很有用的。
2.1.针对各架构主题
* 程序的整体组织结构是否清晰?是否包含一个良好的架构全局观(及其理由)?
* 是否明确定义了主要的构造块(包括每个构造块的职责范围及与其他构造块的接口)?
* 是否明显涵盖了 “需求” 中列出的所有功能(每个功能对应的构造块不太多也不太少)?
* 是否描述并论证了那些最关键的类?
* 是否描述并论证了数据设计?
* 是否详细定义了数据库的组织结构和内容?
* 是否指出了所用关键的业务规则,并描述其对系统的影响?
* 是否描述了用户界面设计的策略?
* 是否将用户界面模块化,使界面的变更不会影响程序其余部分?
* 是否描述并论证了处理i / o 的策略?
* 是否估算了稀缺资源(如线程、数据库连接、句柄、网络带宽等)的使用量,是否描述并论证了资源管理的策略?
* 是否描述了架构的安全需求?
* 架构是否为每个类、每个子系统、或每个功能域(functionality area)提出空间与时间预算?
* 架构是否描述了如何达到可伸缩性?
* 架构是否关注互操作性?
* 是否描述了国际化/本地化的策略?
* 是否提供了一套内聚的错误处理策略?
* 是否规定了容错的办法(如果需要)?
* 是否证实了系统各个部分的技术可行性?
* 是否详细描述了过度工程(overengineering)的方法?
* 是否包含了必要的“买vs造” 的决策?
* 架构是否描述了如何加工被复用的代码,使之符合其他架构目标?
* 是否将架构设计得能够适应很可能出现的变更?
2.2.架构的总体质量
* 架构是否解决了全部需求?
* 有没有哪个部分是“过度架构/overarchitected ”或“欠架构/underarchitected ” ?否明确宣布了在这方面的预期指标?
* 整个架构是否在概念上协调一致?
* 顶层设计是否独立于用作实现它的机器和语言?
* 是否说明了所有主要的决策的动机?
* 你,作为一名实现该系统的程序员,是否对这个架构感觉良好?
核对表3:前期准备 (p59)
* 你是否辨明了自己所从事的软件的类型,并对所用的开发方法做出相应的剪裁?
* 是否充分明确地定义了需求?而且需求足够稳定,能开始构建了? (详见需求核对表。)
* 是否充分明确地定义了架构,以便开始构建? (详见架构核对表。)
* 是否已经指出你的(当前)项目中独有的风险(以避免构建活动面临不必要的风险)?
核对表4:主要的构建实践 (p69)
4.1.编码
* 你有没有确定,多少设计工作将要预先进行,多少设计工作在键盘上进行 (在编写代码的同时)?
* 你有没有规定诸如名称、注释、代码格式等“编码约定” ?
* 你有没有规定特定的由软件架构确定的编码实践,比如如何处理错误条件、如何处理安全性事项、对于类接口有哪些约定、可重用的代码遵循哪些标准、在编码时考虑多少性能因素等?
* 你有没有找到自己在技术浪潮中的位置,并相应调整自己的措施?如果必要,你是否知道如何“深入一种语言去编程” ,而不受限于语言(仅仅“在一种语言上编程”)?
4.2.团队工作
* 你有没有定义一套集成工序—— 即,你有没有定义一套特定的步骤,规定程序员在把代码check in(签入)到主源码(代码库)中之前,必须履行这些步骤?
* 程序员是结对编程、还是独自编程,或者这二者的某种组合?
4.3.质置保证
* 程序员在编写代码之前,是否先为之编写测试用例?
* 程序员会为自己的代码写单元测试吗(无论先写还是后写)?
* 程序员在check in代码之前,会用调试器单步跟踪整个代码流程吗?
* 程序员在checkin代码之前,是否进行集成测试(integration-test) ?
* 程序员会复审(review )或检查别人的代码吗?
4.4.工具
* 你是否选用了某种版本控制工具?
* 你是否选定了一种语言,以及语言的版本或编译器版本?
* 你是否选择了某个编程框架(framework,如J2EE或Microsoft.NET) ,或者明确地决定不使用编程框架?
* 你是否决定允许使用非标准的语言特性?
* 你是否选定并拥有了其他将要用到的工具—— 编辑器、重构工具、调试器、测试框架(test framework) 、语法检查器等?
核对表5:软件构造中的设计 (p122)
5.1.设计实践
* 你已经做过多次迭代,并且从众多尝试结果中选择最佳的一种,而不是简单选择第一次尝试的结果吗?
* 你尝试用多种方案来分解系统,以确定最佳方案吗?
* 你同时用自下而上和自上而下的方法来解决设计问题吗?
* 为了解决某些特定的问题,你对系统中的风险部分或者不熟悉的部分创建过原型、写出数量最少的可抛弃的代码吗?
* 你的设计方案被其他人检查了吗(无论正式与否)?
* 你一直在展开设计,直到实施细节跃然纸上了吗?
* 你用某种适当的技术---比如说Wiki、电子邮件、挂图、数码照片、UML、CRC卡或者在代码写注释——来保留设计成果吗?
5.2.设计目标
* 你的设计是否充分地处理了由系统架构层定义出并且推迟确定的事项?
* 你的设计被划分为层次吗?
* 你对把这一程序分解成为子程序、包和类的方式感到满意吗?
* 你把对这个类分解成为子程序的方法感到满意吗?
* 类与类之间的交互关系是否已设计为最小化了?
核对表6:类的质量 (p157)
6.1.抽象数据类型
* 你是否把程序中的类都看做是抽象数据类型了?是否从这个角度评估它们的接口了?
6.2.抽象
* 类是否有一个中心目的?
* 类的命名是否恰当?其名字是否表达了其中心目的?
* 类的接口是否展现了一致的抽象?
* 类的接口是否能让人清楚明白地知道该如何用它?
* 类的接口是否足够抽象,使你能不必顾虑它是如何实现其服务的?你能把类看做黑盒子吗?
* 类提供的服务是否足够完整,能让其他类无须动用其内部数据?
* 是否已从类中除去无关信息?
* 是否考虑过把类进一步分解为组件类?是否已尽可能将其分解?
* 在修改类时是否维持了其接口的完整性?
6.3.封装
* 是否把类的成员的可访问性降到最小?
* 是否避免暴露类中的数据成员?
* 在编程语言所许可的范围内,类是否己尽可能地对其他的类隐藏了自己的实现细节?
* 类是否避免对其使用者,包括其派生类会如何使用它做了假设?
* 类是否不依赖于其他类?它是松散耦合的吗?
6.4.继承
* 继承是否只用来建立“是一个/is a ” 的关系?也就是说,派生类是否遵循了 LSP (Liskov替换原则)?
* 类的文档中是否记述了其继承策略?
* 派生类是否避免了 “覆盖” 不可覆盖的方法?
* 是否把公用的接口、数据和行为都放到尽可能高的继承层次中了?
* 继承层次是否很浅?
* 基类中所有的数据成员是否都被定义为private而非protected的了?
6.5.跟实现相关的其他问题
* 类中是否只有大约七个或更少的数据成员?
* 是否把类直接或间接调用其他类的子程序的数量减到最少了?
* 类是否只在绝对必要时才与其他的类相互协作?
* 是否在构造函数中初始化了所有的数据成员?
* 除非拥有经过测量的、创建浅层复本的理由,类是否都被设计为当作深层复本使用?
6.6.与语言相关的问题
* 你是否研究过所用编程语言里和类相关的各种特有问题?
核对表7:高质量的子程序 (p185)
7.1.大局事项
* 创建子程序的理由充分吗?
* 一个子程序中所有适于单独提出的部分是不是已经被提出到单独的子程序中了?
* 过程的名字中是否用了强烈、清晰的“动词+宾语” 词组?函数的名字是否描述了其返回值?
* 子程序的名字是否描述了它所做的全部事情?
* 是否给常用的操作建立了命名规则?
* 子程序是否具有强烈的功能上的内聚性?即它是否做且只做一件事,并且把它做得很好?
* 子程序之间是否有较松的耦合?子程序与其他子程序之间的连接是否是小的(small)、明确的(intimate)、可见的(viaible)和灵活的(flexible) ?
* 子程序的长度是否是由其功能和逻辑自然确定,而非遵循任何人为的编码标准?
7.2.参数传递事宜
* 整体来看,子程序的参数表是否表现出一种具有整体性且一致的接口抽象?
* 子程序参数的排列顺序是否合理?是否与类似的子程序的参数排列顺序相符?
* 接口假定是否已在文档中说明?
* 子程序的参数个数是否没超过7个?
* 是否用到了每一个输入参数?
* 是否用到了每一个输出参数?
* 子程序是否避免了把输入参数用做工作变量?
* 如果子程序是一个函数,那么它是否在所有可能的情况下都能返回一个合法的值?
核对表8:防御式编程 (p211)
8.1.一般事宜
* 子程序是否保护自己免遭有害输入数据的破坏?
* 你用断言来说明编程假定吗?其中包括了前条件和后条件吗?
* 断言是否只是用来说明从不应该发生的情况?
* 你是否在架构或高层设计中规定了一组特定的错误处理技术?
* 你是否在架构或高层设计中规定了是让错误处理更倾向于健壮性还是正确性?
* 你是否建立了隔栏来遏制错误可能造成的破坏?是否减少了其他需要关注错误处理的代码的数量?
* 代码中用到辅助调试的代码了吗?
* 如果需要启用或禁用添加的辅助助手的话,是否无须大动干戈?
* 在防御式编程时引入的代码量是否适宜—— 既不过多,也不过少?
* 你在开发阶段是否采用了进攻式编程来使错误难以被忽视?
8.2.异常
* 你在项目中定义了一套标准化的异常处理方案吗?
* 是否考虑过异常之外的其他替代方案?
* 如果可能的话,是否在局部处理了错误而不是把它当成一个异常抛到外部?
* 代码中是否避免了在构造函数和析构函数中抛出异常?
* 所有的异常是否都与抛出它们的子程序处于同一抽象层次上?
* 每个异常是否都包含了关于异常发生的所有背景信息?
* 代码中是否没有使用空的catch语句?(或者如果使用空的catch语句确实很合适,那么明确说明了吗?)
8.3.安全事宜
* 检查有害输入数据的代码是否也检查了故意的缓冲区溢出、SQL注入、HTML注入、整数溢出以及其他恶意输入数据?
* 是否检查了所有的错误返回码?
* 是否捕获了所有的异常?
* 出错消息中是否避免出现有助于攻击者攻入系统所需的信息?
核对表9:伪代码编程过程 (p233)
* 是否检查过已满足所有的先决条件?
* 定义好这个类要解决的问题了吗?
* 高层次的设计是否足够清晰?能给这个类和其中的每一个子程序起一个好的名字吗?
* 考虑过该如何测试这个类及其中每一个子程序了吗?
* 关于效率的问题,你主要从稳定的接口和可读的实现这两个角度考虑吗?还是主要从满足资源和速度的预期目标的角度考虑过呢?
* 在标准函数库或其他代码库中寻找过可用的子程序或者组件了吗?
* 在参考书籍中查找过有用的算法了吗?
* 是否用详尽的伪代码设计好每一个子程序?
* 你在脑海里检查过伪代码吗?这些伪代码容易理解吗?
* 关注过那些可能会让你重返设计的警告信息了吗? (比如说关于全局数据的使用、一些看上去更适合放在另一个类或子程序中的操作等。)
* 是否把伪代码正确地翻译成代码了?
* 你反复使用伪代码编程过程了吗?有没有根据需要把一些子程序拆分成更小的子程序?
* 在做出假定(assum ptions)的时候有没有对它们加以说明?
* 已经删除掉那些冗余的注释了吗?
* 你是否采取了几次迭代中最好的那个结果?还是在第一次迭代之后就停止了?
* 你完全理解你的代码了吗?这些代码是否容易理解?
核对表10:使用数据的一般事项 (p257)
10.1.初始化变量
* 每一个子程序都检查其输入参数的正确性吗?
* 变量声明位置靠近变量第一次使用的位置吗?
* 尽可能地在声明变量的同时初始化变量吗?
* 如果无法同时声明和初始化变量,有没有在靠近第一次使用变量的位置声明变量?
* 计数器和累加器经过了适当的初始化吗?如果需要再一次使用,之前重新初始化了吗?
* 适当地重新初始化“需要重复执行的代码里的变量” 了吗?
* 代码在通过编译器编译的时候是不是没有鳘告信息? (你启用了所有可用的警告选项了吗?)
* 如果你用的语言允许隐式声明,你为由此可能引发的问题做好补偿措施了吗?
10.2.使用数据的其他事项
* 如果可能,所有变量都被定义为具有最小的作用域吗?
* 各变量的引用点都尽可能集中在一起吗?对同一变量的两次相邻引用,或者变量的整个生命期都这样做了吗?
* 控制结构符合数据类型吗?
* 所有声明的变量都用到了吗?
* 变量都在合适的时间绑定了吗?—— 也就是说,你有意识地在晚期绑定所带来的灵活性和增加的复杂度之间做出平衡了吗?
* 每个变量都有且仅有一项用途吗?
* 每个变量的含义都很明确且没有隐含含义吗?
核对表11:变量命名 (p288)
11.1.命名的一般注意事项
* 名字完整并准确地表达了变量所代表的含义吗?
* 名字反映了现实世界的问题而不是编程语言方案吗?
* 名字足够长,可以让你无须苦苦思索吗?
* 如果有计算值限定符,它被放在名字的最后吗?
* 名字中用Count或者Index来代替Num了吗?
11.2.为特定类型的数据命名
* 循环下标的名字有意义吗(如果循环的长度超出了一两行代码或者出现了嵌套循环,那么就应该是i、j或者k以外的其他名字)?
* 所有的“临时”变量都重新命以更有意义的名字了吗?
* 当布尔变量的值为真时,变量名能准确表达其含义吗?
* 枚举类型的名字中含有能够表示其类别的前缀或后缀了吗?例如,把Color_用于Color_Red,Color_Green,Color_Blue 等了吗?
* 具名常量是根据它所代表的抽象实体而不是它所代表的数字来命名的吗?
11.3.命名规则
* 规则能够区分局部数据、类的数据和全局数据吗?
* 规则能够区分类型名、具名常量、枚举类型和变量名吗?
* 规则能够在编译器不强制检测只读参数的语言里标识出子程序中的输入参数吗?
* 规则尽可能地与语言的标准规则兼容吗?
* 名字为了可读性而加以格式化吗?
11.4.短名字
* 代码用了长名字吗(除非有必要使用短名字)?
* 是否避免只为了省一个字符而缩写名字的情况?
* 所有单词的缩写方式都一致吗?
* 名字能够读出来吗?
* 避免使用容易被看错或者读错的名字吗?
* 在缩写对照表里对短名字做出说明吗?
11.5.常见命名问题:你避免使用……
* ……容易让人误解的名字吗?
* ……有相近含义的名字吗?
* ……只有一两个字符不同的名字吗?
* ……发音相近的名字吗?
* ……包含数字的名字吗?
* ……为了缩短而故意拼错的名字吗?
* ……英语中经常拼错的名字吗?
* ……与标准库子程序名或者预定义变量名冲突的名字吗?
* ……过于随意的名字吗?
* ……含有难读的字符的名字吗?
核对表12:基本数据类型 (p316)
12.1.数值概论
* 代码中避免使用神秘数值吗?
* 代码考虑了除零错误吗?
* 类型转换很明显吗?
* 如果在一条语句中存在两个不同类型的变量,那么这条语句会像你期望的那样求值吗?
* 代码避免了混合类型比较吗?
* 程序编译时没有警告信息吗?
12.2.整数
* 使用整数除法的表达式能按预期的那样工作吗?
* 整数表达式避免整数溢出问题吗?
12.3.浮点数
* 代码避免了对数量级相差巨大的数字做加减运算吗?
* 代码系统地阻止了舍入错误的发生吗?
* 代码避免对浮点数做等量比较吗?
12.4.字符和字符串
* 代码避免使用神秘字符和神秘字符串吗?
* 使用字符串时避免了 off-by-one错误吗?
* C代码把字符串指针和字符数组区别对待了吗?
* C代码遵循了把字符串声明为 CONSTANT + 1 长度的规则了吗?
* c代码在适当的时候用字符数组来代替指针了吗?
* C代码把字符串初始化为NULL来避免无终端的字符串了吗?
* C代码用 strncpy ()代替 strcpy ()吗? strncat ()和 strncmp {)呢?
12.5.布尔变量
* 程序用额外的布尔变量来说明条件判断了吗?
* 程序用额外的布尔变量来简化条件判断了吗?
12.6.枚举类型
* 程序用枚举类型而非具名常量来提高可读性、可靠性和可修改性吗?
* 当变量的用法不能仅用true和false表示的时候,程序用枚举类型来取代布尔变量吗?
* 针对枚举类型的测试检测了非法数值吗?
* 把枚举类型的第一项条目保留为“非法的”了吗?
12.7.具名常霣
* 程序用具名常量而不是神秘数值来声明数据和表示循环界限吗?
* 具名常量的使用一致吗?—— 没有在有些位置使用具名常量又在其他位置使用文字量?
12.8.数组
* 所有的数组下标都没有超出数组边界吗?
* 数组引用没有出现off-by-one错误吗?
* 所有多维数组的下标的顺序都正确吗?
* 在嵌套循环里,把正确的变量用于数组下标来避免循环下标串话了吗?
核对表13:使用不常见数据类型的注意事项 (p343)
13.1.结构体
* 你使用结构体而不是使用单纯的变量来组织和操作相关的数据吗?
* 你考虑创建一个类来代替使用结构体吗?
13.2.全局数据
* 所有的变量是否都是局部的或者是类范围的?除非绝对有必要才是全局的?
* 变量的命名规则能把局部数据、类数据和全局数据区分开吗?
* 你对所有的全局变量都加以文档说明吗?
* 避免使用伪全局数据,即被四处传递且包含有杂乱数据的巨大对象吗?
* 用访问器子程序来取代全局数据吗?
* 把访问器子程序和数据组织到类里面吗?
* 访问器子程序提供了一个在底层数据类型实现之上的抽象层吗?
* 所有相关的访问器子程序都位于同一抽象层之上吗?
13.3.指针
* 把指针操作隔离在子程序里吗?
* 指针引用合法吗?或者说指针有可能成为空悬指针吗?
* 代码在使用指针之前检查它的有效性吗?
* 在使用指针所指向的变量之前检查其有效性吗?
* 指针用完后被设置为空值吗?
* 就可读性而言,代码用了所有需要使用的指针变量吗?
* 链表中的指针是按正确的顺序加以释放吗?
* 程序分配了一片保留的内存后备区域,以便在耗尽内存的时候能够优雅地退出吗?
* 是不是在没有其他方法可用的情况下最终才使用指针的?_
核对表14:组织直线型代码 (p353)
* 代码使得语句之间的依赖关系变得明显吗?
* 子程序的名字使得依赖关系变得明显吗?
* 子程序的参数使得依赖关系变得明显吗?
* 如果依赖关系不明确,你是否用注释进行了说明?
* 你用“内务管理变量”(housekeeping variables)来检查代码中关键位置的顺序依赖关系了吗?
* 代码容易按照自上而下的顺序阅读吗?
* 相关的语句被组织在一起吗?
* 把相对独立的语句组放进各自的子程序里吗?
核对表15:使用条件语句 (p365)
15.1.if-then 语句
* 代码的正常路径清晰吗?
* if-then测试对等量分支的处理方式正确吗?
* 使用了 else子句并加以说明吗?
* else子句用得对吗?
* 用对了if和else子句,即没把它们用反吗?
* 需要执行的正常情况是位于i f而不是else子句里吗?
15.2.if-then-else-if 语旬串
* 把复杂的判断封装到布尔函数调用里了吗?
* 先判断最常见的情况了吗?
* 判断包含所有的情况吗?
* if-then-else-if是最佳的实现吗?比case语句还要好吗?
15.3.case语句
* case子句排序得有意义吗?
* 每种情况的操作简单吗?必要的时候调用了其他子程序吗?
* case语句检测的是一个真实的变量,而不是一个只为了滥用case语句而刻意制造变量吗?
* 默认子句用得合法吗?
* 用默认子句来检测和报告意料之外的情况了吗?
* 在C、C++或者Java里,每一个case的末尾都有一个break吗?
核对表16:循环 (p388)
16.1.循环的选择和创建
* 在合适的情况下用while循环取代for循环了吗?
* 循环是由内到外创建的吗?
16.2.进入循环
* 是从循环头部进入的循环吗?
* 初始化代码是直接位于循环前面吗?
* 循环是无限循环或者事件循环吗?它的结构是否清晰?
* 避免使用像for i = 1 to 9999这样的代码吗?
* 如果这是一个C++、C或Java中的for循环,那么把循环头留给循环控制代码了吗?
16.3.循环的内部
* 循环是否用了 “ {}” 或其等价物来括上循环体,以防止因修改不当而出错吗?
* 循环体内有内容吗?它是非空的吗?
* 把内务处理集中地放在循环开始或者循环结束处了吗?
* 循环像定义良好的子程序那样只执行了一件操作吗?
* 循环短得足以一目了然吗?
* 循环的嵌套层次不多于3层吗?
* 把长循环的内容提取成单独的子程序吗?
* 如果循环很长,那么它非常清晰吗?
16.4.循环下标
* 如果这是一个for循环,那么其中的代码有没有随意修改循环下标值?
* 是否把重要的循环下标值保存在另外的变量里,而不是在循环体外使用该循环下标?
* 循环下标是序数类型(整数)或者枚举类型 —— 而不是浮点类型 —— 吗?
* 循环下标的名字有意义吗?
* 循环避免了下标串话问题吗?
16.5.退出循环
* 循环在所有可能的条件下都能终止吗?
* 如果你建立了某种安全计数器标准,循环使用安全计数器了吗?
* 循环的退出条件清晰吗?
* 如果使用了break或者continue,那么它们用对了吗?
核对表17:不常见的控制结构 (p410)
17.1.return
* 每一个子程序都仅在有必要的时候才使用return吗?
* 使用return有助于增强可读性吗?
17.2.递归
* 递归子程序中包含了停止递归的代码吗?
* 子程序用安全计数器来确保该子程序能停下来吗?
* 递归只位于一个子程序里面吗?
* 子程序的递归深度处于程序栈容量可以满足的限度内吗?
* 递归是实现子程序的最佳方法吗?它要好于简单的迭代吗?
17.3.goto
* 是否只有在万不得已的时候才使用goto ?如果用了goto ,是否仅仅是出于增强可读性和可维护性呢?
* 如果是出于效率因素而使用的goto ,那么对这种效率上的提升做出衡量并且加以说明了吗?
* 一个子程序里最多只用了一个goto标号吗?
* 所有的goto都向前跳转,而不是向后跳转吗?
* 所有的goto标号都用到了吗?
核对表18:表驱动法 (p429)
* 你考虑过把表驱动法作为复杂逻辑的替换方案吗?
* 你考虑过把表驱动法作为复杂继承结构的替换方案吗?
* 你考虑过把表数据存储在外部并在运行期间读入,以便在不修改代码的情况下就可以改变这些数据吗?
* 如果无法用一种简单的数组索引(像age示例中那样)去访问表,那么你把计算访问键值的功能提取成单独的子程序,而不是在代码中重复地计算键值吗?
核对表19:控制结构相关事宜 (p459)
* 表达式中用的是true和false ,而不是1和0吗?
* 布尔值和true以及false做比较是隐式进行的吗?
* 对数值做比较是显式进行的吗?
* 有没有通过增加新的布尔变量、使用布尔函数和决策表来简化表达式?
* 布尔表达式是用肯定形式表达的吗?
* 括号配对吗?
* 在需要用括号来明确的地方都使用了括号吗?
* 把逻辑表达式全括起来了吗?
* 判断是按照数轴顺序编写的吗?
* 如果适当的话,Java中的判断用的是a.equals(b)方式,而没有用a==b方式吗?
* 空语句表述得明显吗?
* 用重新判断部分条件、转换成if-then-else或者case语句、把嵌套代码提成单独的子程序、换用一种更面向对象的设计或者其他的改进方法来简化嵌套语句了吗?
* 如果一个子程序的决策点超过10个,那么能提出不重新设计的理由吗?
核对表20:质量保证计划 (p476)
* 是否确定出对项目至关重要的特定质量特性了 ?
* 是否让其他人意识到项目的质量目标了?
* 是否能够区分质量的外在特性和内在特性?
* 是否考虑过某些特性与其他特性相互制约或相互促进的具体方式?
* 在软件开发的每一个阶段,项目是否要求针对不同错误类型使用不同的错误检测技术?
* 项目计划中是否有计划有步骤地保证了软件在开发各阶段的质量?
* 是否使用了某种质量评估方法,并由此确定质量是改善了还是下降了?
* 管理层是否能理解为了质量保证在前期消耗额外成本,目的就是在项目后期减少成本?
核对表21:有效的结对编程 (p484)
* 是否己经有一个编码规范,以便让程序员始终把精力集中到编程,而不是编码风格的讨论上?
* 结对的双方是否都积极地参与?
* 是否避免了滥用结对编程,而是选择那些能够从中获得好处的工作进行结对编程?
* 是否有规律地对人员和工作任务进行轮换?
* 结对组合是否在开发速度和个性方面互相匹配?
* 是否有一个组长专注于项目管理以及与项目外其他人的沟通?
核对表22:有效的详查 (p491)
* 你是否有一个核对表,能让评论员将注意力集中于曾经发生过问题的领域? ’
* 你是否专注于找出错误,而不是修正它们?
* 你是否考虑制定某些视角或者场景,以帮助评论员在准备工作的时候集中注意力?
* 你是否给予评论员足够的时间在详查会议之前进行准备,是否每一个人都做了准备?
* 是否每一个参与者都扮演一个明确的角色—— 主持人、评论员及记录员等?
* 会议是否以某种高效的速度进行?
* 会议是否限制在两个小时以内?
* 是否所有详查会议的参与者都接受了如何进行详查的针对性培训,是否主持人接受了有关主持技巧方面的针对性培训?
* 是否将每次详查所发现的错误数据都收集起来,使你能调整本组织以后使用的核对表?
* 是否收集了准备速度和详查速度方面的数据,以便你去优化以后的准备和详查工作?
* 是否每次详查中被指派下去的活动都被正确跟进了,无论是通过主持人自己还是一次重新详查?
* 管理层是否理解他们不应该参与详查会议?
* 是否有一个用于保证修正正确性的跟进计划?
核对表23:测试用例 (p532)
* 类和子程序所对应的每一项需求是否都有相应的测试用例?
* 类和子程序所对应的每一个设计元素是否都有相应的测试用例?
* 每行代码是否被至少一个测试用例所测试?你是否通过计算测试到每行代码所需的最少测试用例数量来验证这一点?
* 所有巳定义-已使用路径是否至少被一个测试用例测试过了?
* 是否测试过那些不太可能正确的数据流模式,例如已定义-已定义、已定义-已退出以及已定义-己销毁?
* 是否有一张常见错误列表,并据此编写测试用例以检测过去经常出现的错误?
* 所有的简单边界是否都已经测试过了:最大、最小以及off-by-one?
* 是否测试了组合边界——即,多个输入数据的组合导致输出数据过小或者过大?
* 测试用例是否检查了数据类型错误,例如一个薪水记账程序里的雇员数量是负数?
* 是否测试了那些中规中矩的典型数值?
* 是否测试了最小正常形式?
* 是否测试了最大正常形式?
* 是否检查了与旧数据的兼容性?以及是否对旧硬件、旧操作系统版本以及其他旧版本软件的接口进行了测试?
* 测试用例是否容易手工检验?
核对表24:关于调试的建议 (p559 )
24.1.寻找缺陷的方法
* 使用所有可用数据来构造你的假设。
* 不断提炼产生错误的测试用例。
* 在自己的单元测试族中测试代码。
* 借助可以获得的任何工具。
* 用不同的方式重现错误。
* 通过产生更多的数据来构造更多的假设。
* 利用证伪假设的测试结果。
* 用头脑风暴的方式找出可能的假设。
* 在桌上放一个记事本,把需要尝试的事情列出来。
* 缩小被怀疑有问题的代码区域。
* 对之前出现过问题的类和子程序保持警惕。
* 检查最近修改的代码。
* 扩展被怀疑有问题的代码区域。
* 采用增量集成。
* 检查常见的缺陷。
* 和其他人一起讨论你的问题。
* 抛开问题休息一下。
* 在使用快速肮脏调试法的时候,要设置一个时间上限。
* 列出所有的蛮力调试方法,逐条应用。
24.2.解决语法错误的方法
* 不要太信任编译器信息中给出的行号。
* 不要太信任编译器信息。
* 不要太信任编译器所给出的第二条出错信息。
* 分而治之,各个击破。
* 使用具有语法分析功能的编辑器来找出位置错误的注释和引号。
24.3.修正缺陷的方法
* 在动手之前先理解程序。
* 理解整个程序而非具体问题。
* 验证对错误的分析。
* 放松一下。
* 要保存最初的源代码。
* 治本,而非治标。
* 只有当理由充分的时候才去修改代码。
* 一次只做一个改动。
* 检查自己所做的修订。
* 添加单元测试来暴露代码中的缺陷。
* 找出类似的缺陷。
24.4.调试的一般方法
* 你是否会把调试看做是能让你更好地理解程序、错误、代码质量和解决问题方法的良机?
* 你是否会避免采用随机尝试查找错误或迷信式的调试力法?
* 你是否假设错误是你自己造成的?
* 你是否使用了科学的方法将间歇性的错误稳定下来?
* 你是否使用了科学的方法来寻找缺陷?
* 你在寻找缺陷的时候会使用多种不同的方法么?还是每次都是用相同的方法?
* 你会验证你的修改是否正确么?
* 你会在调试中使用编译器警告信息、执行性能分析、利用测试框架和交互式调试方法么?
核对表25:重构的理由 (p570)
* 代码重复。
* 子程序太长。
* 循环太长或者嵌套太深。
* 类的内聚性太差。
* 类的接口的抽象层次不一致。
* 参数表中参数太多。
* 类的内部修改往往局限于某个部分。
* 需要对多个类进行并行修改。
* 对继承体系的并行修改。
* 需要对多个case语句进行并行修改。
* 相关的数据项只是被放在一起,没有组织到类中。
* 成员函数更多地使用了其他类的功能,而非自身类的。
* 过于依赖基本数据类型。
* 一个类不做什么事。
* 一连串传递流浪数据的子程序。
* 中间人对象什么也不干。
* 某个类同其他类关系过于密切。
* 子程序的命名太差。
* 数据成员被设置为公用。
* 派生类仅仅使用了基类的一小部分成员函数。
* 用注释来掩饰拙劣的代码。
* 使用了全局变量。
* 在子程序调用前使用设置代码,调用后使用收尾代码。
* 程序包含的某些代码似乎在将来某个时候才会被用到。
核对表26:重构总结 (p577)
26.1.数据级的重构
* 用具名常量来代替神秘数值。
* 用更明确或更具信息量的名字来重命名变量。
* 将表达式内联化。
* 用函数来代替表达式。
* 引入中间变量。
* 将多用途变量转换为多个单一用途变量。
* 使用局部变量实现局部用途而不是使用参数。
* 将基础数据类型转化为类。
* 将一组类型码转化为类或是枚举类型。
* 将一组类型码转化为含派生类的类。
* 将数组转化为对象。
* 封装群集。
* 用数据类替代传统记录。
26.2.语句级的重构
* 分解布尔表达式。
* 将复杂的的布尔表达式转换为命名精确的布尔函数。
* 将条件语句中不同部分中的重复代码合并。
* 使用break或return而不是循环控制变量。
* 在嵌套的if-then-else语句中一旦知道结果就立刻退出,而不是仅仅赋一个返回值。
* 用多态来代替条件语句(尤其是重复的case语句)。
* 创建并使用空对象代替对空值的检测。
26.3.子程序级的重构
* 提取子程序。
* 将子程序代码内联化。
* 将冗长的子程序转化为类。
* 用简单的算法替代复杂算法。
* 增加参数。
* 减少参数。
* 将查询操作同修改操作区分开来^
* 合并功能相似的子程序,并用参数来区分他们。
* 通过传递不同的参数使子程序体现不同的功能。
* 传递整个对象而非特定成员。
* 传递特定成员而非整个对象。
* 封装向下转型操作。
26.3.类实现的重构
* 将值对象改为引用对象。
* 将引用对象改为值对象。
* 用数据初始化来代替虚函数。
* 改变成员函数或数据的位置。
* 将特定代码提出生成派生类。
* 将相似的代码合并起来放到基类中。
26.4.类接口的重构
* 将某成员子程序放到另一个类中。
* 将一个类转化成两个。
* 删除某个类。
* 隐藏委托关系。
* 去掉中间人。
* 用委托代替继承。
* 用继承代替委托。
* 引入外部子程序。
* 引入扩展类。
* 封装暴露在外的成员变量。
* 对不能修改的成员去掉Set ()函数
* 隐藏在类的外部不会使用的成员函数。
* 封装不会用到的成员函数。
* 如果基类和派生类的代码实现相似,将二者合并。
26.5.系统级的重构
* 为无法控制的数据创建明确的索引源。
* 将单向类联系改为双向类联系。
* 将双向的类联系改为单向类联系。
* 使用工厂函数而非简单的构造函数。
* 用异常代替错误代码,或者反其道而行之。
核对表27:安全的重构 (p584)
* 每一改变都是系统改变策略的一部分么?
* 在重构之前,你保存了初始代码了么?
* 你是否保持较小的重构步伐?
* 你是否同一时间只处理一项重构?
* 在重构时你是否把要做的事情一条条列了出来?
* 你是否设置了一个停车场,把你在重构时所想到的任何东西记下来?
* 在每次重构后你会重新测试么?
* 如果所做的修改非常复杂,或者影响到了关键代码,你会重新检查这些修改么?
* 你是否考虑过特定重构的风险,并以此来调整你的重构方法?
* 你所做的修改是提升还是降低了程序的内在质量?
* 你是否避免了将重构作为先写后改的代名词,或者作为拒绝重写拙劣代码的托词?
核对表28:代码调整策略 (p607)
28.1.程序整体性能
* 你是否考虑通过修改需求来提高性能?
* 你是否考虑通过修改程序的设计来提高性能?
* 你是否考虑通过修改类的设计来提高性能?
* 你是否考虑过减少程序同操作系统的交互从而提高性能?
* 是否考虑过避免I/ O操作以提高性能?
* 是否考虑使用编译型语言替代解释型语言以提高性能?
* 是否考虑过使用编译器优化选项来提高性能?
* 是否考虑过使用不同的硬件来提高性能?
* 是否仅仅将代码调整看做是解决问题的最后一招?
28.2.代码调整方法
* 在开始调整代码之前,程序是完全正确的么?
* 在调整之前是否测量过性能瓶颈在什么地方?
* 是否记录了每一次修改所产生的效果?
* 如果没有带来预期的性能提高,你是否放弃了所做的代码调整改变?
* 你是否对每一个性能瓶颈进行不止一次的修改尝试 —— 也就是说,你是在反复进行代码调整么?
核对表29:代码调整方法 (p642)
29.1.同时改善代码执行速度和规模
* 用查询表替换复杂逻辑。
* 合并循环。
* 使用整型变量而非浮点变量。
* 在编译时初始化数据。
* 使用正确的常量类型。
* 预先计算结果。
* 删除公共子表达式。
* 将关键子程序代码转化为某种低级语言代码。
29.2.仅仅提高代码执行速度
* 在知道答案后就停止执行判断。
* 根据各种情况的出现频率对case语句和if-then-else串排序。
* 比较相似逻辑结构的性能。
* 使用惰性求
* 将循环中的if判断转到外部。
* 展开循环。
* 将循环内部所做的工作减少到最低限度。
* 在查找循环中使用哨兵。
* 把执行最为频繁的循环放在嵌套循环的最里面。
* 减轻内层循环的强度。
* 将多维数组改为一维数组。
* 最大限度减少数组索引。
* 为数据类型扩充索引。
* 对频繁使用的值进行缓存。
* 利用代数恒等式。
* 降低逻辑和数学表达式的强度。
* 注意系统调用。
* 用内联子程序重写代码。
核对表30:配置管理 (p669)
30.1.概要
* 你的软件配置管理计划是否用于帮助程序员,并能将额外负担降至最低?
* 你的软件配置管理方法是否避免了对项目的过度控制?
* 你是否将一些变更请求聚成一组?无论采用非正式的方法(如创建一份未决更改的列表)还是更加系统的方法(如设立变更控制委员会)。
* 你系统地评估了每一项提交的更改对成本、计划和质量的影响吗?
* 你是否把重大的变更看做是需求分析还不够完备的警报信号?
30.2.工具
* 你用版本控制软件来促进配置管理吗?
* 你用版本控制软件来减少团队工作中的协调问题吗?
30.3.备份
* 你定期地备份项目中的所有资料吗?
* 你定期地把项目备份数据转移到off-site storage里了吗?
* 所有的资料,包括源代码、文档、图表和重要的笔记都得到备份了吗?
* 你测试过备份与恢复的过程吗?
核对表31:集成 (p707)
31.1.集成策略
* 该策略是否指明了集成子系统、类、子程序时应该采用的最优顺序?
* 集成的顺序是否与构建顺序协调,以便在适当的时候准备好供集成的类
* 该策略是否易于诊断缺陷?
* 该策略是否使脚手架最少?
* 所选的策略是否好于其他方式?
* 组件之间的接口是否有明确定义? (定义接口不是集成的任务,但要验证这些接口的定义是否明确。)
31.2.Daily build与冒烟测试
* 项目是否经常build —— 理想情况下,每天build一次 —— 以支持增量集成?
* 每次build后是否都运行冒烟测试,让你知道这个build能否工作?
* 你是否已使build和冒烟测试自动进行?
* 开发人员是否频繁地check in他们的代码----------------------"两次check in之间最多间隔一两天?
* 冒烟测试是否与代码同步更新,随代码发展而发展?
* 破坏build是罕见事件吗?
* 是否在有压力的情况下,也对软件进行build和冒烟测试?
核对表32:编程工具 (p724)
* 你有一套有效的IDE吗?
* 你的IDE集成了:源代码控制、build/测试/除错工具,以及其他有用的功能吗?
* 你有能自动进行常用的重构操作的工具吗?
* 你是否使用版本控制工具,对源代码、内容、需求、设计、项目计划及其他的项目构件进行管理?
* 如果你正面对超大型的项目,你是否使用了数据字典或者其他“包含系统中使用的各个类的权威描述”的中央知识库。
* 当可以用到代码库时,你是否考虑用它来代替“编写定制代码” ?
* 你是否充分利用了交互式除错器?
* 你是否使用make或其他“依赖关系控制软件”,用来高效并可靠地build程序?
* 你的测试环境包含有自动化的测试框架、自动测试生成器、覆盖率监视器、系统扰动器、diff工具,以及缺陷跟踪软件吗?
* 你有没有制造过定制工具—— 能满足特定项目的需求的那种,特别是能自动执行重复任务的工具?
* 总而言之,你的工作环境有没有从“充足的工具支援”中获益?
核对表33:布局 (p773)
33.1.一般问题
* 格式化主要是为了展现代码的逻辑结构吗?
* 你的布局方案能统一地运用吗?
* 你的布局方案能让代码易于维护吗?
* 你的布局方案是否有利于代码的可读性?
33.2.控制结构的布局
* 你的代码中避免begin-end对或{}的双重缩进了吗?
* 相邻的块之间用空行分隔了吗?
* 对复杂表达式格式化时考虑到可读性吗?
* 对只有一条语句的块的布局始终如一吗?
* case语句与其他控制结构的格式化保持一致了吗?
* 对goto语句的格式化是否让其显眼了呢?
33.3.单条语句的布局
* 为逻辑表达式、数组下标和子程序参数的可读性而使用空格了吗?
* 不完整的语句在行末是以明显有错的方式结束吗?
* 后续行按照标准数目缩进了吗?
* 每行顶多只有一条语句吗?
* 所写的每个语句都没有副作用吗?
* 每行顶多只声明一个数据吗?
33.4.注释的布局
* 注释与其所注释的代码的缩进量相同吗?
* 注释的风格便于维护吗?
33.5.子程序的布局
* 你对每个子程序参数的格式化方式便于看懂、修改、注释吗?
* 采用空行分隔子程序的各部分了吗?
33.6.类、文件和程序的布局
* 多数类和文件之间是一一对应的关系吗?
* 如果文件内有多个类,各类中的子程序按类分组了吗?各类都清楚标识了吗?
* 文件中的子程序用空行清楚地分开了吗?
* 在没有更好的组织形式的场合,所有子程序都按字母顺序排列了吗?
核对表34:自说明代码 (p780)
34.1.类
* 你的类接口体现出某种一致的抽象吗?
* 你的类名有意义吗,能表明其中心意图吗?
* 你的类接口对十如何使用该类显而易见吗?
* 你的类接口能抽象到不需考虑其实现过程吗?能把类看成是黑盒吗?
34.2.子程序
* 你的每个子程序名都能准确地指示该子程序确切干些什么吗?
* 你的各子程序的任务明确吗?
* 若各子程序中自成一体后更有用,你都将其各自独立出来了吗?
* 每个子程序的接口都清晰明了吗?
34.3.数据名
* 类型名描述有助于说明数据声明吗?
* 你的变量名有意义吗?
* 变量只用在其名字所代表意义的场合吗?
* 你的循环变量名能给出更多信息,而不是i 、j 、k之类的吗?
* 你用了名字有意义的枚举类型,而非临时拼凑的标识或者布尔变量吗?
* 用具名常量代替神秘数值或者字符串了吗?
* 你的命名规范能区分类型名、枚举类型、具名常量、局部变量、类变量以及全局变量吗?
34.4.数据组织
* 你根据编程清晰的需要,使用了额外变量来提高清晰度吗?
* 你对某变量的引用集中吗?
* 数据类型简化到了最低复杂度吗?
* 你是通过抽象访问子程序(抽象数据类型)来访问复杂数据吗?
34.5.控制
* 代码中的正常执行路径很清晰吗?
* 相关语句放在一起了吗?
* 相对独立的语句组打包为子程序了吗?
* 正常情况的处理位于if语句之后,而非在else子句中吗?
* 控制结构简单明了,以使复杂度最低吗?
* 每个循环完成且仅完成一个功能,是像定义良好的子程序那么做吗?
* 嵌套层次是最少吗?
* 逻辑表达式通过额外添加布尔变量、布尔函数和功能表简化了吗?
34.6.布局
* 程序的布局能表现出其逻辑结构吗?
34.7.设计
* 代码直截了当吗?是不是避免了自作聪明或新花样?
* 实现细节尽可能隐藏了吗?
* 程序是尽可能采用问题领域的术语,而非按照计算机科学或者编程语言的术语编写的吗?
核对表35:好的注释技术 (p816)
35.1.一般问题
* 别人拿起你的代码就能立刻明白其意吗?
* 你的注释是在解释代码用意,或概括代码在做什么,而非简单重复代码吗?
* 采用了伪代码编程法来减少注释时间吗?
* 是重写有玄机的代码,而非为其做注释吗?
* 你的注释能否同代码一起更新?
* 注释清楚正确吗?
* 你的注释风格便于修改注释吗?
35.2.语句和段落
* 代码避免用行尾注释了吗?
* 注释是着力说明为什么而非怎么样吗?
* 注释为将要阅读代码的人们做好准备了吗?
* 每个注释都其用处吗?删掉抑或改进了多余的、无关紧要的或随意的注释没有?
* 是否注释了代码的非常规之处?
* 避免使用缩略语了吗?
* 主次注释区别明显吗?
* 含错代码和未公开的代码特性有注释吗?
35.3.数据声明
* 对数据声明的注释说明了数值单位吗?
* 数值数据的取值范围注释出来了吗?
* 注释出了编码含义吗?
* 对输入数据的限制有注释吗?
* 对位标志做注释了吗?
* 在各全局变量声明的地方对其做注释了吗?
* 各全局变量是通过命名规范、注释(或者两者兼用)来标识其意义吗?
* 神秘数值是否以具名常量或变量代替,而非只是标注之?
35.4.控制结构
* 控制语句都注释了吗?
* 冗长或者复杂的控制结构结尾处有注释吗?抑或可能的话,简化之从而省去注释了吗?
35.5.子程序
* 各子程序的意图都注释出了吗?
* 子程序的其他有关情况(诸如输入输出数据、接口假设、局限性、纠错、全局效果和算法来源)都注释出来了吗?
35.6.文件、类和程序
* 程序有简短的文档(就像在“以书本为范例”中说明的那样)给出程序组织的概述吗?
* 每个文件的用途都有说明吗?
* 作者姓名、email及电话号码在代码清单中都有吗?
####