写在前面
经典之作《代码大全》可谓是程序员的圣经,是一本十分完整的软件构建手册,有着高达9.3的豆瓣评分,一直有众多大佬极力推荐。看完这本书不仅让我对软件构建更清晰明了,对如何解决问题、如何构建人生亦有诸多启发,受益匪浅!软件构建不只是写代码(构建),按开发顺序大致可分为:问题定义->需求->架构->构建->系统测试->将来的改进几个过程,越靠前的层次对软件工程影响越大,修改时需要的成本也越高。下文是对该书所有章节要点及核对表(CheckList)的系统整理。
第1部分 打好基础
第1章 欢迎进入软件构建的世界
要点
★ 软件构建是软件开发的核心活动;构建活动是每个项目中唯一一项必不可少的工作。
★ 软件构建的主要活动包括:详细设计,编码,调试,集成,开发者测试(包括单元测试和集成测试)。
★ 构建也常被称作"编码"和"编程"。
★ 构建活动的质量对软件的质量有着实质性的影响。
★ 最后,你对"如何进行构建"的理解程度,决定了你这名程序员的优秀程度。
第2章 用隐喻来更充分地理解软件开发
要点
★ 隐喻是启示而不是算法.因此它们往往有一点随意(sloopy)。
★ 隐喻把软件开发过程与其他你熟悉的活动联系在一起,帮助你更好的理解。
★ 有些隐喻比其他一些隐喻更贴切。
★ 通过把软件的构建过程比作是房屋的建设过程,我们可以发现,仔细的准备是必要的,而大型项目和小型项目之间也是有差别的。
★ 通过把软件开发中的实践比作是智慧工具箱中的工具,我们又发现,每位程序员都有许多工具,但并不存在任何一个能适用于所有工作的工具,因地制宜的选择正确工具是成为能有效编程的程序员的关键。
★ 不同的隐喻彼此并不排斥,应当使用对你最有益处的某种隐喻组合。
第3章 三思而后行:前期准备
架构的典型组成部分 Typical Architectual Components
程序组织 Program Organization
- 应该明确定义各个构造快的责任。每个构造快应该负责一个区域的事情,并且对其他构造块负责的区域知道的越少越好
主要的类 Major Classes
数据设计 Data Design
- 数据通常只应该由一个子系统或一个类直接访问
业务规则 Business Rules
用户界面设计 User Interface Design
- 架构应该模块化,以便在替换为新用户界面时不影响业务规则和程序的输出部分
资源管理 Resource Management
安全性 Security
性能 Performance
可伸缩性 Scalability
互用性 Interoperability
国际化/本地化 Internationalization/Localization
输入输出 Input/Output
错误处理 Error Processing
容错性 Fault Tolerance
架构的可行性 Architectural Feasibility
过度工程 Overengineering
关于『买』还是『造』的决策 Buy-vs.-Build Decisions
关于复用的决策 Reuse Decisions
变更策略 Change Strategy
架构的总体质量 General Architectural Quality
要点
★ 构建活动的准备工作的根本目标在于降低风险。要确认你的准备活动是在降低风险,而非增加风险。
★ 如果你想开发高质量的软件,软件开发过程必须由始至终关注质量。在项目初期关注质量,对产品质量的正面影响比在项目末期关注质量的影响要大。
★ 程序员的一部分工作是教育老板和合作者,告诉他们软件开发过程,包括在开始编程之前进行充分准备的重要性。
★ 你所从事的软件项目的类型对构建活动的前期准备有重大影响——许多项目应该是高度迭代式的,某些应该是序列式的。
★ 如果没有明确的问题定义,那么你可能会在构建期间解决错误的问题。
★ 如果没有做完良好的需求分析工作,你可能没能察觉待解决的问题的重要细节。如果需求变更发生在构建之后的阶段,其代价是“在项目早期更改需求”的 20至 100 倍。因此在开始编程之前,你要确认“需求”已经到位了。
★ 如果没有做完良好的架构设计,你可能会在构建期间用错误的方法解决正确的问题。架构变更的代价随着“为错误的架构编写的代码数量”增加而增加,因此,也要确认“架构”已经到位了。
★ 理解项目的前期准备所采用的方法,并相应地选择构建方法。
第4章 关键的“构建”决策
要点
★ 每种编程语言都有其优点和弱点。要知道你使用的语言的明确优点和弱点。
★ 在开始编程之前,做好一些约定(convention)。“改变代码使之符合这些约定”是近乎不可能的。
★ “构建的实践方法”的种类比任何单个项目能用到的要多。有意识地选择最适合你的项目的实践方法。
★ 问问你自己,你采用的编程实践是对你所用的编程语言的正确响应,还是受它的控制?请记得“深入一种语言去编程”,不要仅“在一种语言上编程”。
★ 你在技术浪潮中的位置决定了哪种方法是有效的——甚至是可能用到的。确定你在技术浪潮中的位置,并相应调整计划和预期目标。
第2部分 创建高质量的代码
第5章 软件构建中的设计
理想的设计特征 Desirable Characteristics of a Design
最小的复杂度(Minimal complexity)。应该做出简单且易于理解的设计。如果你的设计方案不能让你在专注于程序的一部分时安心地忽视其他部分的话,这一设计就没有什么作用
易于维护(Ease of maintenance)。设计出能自明(self-explanatory)的系统
松散耦合(Loose coupling)。设计时让程序的各个组成部分之间关联最小。通过应用类接口中的合理抽象、封装性及信息隐藏等原则,设计出相互关联尽可能最少的类。减少关联也就减少了集成、测试与维护时的工作量
可拓展性(Extensibility)。能够增强系统的功能而无须破坏其底层结构
可重用性(Reusability)。所设计的系统的组成部分能在其他系统中重复使用
高扇入(High fan-in)。让大量的类使用某个给定的类。这意味着设计出的系统很好地利用了在较低层次上的工具类(utility classes)
低扇出(Low fan-out)。让一个类里少量或适中地使用其他的类
可移植性(Portability)。应该这样设计系统,使它能很方便地移植到其他环境中
精简性(Leanness)。设计出的系统没有多余的部分
层次性(Stratification)。尽量保持系统各个分解层的层次性,使你能在任意的层面上观察系统,并得到某种具有一致性的看法
标准技术(Standard techniques)。一个系统所依赖的外来的、古怪的东西越多,别人在第一次想要理解它的时候就越是头疼。要尽量用标准化的、常用的方法,让整个系统给人一种熟悉的感觉
设计的层次 Levels of Design
一个程序中的设计层次。系统首先被组织为子系统;子系统被进一步分解为类,然后类又被分解为子程序和数据。每个子程序的内部也需要进行设计。
第 1 层:软件系统 Software System
- 先从子系统或者包(package)这些类的更高组织层次来思考会更有益处
第 2 层:分解为子系统或包 Division into Subsystems or Packages
- 识别出主要的子系统,确定如何把程序分为主要的子系统,并定义清楚允许各子系统如何使用其他子系统
- 在每个子系统的内部可能要用到不同的设计方法——请对系统中的每一部分选用最恰当的方法
- 不同子系统之间相互通信的规则特别重要。如果所有的子系统都能同其他子系统通信,你就完全失去了把它们分开所带来的好处。应该通过限制子系统之间的通信来让每个子系统更有存在意义
- 为了让自从之间的连接简单易懂且易于维护,就要尽量简化子系统之间的交互关系
- 一个很好的基本原则是使用无环图(acyclic graph)来进行系统层设计
第 3 层:分解为类 Division into Classes
- 识别出系统中所有的类
- 当定义子系统中的类时,也就同时定义了这些类与系统的其余部分打交道的细节
第 4 层:分解成子程序 Division into Routines
- 把每个类细分为子程序
- 完整地定义出类内部的子程序,常常会有助于更好地理解类的接口,反过来,这又有助于对类的接口进行进一步的修改
第 5 层:子程序内部的设计 Internal Routine Design
- 为每个子程序布置详细的功能
- 编写伪代码、选择算法、组织子程序内部的代码块,以及用编程语言编写代码
设计构造块:启发式方法 Design Building Blocks: Heuristics
下面是一些思考问题的方法
找出现实世界中的对象 Find Real-World Objects
- 辨识对象及其属性
- 确定可以对各个对象进行的操作
- 确定各个对象能对其他对象进行的操作
- 确定对象的哪些部分对其他对象可见
- 定义每个对象的公开接口
形成一致的抽象 Form Consistent Abstractions
封装实现细节 Encapsulate Implementation Details
当继承能简化设计时就继承 Inherit-When Inheritance Simplifies the Design
隐藏秘密(信息隐藏) Hide Secrets(Information Hiding)
- 两种方式:隐藏复杂度,隐藏变化源
- 障碍:信息过度分散,循环依赖,把类内数据误认为全局数据,可以察觉的性能损耗
找出容易改变的区域 Identify Areas Likely to Change
- 找出看起来容易变化的项目
- 把容易变化的项目分类出来
- 把看起来容易变化的项目隔离开来
- 一些容易发生变化的区域:业务规则,对硬件的依赖性,输入和输出,非标准的语言特性,困难的设计区域和构建区域,状态变量,数据量的限制
保持松散耦合 Keep Coupling Loose
- 耦合标准:规模,可见性,灵活性
- 耦合的种类:简单数据参数耦合,简单对象耦合,对象参数耦合,语义上的耦合
查阅常用的设计模式 Look for Common Design Patterns
- 抽象工厂 Abstract Factory: 通过指定对象组的种类而非单个对象的类型来支持创建一组相关的对象
- 适配器 Adapter: 把一个类的接口转变为另一个接口
- 桥接 Bridge: 把接口和实现分离开来,使他们可以独立地变化
- 组合 Composite: 创建一个包含其他同类对象的对象,使得客户代码可以与最上层对象交互而无需考虑所有的细节对象
- 装饰器 Decorator: 给一个对象动态地添加职责,而无须为了每一种可能的职责配置情况去创建特定的子类
- 外观 Facade: 为没有提供一致接口的代码提供一个一致的接口
- 工厂方法 Factory Method: 做特定基类的派生类的实例化时,除了在 Factory Method 内部之外均无须了解各派生类对象的具体类型
- 迭代器 Iterator: 提供一个服务对戏那个来顺序地访问一组元素中的各个元素
- 观察者 Observer: 使一组相关对象互相同步,方法是让另一个对象负责:在这组对象中的任何一个发生改变时,由它把这种变化通知给这个组里的所有对象
- 单件 Singleton: 为有且仅有一个实例的类提供一种全局访问功能
- 策略 Strategy: 定义一组算法或者行为,使得它们可以动态地相互替换
- 模板方法 Template Method: 定义一个操作的算法结构,但是把部分实现的细节留给子类
其他的启发式方法 Other Heuristics
- 高内聚性 Aim for Strong Cohesion
- 构造分层结构 Build Hierarchies
- 严格描述类契约 Formalize Class Contracts
- 分配职责 Assign Responsibilities
- 为测试而设计 Design for Test
- 避免失误 Avoid Failure
- 有意识地选择绑定时间 Choose Binding Time Consciously
- 创建中央控制点 Make Central Points of Control
- 考虑使用蛮力突破 Consider Using Brute Force
- 画一个图 Draw a Diagram
- 保持设计的模块化 Keep Your Design Modular
要点
★ 软件的首要技术使命就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助。
★ 简单性可以通过两种方式来获取:一是减少在同一时间所关注的本质性复杂度的量,二是避免生成不必要的偶然的复杂度。
★ 设计是一种启发式的过程。固执于某一种单一方法会损害创新能力,从而损害你的程序。
★ 好的设计都是迭代的。你尝试设计的可能性越多,你的最终设计方案就会变得越好。
★ 信息隐藏是个非常有价值的概念。通过询问“我应该隐藏些什么?”能够解决很多困难的设计问题。
★ 很多有用有趣的、关于设计的信息存在于本书之外。这里所给出的观点只是对这些有价值资源的一点提示而已。
第6章 可以工作的类
要点
★ 类的接口应提供一致的抽象。很多问题都是由于违背该原则而引起的。
★ 类的接口应隐藏一些信息――如某个系统接口、某项设计决策、或一些实现细节。
★ 包含往往比继承更为可取――除非你要对“是一个/is a”的有关系建模。
★ 继承是一种有用的工具,但它却会增加复杂度,这有违于软件的首要技术使命――管理复杂度。
★ 类是管理复杂度的首选工具。要在设计类时给予足够的关注,才能实现这一目标。
第7章 高质量的子程序
要点
★ 创建子程序最主要的目的是提高程序的可管理性,当然也有其他一些好的理由。其中,节省代码空间只是一个次要原因:提高可读性、可靠性和可修改性等原因都更重要一些。
★ 有时候,把一些简单的操作写成独立的子程序也非常有价值。
★ 子程序可以按照其内聚性分为很多类,而你应该让大多数程序具有功能上的内聚性,这是最佳的一种内聚性。
★ 子程序的名字是它的质量的指示器。如果名字糟糕但恰如其分,那就说明这个子程序设计得很差劲。如果名字糟糕而且又不准确,那么它就反映不出程序是干什么的。不管怎样,糟糕的名字都意味着程序需要修改。
★ 只有在某个子程序的主要目的是返回由其名字所描述的特定结果时,才应该使用函数。
★ 细心的程序员会非常谨慎地使用宏,而且只在万不得已时才用。
第8章 防范式编程
要点
★ 最终产品代码中对错误的处理方式要比“垃圾进,垃圾出”复杂的多。
★ 防御式编程技术可以让错误更容易发现、更容易修改,并减少错误对产品代码的破坏。
★ 断言可以帮助人尽早发现错误,尤其是在大型系统和高可靠性的系统中,以及快速变化的代码中。
★ 关于如何处理错误输入的决策是一项关键的错误处理决策,也是一项关键的高层设计决策。
★ 异常提供了一种与代码正常流程角度不同的错误处理手段。如果留心使用异常,它可以成为程序员们知识工具箱中的一项有益补充,同时也应该在异常和其他错误处理手段之间进行权衡比较。
★ 针对产品代码的限制并不适用于开发中的软件。你可以利用这一优势在开发中添加有助于更快地排查错误的代码。
第9章 伪代码编程过程
要点
★ 创建类和子程序通常都是一个迭代的过程。在创建子程序的过程中获得的认识常常会反过来影响类的设计。
★ 编写好的伪代码需要使用易懂的英语,要避免使用特定程序语言中才有的特性,同时要在意图的层面上写伪代码(即描述该做什么,而不是要怎么去做)。
★ 伪代码编程过程是一个行之有效的做详细设计的工具,它同时让编码工作更容易。伪代码会直接转化为注释,从而确保了注释的准确度和实用性。
★ 不要只停留在你所想到的第一个设计方案上。反复使用伪代码做出多种方案,然后选出其中最佳的一种方案再开始编码。
★ 每一步完成后都要检查你的工作成果,还要鼓励其他人帮你来检查。这样你就会在投入精力最少的时候,用最低的成本发现错误。
第3部分 变量
第10章 使用变量的一般事项
要点
★ 数据初始化过程很容易出错,所以请用本章描述的初始化方法来避免由于非预期的初始值而造成的错误。
★ 最小化每个变量的作用域。把同一变量的引用点集中在一起。把变量限定在或类的范围之内。避免使用全局数据。
★ 把使用相同变量的语句尽可能集中在一起。
★ 早期在绑定会减低灵活性,但有助于减小复杂度。晚期绑定可以增加灵活性,同时增加复杂度。
★ 把每个变量用于唯一的用途。
第11章 变量名的力量
要点
★ 好的变量名是提高程序可读性的一项关键要素。对特殊种类的变量,比如循环下标和状态变量,需要加以特殊的考虑。
★ 名字要尽可能地具体。那些太模糊或者太能用以致于能够用于多种目的的名字通常都是很不好的。
★ 命名规则应该能够区分局部数据、类数据和全局数据。它们还应当可以区分类型名、具名常量、枚举类型名字和变量名。
★ 无论做哪种类型项目,你都应该采用某种变量命名规则。你所采用的规则的种类取决于你的程序的规模,以及项目成员的人数。
★ 现代编程语言很少需要用到缩写。如果你真的要使用缩写,请使用项目缩写词典或者标准前缀来帮助理解缩写。
★ 代码阅读的次数远远多于编写的次数。确保你所取的名字更侧重于阅读方便而不是编写方便。
第12章 基本数据类型
要点
★ 使用特定的数据类型就意味着要记住适用于各个类型的很多独立的原则。用本章的核对表来确认你已经对觉问题做了考虑。
★ 如果你的语言支持,创建自定义类型会使得你的程序更容易修改,并更具有自描述性。
★ 当你用typedef或者其等价方式创建了一个简单类型的时候,考虑是否更应该创建一个新的类。
第13章 不常见的数据类型
要点
★ 结构体可以使得程序更简单、更容易理解,以及更容易维护。
★ 每当你打算使用结构体的时候,考虑采用类是不是会工作的更好。
★ 指针很容易出错。用访问器子程序或类以及防御式编程实践来保护自己的代码。
★ 避免用全局变量,不只是因为它们很危险,还是因为你可以用其他更好的方法来取代它们。
★ 如果你不得不使用全局变量,那么就通过访问器来使用它。访问器子程序能为你带来全局变量所能带来的一切优点,还有一些额外的好处。
第4部分语句
第14章 组织直线型代码
要点
★ 组织直线型代码的最主要原则是按照依赖关系进行排列。
★ 可以用好的子程序名、参数列表、注释,以及――如果代码足够重要――内务管理变量来让依赖关系变得更明显。
★ 如果代码之间没有顺序依赖关系,那就设法使相关的语句尽可以地接近。
第15章 使用条件语句
要点
★ 对于简单的if-else语句,请注意if子句和else子句的顺序,特别是用它来处理大量错误的时候。要确认正常的情况是清晰的。
★ 对于if-then-else语句串和case语句,选择一种最有利于阅读的排序。
★ 为了捕捉错误,可以使用case语句中的default子句(默认子句),或者使用if-then-else语句串中的最后那个else子句。
★ 各种控制结构并不是生来平等的。请为代码的每个部分选用最合适的控制结构。
第16章 控制循环
要点
★ 循环很复杂。保持循环简单将有助于别人阅读你的代码。
★ 操持循环简单的技巧包括:避免使用怪异的循环、减少嵌套层次、让入口 和出口 一目了然、把内务操作代码放在一起。
★ 循环下标很容易被滥用。因此命名要准确,并且要把它们各自仅用于一个用途。
★ 仔细地考虑循环,确认它在每一种情况下都运行正常,并且在所有可能的条件下都能退出。
第17章 不常见的控制结构
要点
★ 多个return可以增强子程序的可读性和可维护性,同时可以避免产生很深的嵌套逻辑。但是使用它的时候要多加小心。
★ 递归能够很优雅地解决一小部分问题。对它的使用也要倍加小心 。
★ 在少数情况下,goto是编写可读性和可维护性代码的最佳方法。但这种情况非常罕见。除非万不得忆,不要使用goto。
第18章 表驱动方法
要点
★ 表提供了一种复杂的逻辑和继承结构的替换方案。如果你发现自己对某个应用程序的逻辑或者继承树关系感到困惑,那么问问自己它是否可以通过一个查询表来加以简化。
★ 使用表的一项关键决策是决定如何去访问表。你可以采取直接访问、索引访问或者阶梯访问。
★ 使用表的另一项关键决策是决定应该把什么内容放入表中。
第19章 一般控制问题
要点
★ 使用布尔表达式简单可读,将非常有助于提高 你的代码质量。
★ 深层次的嵌套使得子程序变得难以理解。所幸的是,你可以相对容易地避免这么做。
★ 结构化编程是一种简单并且仍然适用的思想:你可以通过把顺序、选择和循环三者组合起来而开发出任何程序。
★ 将复杂降低到最低水平是编写高质量代码的关键。
第5部分代码改善
第20章 软件质量概述
要点
★ 开发高质量代码最终并没有要求你付出更多,只是你需要对资源进行重新分配,以低廉的成本来防止缺陷出现,从而避免代价高昂的修正工作。
★ 并非所有的质量保证目标都可以全部实现。明确哪些目标是你希望达到的,并就这些目标和团队成员进行沟通。
★ 没有任何一种错误检测方法能够解决全部问题,测试本身并不是排队错误的最有效方法。成功的质量保证计划应该使用多种不同的技术来检查各种不同类型的错误。
★ 在构建期间应当使用一些有效的质量保证技术,但在这之前,一些具有同样强大功能的质量保证技术也是必不可少的。错误发现越早,它与其余代码的纠缠就越少,由此造成的损失也越少。
★ 软件领域的质量保证是面向过程的。软件开发与制造业不一样,在这里并不存在会影响最终产品的重复的阶断,因此,最终产品的质量受到开发软件所用的过程的控制。
第21章 协同构造
要点
★ 协同开发实践往往能比测试发现更多的缺陷,并且更有效率。
★ 协同开发实践所发现错误的类型通常跟测试所发现的不同,这意味着你需要同时使用和测试来保证你软件的质量。
★ 正式检查通过运用核对表、准备工作、明确定义的角色以及对方法的持续改善,将缺陷侦测的效率提升至最高。它往往能比走查发现更多的缺陷。
★ 通常,结对编程拥有和详查相同的成本,并能产生质量相当的代码。当需要缩短开发周期的时候,结对编程就非常有价值。相对于单独工作来说,有些开发人员更喜欢结对工作。
★ 正式检查可以应用在除代码之外的很多工作成果上,例如需求、设计以及测试用例等。
★ 走查的代码阅是详查的替代方案。代码阅读更富有弹性,能有效地利用每个人的时间。
第22章 开发者测试
要点
★ 开发人员测试是完整测试策略的一个关键部分。独立测试也很重要,但这一主题超出了本书的范围。
★ 同编码之后编写测试用例相比较,编码开始之前编写测试用例,工作量和花费的时间差不多,但是后者可以缩短缺陷-侦测-调试-修正这一周期。
★ 即使考虑到了各种可用的测试手段,测试仍然只是良好软件质量计划的一部分。高质量的开发方法至少和测试一样重要,这包括尽可能减少需求和设计阶段的缺陷。在检测错误方面,协同开发的成效至少与测试相当。这些方法所检测错误的类型也不相同。
★ 你可以根据各种不同的思路来产生很多测试用例,这些思路包括基础测试、数据流分析、边界分析、错误数据类型以及正确数据类型等。你可以通过猜测错误的方式得到更多的测试用例。
★ 错误往往集中在少数几个容易出错的类和子程序上。找出这部分代码,重新设计和编写它们。
★ 测试数据本身出错的密度往往比被测代码还要高。查找这种错误完全是浪费时间,又不能对代码有所改善,因此测试数据里面的错误更加让人烦恼。要像代码一样小心地开发测试用例,这样才能避免产生这种问题。
★ 自动化测试总体来说是很有用的,也是进行回归测试的基础。
★ 从长远来看,改善测试过程的最好办法就是将其规范化,并对其进行评估,然后从评估中获得的经验教训来改善这个过程。
第23章 调 试
要点
★ 调试同整个软件开发的成败息息相关。最好的解决之道是使用本书中介绍的其他方法来避免缺陷的产生。然而,花点时间来提高自己的调试技能还是很划算的,因为优秀和拙劣的调试表现之间的差距至少是10:1。
★ 要想成功,系统化地查找和改正错误的方法至关重要。要声浪于你的调试工作,让每一次测试都能让你朝着正确的方向前进一步。要使用科学的调试方法。
★ 在动手解决问题之前,要理解问题的根本。胡乱猜测错误的来源和随机修改将会让你的程序陷入比刚开始调试时更为糟糕的境地。
★ 将编译器警告级别设置为最严格,把警告信息所报告的错误都改正。如果你忽略了明显的错误,那么要改正那些微妙的错误就会非常麻烦。
★ 调试工具对软件开发而言是强有力的支持手段。找出这些工具并加以应用,当然,请记得在调试的时候开动脑筋。
第24章 重构
要点
★ 修改程序是程序一生都要面对的事情,不仅包括最初的开发阶段,还包括首次发布之后。
★ 在修改中软件的质量要么改进,要么恶化。软件演化的首要法则就是代码演化应当提升程序的内存质量。
★ 重构成功之关键在于程序员应学会关注那些标志着代码需要重构的众多的警告或“代码臭味”。
★ 重要成功的另一要素是程序员应当掌握大量特定的重构方法。
★ 重构成功的最后要点在于要有安全重构的策略。一些重构方法会比其他重构的方法要好。
★ 开发阶段的重构是提升程序质量的最佳时机,因为你可以立刻让刚刚产生的改变梦想变成现实。请珍惜这些开发阶段的天赐良机!
第25章 代码调整策略
要点
★ 性能只是软件整体质量的一个方面,通常不是最重要的。精细的代码调整也只是实现整体性能的一种方法,通常也不是决定性的。相对于代码本身的效率而言,程序的架构、细节设计以及数据结构和算法选择对程序的运行速度和资源占用的影响通常会更大。
★ 定量测量是实现性能最优化的关键。定量测量需要找出能真正决定程序性能的部分,在修改之后,应当通过重复测量来明确修改是提高还是降低了软件的性能。
★ 绝大多数的程序都有那么一小部分代码耗费了绝大部分的运行时间。如果没有测量,你不会知道是哪一部分代码。
★ 代码调整需要反复尝试,这样才能获得理想的性能提高。
★ 为性能优化工作做好准备的最佳方式就是在最初阶段编写清晰的代码,从而使代码在后续工作中易于理解和修改。
第26章 代码调整方法
要点
★ 优化结果在不同的语言、编译器和环境下有很大差异。如果没有对每一次的优化进行测量,你将无法判断优化到底是帮助还是损害了这个程序 。
★ 第一次优化通常不会是最好的。即使找到了效果很不错的,也不要停下扩大战果的步伐。
★ 代码调整这一话题有点类似于核能,富于争议,甚至会让人冲动。一些人认为代码调整损害了代码可读性和可维护性,他们绝对会将其弃之不用。其他人则认为只要有适当的安全保障,代码调整对程序是有益的。如果你决定使用本章所述的调整方法,请务必谨慎行事。
第6部分系统考虑
第27章 程序规模对“构筑”的影响
要点
★ 随着项目规模的扩大,交流需要加以支持。大多数方法论的关键点都在于减少交流中的问题,而一项方法论的存亡关键也应取决于它能否促进交流。
★ 在其他条件都相等的时候,大项目的生产率会低于小项目。
★ 在其他条件都相等的时候,大项目的每千行代码错误率会高于小项目。
★ 在小项目里的一些看起来“理当如此”的活动在大项目中必须仔细地计划。随着项目规模扩大,构建活动的主导地位逐渐降低。
★ 放大轻量级的方法论要好于缩小重量级的方法论。最有效的办法是使用“适量级”方法论。
第28章 管理“构筑”
要点
★ 好的编码实践可以通过“贯彻标准”或者“使用更为灵活的方法”来达到。
★ 配置管理,如果应用得当,会使程序序员的工作变得更加轻松。特别包括为变更控制。
★ 好的软件评估是一项重大挑战。成功的关键包括采用多种方法、随着项目的开展而修缮评估结果,以及很好地利用数据来创建评估等。
★ 度量是构建管理成功的关键。你可以采取措施度量项目的任何方面,而这要比根本不度量好的多。准确的度量是制定准确的进度表、质量控制和改进开发过程的关键。
★ 程序员和管理人员都是人,在把他们当人看的时候工作得最好。
第29章 集成
要点
★ 构建的先后次序和集成的步骤会影响设计、编码、测试各类的顺序。
★ 一个经过充分思考的集成顺序能减少测试的工作量,并使用调试变容易。
★ 增量集成有若干变型,而且――除非项目是微不足道的――任何一种形式的增量集成都比阶段式集成好。
★ 针对每个特定的项目,最佳的集成步骤通常是自顶向下、自底向上、风险导向及其他集成方法的某种组合。T-型集成和竖直分块集成通常都能工作得很好。
★ Daily build能减少集成的问题,提升开发人员的士气,并提供非常有用的项目管理信息。
第30章 编程工具
要点
★ 程序员有时会长达数年的时间里忽视某些最强大的工具,之后才发现并使用之。
★ 好的工具能让你的日子过的安逸得多。
★ 下面这些工具已经可用了:编辑、分析代码质量、重构、版本控制、除错、测试、代码调整。
★ 你能打造许多自己用的专用工具。
★ 好的工具能减少软件开发中最单调乏味的工作的量,但它不能消除对“编程”的需要,虽然它会持续地重塑(reshape)编程的含义。
第7部分软件工艺
第31章 布局与风格
要点
★ 可视化布局的首要任务是指明代码的逻辑组织。评估该任务是否实现的指标包括准确性、一致性、易读性和易维护性。
★ 外表悦目比起其他指标是最不重要的。然而,如果其他指标都达到了,代码又质量好,那么布局效果看上去也会不错。
★ Visual Basic具有纯代码块风格,而Java的传统做法就是使用纯块风格,所以若用这些语言编程,就请使用纯代码块风格。C++中,模拟纯代码块或者begin-end块边界都行之有效。
★ 结构化代码有其自身目的。始终如一地沿用某个习惯而少来创新。不能持久的布局规范只会损害可读性。
★ 布局的很多方面涉及信仰问题。应试着将客观需要和主观偏好区分开来。定出明确的指标,在此基础上再讨论风格参数的选择。
第32章 自说明代码
要点
★ 该不该注释是一个需要认真对待的问题。差劲的注释只会浪费时间,帮倒忙:好的注释才有价值。
★ 源代码应当含有程序大部分的关键信息。只要程序谷底大用,源代码比其他资料都能保持更新,故而将重要信息融入代码是很有用处的。
★ 好代码本身就是更好的说明。如果代码太糟,需要大量注释,应先试着改进代码,直至无须过多注释为止。
★ 注释应说出代码无法说出的东西――例如概述或用意等信息。
★ 有的注释风格需要许多重复性劳动,应舍弃之,改用易于维护的注释风格。
第33章 个人性格
要点
★ 人的个性对其编程能力有直接影响 。
★ 最有关系的性格为:谦虚、求知欲、诚实、创造性和纪律,以及高明的偷懒。
★ 程序员高手的性格与天分无关,而任何事都与个人发展相关。
★ 出乎意料的是,小聪明、经验、坚持和疯狂既有助也有害。
★ 很多程序员不愿意主要吸收新知识和技术,只依靠工作时偶尔接触新的信息。如果你能抽出少量时间阅读和学习编程知识,要不了多久就能鹤立鸡群。
★ 好性格与培养正确的习惯关系甚大。要成为杰出的程序员,先要养成良好习惯,其他自然水到渠成。
第34章 软件开发艺术的有关问题
要点
★ 编程的主要主的之一是管理复杂性。
★ 编程过程对最终产品有深远影响。
★ 合作开发要求团队成员之间进行广泛沟通,甚于同计算机的交互;而单人开发则是自我交流,其次才是计算机。
★ 编程规范一旦滥用,只会雪上加霜;使用得当则能为开发环境带来良好机制,有助于管理复杂性和相互沟通。
★ 编程应基于问题域而非解决方案,这样便于复杂性管理。
★ 注意警告信息,将其作为编程的疑点,因为编程几乎是纯粹的智力活动。
★ 开发时迭代次数越多,产品的质量越好。
★ 墨守成规的方法有悖于高质量的软件开发。请将编程工具箱中填满各种编程工具,不断提高自己换行合适工具的能力。
第35章 何处有更多信息
核对表集
核对表:需求
针对功能需求
□ 是否详细定义了系统的全部输入,包括其来源、精度、取值范围、出现频率等?
□ 是否详细定义了系统的全部输出,包括目的地、精度、取值范围、出现频率、格式等?
□ 是否详细定义了所有输出格式(Web页面、报表,等等)?
□ 是否详细定义了所有硬件及软件的外部接?
□ 是否详细定义了全部外部通信接,包括握手协议、纠错协议、通信协议等?
□ 是否列出了用户想要做的全部事情?
□ 是否详细定义了每个任务所用的数据,以及每个任务得到的数据?
针对非功能需求(质量需求)
□ 是否为全部必要的操作,从用户的视角,详细描述了期望响应时间?
□ 是否详细描述了其他与计时有关的考虑,例如处理时间、数据传输率、系统吞吐量?
□ 是否详细定义了安全级别?
□ 是否详细定义了可靠性,包括软件失灵的后果、发生故障时需要保护的至关重要的信息、错误检测与恢复的策略等?
□ 是否详细定义了机器内存和剩余磁盘空间的最小值?
□ 是否详细定义了系统的可维护性,包括适应特定功能的变更、操作环境的变更、与其他软件的接口的变更能力?
□ 是否包含对“成功”的定义?“失败”的定义呢?
需求的质量
□ 需求是用用户的语言书写的吗?用户也这么认为吗?
□ 每条需求都不与其他需求冲突吗?
□ 是否详细定义了相互竞争的特性之间的权衡一一例如,健壮性与正确性之间的权衡?
□ 是否避免在需求中规定设计(方案)?
□ 需求是否在详细程度上保持相当一致的水平?有些需求应该更详细地描述吗?有些需求应该更粗略地描述吗?
□ 需求是否足够清晰,即使转交给一个独立的小组去构建,他们也能理解吗?开发者也这么想吗?
□ 每个条款都与待解决的问题及其解决方案相关吗?能从每个条款上溯到它在问题域中对应的根源吗?
□ 是否每条需求都是可测试的?是否可能进行独立的测试,以检验满不满足各项需求?
□ 是否详细描述了所有可能的对需求的改动,包括各项改动的可能性?
需求的完备性
□ 对于在开始开发之前无法获得的信息,是否详细描述了信息不完全的区域?
□ 需求的完备度是否能达到这种程度:如果产品满足所有需求,那么它就是可接受的?
□ 你对全部需求都感到很舒服吗?你是否已经去掉了那些不可能实现的需求——那些只是为了安抚客户和老板的东西?
核对表:架构
针对各架构主题
□ 程序的整体组织结构是否清晰?是否包含一个良好的架构全局观(及其理由)?
□ 是否明确定义了主要的构造块(包括每个构造块的职责范围及与其他构造块的接口)?
□ 是否明显涵盖了“需求”中列出的所有功能(每个功能对应的构造块不太多也不太少)?
□ 是否描述并论证了那些最关键的类?
□ 是否描述并论证了数据设计?
□ 是否详细定义了数据库的组织结构和内容?
□ 是否指出了所用关键的业务规则,并描述其对系统的影响?
□ 是否描述了用户界面设计的策略?
□ 是否将用户界面模块化,使界面的变更不会影响程序其余部分?
□ 是否描述并论证了处理I/O的策略?
□ 是否估算了稀缺资源(如线程、数据库连接、句柄、网络带宽等)的使用量,是否描述并论证了资源管理的策略?
□ 是否描述了架构的安全需求?
□ 架构是否为每个类、每个子系统、或每个功能域(functionality area)提出空间与时间预算?
□ 架构是否描述了如何达到可伸缩性?
□ 架构是否关注互操作性?
□ 是否描述了国际化/本地化的策略?
□ 是否提供了一套内聚的错误处理策略?
□ 是否规定了容错的办法(如果需要)?
□ 是否证实了系统各个部分的技术可行性?
□ 是否详细描述了过度工程(overengineering)的方法?
□ 是否包含了必要的“买 vs. 造”的决策?
□ 架构是否描述了如何加工被复用的代码,使之符合其他架构目标?
□ 是否将架构设计得能够适应很可能出现的变更?
架构的总体质量
□ 架构是否解决了全部需求?
□ 有没有哪个部分是“过度架构/overarchitected ”或“欠架构/underarchitected”?是否明确宣布了在这方面的预期指标?
□ 整个架构是否在概念上协调一致?
□ 顶层设计是否独立于用作实现它的机器和语言?
□ 是否说明了所有主要的决策的动机?
□ 你,作为一名实现该系统的程序员,是否对这个架构感觉良好?
核对表:前期准备
□ 你是否辨明了自己所从事的软件的类型,并对所用开发的方法做出相应的剪裁?
□ 是否充分明确地定义了需求?而且需求足够稳定,能开始构建了?(详见需求核对表。)
□ 是否充分明确地定义了架构,以便开始构建?(详见架构核对表。)
□ 是否已经指出你的(当前)项目中独有的风险(以便避免构建活动面临不必要的风险)?
核对表:主要的构建实践
编码
□ 你有没有确定,多少设计工作要预先进行,多少设计工作在键盘上进行(在编写代码的同时)?
□ 你有没有规定诸如名称、注释、代码格式等“编码约定”?
□ 你有没有规定特定的有软件架构确定的编码实践,比如如何处理错误条件、如何处理安全事项、对于类接口有哪些规定、可重用的代码遵循哪些标准、在编码时考虑多少性能因素?
□ 你有没有找到自己在技术浪潮中的位置,并相应调整自己的措施?如果必要,你是否知道如何“深入一门语言去编程”,而不是受限于语言(仅仅“在一种语言上编程”)?
团队工作
□ 你有没有定义一套集成工序——即,你有没有定义一套特定的步骤,规定程序员在把代码check in到主源码(代码库)中之前,必须履行这些步骤?
□ 程序员是结对编程、还是独自编程,或者这两者的某种组合?
质量保证
□ 程序员在写代码之前,是否先为之编写测试用例?
□ 程序员会为自己的代码写单元测试吗?(无论先写还是后写)
□ 程序员在check in代码之前,是否进行集成测试?
□ 程序员会复审(review)或检查别人的代码吗?
工具
□ 你是否选用了某种版本控制工具?
□ 你是否选定了一种语言,以及语言的版本或编译器版本?
□ 你是否选择了某个编译框架(framework,如J2EE或Microsoft.NET),或者明确地决定不使用编程框架?
□ 你是否决定允许使用非标准的语言特性?
□ 你是否选定并拥有了其他将要用到的工具——编辑器、重构工具、调试器、测试框架、语法检查器等?
核对表:软件构造中的设计
设计实践
□ 你已经做过多次选代,并且从众多尝试结果中选择最佳的一种,而不是简单选择第一次尝试的结果吗?
□ 你尝试用多种方案来分解系统,以确定最佳方案吗?
□ 你向时用自下而上和自上而下的方法来解决设计问题吗?
□ 为了解决某些特定的问题,你对系统中的风险部分或者不熟悉的部分创建过原型、写出数量最少的可抛弃的代码吗?
□ 你的设计方案被其他人检查了吗(无论正式与否)?
□ 你一直在展开设计,直到实施细节跃然纸上了吗?
□ 你用某种适当的技术——比如说Wiki、电子邮件、挂图、数码照片、UML、CRC卡或者在代码写注释——来保留设计成果吗?
设计目栋
□ 你的设计是否充分地处理了由系统架构层定义出并且推迟确定的事项?
□ 你的设计被划分为层次吗?
□ 你对把这一程序分解成为子程序、包和类的方式感到满意吗?
□ 你把对这个类分解成为子程序的方法感到满意吗?
□ 类与类之间的交互关系是否已设计为最小化了?
□ 类和子程序是否被设计为能够在其他的系统中重用?
□ 程序是不是易于维护?
□ 设计是否精简?设计出来的每一部分都绝对必要吗?
□ 设计中是否采用了标准的技术?是否避免使用怪异且难以理解的元素?
□ 整体而言,你的设计是否有助于最小化偶然性的和本质性的复杂度吗?
核对表:类的质量
抽象数据类型
□ 你是否把程序中的类都看做是抽象数据类型了?是否从这个角度评估它们的接口了?
抽象
□ 类是否有一个中心目的?
□ 类的命名是否恰当?其名字是否表达了其中心目的?
□ 类的接口是否展现了一致的抽象?
□ 类的接口是否能让人清楚明白地知道该如何用它?
□ 类的接口是否足够抽象,使你能不必顾虑它是如何实现其服务的?你能把类看做黑盒子吗?
□ 类提供的服务是否足够完整,能让其他类无须动用其内部数据?
□ 是否己从类中除去无关信息?
□ 是否考虑过把类进一步分解为组件类?是否已尽可能将其分解?
□ 在修改类时是否维持了其接口的完整性?
封装
□ 是否把类的成员的可访问性降到最小?
□ 是否避免暴露类中的数据成员?
□ 在编程语言所许可的范围内,类是否已尽可能地对其他的类隐藏了自己的实现细节?
□ 类是否避免对其使用者,包括其派生类会如何使用它做了假设?
□ 类是否不依赖于其他类?它是松散耦合的吗?
继承
□ 继承是否只用来建立“是一个/is a”的关系?也就是说,派生类是否遵循了LSP(Liskov替换原则)?
□ 类的文档中是否记述了其继承策略?
□ 派生类是否避免了“覆盖”不可覆盖的方法?
□ 是否把公用的接口、数据和行为都放到尽可能高的继承层次中了?
□ 继承层次是否很浅?
□ 基类中所有的数据成员是否都被定义为private而非protected的了?
跟实现相关的其他问题
□ 类中是否只有大约七个或更少的数据成员?
□ 是否把类直接或间接调用其他类的子程序的数量减到最少了?
□ 类是否只在绝对必要时才与其他的类相互协作?
□ 是否在构造函数中初始化了所有的数据成员?
□ 除非拥有经过测量的、创建浅层复本的理由,类是否都被设计为当作深层复本使用?
与语言相关的问题
□ 你是否研究过所用编程语言里和类相关的各种特有问题?
核对表:高质量的子程序
大局事顶
□ 创建子程序的理由充分吗?
□ 一个子程序中所有适于单独提出的部分是不是已经被提出到单独的子程序中了?
□ 过程的名字中是否用了强烈、清晰的“动词+宾语”词组?函数的名字是否描述了其返回值?
□ 子程序的名字是否描述了它所做的全部事情?
□ 是否给常用的操作建立了命名规则?
□ 子程序是否具有强烈的功能上的内聚性?即它是否做且只做一件事,并且把它做得很好?
□ 子程序之间是否有较松的耦合?子程序与其他子程序之间的连接是否是小的(small)、明确的(intimate)、可见的(viaible)和灵活的(flexible)?
□ 子程序的长度是否是由其功能和逻辑自然确定,而非遵循任何人为的编码标准?
参数传递事宜
□ 整体来看,子程序的参数表是否表现出一种具有整体性且一致的接口抽象?
□ 子程序参数的排列顺序是否合理?是否与类似的子程序的参数排列顺序相符?
□ 接口假定是否已在文档中说明?
□ 子程序的参数个数是否没超过7个?
□ 是否用到了每一个输入参数?
□ 是否用到了每一个输出参数?
□ 子程序是否避免了把输入参数用做工作变量?
□ 如果子程序是一个函数,那么它是否在所有可能的情况下都能返回一个合法的值?
核对表:防御式编程
一般事宜
□ 子程序是否保护自己免遭有害输入数据的破坏?
□ 你用断言来说明编程假定吗?其中包括了前条件和后条件吗?
□ 断言是否只是用来说明从不应该发生的情况?
□ 你是否在架构或高层设计中规定了一组特定的错误处理技术?
□ 你是否在架构或高层设计中规定了是让错误处理更倾向于健壮性还是正确性?
□ 你是否建立了隔栏来遏制错误可能造成的破坏?是否减少了其他需要关注错误处理的代码的数量?
□ 代码中用到辅助调试的代码了吗?
□ 如果需要启用或禁用添加的辅助助手的话,是否无须大动干戈?
□ 在防御式编程时引入的代码量是否适宜——既不过多,也不过少?
□ 你在开发阶段是否采用了进攻式编辑来使错误难以被忽视?
异常
□ 你在项目中定义了一套标准化的异常处理方案吗?
□ 是否考虑过异常之外的其他替代方案?
□ 如果可能的话,是否在局部处理了错误而不是把它当成一个异常抛到外部?
□ 代码中是否遮免了在构造函数和析构函数中抛出异常?
□ 所有的异常是否都与抛出它们的子程序处于同一抽象层次上?
□ 每个异常是否都包含了关于异常发生的所有背景信息?
□ 代码中是否没有使用空的catch语句?(或者如果使用空的catch语句确实很合适,那么明确说明了吗?
安全事宜
□ 检查有害输入数据的代码是否也检查了故意的缓冲区溢出、SQL注入、HTML注入、整数溢出以及其他恶意输入数据?
□ 是否检查了所有的错误返回码?
□ 是否捕获了所有的异常?
□ 出错消息中是否避免出现有助于攻击者攻入系统所需的信息?
核对表:伪代码编程过程
□ 是否检查过巳满足所有的先决条件?
□ 定义好这个类要解决的问题了吗?
□ 高层次的设计是否足够清晰?能给这个类和其中的每一个子程序起一个好的名字吗?
□ 考虑过该如何测试这个类及其中每一个子程序了吗?
□ 关于效率的问题,你主要从稳定的接口和可读的实现这两个角度考虑吗?还是主要从满足资源和速度的预期目标的角度考虑过呢?
□ 在标准函数库或其他代码库中寻找过可用的子程序或者组件了吗?
□ 在参考书籍中查找过有用的算法了吗?
□ 是否用详尽的伪代码设计好每一个子程序?
□ 你在脑海里检查过伪代码吗?这些伪代码容易理解吗?
□ 关注过那些可能会让你重返设计的警告信息了吗?(比如说关于全局数据的使用、一些看上去更适合放在另一个类或子程序中的操作等。)
□ 是否把伪代码正确地翻译成代码了?
□ 你反复使用伪代码编码过程了吗?有没有根据需要把一些子程序拆分成更小的子程序?
□ 在做出假定的时候有没有对它们加以说明?
□ 已经删除掉那些冗余的注释了吗?
□ 你是否采取了几次迭代中最好的那个结果?还是在第一次迭代之后就停止了?
□ 你完全理解你的代码了吗?这些代码是否容易理解?
核对表:使用数据的一般事顶
初始化变量
□ 每一个子程序都检查其输入参数的正确性吗?
□ 变量声明位置靠近变量第一次使用的位置吗?
□ 尽可能地在声明变量的同时初始化变量吗?
□ 如果无法同时声明和初始化变量,有没有在靠近第一次使用变量的位置声明变量?
□ 计数器和累加器经过了适当的初始化吗?如果需要再一次使用,之前重新初始化了吗?
□ 适当地重新初始化“需要重复执行的代码里的变量”了吗?
□ 代码在通过编译器编译的时候是不是没有警告信息?(你启用了所有可用的警告选项了吗?)
□ 如果你用的语言允许隐式声明,你为由此可能引发的问题做好补偿措施了吗?
使用数据的真他事项
□ 如果可能,所有变量都被定义为具有最小的作用域吗?
□ 各变量的引用点都尽可能集中在一起吗?对同一变量的两次相邻引用,或者变量的整个生命期都这样做了吗?
□ 控制结构符合数据类型吗?
□ 所有声明的变量都用到了吗?
□ 变量都在合适的时间绑定了吗?一一一也就是说,你有意识地在晚期绑定所带来的灵活性和增加的复杂度之间做出平衡了吗?
□ 每个变量都有且仅有一项用途吗?
□ 每个变量的含义都很明确且没有隐含含义吗?
核对表:变量命名
命名的一般注意事顶
□ 名字完整并准确地表达了变量所代表的含义吗?
□ 名字反映了现实世界的问题而不是编程语言方案吗?
□ 名字足够长,可以让你无须苦苦思索吗?
□ 如果有计算值限定符,它被放在名字的最后吗?
□ 名字中用Count或者Index来代替Num了吗?
为特定类型的数据命名
□ 循环下标的名字有意义吗(如果循环的长度超出了一两行代码或者出现
了嵌套循环,那么就应该是i、j或者k以外的其他名字)? □ 所有的“临时”变量都重新命以更有意义的名字了吗?
□ 当布尔变量的值为真时,变量名能准确表达其含义吗?
□ 枚举类型的名字中含有能够表示其类别的前缀或后缀了吗?例如,把Color用于ColorRed, ColorGreen, ColorBlue等了吗?
□ 具名常量是根据它所代表的抽象实体而不是它所代表的数字来命名的吗?
命名规则
□ 规则能够区分局部数据、类的数据和全局数据吗?
□ 规则能够区分类型名、具名常量、枚举类型和变量名吗?
□ 规则能够在编译器不强制检测只读参数的语言里标识出于程序中的输入参数吗?
□ 规则尽可能地与语言的标准规则兼容吗?
□ 名字为了可读性而加以格式化吗?
短名字
□ 代码用了长名字吗(除非有必要使用短名字)?
□ 是否避免只为了省一个字符而缩写名字的情况?
□ 所有单词的缩写方式都一致吗?
□ 名字能够读出来吗?
□ 避免使用容易被看错或者读错的名字吗?
□ 在缩写对照表里对短名字做出说明吗?
常见命名问题:你避免使用……
□ 容易让人误解的名字吗?
□ 有相近含义的名字吗?
□ 只有一两个字符不同的名字吗?
□ 发音相近的名字吗?
□ 包含数字的名字吗?
□ 为了缩短而故意拼错的名字吗?
□ 英语中经常拼错的名字吗?
□ 与标准库于程序名或者预定义变量名冲突的名字吗?
□ 过于随意的名字吗?
□ 含有难读的字符的名字吗?
核对表:基本数据类型
数值概论
□ 代码中避免使用神秘数值吗?
□ 代码考虑了除零错误吗?
□ 类型转换很明显吗?
□ 如果在一条语句中存在两个不同类型的变量,那么这条语句会像你期望的那样求值吗?
□ 代码避免了混合类型比较吗?
□ 程序编译时没有警告信息吗?
整数
□ 使用整数除法的表达式能按预期的那样工作吗?
□ 整数表达式避免整数溢出问题吗?
浮点数
□ 代码避免了对数量级相差巨大的数字做加减运算吗?
□ 代码系统地阻止了舍入错误的发生吗?
□ 代码避免对浮点数做等量比较吗?
字待和字符串
□ 代码避免使用神秘字符和神秘字符串吗?
□ 使用字符串时避免了off-by-one错误吗?
□ C代码把字符串指针和字符数组区别对待了吗?
□ C代码遵循了把字符串声明为CONSTANT + 1长度的规则了吗?
□ C代码在适当的时候用字符数组来代替指针了吗?
□ C代码把字符串初始化为NULL来避免无终端的字符串了吗?
□ C代码用strncpy()代替strcpy()吗?strncat()和strncmp()呢?
布尔变量
□ 程序用额外的布尔变量来说明条件判断了吗?
□ 程序用额外的布尔变量来简化条件判断了吗?
枚举类型
□ 程序用枚举类型而非具名常量来提高可读性、可靠性和可修改性吗?
□ 当变量的用法不能仅用true和false表示的时候,程序用枚举类型来取代布尔变量吗?
□ 针对枚举类型的测试检测了非法数值吗?
□ 把枚举类型的第一项条目保留为“非法的”了吗?
臭名常量
□ 程序用具名常量而不是神秘数值来声明数据和表示循环界限吗?
□ 具名常量的使用一致吗?——没有在有些位置使用具名常量又在其他位置使用文字量?
数组
□ 所有的数组下标都没育超出数组边界吗?
□ 数组引用没有出现off-by-one错误吗?
□ 所有多维数组的下标的顺序都正确吗?
□ 在嵌套循环里,把正确的变量用于数组下标来避免循环下标串话了吗?
创建类型
□ 程序对每一种可能变化的数据分别采用不同的类型吗?
□ 类型名是以该类型所表示的现实世界实体为导向,而不是以编程语言类型为导向的吗?
□ 类型名的描述性足够强,可以帮助解释数据声明吗?
□ 你避免重新定义预定义类型吗?
□ 与简单地重定义一个类型相比,你考虑过创建一个新类吗?
核对表:使用不常见类型的注意事项
结构体
□ 你使用结构体而不是单纯的变量来组织和操作相关的数据吗?
□ 你考虑创建一个类来代替使用结构体吗?
全局数据
□ 所有的变量是否都是局部的或者是类范围的?除非绝对有必要才是全局的?
□ 变量的命名规则是能把局部数据、类数据和全局数据分开吗?
□ 你对所有的全局变量都加以文档说明吗?
□ 避免使用伪全局数据,即被到处传递且包含有杂乱数据的巨大对象吗?
□ 用访问权子程序来取代全局数据吗?
□ 把访问权子程序和数据组织到类里面吗?
□ 访问权子程序提供了一个在底层数据类型实现之上的抽象层吗?
□ 所有相关的访问器子程序都位于同一抽象层次吗?
指针
□ 把指针操作隔离在子程序里了吗?
□ 指针引用合法吗?或者说指针有可能成为空悬指针吗?
□ 代码在使用指针之前检查它的有效性吗?
□ 在使用指针所指向的变量之前检查其有效性吗?
□ 指针用完后被设置为空值吗?
□ 就可读性而言,代码用了所有需要使用的指针变量吗?
□ 链表中的指针是按正确的顺序加以释放吗?
□ 程序分配了一片保留的内存后备区域,以便在耗尽内存的时候能够优雅地退出吗?
□ 是不是在没有其他方法可用的情况下最终才使用指针的?
核对表:组织直线型代码
□ 代码便得语旬之间的依赖关系变得明显吗?
□ 子程序的名字使得依赖关系变得明显吗?
□ 子程序的参数使得依赖关系变得明显吗?
□ 如果依赖关系不明确,你是否用注释进行了说明?
□ 你用“内务管理变量”(housekeeping variables)来检查代码中关键位置的顺序依赖关系了吗?
□ 代码容易按照自上而下的顺序阅读吗?
□ 相关的语句被组织在一起吗?
□ 把相对独立的语句组放进各自的子程序里吗?
核对表:使用条件语句
if-then语句
□ 代码的正常路径清晰吗?
□ if-then测试对等量分支的处理方式正确吗?
□ 使用了else子句并加以说明吗?
□ else子句用得对吗?
□ 用对了if和else子句,即没把它们用反吗?
□ 需要执行的正常情况是位于if而不是else子句里吗?
if-then-else-if语句串
□ 把复杂的判断封装到布尔函数调用里了吗?
□ 先判断最常见的情况了吗?
□ 判断包含所有的情况吗?
□ if-then-else-if是最佳的实现吗?比case 句还要好吗?
case 语句
□ case子句排序得有意义吗?
□ 每种情况的操作简单吗?必要的时候调用了其他子程序吗?
□ case语句检测的是一个真实的变量,而不是一个只为了滥用case语句而刻意制造变量吗?
□ 默认子句用得合法吗?
□ 用默认子句来检测和报告意料之外的情况了吗?
□ 在C、C++或者Java里,每一个case的末尾都有一个break吗?
核对表:循环
循环的选择和创建
□ 在合适的情况下用while循环取代for循环了吗?
□ 循环是由内到外创建的吗?
进入循环
□ 是从循环头部进入的循环吗?
□ 初始化代码是直接位于循环前面吗?
□ 循环是无限循环或者事件循环吗?它的结构是否清晰?
□ 避免使用像for i = 1 to 9999这样的代码吗?
□ 如果这是一个C++、C 或Java 中的for循环,那么把循环头留给循环控制代码了吗?
循环的内部
□ 循环是否用了“{}”或其等价物来括上循环体,以防止因修改不当而出错吗?
□ 循环体内有内容吗?它是非空的吗?
□ 把内务处理集中地放在循环开始或者循环结束处了吗?
□ 循环像定义良好的子程序那样只执行了一件操作吗?
□ 循环短得足以一目了然吗?
□ 循环的嵌套层次不多于3层吗?
□ 把长循环的内容提取成单独的子程序吗?
□ 如果循环很长,那么它非常清晰吗?
循环下标
□ 如果这是一个for循环,那么其中的代码有没有随意修改循环下标值?
□ 是否把重要的循环下标值保存在另外的变量里,而不是在循环体外使用该循环下标?
□ 循环下标是序数类型(整数)或者枚举类型而不是浮点类型吗?
□ 循环下标的名字有意义吗?
□ 循环避免了下标串话问题吗?
退出循环
□ 循环在所有可能的条件下都能终止吗?
□ 如果你建立了某种安全计数器标准,循环使用安全计数器了吗?
□ 循环的退出条件清晰吗?
□ 如果使用了break或者continue,那么它们用对了吗?
核对表:不常见的控制结构
return
□ 每一个子程序都仅有在必要的时候才使用return吗?
□ 使用return有助于增强可读性吗?
递归
□ 递归子程序中包含了停止递归的代码吗?
□ 子程序用安全计数器来确保该子程序能停下来吗?
□ 递归只位于一个子程序里面吗?
□ 子程序的递归深度处于程序栈容量可以满足的限度内吗?
□ 递归是实现了子程序的最佳方法吗?它要好于简单的迭代吗?
goto
□ 是否只有在万不得已的时候才使用goto?如果使用了goto,是否仅仅是出于增强可读性和可维护性呢?
□ 如果是出于效率因素而使用的goto,那么对这种效率上的提升做出衡量并且加以说明了吗?
□ 一个子程序里最多只用了一个goto标号吗?
□ 所有的goto都向前跳转,而不是向后跳转吗?
□ 所有的goto标号都用到了吗?
核对表:表驱动法
□ 你考虑过把表驱动法作为复杂逻辑的替换方案吗?
□ 你考虑过把表驱动法作为复杂继承结构的替换方案吗?
□ 你考虑过把表数据存储在外部并在运行期间读入,以便在不修改代码的情况下就可以改变这些数据吗?
□ 如果无法用一种简单的数组索引(像age示例中那样)去访问表,那么你把计算访问键值的功能提取成单独的子程序,而不是在代码中重复地计算键值吗?
核对表:控制结构相关事宜
□ 表达式中用的是true和false,而不是1和0吗?
□ 布尔值和true以及false做比较是隐式进行的吗?
□ 对数值做比较是显式进行的吗?
□ 有没有通过增加新的布尔变量、使用布尔函数和决策表来简化表达式?
□ 布尔表达式是用肯定形式表达的吗?
□ 括号配对吗?
□ 在需要用括号明确的地方都使用了括号吗?
□ 把逻辑表达式全括起来了吗?
□ 判断是按照数轴顺序编写的吗?
□ 如果适当的话,Java中的判断用的是a.equals(b)方式,而没有用a==b方式吗?
□ 空语句表述得明显吗?
□ 用重新判断部分条件、转换成if-then-else或者case语句、把嵌套码提取成单独的子程序、换用一种更面向对象的设计或者其他的改进方法来简化嵌套语句了吗?
□ 如果一个子程序的决策点超过10个,那么能不能提出不重新设计的理由吗?
核对表:质量保证计划
□ 是否确定出对项目至关重要的特定质量特性了?
□ 是否让其他人意识到项目的质量目标了?
□ 是否能够区分质量的外在特性和内在特性?
□ 是否考虑过某些特性与其他特性相互制约或相互促进的具体方式?
□ 在软件开发的每一个阶段,项目是否要求针对不同错误类型使用不同的错误检测技术?
□ 项目计划中是否有计划有步骤地保证了软件在开发各阶段的质量?
□ 是否使用了某种质量评估方法,并由此确定质量是改善了还是下降了?
□ 管理层是否能理解为了质量保证在前期消耗额外成本,目的就是在项目后期减少成本?
核对表:有效的结对编程
□ 是否己经有一个编码规范,以便让程序员始终把精力集中到编程,而不是编码风格的讨论上?
□ 结对的双方是否都积极地参与?
□ 是否避免了滥用结对编程,而是选择那些能够从中获得好处的工作进行结对编程?
□ 是否有规律地对人员和工作任务进行轮换?
□ 结对组合是否在开发速度和个性方面互相匹配?
□ 是否有一个组长专注于项目管理以及与项目外其他人的沟通?
核对表:有效的详查
□ 你是否有一个核对表,能让评论员将注意力集中于曾经发生过问题的领域?
□ 你是否专注于找出错误,而不是修正它们?
□ 你是否考虑制定某些视角或者场景,以帮助评论员在准备工作的时候集中注意力?
□ 你是否给予评论员足够的时间在详查会议之前进行准备,是否每一个人都做了准备?
□ 是否每一个参与者都扮演一个明确的角色——主持人、评论员及记录员等?
□ 会议是否以某种高效的速度进行?
□ 会议是否限制在两个小时以内?
□ 是否所有详查会议的参与者都接受了如何进行详查的针对性培训?是否主持人接受了有关主持技巧方面的针对性培训?
□ 是否将每次详查所发现的错误数据都收集起来,使你能调整本组织以后使用的核对表?
□ 是否收集了准备速度和详查速度方面的数据,以便你去优化以后的准备和详查工作?
□ 是否每次详查中被指派下去的活动都被正确跟进了,无论是通过主持人自己还是一次重新详查?
□ 管理层是否理解他们不应该参与详查会议?
□ 是否有一个用于保证修正正确性的跟进计划?
核对表:测试用例
□ 类和子程序所对应的每一项需求是否都有相应的测试用例?
□ 类和子程序所对应的每一个设计元素是否都有相应的测试用例?
□ 每行代码是否被至少一个测试用例所测试?你是否通过计算测试到每行代码所需要的最少测试用例数量来验证这一点?
□ 所有已定义-已使用路径是否至少被一个测试用例测试过了?
□ 是否测试过哪些不太可能正确的数据流格式,例如已定义-已定义、已定义-已退出以及已定义-已销毁?
□ 是否有一张常见错误列表,并据此编写测试用例以检测过去经常出现的错误?
□ 所有的简单边界是否都已经测试过了:最大、最小以及off-by-one?
□ 是否测试了组合边界——即,多个输入数据的组合导致输出数据过小或过大?
□ 测试用例是否检查了数据类型错误,例如一个薪水记账程序里的雇员数量是负数?
□ 是否测试了那些中规中矩的典型数值?
□ 是否测试了最小正常形式?
□ 是否测试了最大正常形式?
□ 是否检查了与旧数据的兼容性?以及是否对旧硬件、旧操作系统版本以及其他旧版本的接口进行了测试?
□ 测试用例是否容易手工校验?
核对表:关于调试的建议
寻找缺陷的方法
□ 使用所有可用数据来构造你的假设。
□ 不断提炼产生错误的测试用例。
□ 在自己的单元测试族中测试代码。
□ 借助可以获得的任何工具。
□ 用不同的方式重现错误。
□ 通过产生更多的数据来构造更多的假设。
□ 利用证伪假设的测试结果。
□ 用头脑风暴的方式找出可能的假设。
□ 在桌上放一个记事本,把需要尝试的事情列出来。
□ 缩小被怀疑有问题的代码区域。
□ 对之前出现过问题的类和子程序保持警惕。
□ 检查最近修改的代码。
□ 扩展被怀疑有问题的代码区域。
□ 采用增量集成。
□ 检查常见的缺陷。
□ 和其他人一起讨论你的问题。
□ 抛开问题休息一下。
□ 在使用快速肮脏调试法的时候,要设置一个时间上限。
□ 列出所有的蛮力调试方法,逐条应用。
解决语法错误的方法
□ 不要太信任编译器信息中给出的行号。
□ 不要太信任编译器信息。
□ 不要太信任编译器所给出的第二条出错信息。
□ 分而治之,各个击破。
□ 使用具有语法分析功能的编辑器来找出位置错误的注释和引号。
修正缺陷的方法
□ 在动手之前先理解程序。
□ 理解整个程序而非具体问题。
□ 验证对错误的分析。
□ 放松一下。
□ 要保存最初的源代码。
□ 治本,而非治标。
□ 只有当理由充分的时候才去修改代码。
□ 一次只做一个改动。
□ 检查自己所做的修订。
□ 添加单元测试来暴露代码中的缺陷。
□ 找出类似的缺陷。
调试的一般方法
□ 你是否会把调试看做是能让你更好地理解程序、错误、代码质量和解决问题方法的良机?
□ 你是否会避免采用随机尝试查找错误或迷信式的调试方法?
□ 你是否假设错误是你自己造成的?
□ 你是否使用了科学的方法将间歇性的错误稳定下来?
□ 你是否使用了科学的方法来寻找缺陷?
□ 你在寻找缺陷的时候会使用多种不同的方法么?还是每次都是用相同的方法?
□ 你会验证你的修改是否正确么?
□ 你会在调试中使用编译器警告信息、执行性能分析、利用测试框架和交互式调试方法么?
核对表:重构的理由
□ 代码重复。
□ 子程序太长。
□ 循环太长或者嵌套太深。
□ 类的内聚性太差。
□ 类的接口的抽象层次不一致。
□ 参数表中参数太多。
□ 类的内部修改往往局限于某个部分。
□ 需要对多个类进行并行修改。
□ 对继承体系的并行修改。
□ 需要对多个case语句进行并行修改。
□ 相关的数据项只是被放在一起,没有组织到类中。
□ 成员函数更多地使用了其他类的功能,而非自身类的。
□ 过于依赖基本数据类型。
□ 一个类不做什么事。
□ 连串传递流浪数据的子程序
□ 中间人对象什么也不干。
□ 某个类同其他类关系过于密切。
□ 子程序的命名太差。
□ 数据成员被设置为公用。
□ 派生类仅仅使用了基类的一小部分成员函数。
□ 用注释来掩饰拙劣的代码。
□ 使用了全局变量。
□ 在子程序调用前使用设青代码,调用后使用收尾代码。
□ 程序包含的某些代码似乎在将来某个时候才会被用到。
核对表:重构总结
数据级的重构
□ 用具名常量来代替神秘数值。
□ 用更明确或更具信息量的名字来重命名变量。
□ 将表达式内联化。
□ 用函数来代替表达式。
□ 引入中间变量。
□ 将多用途变量转换为多个单一用途变量。
□ 使用局部变量实现局部用途而不是使用参数。
□ 将基础数据类型转化为类。
□ 将一组类型码转化为类或是枚举类型。
□ 将一组类塑码转化为含派生类的类。
□ 将数组转化为对象。
□ 封装群集。
□ 用数据类替代传统记录。
语句级的重构
□ 分解布尔表达式。
□ 将复杂的的布尔表达式转换为命名精确的布尔函数。
□ 将条件语句中不同部分中的重复代码合并。
□ 使用break或return而不是循环控制变量。
□ 在嵌套的if-then-else语句中一旦知道结果就立刻退出,而不是仅仅赋一个返回值。
□ 用多态来代替条件语句(尤其是重复的case语句)。
□ 创建并使用空对象代替对空值的检测。
子程序级的重构
□ 提取子程序。
□ 将于程序代码内联化。
□ 将冗长的于程序转化为类。
□ 用简单的算法替代复杂算法。
□ 增加参数。
□ 减少参数。
□ 将查询操作同修改操作区分开来。
□ 合并功能相似的子程序,并用参数来区分他们。
□ 通过传递不同的参数使于程序体现不同的功能。
□ 传递整个对象而非特定成员。
□ 传递特定成员而非整个对象。
□ 封装向下转型操作。
类实现的重构
□ 将值对象改为引用对象。
□ 将引用对象改为值对象。
□ 用数据初始化来代替虚函数。
□ 改变成员函数或数据的位置。
□ 将特定代码提出生成派生类。
□ 将相似的代码合并起来放到基类中。
类接口的重构
□ 将某成员子程序放到另一个类中。
□ 将一个类转化成两个。
□ 删除某个类。
□ 隐藏委托关系。
□ 去掉中间人。
□ 用委托代替继承。
□ 用继承代替委托。
□ 引入外部子程序。
□ 引入扩展类。
□ 封装暴露在外的成员变量。
□ 对不能修改的成员去掉Set()函数。
□ 隐藏在类的外部不会使用的成员函数。
□ 封装不会用到的成员函数。
□ 如果基类和派生类的代码实现相似,将二者合并。
系统级的重构
□ 为无法控制的数据创建明确的索引源。
□ 将单向类联系改为双向类联系。
□ 将双向的类联系改为单向类联系。
□ 使用工厂函数而非简单的构造函数。
□ 用异常代替错误代码,或者反其道而行之。
核对表:安全的重构
□ 每一改变都是系统改变策略的一部分么?
□ 在重构之前,你保存了初始代码了么?
□ 你是否保持较小的重构步伐?
□ 你是否同一时间只处理一项重构?
□ 在重构时你是否把要做的事情一条条列了出来?
□ 你是否设置了一个停车场,把你在重构时所想到的任何东西记下来?
□ 在每次重构后你会重新测试么?
□ 如果所做的修改非常复杂,或者影响到了关键代码,你会重新检查这些修改么?
□ 你是否考虑过特定重构的风险,并以此来调整你的重构方法?
□ 你所做的修改是提升还是降低了程序的内在质量?
□ 你是否避免了将重构作为先写后改的代名词,或者作为拒绝重写拙劣代码的托词?
核对表:代码调整策略
程序整体性能
□ 你是否考虑通过修改需求来提高性能?
□ 你是否考虑通过修改程序的设计来提高性能?
□ 你是否考虑通过修改类的设计来提高性能?
□ 你是否考虑过减少程序同操作系统的交互从而提高性能?
□ 是否考虑过避免I/O操作以提高性能?
□ 是否考虑使用编译型语言替代解释型语言以提高性能?
□ 是否考虑过使用编译器优化选项来提高性能?
□ 是否考虑过使用不同的硬件来提高性能?
□ 是否仅仅将代码调整看做是解决问题的最后一招?
代码调整方法
□ 在开始调整代码之前,程序是完全正确的吗?
□ 在调整之前是否测量过性能瓶颈在什么地方?
□ 是否记录了每一次修改所产生的效果?
□ 如果没有带来预期的性能提高,你是否放弃了所做的代码调整改变?
□ 你是否对每一个性能瓶颈都进行不止一次的修改尝试——也就是说,你是在反复进行代码调整么?
核对表:代码调整方法
同时改善代码执行速度和规模
□ 用查询表替换复杂逻辑。
□ 合并循环。
□ 使用整型变量而非浮点变量。
□ 在编译时初始化数据。
□ 使用正确的常量类型。
□ 预先计算结果。
□ 删除公共子表达式。
□ 将关键子程序代码转化为某种低级语言代码。
仅仅提高代码执行速度
□ 在知道答案后就停止执行判断。
□ 根据各种情况的出现频率对case语句和if-then-else串排序。
□ 比较相似逻辑结构的性能。
□ 使用惰性求值。
□ 将循环中的if判断转到外部。
□ 展开循环。
□ 将循环内部所做的工作减少到最低限度。
□ 在查找循环中使用哨兵。
□ 把执行最为频繁的循环放在嵌套循环的最里面。
□ 减轻内层循环的强度。
□ 将多维数组改为一维数组。
□ 最大限度减少数组索引。
□ 为数据类型扩充索引。
□ 对频繁使用的值进行缓存。
□ 利用代数恒等式。
□ 降低逻辑和数学表达式的强度。
□ 注意系统调用。
□ 用内联子程序重写代码。
核对表:配置管理
概要
□ 你的软件配置管理计划是否用于帮助程序员,并能将额外负担降至最低?
□ 你的软件配置管理方法是否避免了对项目的过度控制?
□ 你是否将一些变更请求聚成一组?无论采用非正式的方法(如创建一份未决更改的列表)还是更加系统的方法(如设立变更控制委员会)。
□ 你系统地评估了每一项提交的更改对成本、计划和质量的影响吗?
□ 你是否把重大的变更看做是需求分析还不够完备的警报信号?
工具
□ 你用版本控制软件来促进配置管理吗?
□ 你用版本控制软件来减少团队工作中的协调问题吗?
备份
□ 你定期地备份项目中的所有资料吗?
□ 你定期地把项目备份数据转移到off-site storage里了吗?
□ 所有的资料,包括源代码、文档、图表和重要的笔记都得到备份了吗?
□ 你测试过备份与恢复的过程吗?
核对表:集成
集成策略
□ 该策略是否指明了集成于系统、类、子程序时应该采用的最优顺序?
□ 集成的顺序是否与构建顺序协调,以便在适当的时候准备好供集成的类?
□ 该策略是否易于诊断缺陷?
□ 该策略是否使脚手架最少?
□ 所选的策略是否好于其他方式?
□ 组件之间的接口是否有明确定义?(定义接口 不是集成的任务,但要验证这些接口的定义是否明确。)
Daily build与冒烟测试
□ 项目是否经常build——理想情况下,每天build一次——以支持增量集成?
□ 每次build 后是否都运行冒烟测试,让你知道这个build 能否工作?
□ 你是否己使build 和冒烟测试自动进行?
□ 开发人员是否频繁地check in他们的代码——两次check in之间最多间隔一两天?
□ 冒烟测试是否与代码同步更新,随代码发展而发展?
□ 破坏build是罕见事件吗?
□ 是否在有压力的情况下,也对软件进行build和冒烟测试?
核对表:编程工具
□ 你有一套有效的IDE吗?
□ 你的IDE集成了:源代码控制、build/测试/除错工具,以及其他有用的功能吗?
□ 你有能自动进行常用的重构操作的工具吗?
□ 你是否使用版本控制工具,对源代码、内容、需求、设计、项目计划及其他的项目构件进行管理?
□ 如果你正面对超大型的项目,你是否使用了数据字典或者其他“包含系统中使用的各个类的权威描述”的中央知识库。
□ 当可以用到代码库时,你是否考虑用它来代替“编写定制代码”?
□ 你是否充分利用了交互式除错器?
□ 你是否使用make或其他“依赖关系控制软件”,用来高效并可靠地build程序?
□ 你的测试环境包含有自动化的测试框架、自动测试生成器、覆盖率监视器、系统扰动器、diff工具,以及缺陷跟踪软件吗?
□ 你有没有制造过定制工具——能满足特定项目的需求的那种,特别是能自动执行重复任务的工具?
□ 总而言之,你的工作环境有没有从“充足的工具支援”中获益?
核对表:布局
一般问题
□ 格式化主要是为了展现代码的逻辑结构吗?
□ 你的布局方案能统一地运用吗?
□ 你的布局方案能让代码易于维护吗?
□ 你的布局方案是否有利于代码的可读性?
控制结构的布局
□ 你的代码中避免begin-end对或{}的双重缩进了吗?
□ 相邻的块之间用空行分隔了吗?
□ 对复杂表达式格式化时考虑到可读性吗?
□ 对只有一条语句的块的布局始终如一吗?
□ case语句与其他控制结构的格式化保持一致了吗?
□ 对goto语句的格式化是否让其显眼了呢?
单条语句的布局
□ 为逻辑表达式、数组下标和子程序参数的可读性而使用空格了吗?
□ 不完整的语句在行末是以明显有错的方式结束吗?
□ 后续行按照标准数目缩进了吗?
□ 每行顶多只有一条语句吗?
□ 所写的每个语句都没有副作用吗?
□ 每行顶多只声明一个数据吗?
注释的布局
□ 注释与其所注释的代码的缩进量相同吗?
□ 注释的风格便于维护吗?
子程序的布局
□ 你对每个子程序参数的格式化方式便于看懂、修改、注释吗?
□ 采用空行分隔子程序的各部分了吗?
类、文件和程序的布局
□ 多数类和文件之间是一一对应的关系吗?
□ 如果文件内有多个类,各类中的子程序披类分组了吗?各类都清楚标识了吗?
□ 文件中的子程序用空行清楚地分开了吗?
□ 在没有更好的组织形式的场合,所有子程序都按字母顺序排列了吗?
核对表:自说明代码
类
□ 你的类接口体现出某种一致的抽象吗?
□ 你的类名有意义吗,能表明其中心意图吗?
□ 你的类接口对于如何使用该类显而易见吗?
□ 你的类接口能抽象到不需考虑其实现过程吗?能把类看成是黑盒吗?
子程序
□ 你的每个子程序名都能准确地指示该子程序确切干些什么吗?
□ 你的各子程序的任务明确吗?
□ 若各子程序中自成一体后更有用,你都将其各自独立出来了吗?
□ 每个子程序的接口都清晰明了吗?
数据名
□ 类型名描述有助于说明数据声明吗?
□ 你的变量名有意义吗?
□ 变量只用在其名字所代表意义的场合吗?
□ 你的循环变量名能给出更多信息,而不是i、j、k之类的吗?
□ 你用了名字有意义的枚举类型,而非临时拼凑的标识或者布尔变量吗?
□ 用具名常量代替神秘数值或者字符串了吗?
□ 你的命名规范能区分类型名、枚举类型、具名常量、局部变量、类变量以及全局变量吗?
数据组织
□ 你根据编程清晰的需要,使用了额外变量来提高清晰度吗?
□ 你对某变量的引用集中吗?
□ 数据类型简化到了最低复杂度吗?
□ 你是通过抽象访问子程序(抽象数据类型)来访问复杂数据吗?
控制
□ 代码中的正常执行路径很清晰吗?
□ 相关语句放在一起了吗?
□ 相对独立的语句组打包为子程序了吗?
□ 正常情况的处理位于if语句之后,而非在else子句中吗?
□ 控制结构简单明了,以使复杂度最低吗?
□ 每个循环完成且仅完成二个功能,是像定义良好的子程序那么做吗?
□ 嵌套层次是最少吗?
□ 逻辑表达式通过额外添加布尔变量、布尔函数和功能表简化了吗?
布局
□ 程序的布局能表现出其逻辑结构吗?
设计
□ 代码直截了当吗?是不是避免了自作聪明或新花样?
□ 实现细节尽可能隐藏了吗?
□ 程序是尽可能采用问题领域的术语,而非按照计算机科学或者编程语言的术语编写的吗?
核对表:好的注释技术
一般问题
□ 别人拿起你的代码就能立刻明白其意吗?
□ 你的注释是在解释代码用意,或概况代码在做什么,而非简单重复代码吗?
□ 采用了伪代码编程法来减少注释时间吗?
□ 是重写有玄机的代码,而非为其做注释吗?
□ 你的注释能否同代码一起更新?
□ 注释清楚正确吗?
□ 你的注释风格便于修改注释吗?
语句和段落
□ 代码避免用行尾注释了吗?
□ 注释是着力说明为什么而非怎么样嘛?
□ 注释为将要阅读代码的人们做好准备了吗?
□ 每个注释都有其用处吗?删掉抑或改进了多余的、无关紧要的或随意的注释没有?
□ 是否注释了代码的非常规之处?
□ 避免使用缩略语了吗?
□ 主次注释区别明显吗?
□ 含错误码或未公开的代码特性有注释吗?
数据声明
□ 对数据声明的注释说明了数值单位吗?
□ 数值数据的取值范围注释出来了吗?
□ 注释出了编码含义吗?
□ 对输入数据的限制有注释吗?
□ 对标志位做注释了吗?
□ 在各全局变量声明的地方对其做注释了吗?
□ 各全局变量是通过命名规范、注释(或者两者兼用)来标识其意义吗?
□ 神秘数值是否以具名常量或变量代替,而非只是标注之?
控制结构
□ 控制语句都注释了吗?
□ 冗长或者复杂的控制结构结尾处有注释吗?抑或可能的话,简化之从而省去注释了吗?
子程序
□ 各子程序的意图都注释出了吗?
□ 子程序的其他有关情况(诸如输入输出数据、接口假设、局限性、纠错、全局效果和算法来源)都注释出来了吗?
文件、类和程序
□ 程序有简短的文档(就像在“以书本为范例”中说明的那样)给出程序组织的概述吗?
□ 每个文件的用途都有说明吗?
□ 作者姓名、email及电话在代码清单中都有吗?