Code Complete[Note]Keypoints

Posted on 2016-08-29

第1章 欢迎进入软件构建的世界

* 软件构建是软件开发的核心活动;构建活动是每个项目中唯一一项必不可少的工作。
* 软件构建的主要活动包括:详细设计、编码、调试、集成、开发者测试(developer testing) (包括单元测试和集成测试)。
* 构建也常被称作“编码” 和“编程”。
* 构建活动的质量对软件的质量有着实质性的影响。
* 最后,你对“如何进行构建” 的理解程度,决定了你这名程序员的优秀程度——这就是本书其余部分的主题了。

第2章 用隐喻来更充分地理解软件开发

* 隐喻是启示而不是算法。因此它们往往有一点随意(sloopy) 。
* 隐喻把软件开发过程与其他你熟系的活动联系在一起,帮助你更好地理解。
* 有些隐喻比其他一些隐喻更贴切。
* 通过把软件的构建过程比作是房屋的建设过程,我们可以发现,仔细的准备是必要的,而大型项目和小型项目之间也是有差异的。
* 通过把软件开发中的实践比作是智慧工具箱中的工具,我们又发现,每位程序员都有许多工具,但并不存在任何一个能适用于所有工作的工具,因地制宜地选择正确工具是成为能有效编程的程序员的关键。
* 不同的隐喻彼此并不排斥,应当使用对你最有益处的某种隐喻组合。

第3章 三思而后行:前期准备

* 构建活动的准备工作的根本目标在于降低风险。要确认你的准备活动是在降低风险,而非增加风险。
* 如果你想开发高质量的软件,软件开发过程必须由始至终关注质量。在项目初期关注质量,对产品质量的正面影响比在项目末期关注质量的影响要大。
* 程序员的一部分工作是教育老板和合作者,告诉他们软件开发过程,包括在开始编程之前进行充分准备的重要性。
* 你所从事的软件项目的类型对构建活动的前期准备有重大影响—— 许多项目应该是高度迭代式的,某些应该是序列式的。
* 如果没有明确的问题定义,那么你可能会在构建期间解决错误的问题。
* 如果没有做完良好的需求分析工作,你可能没能察觉待解决的问题的重要细节。如果需求变更发生在构建之后的阶段,其代价是“在项目早期更改需求” 的20至100倍。因此在幵始编程之前,你要确认“需求” 已经到位了。
* 如果没有做完良好的架构设计,你可能会在构建期间用错误的方法解决正确的问题。架构变更的代价随着“为错误的架构编写的代码数量”增加而增加,因此,也要确认“架构” 已经到位了。
* 理解项目的前期准备所采用的方法,并相应地选择构建方法。

第4章 关键的“构建”决策

* 每种编程语言都有其优点和弱点。要知道你使用的语言的明确优点和弱点。
* 在开始编程之前,做好一些约定(convention) 。 “改变代码使之符合这些约定” 是近乎不可能的。
* “构建的实践方法” 的种类比任何单个项目能用到的要多。有意识地选择最适合你的项目的实践方法。
* 问问你自己,你采用的编程实践是对你所用的编程语言的正确响应,还是受它的控制?请记得“深入一种语言去编程”,不要仅“在一种语言上编程”。
* 你在技术浪潮中的位置决定了哪种方法是有效的—— 甚至是可能用到的。确定你在技术浪潮中的位置,并相应调整计划和预期目标。

第5章 软件构建中的设计

* 软件的首要技术使命就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助。
* 简单性可以通过两种方式来获取:一是减少在同一时间所关注的本质性复杂度的量,二是避免生成不必要的偶然的复杂度。
* 设计是一种启发式的过程。固执于某一种单一方法会损害创新能力,从而损害你的程序。
* 好的设计都是迭代的。你尝试设计的可能性越多,你的最终设计方案就会变得越好。
* 信息隐藏是个非常有价值的概念。通过询问“我应该隐藏些什么?  ”能够解决很多困难的设计问题。
* 很多有用有趣的、关于设计的信息存在于本书之外。这里所给出的观点只是对这些有价值资源的一点提示而已。

第6章 可以工作的类

* 类的接口应提供一致的抽象。很多问题都是由于违背该原则而引起的。
* 类的接口应隐藏一些信息—— 如某个系统接口、某项设计决策、或一些实现细节。
* 包含往往比继承更为可取—— 除非你要对“是一个/is a” 的关系建模。
* 继承是一种有用的工具,但它却会增加复杂度,这有违于软件的首要技术使命—— 管理复杂度。
* 类是管理复杂度的首选工具。要在设计类时给予足够的关注,才能实现这一目标。

第7章 高质量的子程序

* 创建子程序最主要的目的是提高程序的可管理性,当然也有其他一些好的理由。其中,节省代码空间只是一个次要原因;提高可读性、可靠性和可修改性等原因都更重要一些。
* 有时候,把一些简单的操作写成独立的子程序也非常有价值。
* 子程序可以按照其内聚性分为很多类,而你应该让大多数子程序具有功能上的内聚性,这是最佳的一种内聚性。
* 子程序的名字是它的质量的指示器。如果名字糟糕但恰如其分,那就说明这个子程序设计得很差劲。如果名字糟糕而且又不准确,那么它就反映不出程序是干什么的。不管怎样,糟糕的名字都意味着程序需要修改。
* 只有在某个子程序的主要目的是返回由其名字所描述的特定结果时,才应该使用函数。
* 细心的程序员会非常谨慎地使用宏,而且只在万不得已时才用。

第8章 防范式编程

* 最终产品代码中对错误的处理方式要比“垃圾进,垃圾出”复杂得多。
* 防御式编程技术可以让错误更容易发现、更容易修改,并减少错误对产品代码的破坏。
* 断言可以帮助人尽早发现错误,尤其是在大型系统和高可靠性的系统中,以及快速变化的代码中。
* 关于如何处理错误输入的决策是一项关键的错误处理决策,也是一项关键的高层设计决策。
* 异常提供了一种与代码正常流程角度不同的错误处理手段。如果留心使用异常,它可以成为程序员们知识工具箱中的一项有益补充,同时也应该在异常和其他错误处理手段之间进行权衡比较^
* 针对产品代码的限制并不适用于开发中的软件。你可以利用这一优势在开发中添加有助于更快地排查错误的代码。

第9章 伪代码编程过程

* 创建类和子程序通常都是一个迭代的过程。在创建子程序的过程中获得的认识常常会反过来影响类的设计。
* 编写好的伪代码需要使用易懂的英语,要避免使用特定编程语言中才有的特性,同时要在意图的层面上写伪代码(即描述该做什么,而不是要怎么去做)。
* 伪代码编程过程是一个行之有效的做详细设计的工具,它同时让编码工作更容易。伪代码会直接转化为注释,从而确保了注释的准确度和实用性。
* 不要只停留在你所想到的第一个设计方案上。反复使用伪代码做出多种方案,然后选出其中最佳的一种方案再开始编码。
* 每一步完成后都要检查你的工作成果,还要鼓励其他人帮你来检查。这样你就会在投入精力最少的时候,用最低的成本发现错误。

第10章 使用变量的一般事项

* 数据初始化过程很容易出错,所以请用本章描述的初始化方法来避免由于非预期的初始值而造成的错误。
* 最小化每个变量的作用域。把同一变量的引用点集中在一起。把变量限定在子程序或类的范围之内。避免使用全局数据。
* 把使用相同变量的语句尽可能集中在一起。
* 早期绑定会减低灵活性,但有助于减小复杂度。晚期绑定可以增加灵活性,同时增加复杂度。
* 把每个变量用于唯一的用途。

第11章 变量名的力量

* 好的变量名是提高程序可读性的一项关键要素。对特殊种类的变景,比如循环下标和状态变量,需要加以特殊的考虑。
* 名字要尽可能地具体。那些太模糊或者太通用以致于能够用于多种目的的名字通常都是很不好的。
* 命名规则应该能够区分局部数据、类数据和全局数据。它们还应当可以区分类型名、具名常量、枚举类型名字和变量名。
* 无论做哪种类型项目,你都应该釆用某种变量命名规则。你所采用的规则的种类取决于你的程序的规模,以及项目成员的人数。
* 现代编程语言很少需要用到缩写。如果你真的要使用缩写,请使用项目缩写词典或者标准前缀来帮助理解缩写。
* 代码阅读的次数远远多于编写的次数。确保你所取的名字更侧重于阅读方便而不是编写方便。

第12章 基本数据类型

* 使用特定的数据类型就意味着要记住适用于各个类型的很多独立的原则。用本章的核对表来确认你己经对常见问题做了考虑。
* 如果你的语言支持,创建自定义类型会使得你的程序更容易修改,并更具有自描述性。
* 当你用typedef或者其等价方式创建了一个简单类型的时候,考虑是否更应该创建一个新的类。

第13章 不常见的数据类型

* 结构体可以使得程序更简单、更容易理解,以及更容易维护。
* 每当你打算使用结构体的时候,考虑采用类是不是会工作得更好。
* 指针很容易出错。用访问器子程序或类以及防御式编程实践来保护自己的码。
* 避免用全局变量,不只是因为它们很危险,还是因为你可以用其他更好的方法来取代它们。
* 如果你不得不使用全局变量,那么就通过访问器子程序来使用它。访问器子程序能为你带来全局变量所能带来的一切优点,还有一些额外好处。

第14章 组织直线型代码

* 组织直线型代码的最主要原则是按照依赖关系进行排列。
* 可以用好的子程序名、参数列表、注释,以及—— 如果代码足够重要—— 内务管理变量来让依赖关系变得更明显。
* 如果代码之间没有顺序依赖关系,那就设法使相关的语句尽可能地接近。

第15章 使用条件语句

* 对于简单的if-else语句,请注意if子句和else子句的顺序,特别是用它来处理大量错误的时候。要确认正常的情况是清晰的或者使
* 对于if-then-else语句串和case语句,选择一种最有利于阅读的排序。
* 为了捕捉错误,可以使用case语句中的default子句(默认子句)用if-then-else语句串中的最后那个else子句。
* 各种控制结构并不是生来平等的。请为代码的每个部分选用最合适的控制结构。

第16章 控制循环

* 循环很复杂。保持循环简单将有助于别人阅读你的代码。
* 保持循环简单的技巧包括:避免使用怪异的循环、减少嵌套层次、让入口和出口一目了然、把内务操作代码放在一处。
* 循环下标很容易被滥用。因此命名要准确,并且要把它们各自仅用于一个用途。
* 仔细地考虑循环,确认它在每一种情况下都运行正常,并且在所有可能的条件下都能退出。

第17章 不常见的控制结构

* 多个return可以增强子程序的可读性和可维护性,同时可以避免产生很深的嵌套逻辑。但是使用它的时候要多加小心。
* 递归能够很优雅地解决一小部分问题。对它的使用也要倍加小心。
* 在少数情况下,goto是编写可读性和可维护代码的最佳方法。但这种情况非常罕见。除非万不得已,不要使用goto。

第18章 表驱动方法

* 表提供了一种复杂的逻辑和继承结构的替换方案。如果你发现自己对某个应用程序的逻辑或者继承树关系感到困惑,那么问问自己它是否可以通过一个查询表来加以简化。
* 使用表的一项关键决策是决定如何去访问表。你可以采取直接访问、索引访问或者阶梯访问。
* 使用表的另一项关键决策是决定应该把什么内容放入表中。

第19章 一般控制问题

* 使布尔表达式简单可读,将非常有助于提高你的代码的质量。
* 深层次的嵌套使得子程序变得难以理解。所幸的是’你可以相对容易地避免这么做。
* 结构化编程是一种简单并且仍然适用的思想:你可以通过把顺序、选择和循环三者组合起来而开发出任何程序。
* 将复杂度降低到最低水平是编写高质量代码的关键。

第20章 软件质量概述

* 开发高质量代码最终并没有要求你付出更多,只是你需要对资源进行重新分配,以低廉的成本来防止缺陷出现,从而避免代价高昂的修正工作。
* 并非所有的质量保证目标都可以全部实现。明确哪些目标是你希望达到的并就这些目标和团队成员进行沟通。
* 没有任何一种错误检测方法能够解决全部问题,测试本身并不是排除错误的最有效方法。成功的质量保证计划应该使用多种不同的技术来检查各种不同类型的错误。
* 在构建期间应当使用一些有效的质量保证技术,但在这之前,一些具有同样强大功能的质量保证技术也是必不可少的。错误发现越早,它与其余代码的纠缠就越少,由此造成的损失也越小。
* 软件领域的质量保证是面向过程的。软件开发与制造业不一样,在这里并不存在会影响最终产品的重复的阶段,因此,最终产品的质量受到开发软件所用的过程的控制。

第21章 协同构造

* 协同幵发实践往往能比测试发现更多的缺陷,并且更有效率。
* 协同开发实践所发现错误的类型通常跟测试所发现的不同,这意味着你需要同时使用详查和测试来保证你软件的质量。
* 正式检查通过运用核对表、准备工作、明确定义的角色以及对方法的持续改善,将缺陷侦测的效率提升至最高。它往往能比走查发现更多的缺陷。
* 通常,结对编程拥有和详查相同的成本,并能产生质量相当的代码。当需要缩短开发周期的时候,结对编程就非常有价值。相对于单独工作来说, 有些开发人员更喜欢结对工作。
* 正式检查可以应用在除代码之外的很多工作成果上,例如需求、设计以及测试用例等。
* 走査和代码阅读是详查的替代方案。代码阅读更富有弹性,能有效地利用每个人的时间。

第22章 开发者测试

* 开发人员测试是完整测试策略的一个关键部分。独立测试也很重要,但这一主题超出了本书的范围。
* 同编码之后编写测试用例相比较,编码开始之前编写测试用例,工作量和花费的时间差不多,但是后者可以缩短缺陷-侦测-调试,修正这一周期。
* 即使考虑到了各种可用的测试手段,测试仍然只是良好软件质量计划的一部分。高质量的开发方法至少和测试一样重要,这包括尽可能减少需求和设计阶段的缺陷。在检测错误方面,协同开发的成效至少与测试相当。这些方法所检测错误的类型也各不相同。
* 你可以根据各种不同的思路来产生很多测试用例,这些思路包括基础测试、数据流分析、边界分析、错误数据类型以及正确数据类型等。你还可以通过猜测错误的方式得到更多的测试用例。
* 错误往往集中在少数几个容易出错的类和子程序上。找出这部分代码,重新设计和编写它们。
* 测试数据本身出错的密度往往比被测代码还要高。查找这种错误完全是浪费时间,又不能对代码有所改善,因此测试数据里面的错误更加让人烦恼。要像写代码一样小心地开发测试用例,这样才能避免产生这种问题。
* 自动化测试总体来说是很有用的,也是进行回归测试的基础。
* 从长远来看,改善测试过程的最好办法就是将其规范化,并对其进行评估,然后用从评估中获得的经验教训来改善这个过程。

第23章 调 试

* 调试同整个软件开发的成败息息相关。最好的解决之道是使用本书中介绍的其他方法来避免缺陷的产生。然而,花点时间来提高自己的调试技能还是很划算的,因为优秀和拙劣的调试表现之间的差距至少是10:1。
* 要想成功,系统化地查找和改正错误的方法至关重要。要专注于你的调试工作,让每一次测试都能让你朝着正确的方向前进一步。要使用科学的调试方法。
* 在动手解决问题之前,要理解问题的根本。胡乱猜测错误的来源和随机修改将会让你的程序陷入比刚开始调试时更为糟糕的境地。
* 将编译器警告级别设置为最严格,把警告信息所报告的错误都改正。如果你忽略了明显的错误,那么要改正那些微妙的错误就会非常麻烦。
* 调试工具对软件开发而言是强有力的支持手段。找出这些工具并加以应用,当然,请记得在调试的时候开动脑筋。

第24章 重构

* 修改是程序一生都要面对的事情,不仅包括最初的开发阶段,还包括首次发布之后。
* 在修改中软件的质量要么改进,要么恶化。软件演化的首要法则就是代码演化应当提升程序的内在质量。
* 重构成功之关键在于程序员应学会关注那些标志着代码需要重构的众多的警告或“代码臭味”。
* 重构成功的另一要素是程序员应当掌握大量特定的重构方法。
* 重构成功的最后要点在于要有安全重构的策略。一些重构方法会比其他重构方法要好。
* 开发阶段的重构是提升程序质量的最佳时机,因为你可以立刻让刚刚产生的改变梦想变成现实。请珍惜这些开发阶段的天赐良机!

第25章 代码调整策略

* 性能只是软件整体质量的一个方面,通常不是最重要的。精细的代码调整也只是实现整体性能的一种方法,通常也不是决定性的。相对于代码本身的效率而言,程序的架构、细节设计以及数据结构和算法选择对程序的运行速度和资源占用的影响通常会更大。
* 定量测量是实现性能最优化的关键。定量测量需要找出能真正决定程序性的部分,在修改之后,应当通过重复测量来明确修改是提高还是降低了软件的性能。
* 绝大多数的程序都有那么一小部分代码耗费了绝大部分的运行时间。如果没有测量,你不会知道是哪一部分代码。
* 代码调整需要反复尝试,这样才能获得理想的性能提高。
* 为性能优化工作做好准备的最佳方式就是在最初阶段编写清晰的代码,从而使代码在后续工作中易于理解和修改。

第26章 代码调整方法

* 优化结果在不同的语言、编译器和环境下有很大差异。如果没有对每一次的优化进行测量,你将无法判断优化到底是帮助还是损害了这个程序。
* 第一次优化通常不会是最好的。即使找到了效果很不错的,也不要停下扩大战果的步伐。
* 代码调整这一话题有点类似于核能,.富有争议,甚至会让人冲动。一 些人认为代码调整损害了代码可读性和可维护性,他们绝对会将其弃之不用。其他人则认为只要有适当的安全保障,代码调整对程序是有益的。如果你决定使用本章所述的调整方法,请务必谨慎行事。

第27章 程序规模对“构筑”的影响

* 随着项目规模的扩大,交流需要加以支持。大多数方法论的关键点都在于减少交流中的问题,而一项方法论的存亡关键也应取决于它能否促进交流。
* 在其他条件都相等的时候,大项目的生产率会低于小项目。
* 在其他条件都相等的时候,大项目的每千行代码错误率会高于小项目。
* 在小项目里的一些看起来“理当如此” 的活动在大项目中必须仔细地计划。随着项目规模扩大,构建活动的主导地位逐渐降低。
* 放大轻量级的方法论要好于缩小重量级的方法论。最有效的办法是使用“适量级”方法论。

第28章 管理“构筑”

* 好的编码实践可以通过“贯彻标准”或者“使用更为灵活的方法”来达到。
* 配置管理,如果应用得当,会使程序员的工作变得更加轻松。特别包括变控制。
* 好的软件评估是一项重大挑战。成功的关键包括采用多种方法、随着项目的开展而修缮评估结果,以及很好地利用数据来创建评估等。
* 度量是构建管理成功的关键。你可以采取措施度量项目的任何方面,而这要比根本不度量好得多。准确的度量是制定准确的进度表、质量控制和改进开发过程的关键。
* 程序员和管理人员都是人,在把他们当人看的时候工作得最好。

第29章 集成

* 构建的先后次序和集成的步骤会影响设计、编码、测试各类的顺序。
* 一个经过充分思考的集成顺序能减少测试的工作量,并使调试变容易。
* 增量集成有若干变型,而且—— 除非项目是微不足道的—— 任何一种形式的增量集成都比阶段式集成好。
* 针对每个特定的项目,最佳的集成步骤通常是自顶向下、自底向上、风险向及其他集成方法的某种组合。T-型集成和竖直分块集成通常都能工作得很好。
* daily build能减少集成的问题,提升开发人员的士气,并提供非常有用的项目管理信息。

第30章 编程工具

* 程序员有时会在长达数年的时间里忽视某些最强大的工具,之后才发现并使用之。
* 好的工具能让你的日子过得安逸得多。
* 下面这些工具已经可用了:编辑、分析代码质量、重构、版本控制、除错、测试、代码调整。
* 你能打造许多自己用的专用工具。
* 好的工具能减少软件开发中最单调乏味的工作的量,但它不能消除对“编程”的需要,虽然它会持续地重塑(reshape) “编程”的含义。

第31章 布局与风格

* 可视化布局的首要任务是指明代码的逻辑组织。评估该任务是否实现的指标包括准确性、一 致性、易读性和易维护性。
* 外表悦目比起其他指标是最不重要的。然而,如果其他指标都达到了,代码又质量好,那么布局效果看上去也会不错。
* Visual Basic具有纯代码块风格,而Ja v a的传统做法就是使用纯块风格所以若用这些语言编程,就请使用纯代码块风格。C++中,模拟纯代码或者begin- end块边界都行之有效。
* 结构化代码有其自身目的。始终如一地沿用某个习惯而少来创新。不能持久的布局规范只会损害可读性。
* 布局的很多方面涉及信仰问题。应试着将客观需要和主观偏好区分开来。定出明确的指标,在此基础上再讨论风格参数的选择。

第32章 自说明代码

* 该不该注释是个需要认真对待的问题。差劲的注释只会浪费时间,帮倒忙;好的注释才有价值。
* 源代码应当含有程序大部分的关键信息。只要程序依然在用,源代码比其他资料都能保持更新,故而将重要信息融入代码是很有用处的。
* 好代码本身就是最好的说明。如果代码太糟,需要大量注释,应先试着改进代码,直至无须过多注释为止。
* 注释应说出代码无法说出的东西一 例如概述或用意等信息。
* 有的注释风格需要许多重复性劳动,应舍弃之,改用易于维护的注释风格。

第33章 个人性格

* 人的个性对其编程能力有直接影响。
* 最有关系的性格为:谦虚、求知欲、诚实、创造性和纪律,以及高明的偷懒。 
* 程序员高手的性格与天分无关,而任何事都与个人发展相关。
* 出乎意料的是,小聪明、经验、坚持和疯狂既有助也有害。
* 很多程序员不愿主动吸收新知识和技术,只依靠工作时偶尔接触新的信息。如果你能抽出少量时间阅读和学习编程知识,要不了多久就能鹤立鸡群。
* 好性格与培养正确的习惯关系甚大。要成为杰出的程序员,先要养成良好习惯,其他自然水到渠成。

第34章 软件开发艺术的有关问题

* 编程的主要目的之一是管理复杂性。
* 编程过程对最终产品有深远影响。
* 合作开发要求团队成员之间进行广泛沟通,甚于同计算机的交互;而单人开发则是自我交流,其次才是与计算机。
* 编程规范一旦滥用,只会雪上加霜;使用得当则能为开发环境带来良好机制,有助于管理复杂性和相互沟通。
* 编程应基于问题域而非解决方案,这样便于复杂性管理。
* 注意警告信息,将其作为编程的疑点,因为编程几乎是纯粹的智力活动。
* 开发时迭代次数越多,产品的质量越好。
* 墨守成规的方法有悖于高质量的软件开发。请将编程工具箱中填满各种编程工具,不断提高自己挑选合适工具的能力。

第35章 何处有更多信息


####

参考资料
1.史蒂夫•迈克康奈尔:代码大全