【转】读《代码大全2》

第1部分 打好基础
译序
  应该首先为人编写代码,其次才是为机器
 
第1章 欢迎进入软件构建的世界
 
  提高关键的质量和开发者的生产效率都是很重要的
  构建活动会占到整个软件开发时间的30%~80%,占整个项目成本的65%左右
  不同程序员的生产率差异可达10到30倍
  软件构建是软件开发的核心活动,构建活动是每个项目中唯一一项不可少的工作
  软件构建的主要活动包括:详细设计、编码、调试、集成、开发者测试(包括单元测试和集成测试)
 
第2章 用隐喻来更充分的理解软件开发
 
  重要的研发成果常常产自类比
  模型的威力就在于其生动性,让你能够把握整个概念
  数据处理正在从“以计算机为中心”的观点向“以数据库为核心”的观点转变
  算法直接给你解决问题的指导,而启发式方法则告诉你该如何发现这些指导信息,或者至少到那里去寻找他们
  三分之二的项目在首次发布之后还有90%的工作量要做
  精心计划并不意味着事无巨细的计划或者过度的计划
  隐喻是启示而不是算法,因此往往有一点随意
  隐喻把软件开发过程与其他你熟悉的活动联系在一起,帮助你更好的理解
  有些隐喻比其他隐喻更贴切
  通过把软件的构建过程必做是房屋的建设过程,我们可以发现,仔细的准备是必要的,而大型项目和小型项目之间也是有差异的。
  通过把软件开发中的实践比作是智慧工具箱中的工具,我们又发现,每位程序员都有许多工具,但并不存在任何一个能适用于所有工作的工具,因地制宜的选择正确工具是成为能有效编程的程序员的关键
  不同的隐喻彼此并不排斥,应该使用某种对你最有益处的隐喻组合
第3章 三思而后行:前期准备
  软件开发中最常见的项目风险是糟糕的需求分析和糟糕的项目计划
   人生苦短,当有大量更好的选择摆在你面前的时候,在一个荒蛮的软件企业中工作是不明智的。
  尽早把哪些是最关键的需求要素和架构要素确定下来
  选择序列式开发的理由:
       需求相当稳定
       设计直截了当,而且理解透彻
       开发团队对于这一领域十分熟悉
       项目的风险很小
       “长期可预测性”很重要
       后期改变需求、设计和编码的代价很可能较昂贵
  选择迭代开发的理由:
       需求并没有被理解透彻,或者出于其他理由你认为它是不稳定的
       开发团队对于这一领域不熟悉
       项目包含许多风险
       “长期可预测性”不重要
       后期改变需求、设计和编码的代价很可能较低
  在射击之前,确信你瞄准了正确的目标
  一般项目在开发过程中需求会有25%的变化,需求变更导致的返工占总返工量的75%~85%
需求核对表:
  针对功能需求:
       是否详细定义了系统的全部输入,包括其他源、精度、取值范围、出现频率等
       是否定义了系统的全部输出,包括目的地、精度、取值范围、出现频率、格式等
       是否详细定义了所有输出格式,web页面、报表等
       是否详细定义了所有硬件和软件的外部接口
       是否详细定义了全部外部通信接口,包括握手协议,纠错协议、通信协议等
       是否列出了用户想要做的全部事情
       是否详细定义了每个任务所用的数据,以及每个任务得到的数据
  针对非功能需求(质量需求):
       是否为全部必要的操作,从用户的视角,详细描述了期望响应时间
       是否详细描述了其他与计时有关的考虑,例如处理时间、数据传输率、系统吞吐量等
       是否详细定义了安全级别
       是否详细定义了可靠性,包括软件失灵的后果、发生故障时需要保护的至关重要的信息、错误检测与恢复的策略等
       是否定义了机器内存和剩余磁盘空间的最小值
       是否定义了系统的可维护性,包括适应特定功能的变更、操作环境的变更、与其他软件接口变更的能力
       是否包含对“成功”的定义?“失败”的定义呢?
  需求的质量:
       需求是用用户的语言书写的吗?用户也这么认为吗?
       每条需求都不与其他需求冲突吗?
       是否详细定义了相互竞争的特性之间的权衡——例如健壮性与正确性之间的权衡
       是否避免在需求中规定设计(方案)
       需求是否的详细程度上保持相当一致的水平,有哪些需求应该更详细的描述吗?有些需求应该更粗略的描述吗?
       需求是否够清晰,即使转交给一个独立的小组去构建,他们也能理解吗?开发者也这么认为吗?
       每个条款都与待解决的问题及其解决方案相关吗?能从每个条款上溯到它在问题域中对应的根源吗?
       是否每条需求都是可测试的,是否可能进行独立的测试,以检验满不满足各项需求
       是否详细描述了所有可能的对需求的改动,包括各项改动的可能性
  需求的完备性:
       对于在开发开始之前无法获得的信息,是否详细描述了问题不完全的区域
       需求的完备度是否达到这种程度:如果产品满足需求,哪么它就是可接受的
       你对全部需求都感到很舒服吗?你是否已经去掉了那些不可能实现的需求——那些只是为了安抚客户和老板的东西
架构的典型组成部分:
       程序组织
       主要的类
       数据设计
       业务规则
       用户界面设计
       资源管理
       安全性
       性能
       可伸缩性
       互用性
       国际化/本地化
       输入输出
       错误处理
       容错性
       架构的可行性
       过度工程
       关于“买”还是“造”的决策
       关于复用的决策
       变更策略
       架构的总体质量
架构核对表:
  针对架构的主题
       程序的整体组织结构是否清晰?是否包含一个良好的架构全局观(包括理由)?
       是否明确的定义了主要的构造块?(包括每个构造块的职责范围以及与其他构造块的接口)
       是否明确涵盖了“需求”中列出的所有功能(每个功能对应的构造块也不太多也不太少)?
       是否描述并论证了那些最关键的类
       是否描述并论证了数据设计?
       是否详细定义了数据库的组织结构和内容
       是否指出了所用的关键业务规则,并描述其对系统的影响
       是否描述了用户界面设计的策略
       是否将用户界面模块化,使界面的变更不会影响程序的其余部分
       是否描述并论证了处理IO的策略
       是否估算了稀缺资源(如线程、数据库连接、句柄、网络带宽等)的使用量,是否描述并论证了组员管理的策略?
       是否描述了架构的安全需求
       架构是否为每个类,每个子系统,或每个功能域提出空间与时间预算
       架构是否描述了如何达到可伸缩性
       架构是否关注户操作性
       是否描述了国际化/本地化策略
       是否提供了一套内聚的错误处理策略
       是否规定了容错的办法(如果需要)?
       是否证实了系统各个部分的技术可行性
       是否详细描述了过度工程的方法
       是否描述了必要的“买vs.造”的策略
       架构是否描述了如何加工被复用的代码,使之符合其他架构目标
       是否将架构设计得能够适应很可能出现的变更
  架构的总体质量:
       架构是否解决了全部需求
       有没有哪个部分是“过度架构”或“欠架构”,是否明确宣布了在这方面的预期指标
       整个架构是否在概念上协调一致
       顶层设计是否独立于用作实现它的机器和语言
       是否明确了所有主要决策的动机
       你,作为一名实现该系统的程序员,是否对整个架构感觉良好?
       一般来说,一个运作良好的项目会在需求、架构以及其他前期计划方面投入10%~20%的工作量和20%~30%的时间
前期准备核对表:
       你是否辨明了自己所从事的软件类型,并对所用的开发方法做出了相应的剪裁
       你是否充分明确的定义了需求,而且需求足够稳定,能开始构建了?
       你是否明确的定义了架构以便开始构建
       是否已经指出项目中独有的风险,以避免构建活动面临不必要的风险
       构建活动的准备工作的根本目标在于降低风险,要明确你的准备活动在降低风险,而非增加风险
       如果你想开发高质量的软件,软件开发过程中必须由始至终关注质量。在项目初期关注质量,对产品质量的正面影响要比在项目末期关注质量的影响大
       程序员的一部分工作是教育老板和合作者,告诉他们软件开发过程,包括在编码之前进行充分准备的重要性
       你所从事的软件项目的类型对构建活动的前期准备工作有重大影响——许多项目应该是高度迭代的,某些应该是序列式的
       没有明确的问题定义,那么你可能在构建期间解决错误的问题
       如果没有做完良好的需求分析工作,你可能没能察觉待解决问题的重要细节。如果需求变更发生在构建之后的阶段,其代价是“在项目早起更改需求”的20至100倍。因此在开始编程之前,你要确认“需求”已经到位了
       如果没做完良好的架构设计,你可能会在建构期间用错误的方法解决正确的问题。架构变更的代价随着“为错误的架构编写的代码数量”增加而增加,因此,也要确认“架构”已经到位了
       理解项目的前期准备所采用的方法,并相应的选择构建方法

第4章 关键的构建决策
 
  每种编程语言都有其优点和弱点,要明确知道你所使用语言的有点和弱点
  在开始编程约定。“改变代码使之符合这些约定”是近乎不可能的
  “构建的实践方法”的种类比任何单个项目能用到的要多。有意识的选择最适合你的项目的实践方法
问问你自己,你所采用的编程实践是对你所采用编程语言的正确响应,还是受它的控制,请记住“深入一种语言去编程”不要仅“在一种语言上编程”
  你在技术浪潮中的位置决定了哪些方法是有效的——甚至是可能用到的。确定你在技术浪潮中的位置,并相应调整计划和预期目标
 
第2部分 创建高质量的代码
第5章 软件构建中的设计
       如何管理复杂度:
              把任何人在同一时间需要处理的本质复杂度的量减到最小
              不要让偶然复杂度无谓的快速增长
        理想的设计特征:
              最小的复杂度:“聪明的”设计往往都是难以理解的。如果你的设计方案不能让你在专注于程序的一部分时安心的忽视其他部分的话,就不是成功的设计
              易于维护:把做维护的程序员做为你的听众,进而设计出能自明的系统
              松散耦合:让程序的各个组成部分之间的关系最小
              可扩展性:能增强系统的功能而无须破坏其底层结构
              可重用性:系统的组成部分能在其他系统中重用
              高扇入:让大量的类使用某个给定的类
              低扇出:一个类里少量或适中的使用其他的类(最好不要超过7个)
              可移植性:
              精简性:系统没有多余的部分。伏尔泰:“一本书的完成,不在它能不能再加入任何内容的时候,而在不能再删去任何内容的时候”。每一行代码都是成本。
              层次性:能在任意的层面上观察系统,并得到某种具有一致性的看法。系统应该能在任意层次上观察而不需要进入其他层次。
              标准技术:系统所依赖的外来的、古怪的东西越多,别人想要第一次理解它的时候就越头疼。要尽量用标准的、常用的方法,给人一种熟悉的感觉。
        设计的层次:
              软件系统
              子系统和包:应限制不同子系统间的通信来让每个系统都有存在的意义
              包中的类
              类中的数据和子程序
              子程序内部
       抽象是一种能让你在关注某一概念的同时可以放心的忽略其中一些细节的能力——在不同的层次处理不同的细节
       抽象:可以让你从高层的细节来看待一个对象;封装:除此之外,你不能看到对象的任何其他细节层次
       信息隐藏:
              隐藏复杂度:这样你就不用再去应付它,除非你要特别关注的时候
              隐藏变化源:这样当变化发生时,其影响就能被限制在局部范围内
       大多数让信息无法隐藏的障碍都是由于惯用某些技术而导致的心理障碍
       设计模式提供的益处:
              设计模式通过提供现成的抽象来减少复杂度
              设计模式通过把常见解决方案的细节予以制度化来减少出错
              设计模式通过提供多种设计方案而带来启发性的价值
              设计模式通过把设计对话提升到一个更高的层次上来简化交流
常见的设计模式:
       Abstract Factory: 通过指定对象组的种类而非单个对象的类型来支持创建一组相关的对象
       Adapter:把一个类的接口转变为另一个接口
       Bridge:把接口和实现分类开来,使它们可以独立的变化
       Composite:创建一个包含其他同类对象的对象,使得客户代码可以与最上层对象交互而无须考虑所有的细节对象
       Decrorator:给一个对象动态的添加职责,而无须为了每一种可能的职责配置情况去创建特定的子类
       Fa?ade:为没有提供一致接口的代码提供一个一致的接口
       Factory Method:做特定基类的派生类的实例化,除了在Factory Method内部之外均无需了解各派生对象的具体类型
       Iterator:提供一个服务对象来顺序的访问一组元素中的各个元素
       Observer:使一组相关对象同步,方法是让另一个对象负责:在这组对象中的任何一个发生变化时,由它把这种变化通知给这个组里的所有对象
       Singleton:为有且仅有一个实例的类提供一种全局访问功能
       Stratery:定义一组算法或行为,使得他们可以动态的相互替换
       Template Method:定义一个操作的算法结构,但是把部分实现的细节留给子类
       不能去强迫代码去适应某个模式,不能“为了模式而模式”
       内聚——类内部的子程序或者子程序内的所有代码在支持一个中心目标上的紧密程度。内聚性是用来管理复杂度的有力工具。
  分层是管理复杂度的另一个有力工具。
  分层结构是实现软件的首要技术使用的有用工具,它能使你只关注于当前正在关注的那一层细节。
  为测试而设计通常能产生更为规整的类接口
设计中的启发式方法:
       寻找现实中的对象
       形成一致的抽象
       封装实现细节
       在可能的情况下继承
       藏住秘密(信息隐藏)
       找出容易改变的区域
       保持松散耦合
       探寻通用的设计模式
       高内聚性
       构造分层结构
       严格描述类契约
       分配职责
       为测试而设计
       避免失误
       有意识的选择绑定时间
       创建中央控制点
       考虑使用蛮力
       画一个图
       保持设计模块化
  最有效的原则之一就是不要卡在单一的方法上
  如果目前的解决方案对你来说都有些棘手,那么日后他人再去维护时肯定更感觉到困难重重
  除非你完全的解决了某一设计问题,否则你无法完整的定义出该设计问题
  原型代码本来就是写来为扔掉的,所以应该用最少的代码和最简单的做法去快速完成原型设计
核对表:软件构造中的设计
       设计实践:
       你已经做过多次迭代,并且从众多尝试结果中选择最佳的一种,而不是简单选择第一次尝试的结果?
       你尝试用多种方案来分析系统,以确定最佳方案吗?
       你同时用自上而下和自下而上的方法来解决设计问题吗?
       为了解决某些特定问题,你对系统中的风险部分或者不熟悉的部分创建过原型、写出数量最少的可抛弃代码吗?
       你的设计被其他人检查了吗?(无论正式与否)
       你一直在展开设计,直到实施细节跃然纸上了吗?
       你用某种适当的技术——比如wiki、电子邮件、挂图、数码相机、UML或者在代码里写注释——来保留设计成果吗?
       设计目标:
       你的设计是否充分的处理了由系统架构层定义出并且推迟确定的事项?
       你的设计被划分为层次吗?
       你对把这一程序分解成为子程序、包和类的方法感到满意吗?
       你对把这个类分解成为子程序的方法感到满意吗?
       类与类之间的交互关系是否已设计为最小化?
       类和子程序是否被设计为能够在其他的系统中重用?
       程序是不是易于维护?
       设计是否精简?设计出来的每一部分都绝对必要吗?
       设计中是否采用了标准技术?是否避免使用怪异且难以理解的元素?
       整体而言,你的设计是否有助于最小化偶然性的和本质的复杂度吗?
  软件的首要技术使命就是管理复杂度。以简单性作为努力目标的设计方案对此最有帮助
  简单性可以通过两种方法来获取:一是减少在同一时间所关注的本质性复杂度的量,二是避免生成不必要的偶然复杂度。
  设计是一种启发式的过程。固执于某一种单一方法会损害创新能力,从而损害你的程序
  好的设计都是迭代的。你尝试设计的可能性越多,你最终解决方案就会变得越好
  信息隐藏是个非常有价值的概念。通过询问“我应该隐藏些什么?”能够解决很多困难的设计问题
第6章 可以工作的类
  1.  成为高效程序员的一个关键就是在于,当你开发程序任一部分的代码时,都能安全地忽视程序中尽可能多的其余部分。
  2. 使用ADT的益处:
    1. 可以隐藏实现细节
    2. 改动不会影响到整个程序
    3. 让接口能提供更多信息
    4. 更容易提高性能
    5. 让程序的正确性更显而易见
    6. 程序更具自我说明性
    7. 无须在程序内到处传递数据
    8. 你可以像在现实世界中那样操作实体,而不用在底层实现上操作它
  3. 创建ADT的建议:
    1. 把常见的底层数据类型创建为ADT并使用ADT,而不再使用底层数据类型
    2. 把像文件这样的常见对象当成ADT
    3. 简单的事物也可当做ADT
    4. 不要让ADT依赖于其存贮介质
  4. 类可以看做是ADT+多态+继承
  5. 创建类接口的建议:
    1. 类的接口应该展现一致的抽象层次
    2. 一定要理解类所实现的抽象是什么
    3. 提供成对的服务
    4. 把不相关的信息转移到其他类中
    5. 尽可能让接口可编程,而不是表达语义
      1. 一个接口中任何无法通过编译器强制实施的部分,就是一个可能被无用的部分
    6. 谨慎在修改时破坏接口的抽象
    7. 不要添加与接口抽象不一致的公用成员
    8. 同时考虑抽象性和内聚性
  6. 良好的封装:
    1. 尽可能的限制类和成员的可访问性
    2. 不要公开暴露成员数据
    3. 避免把私用的实现字节放入类的接口中
    4. 不要对类的使用者做出任何假设
    5. 避免使用友元类
    6. 不要因为一个子程序里仅使用公用子程序,就把它归入公开接口
    7. 让阅读代码比编写代码更方便
      1. 阅读代码的次数比编写代码的次数多得多,即使在开发初期也是如此
    8. 要格外警惕从语义上破坏封装性
    9. 留意过于紧密的耦合关系
      1. 避免使用友元,因为它们之间是紧耦合的
      2. 在基类中把数据声明为private而不是protected,以降低派生类和基类之间的耦合程度
  7. has a:
    1. 通过包含类实现has a关系
    2. 在万不得已时通过private继承来实现has a关系
    3. 警惕有超过约7个数据成员的类
      1. 人类能记住的离散项目的个数是7±2
  8. is a:
    1. 用public继承来实现is a关系
    2. 要么使用继承并进行详细说明,要么就不要使用它
      1. 继承给程序增加了复杂度,是一种危险的技术
    3. 遵循Liskov替换原则
      1. 派生类必须能通过积累的接口而被使用,且使用者无须了解两者之间的差异
    4. 确保之继承需要继承的部分
      1. 如果你只是想使用一个类的实现而不是接口,请选择包含而不是继承
    5. 不要“覆盖”一个不可覆盖的成员函数
      1. 派生类中的成员函数尽量不要与基类中的相同
    6. 把公用的接口、数据及操作放到继承树种尽可能高的位置
    7. 只有一个实例的类是值得怀疑的
      1. 可能意味着:将类和对象混为一谈
      2. Singleton是特例
    8. 只有一个派生类的基类也值得怀疑
      1. 可能意味着:提前设计
      2. 不要创建任何并非绝对必要的继承结构
    9. 派生后覆盖了某个子程序,但在其中没做任何操作的情况也值得怀疑
    10. 避免让继承体系过深
      1. 大多数人在脑中同时应付超过2到3层的继承时就感觉到吃力了
    11. 尽量使用多态,避免大量的类型检查
    12. 让所有数据都是private,而非protected
  9. 继承和包含的决定:
    1. 如果多个类共享数据而非行为,应该创建这些类可以包含的共用对象
    2. 如果多个类共享行为而非数据,应该让他们从共同的基类继承而来,并在基类里定义共用的子程序
    3. 如果多个类既共享数据也共享行为,应该让他们从共同的基类继承而来,并在基类里定义共用的子程序
    4. 当你想由基类控制接口时,使用继承;当你想自己控制接口时,使用包含
  10. 成员函数和数据成员建议:
    1. 让类中子程序的数量尽可能的少
    2. 禁止隐式的产生你不需要的成员函数和运算符
    3. 减少类所调用的不同子程序的数量
    4. 对其他类的子程序间接调用要尽可能少
    5. 应尽量减少类和类之间相互合作的范围
  11. 构造函数建议:
    1. 应该在所有的构造函数中初始化所有的数据成员
      1. 属于防御式编程实践
    2. 用private构造函数来强制实现单件属性
    3. 优先采用深拷贝,除非论证可行才采用浅拷贝
      1. 采用浅拷贝一般是基于性能考虑的,但为了不确定的性能提高而增加复杂度是不妥的
  12. 创建类的原因:
    1. 为现实世界中的对象建模
    2. 为抽象的对象建模
      1. 得出恰当的抽象对象是OOD中一项主要挑战
    3. 降低复杂度
    4. 隔离复杂度
    5. 隐藏实现细节
    6. 限制变动的影响范围
    7. 隐藏全局数据
    8. 让参数传递更顺畅
    9. 建立中心控制点
    10. 让代码更易于重用
    11. 为程序族做计划
    12. 把相关的操作包装到一起
    13. 实现某种特定的重构
  13. 应该避免创建的类:
    1. 避免创建万能类
    2. 消除无关紧要的类
    3. 避免用动词命名的类
  14. 不同语言在“类”方面的差异:
    1. 在继承层次中被覆盖的构造函数和析构函数的行为
    2. 在异常处理时构造函数和析构函数的行为
    3. 默认无参构造函数的重要性
    4. 析构函数或Finalizer终结器的调用时机
    5. 和覆盖语言内置的运算符(赋值和等号)相关的知识
    6. 当对象被创建和销毁时,或当其被声明时,或者它所在的作用域退出时,处理内存的方式
  15. 类是当前程序员实现模块化的最佳方式
  16. Key Points:
    1. 类的接口应提供一致的抽象。很多问题都是由于违背该原则而引起的
    2. 类的接口应隐藏一些信息——如某个系统接口、某项设计决策、或一些实现细节
    3. 包含往往比继承更为可取——除非你要对“is a”的关系建模
    4. 继承是一种有用的工具,但它却会增加复杂度,这有违于软件的首要技术使命——管理复杂度
    5. 类是管理复杂度的首选工具。要在设计类时给予足够的关注才能实现该目标
 
第7章 高质量的子程序
  1.  创建子程序的正当理由: 
    1. 降低复杂度。如果没有子程序的抽象能力,我们的智力将根本无法管理复杂的程序
    2. 引入中间、易懂的抽象。将一段晦涩的代码组织成名称明显的子程序
    3. 避免代码重复
    4. 支持子类化覆盖
    5. 隐藏顺序
    6. 隐藏指针操作。指针操作的可读性通常都很差,而且也很容易出错
    7. 提高可移植性
    8. 简化复杂的布尔判断
    9. 改善性能
    10. 确保子程序都很小。不绝对,有时写一个大的子程序会更好
  2. 理解一些概念要比记住一些特定的术语更重要
  3. 功能内聚性:让一个子程序仅执行一项操作
  4. 好的子程序名字:
    1. 描述子程序所做的所有事情
    2. 避免使用无意义的、模糊或者表述不清的动词
    3. 不要仅通过数字来形成不同的子程序名字
    4. 根据需要确定子程序名字的长度。变量名的最佳长度是9到15个字符
    5. 给函数命名时要对返回值有所描述
    6. 给过程起名时使用语气强烈的动词加宾语形式。OO语言中无需在过程名字加入对象的名字
    7. 准确使用对仗词
    8. 为常用操作确立命名规则
  5. 尽量不要让子程序超过200行,50到150行是较好的选择
  6. Key Points:
    1. 创建子程序最主要的目的是提高程序的可管理性,当然也有其他一些好的理由。其中,节省代码空间只是一个次要原因;提高可读性、可靠性和可修改性等原因更重要一些
    2. 有时候,把一些简单的操作写成独立的子程序也非常有价值
    3. 子程序可以按照其内聚性非为很多类,而你应该让大多数子程序具有功能上的内聚性,这是最佳的一种内聚性
    4. 子程序的名字是它质量的指示器。如果名字糟糕但恰如其分,那就说明这个子程序设计的很差劲。如果名字糟糕而且又不准确,那么它就反映不出程序是干什么的。不管怎样,糟糕的名字都意味着程序需要修改
    5. 只有在某个子程序的主要目的是返回其名字所描述的特定结果时,才应该使用函数。(有些语言把无返回值的子程序叫“过程”,有返回值的才叫“函数”)
    6. 细心的程序员会非常谨慎的使用宏,而且只在万不得已时才用。
第8章 防御式编程
  1. 防御式编程主要思想:子程序应该不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据
  2. 使用断言的指导建议:
    1. 用错误处理代码来处理预期会发生的情况,用断言处理绝不应该发生的状况
    2. 避免把需要执行的代码放到断言中
    3. 用断言来注解并验证前条件和候条件——契约式设计
    4. 对于高健壮性的代码,应该先使用断言再处理错误
  3. 异常的应用情形和断言相似——都是用来处理那些不仅罕见甚至永远都不该发生的情况
  4. 异常实际上在设计上破坏了封装性
  5. Key Points:
    1. 最终产品代码中对错误的处理方式要比“垃圾进,垃圾出”复杂得多
    2. 防御式编程技术可以让错误更容易发现、更容易修改,并减少错误对产品代码的破坏
    3. 断言可以帮助人尽早发现错误,尤其是在大型系统多和高可靠性的系统中,以及快速变化的代码中
    4. 关于如何处理错误输入的决策是一项关键的错误处理决策,也是一项关键的高层设计决策
    5. 异常提供了一种与代码正常流程角度不同的错误处理手段。如果留心使用异常,它可以成为程序员们知识工具箱中的一项有益补充,同时也应该在异常和其他错误处理手段之间进行权衡比较。
    6. 针对产品代码的限制并不适用于开发中的软件。你可以利用这一优势在开发中添加有助于更快的排查错误的代码

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

 

  

第3部分 变量
第10章 使用变量的一般事项
  1. 隐式变量声明对于任何一种语言来说都是最具危险性的特性之一(动态语言不都是这样的吗)。要求显式声明数据的语言实际是在提醒你要更加仔细的使用这些数据。
  2. 开始时采用最严格的可见性,然后根据需求扩展变量的作用域
  3. 你能够隐藏的信息越多,在同一时间所需要考虑的信息就越少
  4. Key Points
    1. 数据初始化过程很容易出错
    2. 最小化每个变量的作用域。把同一变量的引用点集中在一起。把变量限定在子程序或类的范围内。避免使用全局数据
    3. 把使用相同变量的语句尽可能集中在一起
    4. 早期绑定会降低灵活性,但有助于减少复杂度。晚期绑定可以增加灵活性,同时增加复杂度
    5. 把每个变量用于唯一的用途
第11章 变量名的力量
  1. 要求使用短变量名是早期编译器的遗留物
  2. 不要采用给变量后后面加数字后缀的方法,请使用数组来代替一组单个变量
  3. Key Points:
    1. 好的变量名是提高程序可读性的一项关键要素。对特殊种类的变量,比如循环下标和状态变量,需要加以特殊的考虑。
    2. 名字要尽可能的具体。那些太模糊或者太通用以至于能够用于多种目的的名字通常都是很不好的。
    3. 名字规则应该能够区分局部数据、类数据和全局数据。他们还应当可以区分类型名、常量、枚举类型名字和变量名。
    4. 无论做那种类型项目,你都应该采用某种变量命名规则。你所采用规则的种类取决于你的程序规模,以及项目成员的人数
    5. 现代编程语言很少需要用到缩写。如果你真的要使用缩写,请使用项目缩写词典或者标准前缀来帮助理解缩写。
    6. 代码阅读的次数远远多于编写的次数。确保你所取的名字更侧重于阅读方便而不是编写方便
第12章 基本数据类型
  1. 一堆相差较大的浮点数求和,应该先排序,然后从小到大依次相加,以防止大数吃小数。
  2. 如果需要,程序中出现0和1的MagicNumber是可以接受的,其他的都可以换掉
  3. 避免硬编码字符串
  4. 在程序生命期中尽早决定国际化/本地化策略,尽早决定是否采用Unicode
  5. calloc在分配内存同时自动初始化为0,malloc仅负责分配内存,_alloca和_malloca则是在栈上进行空间分配
  6. 把枚举类型的第一个元素留作非法值
  7. 像C++这样要求通过头文件来分发定义的语言是较难实现真正的信息隐藏的
  8. Key Points:
    1. 使用特定的数据类型就意味着要记住适用于各个类型的很多独立原则。
    2. 如果你的语言支持,创建自定义类型会使得你的程序更容易修改,并更具有自描述性
    3. 当你用typedef或者其等价方式创建了一个简单类型的时候,考虑是否更应该创建一个新的类
第13章 不常见的数据类型
  1. 每一个指针都包含两部分:内存中的某处位置;如果解释该位置中的内容。
  2. 在与指针分配相同的作用域中删除指针
  3. Key Points:
    1. 结构体可以使得程序更简洁、更容易理解,以及更容易维护(对没有类概念的语言来说
    2. 每当你打算使用结构体的时候,考虑采用类是不是会工作的更好
    3. 指针很容易出错。用访问器子程序或类以及防御式编程实践来保护自己的代码
    4. 避免用全局变量,不只是因为它们很危险,还是因为你可以用其他更好的方法取代它们
    5. 如果你不得不使用全局变量,那么就通过访问器子程序来使用它。访问器子程序能为你带来全局变量所能带来的一切优点,还有一些额外好处

 

 

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

第15章 使用条件语句
  1. 首先写正常代码路径;再处理不常见情况(这和秒大刀惯用的“短路”写法是相反的
  2. 把正常的处理放在if后面而不要放在else后面
  3. switch-case中的default子句只用于检测真正的默认情况或用来检测错误
  4. Key Points:
    1. 对于简单的if-else语句,请注意if子句和else子句的顺序,特别是用它来处理大量错误的时候。要确认正常的情况是清晰的。
    2. 对于if-then-else语句串和case语句,选择一种最有利于阅读的顺序
    3. 为了捕获错误,可以使用case语句中的default子句,或者使用if-then-else语句串中的最后那个else子句
    4. 各种控制结构并不是生来平等的。请为代码的每个部分选用最合适的控制结构

第16章 控制循环
  1. for循环就是为了简单的用途,更复杂的循环最好用while循环去处理。在适当的情况下多使用for循环。
  2. 一个循环只做一件事
  3. 循环嵌套应控制在3层以内
  4. Key Points:
    1. 循环很复杂。保持循环简单将有助于别人阅读你的代码
    2. 保持循环简单的技巧包括:避免使用怪异的循环、减少嵌套层次、让入口和出口一目了然、把内务操作代码放在一起
    3. 循环下标很容易被滥用。隐私命名要准确,并且要把它们各自仅用于一个用途
    4. 仔细的考虑循环,确认它在每一种情况下都运转正常,并且在所有可能的条件下都能退出

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

第18章 表驱动法
  1. 凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。查表法在应对复杂逻辑时会更有效。
  2. 编写到表里面的数据比嵌入代码的数据更容易维护
  3. 表驱动非常灵活并且容易修改
  4. 最好是去找一种好的方案而且同时避免引发灾难,而不要试图去寻找最佳方案
  5. Key Points:
    1. 表提供了一种复杂的逻辑和集成结构的替代方案。如果你发现自己对某个应用程序的逻辑或者继承树关系感到困惑,那么问问自己它是否可以通过一个查询表来加以简化
    2. 使用表的一项关键决策是决定如何去访问表。你可以采取直接访问、索引访问或者阶梯访问
    3. 使用表的另一项关键决策是决定应该把什么内容放入表中

第19章 一般控制问题
  1. 在bool表达式中应该用标识符true和false,而不要用0和1等数值
  2. 清晰的程序比对混乱的程序写清楚的注释好,因为程序员更关心代码,而不是注释
  3. 复杂的逻辑决策可以采用决策表而不是复杂的条件判断
  4. 有一些C传统并不是基于最大化的可读性或者可维护性的
  5. 可以通过重复检测条件中的某一部分来简化嵌套的if语句
  6. 复杂的代码表明你还没有充分的理解你的程序,所以无法简化它
  7. 对减少嵌套层次的技术总结:
    1. 重复判断一部分条件
    2. 转换成if-then-else
    3. 转换成cast语句
    4. 把深层嵌套的代码提取到单独的子程序
    5. 使用对象和多态派分
    6. 用状态变量重写代码
    7. 用防卫子句来退出子程序,从而使代码的主要路径更为清晰
    8. 使用异常
    9. 完全重新设计深层嵌套的代码
  8. 程序复杂度——为了理解应用程序,你必须在同一时间记住的智力实体的数量
  9. “有能力的程序员会充分的认识到自己的大脑容量是多么的有限,所以,他会非常谦卑的处理编程任务”——底杰斯特拉
  10. 人类很难处理好超过5到9个(7±2)智力实体,提高的可能性不大
  11. Key Points
    1. 使bool表达式简单可读,将非常有助于提高你的代码质量
    2. 深层次嵌套使得子程序变得难以理解。所幸的是,你可以相对容易的避免这么做
    3. 结构化编程是一种简单并且仍然适用的思想:你可以通过把顺序、选择和循环三者组合起来而开发出任何程序
    4. 将复杂度降低到最低水平是编写高质量代码的关键

 

 

第5部分 代码改善
第20章 软件质量概述
  1. 要让所有的特性都表现的尽善尽美是绝无可能的,需要根据一组互相竞争的目标寻找出一套优化的解决方案,正是这种情况使软件开发成为一个真正的工程学科。
  2. 有效管理变更是实现高质量的一个关键
  3. 程序员有很高的成就激励:他们会向明确的目标进发,但必须有人告诉他们目标是什么
  4. 不同目标之间可能是有冲突的,并且软件通常不可能在所有方面都做的很好
  5. 缺陷检测率:
  6. 检错措施 最低检出率(%)              典型检出率(%)              最高检出率(%)             
    非正式设计复查 25 35 40
    正式设计检查 45 55 65
    非正式代码复查 20 25 35
    正式代码检查 45 60 70
    建立模型或原型 35 65 80
    个人桌面代码检查 20 40 60
    单元测试 15 30 50
    新功能(组件)测试 20 30 35
    集成测试 25 35 40
    回归测试 15 25 30
    系统测试 25 40 55
    小规模Beta测试(小于10人参与) 25 35 40
    大规模Beta测试(大于1000人参与)              60 75 85
  7. 以上表格数据说明:
    1. 单独使用任何一个方法,其典型检出率都没有超过75%,平均来说仅在40%左右。
    2. 最常见的缺陷检测方法——单元测试和集成测试,一般检测率仅仅在30%到35%之间
    3. 借助大规模测试来检测缺陷也仅仅能达到85%左右(业界的平均缺陷检出率)
    4. 采取使用范围更广泛的更多方法才能够火的95%以上的检出率
  8. 不同的人可能找出不同的缺陷,群体测试中只有20%的bug被不同的人重复检出
  9. 阅读代码每小时能够检测出的缺陷要比测试高出80%左右,使用测试来检测缺陷的成本是检查的6倍。
  10. IBM:检查发现一个错误需要3.5h,而测试则需要15~25h
  11. MS:采用代码检查发现bug并修正的一步到位方法,一个错误需要3h,而通过测试然后再修正的两布方法需要花费12h
  12. 一个由超过400名程序员创建的70w行代码的程序中,代码复查的投资回报比为1.38,而测试的投资回报比仅为0.17
  13. 推荐的质量控制阵容:
    1. 对所有的需求、架构及系统关键部分的设计进行正式检查
    2. 建模或者创建原型
    3. 代码阅读或者检查(review)
    4. 执行测试
  14. 效果最明显的缩短开发周期的办法就是改善产品的质量,由此减少花费在调试和软件返工上面的时间总量
  15. 更多的质量保证工作能降低错误率,但不会增加开发的总成本
  16. 既不是最快的,也不是最慢的开发方法开发出的软件缺陷最多。最慢的开发时间大约是最快开发时间的5倍,但缺陷率却不相上下。编写无缺陷的软件可能让我们花费更少的时间。
  17. Key Points
    1. 开发高质量代码最终并没有要求你付出更多,只是你需要对资源进行重新分配,以低廉的成本来防止缺陷出现,从而避免代价高昂的修正工作。
    2. 并非所有的质量保证目标都可以全部实现。明确哪些目标是你希望达到的,并就这些目标和团队成员进行沟通
    3. 没有任何一种错误检测方法能够解决全部的问题,测试本身不是排除错误的最有效方法。成功的质量保证计划应该使用多种不同的技术来检查各种不同类型的错误。
    4. 在构建期间应当使用一些有效的质量保证技术,但在这之前,一些具有同样强大功能的质量保证技术也是必不可少的。错误发现越早,它与其余代码的纠缠就越少,由此造成的损失也越小
    5. 软件领域的质量保证是面向过程的。软件开发与制造业一样,在这里并不存在会影响最终产品的重复的阶段,因此,最终产品的质量受到开发软件所用的过程的控制

第21章 协同构建
  1. 协同构建——包括结对编程、正式检查、非正式技术复查、文档阅读,以及其他让开发人员共同承担创建代码及其他工作产品责任的技术。
  2. 设计过程中开发人员平均每小时引入1~3个缺陷,编码阶段平均每小时引入5~8个缺陷
  3. 全程结对编程的成本会比单人开发高10%~25%,但开发周期大约会缩短45%
  4. 代码复查可以快速地将所有开发者的水平提升到最优秀的开发者的高度
  5. 成功运行结对编程的关键:
    1. 用编码规范来支持结对编程
    2. 不要让结对编程变成旁观
    3. 不要强迫在简单的问题上使用结对编程
    4. 有规律的对结对人员和分配的工作任务进行轮换
    5. 鼓励双方跟上对方的步伐
    6. 确认两个人都能够看到显示器
    7. 不要强迫程序员与自己关系紧张的人组队
    8. 避免新手组合
    9. 指定一个组长
  6. 任何新的方法论都需要事实来证明它存在的必要性
  7. 采用定量分析,确定新方案比现行的更有效后再去真正实施。
  8. 承认一个批评并不意味着认同批评的内容
  9. 源码阅读每小时可发现3.3个缺陷,测试每小时仅可发现1.8个错误
  10. Key Points:
    1. 协同开发实践往往能比测试发现更多的缺陷,并且更有效率
    2. 协同开发实践所发现错误的类型通常跟测试所发现的不同,这意味着你需要同时使用详查和测试来保证你软件的质量
    3. 正式检查通过运用核查表、准备工作、明确定义的角色以及对方法的持续改善,将缺陷侦测的效率提升至最高。它往往能比走查发现更多的缺陷。
    4. 通常,结对编程拥有和详查相同的成本,并能产生质量相当的代码。当需要缩短开发周期的时候,结对编程就非常有价值。相对于单独工作来说,有些开发人员更喜欢结对工作。
    5. 正式检查可以应用在除代码之外的很多工作成果上,例如需求、设计以及测试用例等。
    6. 走查和代码阅读是详查的替代方案。代码阅读更富有弹性,能有效的利用每个人的时间。
第22章 开发者测试
  1. 首先写测试用例可以将从引入缺陷到发现并排除缺陷之间的时间缩减至最短
    1. 在开始写代码之前先写测试用例,并不比之后再写要多花工夫,只是调整了下测试用例编写活动的工作顺序而已
    2. 加入你首先编写测试用例,那么你将可以更早的发现缺陷,同时也更容易修正它们
    3. 首先编写测试用例,将迫使你在开始写代码之前至少思考一下需求和设计,而这往往会催生更高质量的代码
    4. 在编写代码之前先编写测试用例,能更早的把需求上的问题暴露出来,因为对于一个糟糕的需求来说,要写出测试用例是一件困难的事情
    5. 如果你保存了最初编写的测试用例——这是你应该做的,那么闲进行测试并非唯一选择,你仍然可以最后再进行测试。
  2. 测试先行的编程是过去十年中所形成的最有用的软件开发实践之一,同时也是一个非常好的通用方法。
  3. 数据使用的出错率至少不亚于控制流
  4. 80%的错误存在于项目20%的类或者子程序当中
  5. 50%的错误被发现存在于项目5%的类当中
  6. 项目中20%的子程序占用了80%的开发成本,但这20%的代码并不一定就是bug最多的那部分代码
  7. 提高质量就能缩短开发周期,同时降低开发成本
  8. 开发高质量的软件比开发低质量软件然后修正成本要低廉
  9. 如果你无法重复,那么就不可能改善
  10. Key Points:
    1. 开发人员测试是完整测试策略的一个关键部分。独立测试也很重要。
    2. 同编码之后编写测试用例相比较,编码开始之前编写测试用例,工作量和花费的时间差不多,但是后者可以缩短缺陷-侦测-调试-修正这一周期。
    3. 即使考虑到了各种可用的测试手段,测试仍然只是良好软件质量计划的一部分。高质量的开发方法至少和测试一样重要,这包括尽可能减少需求和设计阶段的缺陷。在检测错误方面,协同开发的成效至少与测试相当。这些方法所检测错误的类型也各不相同。
    4. 你可以根据各种不同的思路来产生很多测试用例,这些思路包括基础测试、数据流分析、边界分析、错误数据类型以及正确数据类型等。你还可以通过猜测错误的方式得到更多的测试用例。
    5. 错误往往集中在少数几个容易出错的类和子程序上。找出这部分代码,重新设计和编写它们。
    6. 测试数据本身出错的密度往往比被测试代码还要高。查找这种错误完全是浪费时间,又不能对代码有所改善,因此测试数据里面的错误更加让人烦恼。要像写代码一样下小心的开发测试用例,也是进行回归测试的基础。
    7. 自动化测试总体来说是很有用的,也是进行回归测试的基础。
    8. 从长远来看,改善测试过程的最好办法就是将其规范化,并对其进行评估,然后用从评估中获得的经验教训来改善这个过程。
第23章 调试
  1. 如果一个错误无法重现,这通常会是一个初始化错误,或者是一个同时间有关的问题,或者是悬野指针问题。
  2. 如果你大全通过捷径摘取胜利果实,那么请为你尝试捷径的时间设置一个上限。
  3. 程序员在第一次对缺陷进行修正的时候,有超过50%的几率出错
  4. 匆忙动手解决问题是你所能做的最低效的事情之一了
  5. 休息足够长的时间能让你肯定自己的解决方案是对的。不要受到所谓捷径的诱惑。休息一下或许会让你花掉更多的时间,但在多数情况下,你为问题所付出的成本会更少。
  6. 人们看到的是他们所希望看到的东西
  7. 某种工具可能被错误的使用,但这并不表示这种工具应该被抛弃
  8. Key Points
    1. 调试同整个软件开发的成败息息相关。最好的解决之道是避免缺陷的产生。然而,花点时间来提高自己的调试技能还是很划算的,因为优秀和拙劣的调试表现之间的差距至是10:1
    2. 要想成功,系统化的查找和改正错误的方法至关重要。要专注于你的调试工作,让每一次调试都能让你朝着正确的方向前进一步。要使用科学的调试方法。
    3. 在动手解决问题之前,要理解问题的根本。胡乱猜测错误的来源和随机修改将会让你的程序陷入比刚开始调试时更为糟糕的境地。
    4. 将编译器警告级别设置为最严格,把警告信息所报告的错误都改正。如果你忽略了明显的错误,那么要改正那些微妙的错误就会非常麻烦。
    5. 调试工具对软件开发而言是强有力的支持手段。找出这些工具并加以应用,当然,请记得在调试的时候开动脑筋。
第24章 重构
  1. 软件演化就像生物进化一样,有些突变对物种是有益的,另外一些则是有害的。
  2. 重构——在不改变软件外部行为的前提下,对其内部结构进行改变,使之更容易理解并便于修改。
  3. 宁可让代码因较强的封装而出错,也不要减弱封装
  4. 只要看到某个子程序名优问题,就应该立刻着手修改
  5. 不要为拙劣的代码编写文档——应该重写代码
  6. 早期修订阶段是提升代码质量的最佳时机
  7. 超前设计整体效应就是拖了项目的后腿
  8. 对未来需求有所准备的办法并不是去编写空中楼阁式的代码,而是尽可能将满足当前需求的代码清晰直白的表现出来,使未来的程序员理解这些代码完成了什么功能,没有完成什么功能,从而根据他们的需求进行修改。
  9. 对于有一定风险的重构,谨慎才能避免出错。务必一次只处理一项重构。除了完成通常要做的编译检查和单元测试之外,还应该让其他人来检查你的重构工作,或是针对重构采用结对编程。
  10. Key Points:
    1. 修改是程序一声都要面对的事情,不仅包括最初的开发阶段,还包括首次发布之后
    2. 在修改中软件的质量要么改进,要么恶化。软件演化的首要法则就是代码演化应当提升程序的内在质量
    3. 重构成功之关键在于程序员应当学会关注那些标志着代码需要重构的众多的警告或“代码臭味”
    4. 重构成功的另一要素是程序员应当掌握大量特定的重构方法
    5. 重构成功的最后要点在于要有安全重构的策略。一些重构方法会比其他重构方法要好
    6. 开发阶段的重构是提升程序质量的最佳时机,因为你可以立刻让刚刚产生的改变梦想变成现实。请珍惜这些开发阶段的天赐良机!
第25章 代码调整策略
  1. 对用户来说,程序员按时交付软件,提供一个清爽的用户界面,避免系统死机常常比程序的性能更为重要
  2. 性能不一定代表的是程序的运行速度,往往是吞吐量、响应或某种表现
  3. 在花费时间处理一个性能为题之前,请想清楚你的确是在解决一个确实需要解决的问题。
  4. 有时,最经济也是最有效的提升程序性能的方法就是购买新的硬件设备。
  5. 高效的代码不一定就是“更好”的代码
  6. The best is the enemy of the good. 完美是优良之大敌。
  7. 不成熟优化的主要缺陷在于它缺乏前瞻性。过早的优化会对软件的整体质量产生严重的威胁,甚至包括软件的性能。
  8. Key Points:
    1. 性能只是软件整体质量的一个方面,通常不是最重要的。精细的代码调整也只是实现整体性能的一种方法,通常也不是决定性的。相对于代码本身的效率而言,程序的架构、细节设计以及数据结构和算法选择对程序的运行速度和资源占用的影响通常会更大。
    2. 定量测量是实现性能最优化的关键。定量测量需要找出能真正决定程序性能的部分,在修改之后,应当通过重复测量来明确修改是提高还是降低了软件的性能。
    3. 绝大多数的程序都有那么一小部分代码耗费了绝大部分的运行时间。如果没有测量,你不会知道是那一部分代码。
    4. 代码调整需要反复尝试,这样才能获得理想的性能提高。
    5. 为性能优化工作做好准备的最佳方式就是在最初阶段编写清晰的代码,从而使代码在后续工作中易于理解和修改。
第26章 代码调整技术
  1. Key Points:
    1. 优化结果在不同的语言、编译器和环境下有很大差异。如果没有对每一次的优化进行测量,你将无法判断优化到底是帮助还是损害了这个程序。
    2. 第一次优化通常不是最好的。即使找到了效果很不错的,也不要停下来扩大战果的步伐。
    3. 代码调整这一话题有点类似于核能,赋予争议,甚至会让人冲动。一些人认为代码调整损害了代码可读性和可维护性,他们绝对会将其弃之不用。其他人则认为只要有适当的安全保障,代码调整对程序是有益的。如果你决定使用代码调整,请务必谨慎行事。

 

第6部分 系统考虑

第27章 程序规模对构建的影响
  1. 改善交流效率的常用方法是采用正式的文档
  2. 对于小项目,影响生产率的最大因素莫过于单个程序员的技巧。随着项目规模和团队规模的增大,组织方式对生产率的影响也将随之增大。
  3. 开发软件产品的成本大约是开发“软件程序”的3倍。开发一组能够结合起来工作的程序的软件系统的成本是简单程序开发成本的3倍。开发系统产品的成本是简单程序的9倍。
  4. 建造摩天大楼的方法和搭狗窝的方法是不一样的。
  5. 对于大项目来说,如果不有意识的去选择方法论,就将无法完成任务。
  6. Key Points:
    1. 随着项目规模的扩大,交流需要加以支持。大多数方法论的关键点都在于减少交流中的问题,而一项方法论的存亡关键也应取决于它能否促进交流。
    2. 在其他条件都相等的时候,大项目的生产率会低于小项目。
    3. 在其他条件都相等的时候,大下次昂木的每千行代码错误率会高于小项目。
    4. 在小项目里的一些看起来“理应如此”的活动在大项目中必须仔细的计划。随着项目规模扩大,构建活动的主导地位逐渐降低。
    5. 放大轻量级的方法论要好于缩小重量级的方法论。最有效的办法是使用“适量级”方法论。

第28章 管理构建
  1. 质量目标和项目规模都会显著影响这个软件项目的管理方式。
  2. 项目中编程标准的制定,应该由一位受人尊敬的架构师来做,而不应该是管理者。在软件项目中,“专家层”起的作用至少与“管理层”相同。
  3. 强调代码是共有财产
  4. 需求变更和设计变更
    1. 开发过程中,你一定会有很多关于如何改善系统的想法。如果每产生一个想法就实施相应的变更,你会发现自己走上了软件开发的treadmill——虽然系统在发生变化,但却没有向着“完成”的方向迈进。
    2. 遵循某种系统化的变更控制手续。当你面临很多变更需求时,系统化的变更控制手续犹如天赐之物。通过建立一套系统化的手续,你就能将变更放在“在对系统整体最为有利”的环境下进行考虑。
    3. 成组的处理变更请求。人们倾向于一有想法就去实现那些较容易的变更,因此类变更消耗了资源,会导致更好的变更因资源不足而被丢弃。应该记下所有的想法和建议,不管它实现起来有多容易。把它记录下来,直到你有时间去处理它们。到那时,把所有变更当做一个整体来看待,从中选出最有益的一些变更来加以实施。
    4. 评估每项变更的成本。评估变更成本时要仔细考虑所有环节的代价,而不仅仅是编码。
    5. 提防大量的变更请求。变更请求数量太大是一个很关键的警报信号,这表明需求、架构或者上层设计做得不够好,从而无法有效的支持构建活动。
    6. 成立变更控制委员会或者类似机构。变更控制委员会是一项“设定需求变更的优先级”和“控制需求变更”的最佳实践。
    7. 警惕官僚主义,但也不要因为害怕官僚主义而排斥有效的变更控制。缺乏规范的变更控制是当今软件业面临的主要管理难题之一。不要去考虑“未做跟踪但却同意执行的变更”。但变更控制有滋生官僚主义的倾向。变更控制的实质就是确定什么最重要,所以不要因为害怕官僚主义就不去享受变更控制的诸多益处。
  5. 要定期对项目做重新评估。项目越接近完成,评估的准确度应该越高。
  6. 如果你落后了改怎么办?
    1. 项目通常会超出原定完成时间的100%
    2. 希望自己能赶上。当项目落后于进度安排时,人们通常的反应是对此抱有乐观态度。事实上,越接近项目后期,延误和超值现象就越严重,项目并不能在后期把时间补回来,而是会越落越后。
    3. 扩充团队。Fred Brooks定律:往一个已经落后的软件项目里加人手只会使得它更加落后。发现进度落后时扩充团队无异于火上浇油。一位妇女可以怀胎十月诞下一子,并不意味着10位妇女能只用一个月时间生下一个孩子。更多的工人在工作并不一定意味着能做更多的工作。当一个项目中的任务是不可分割的、不能各个击破,那么增加人手是无济于事的;但如果项目中的任务是可以分割的,那么就可以把任务进一步细分,然后分给不同的人来做,甚至可以分配给项目后期才加入进来的人。
    4. 缩减项目范围。最初做版本计划的时候,要把产品的功能划分成“必须有”、“有了更好”和“可选择”三类。当资源不足时可以针对不同的类型采取不同的策略,或者实现功能的粗略版本。
  7. 度量
    1. 任何一种项目特征都是可以用某种方法来度量的,而且总会比根本不度量好得多。度量结果也许不会完全精确,度量也许很难做,而且也许需要持续的改善结果,但是它能使你对软件开发过程进行控制,而如果不度量就根本不可能控制。
    2. 留心度量的副作用。度量乎会对动机产生影响。人们会对那些被度量的事物更加用心,他们认为度量的目标是在评价他们。要谨慎的选择哪些环节需要被度量。人们会倾向于集中做哪些被度量的工作,而忽视未被度量的工作。
    3. 反对度量就是认为最好不要去了解项目中到底在发生什么
  8. 如果你为了聘请到10%最优秀的程序员而需要多支付报酬,那么就请欣然接受这一现实。你会因为所雇用的程序员的高品质和高生产力而迅速得到回报,而且这么做还有一个剩余效应,那就是你的组织中其他程序员的品质和生产力不会下降,因为好的程序员倾向于聚到一起。
  9. 当试图要求统一某些编程实践时,可能会遇到敏感的程序员信仰问题,这些信仰涉及程序员自己的个人风格,为避免激怒你的程序员:
    1. 要清楚的知道你是在处理一个敏感的问题。在全心全意投入之前要先试探程序员对有关敏感问题的看法。
    2. 对这些领域要使用“建议”或者“指导原则”。避免指定僵硬的“规则”或者“标准”。
    3. 避免流露明显的意图通过一些技巧来避免针对敏感问题直接对话
    4. 让程序员们制定他们自己的标准。不要给你的程序员们设立标准,但一定要坚持让他们在“那些对你来说非常重要的领域里”标准化。
    5. 要注意权衡编码风格等的统一和对士气造成的影响。
  10. 宽敞、安静、私密的办公室有利于提高生产力
  11. 管理你的管理者——你需要告诉他应该这样做而不应该那样做。你要表现得使你的管理者认为他仍然在管理你。
  12. Key Points:
    1. 好的编码实践可以通过“贯彻标准”或者“使用更为灵活的方法”来达到
    2. 配置管理,如果应用得当,会使程序员的工作变得更加轻松。特别包括变更控制。
    3. 好的软件评估是一项重大挑战。成功的关键包括采用多种方法、随着项目的开展而修缮评估结果,以及很好的利用数据来创建评估等。
    4. 度量是构建管理成功的关键。你可以采取措施度量项目的任何方面,而这要比根本不度量好得多。准确的度量是制定准确的进度表、质量控制和改进开发过程的关键。
    5. 程序员和管理人员都是人,在把他们当人看的时候工作的最好。

第29章 集成
  1. 集成——将一些独立的软件组件组合为一个完整系统。
  2. 华盛顿大学的露天足球场坍塌了,因为它在建造时不能支撑自己的重量。很可能在完工后它会足够览古,但是它的建造顺序是错的——这是一个“集成”错误。
  3. 不适当的过分关注细小的缺陷,会使项目进展瘫痪。
  4. Daily Build and Smoke Test:
    1. 每日构建。构建可以视为项目的脉搏。项目将保证在每次构建时保持同步。
    2. 检查失败的build。好的build:能成功编译、链接,并不包含任何使程序无法启动的bug。
    3. 每天进行冒烟测试。从头到尾的演练整个系统,但不必做到毫无遗漏,但应该能够暴露主要的问题。通过了冒烟测试,就可以认为产品足够稳定,可以接受更加彻底的测试了。
    4. 让冒烟测试与时俱进
    5. 将daily build和冒烟测试自动化。照料并给build喂食是耗时的事。
    6. 成立build小组
    7. 仅当有意义时,才将修订加入build中。但是别等太久才将修订加入进来。
    8. 要求开发人员在把他的代码添加到系统之前,进行冒烟测试
    9. 为即将添加到build的代码准备一块暂存区
    10. 惩罚破坏build的人
    11. 在早上发布build。“在每天快结束时开始冒烟测试,如果发现问题,就在半夜把人召集起来”的做法似乎很有男子气概,但这样的团队过于刻薄,浪费了时间,得不偿失。
    12. 即使有压力,也要进行daily build和冒烟测试
  5. 项目越大,增量继承就越重要。
  6. Key Points:
    1. 构建的先后次序和集成的步骤会影响设计、编码、测试各类的顺序
    2. 一个经过充分思考的集成顺序能减少测试的工作量,并使调试变容易。
    3. 增量集成有若干变型,而且——除非项目是微不足道的——任何一种形式的增量继承都比阶段式集成好。
    4. 针对每个特定的项目,最佳的集成步骤通常自顶向下、自底向上、风险导向及其他集成方法的某种组合。T-型集成和竖直分块集成通常都能工作的很好。
    5. daily build能减少集成的问题,提升开发人员的士气,并提供非常有用的项目管理信息。

第30章 编程工具
  1. 为项目做计划时,就应该花一部分时间来思考需要哪些工具,并为制造这些工具分配时间
  2. 编程——始终需要人来填补真实世界里需要解决的问题和准备用来解决问题的计算机之间的鸿沟。这些人将会被称作程序员,无论他是以汇编语言操控机器的寄存器,还是用VB操控对话框。只要有计算机,就需要能告诉计算机该去做什么的人,这一活动将会被称作编程。
  3. Key Points:
    1. 程序员有时会在长达数年的时间里忽视某些最强大的工具,之后才发现并使用之。
    2. 好的工具能让你的日子过得安逸的多。
    3. 下面这些工具已经可用了:编辑、分析代码质量、重构、版本控制、出错、测试、代码调整。
    4. 你能打造许多自己用的专用工具。
    5. 好的工具能减少软件开发中最单调乏味的工作的量,但它不能消除对“编程”的需要,虽然它会持续的重塑“编程”的含义。

 

  

第7部分 软件工艺

第31章 布局与风格
  1. 出色的程序员在布局实践方面应当头脑开放,接受已证实为更好的方法,即使调适过程最初会感觉有些不舒服。
  2. 程序中空行的最佳比例约为8%~16%,超过16%后调试时间将显著延长。
  3. Key Points:
    1. 可视化布局的首要任务是指明代码的逻辑组织。评估该任务是否实现的指标包括准确性、一致性、易读性、和易维护性。
    2. 外表悦目比起其他指标是最不重要的。然而,如果其他指标都达到了,代码又质量好,那么布局效果看上去也会不错。
    3. VB具有纯代码块风格,而java的传统做法就是使用纯块风格,所以若用这些语言编程,就请使用纯代码块风格。C++中,模拟纯代码块或者begin-end块边界都行之有效。
    4. 结构化代码有其自身目的。始终如一的沿用某个习惯而少来创新。不能持久的布局规范只会损害可读性。
    5. 布局的很多方面涉及信仰问题。应试着将客观需要和主观偏好区分开来。定出明确的指标,在此基础上再讨论风格参数的选择。
第32章 自说明代码
  1. 自说明代码:易理解性、易读性的最高水平。
  2. 在代码层文档中起主要作用的因素并非注释,而是好的编程风格。
  3. IBM:约每十条语句有一个注释,这样的密度时程序清晰度最高。
  4. 对数据注释比对使用数据的过程作注释更重要。
  5. Key Points:
    1. 该不该注释是个需要认真对待的问题。差劲的注释只会浪费时间,帮倒忙;好的注释才有价值。
    2. 源代码应当含有某个程序大部分的关键信息。只要程序依然有用,源代码比其他资料都能保持更新,故而将重要信息融入代码是很有用处的。
    3. 好代码本身就是最好的说明。如果代码太糟,需要大量注释,应先试着改进代码,直至无须过多注释为止。
    4. 注释应说出代码无法说出的东西——例如概述或用意等信息。
    5. 有的注释风格需要许多重复性劳动,应舍弃之,改用易于维护的注释风格。
第33章 个人性格
  1. 编程过程非常耗用脑力,这种特性使得个人性格显得很重要。
  2. 编程工作本质上是项无法监督的工作,因为没有人真正清楚你正在做什么。
  3. 个人性格对于造就出程序员高手具有决定性意义。
  4. 如何专注你的聪明才智,比你有多聪明更重要。
  5. Dijkstra:大部分编程工作都旨在弥补我们有限的智力。精通编程的人是那些了解自己头脑有多大局限性的人,都很谦虚。而那些编程糟糕的人,总是拒绝接受自己脑瓜不能胜任工作的事实,自负使得他们无法成为优秀的程序员。承认自己智力有限并通过学习来弥补,你会成为更好的程序员。你越是谦虚,进步就越快。
  6. 程序员都是大忙人,常常没有时间去考虑怎样改进自己的工作。
  7. 在开发过程中建立自我意识。你越了解软件开发过程,无论通过阅读还是通过自己对软件开发的观察,你就越能理解变化,使团队朝着正确的方向发展。
  8. 如果分配给你的工作净是些不能提高自身技能的短期任务,你理应表示不满。如果在工作中学不到什么,就找一份新工作吧。
  9. 假如不坚持学习,你就会落伍。
  10. 有效编程的关键之一就是要学会迅速制造错误,并且每次都能从中有所收获。犯错不是罪过,从中学不到什么才是罪过。
  11. 解决问题是软件创作过程中的核心行为。
  12. 就算你想再发明个车轮,也不会注定成功,你发明的也许是方车轮。
  13. 只要稍微看一些书就会使你的专业知识又迈进一步。如果每两个月能看一本计算机好书,大约每周35页,过不了多久,你就能够把握本行业的脉搏,并脱颖而出。
  14. 好的程序员总是不断寻找机会来提高自己。
  15. 专业开发阶梯:
    1. 第一级:入门级。入门者会利用某种语言的基本功能,能够编写类、子程序、循环和条件语句,会使用语言的许多特性。
    2. 第二级:中级。中级的程序员已经度过入门级,能利用多种语言的基本功能,并会得心应手的使用至少一种语言。
    3. 第三级:熟练级。熟练级的程序员对语言或环境有着专业技能。这些程序员都是所在公司的活宝,很多程序员再也不能超越该层次。
    4. 第四级:技术带头人级。技术带头人具有第三级的专业才学。并明白编程工作中只有15%用来和计算机交互,其余都是与人打交道的。程序员一般只花30%的时间单独工作,与计算机交互的时间则更少。技术带头人会为人写代码,而非为机器。真正高手所写的代码,像水晶一样晶莹剔透,还配有文档。
  16. 编程生涯成熟的部分标志就是发展处一种不屈不挠的诚实感。
    1. 不是高手时不假装是高手。
    2. 乐于承认错误。
    3. 力图理解编译器的警告,而非弃之不理。
    4. 透彻理解自己的程序,而不要只是编译看看能否运行。
    5. 提供实际的状况报告。
    6. 提供现实的进度方案,在上司面前坚持自己的意见。
  17. 上司通常愿意得到对项目状态的真实汇报,即使那可能并非他们想听到的内容。如果你的汇报经过深思熟虑,应尽量冷静的在私下说出来。管理者需要准确的信息,以便协调开发活动,充分的配合是必要的。
  18. 妥协会让自己失去信用,而坚持观点可能会赢得尊敬。
  19. 编程首先是与人交流,其次才是与计算机交流。
  20. 不要将创造力花到无关紧要的事物上,在非关键之处建立规范,从而在重要地方倾力发挥你的创造性。
  21. 人们容易混淆行动与进展,混淆忙碌与多产。
  22. 坚持可能是财富,也可能是负担。
  23. 知道何时放弃很难,但这是必须面对的问题。
  24. 如果你不能与时俱进,经验不但不会有所帮助,反而是个累赘。
  25. 为了让自己仍然有用,你必须紧跟潮流。
  26. 如果你工作10年,你会得到10年经验还是1年经验的10次重复?必须检讨自己的行为,才能获得真正的经验。只有坚持不懈的学习,才能获取经验;如果不这样做,就无法得到经验,无论你工作多少年。
  27. 可以热爱编程,但热情不能代替熟练的能力。
  28. 初涉某事时,就应该端正态度来学。开始做事情时,你还会积极思考,轻松决定做的好坏。干了一段时间后,就会习以为常,“习惯的力量”开始起作用。请确保这些习惯是你所希望的东西。
  29. 不必为失去坏习惯而多虑,有了新习惯,坏习惯自然就会消失。
  30. Key Points:
    1. 人的个性对其编程能力有直接影响。
    2. 最有关系的性格为:谦虚、求知欲、诚实、创造性和纪律,以及高明的偷懒。
    3. 程序员高手的性格与天分无关,而任何事都与个人发展相关。
    4. 出乎意料的是,小聪明、经验、坚持和疯狂既有助也有害。
    5. 很多程序员不愿主动吸收新知识和技术,只依靠工作时偶尔接触新的信息。如果你能抽出少量时间阅读和学习编程知识,要不了多久就能鹤立鸡群。
    6. 好性格与培养正确的习惯关系甚大。要成为杰出的程序员,先要养成良好习惯,其他自然水到渠成。
第34章 软件工艺的话题
  1. 致力于降低复杂度是软件开发的核心。
    1. 在架构层将系统分为多个子系统,以便让思绪在某段时间内能专注于系统的一小部分。
    2. 仔细定义类接口,从而可以忽略类内部的工作机理。
    3. 保持类接口的抽象性,从而不必记住不必要的细节。
    4. 避免全局变量,因为它会大大增加总是需要兼顾的代码比例。
    5. 避免深层次的继承,因为这样会耗费很大精力。
    6. 避免深度嵌套的循环或条件判断,因为它们都能用简单的控制结构取代,后者占用较少的大脑资源。
    7. 别用goto,因为他们引入了费顺序执行,多数人都不容易弄懂。
    8. 小心定义错误处理的方法,不要滥用不同的错误处理技术。
    9. 以系统的观点对待内置的异常机制,后者会成为非线性的控制结构。异常如果不受约束的使用,会和goto一样难以理解。
    10. 不要让类过度膨胀,以至于占据整个程序。
    11. 子程序应保持短小。
    12. 使用清楚、不言自明的变量名,从而大脑不必费力太多的细节信息。
    13. 传递给子程序的参数数目应尽量少。更重要的是,只传递保持子程序接口抽象所必须的参数。
    14. 用规范和约定来使大脑从记忆不同代码段的随意性、偶然性差异中解脱出来。
  2. 降低复杂度是衡量程序员成果的最重要依据。
  3. 对于小的项目,程序员的个人才能对软件质量影响最大。对于多个程序员参与的项目,组织性和重要程度超过了个人技能。人们一起工作时的方式将决定其能力是珠联璧合还是相互抵挡。
  4. 规范是一套用于管理复杂度的智力工具。
  5. 如果没有充分了解问题就定下解决方法,说明你还不够成熟。受限于所坚持的思路,你很可能与最有效的方法失之交臂。
  6. Key Points:
    1. 编程的主要目的之一是管理复杂度
    2. 编程过程对最终产品有深远影响
    3. 合作开发要求团队成员之间进行广泛沟通,甚至同计算机的交互;而单人开发则是自我交流,其次才是计算机
    4. 编程规范一旦滥用,只会雪上加霜;使用得当则能为开发环境带来良好机制,有助于管理复杂度和相互沟通。
    5. 编程应基于问题域而非解决方案,这样便于复杂性管理
    6. 注意警告信息,将其作为编程的疑点,因为编程几乎是纯粹的智力活动。
    7. 开发时迭代次数越多,产品质量越好
    8. 墨守成规的方法有悖于高质量的软件开发。请将编程工具箱中填满各种编程工具,不断提高自己挑选合适工具的能力。
第35章 何处有更多信息
  1. 你犯的错误别人早已犯过,要是不想自讨苦吃,就读读他们的书吧,这样能够避免再走弯路,并找出解决问题的新方法。
  2. 阅读大纲:
    1. 入门级:
      1. 《Conceptual Blockbusting: A Guide to Better Ideas》
      2. 《Programming Pearls》
      3. 《Facts and Fallacies of Software Engineering》
      4. 《Software Project Survival Guide》
      5. 《Code Complete》
    2. 熟练级:
      1. 《Software Configuration Management Patterns: Effective Teamwork, Practical Integration》
      2. 《UML Distilled: A Brief Guide to the Standard Object Modeling Language》
      3. 《Software Creativity》
      4. 《Testing Computer Software》
      5. 《Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and the Unified Process》
      6. 《Rapid Development》
      7. 《Software Requirements》
    3. 专业级:
      1. 《Software Architecture in Practice》
      2. 《Refactoring: Improving the Design of Existing Code》
      3. 《Design Patterns》
      4. 《Principles of Software Engineering Management》
      5. 《Writing Solid Code》
      6. 《Object-Oriented Software Construction》

你可能感兴趣的:(Coffee&Mac)