《代码大全》读书笔记

第一部分 打好基础 Laying the Foundation

  • 第一章 欢迎进入软件构建的世界 Welcome to Software Construction
  • 什么是软件的构建
    • 定义问题
    • 需求分析
    • 规划构建
    • 软件架构 或者 高层设计
    • 详细设计
    • 编码与调试
    • 单元测试
    • 集成测试
    • 集成
    • 系统测试
    • 保障维护
  • 总结
    • 软件构建是软件开发中唯一不可缺少的部分,也就是必须完成的部分
    • 软件的构建主要包括:详细设计、编码调试、集成和开发者测试(单元测试和集成测试)
    • 你对软件构建的理解程度决定 程序员的优秀程度
  • 第二章 用隐喻来更充分地理解软件开发 Metaphors for a Richer Understanding of Software Development
  • 第三章 三思而后行:前期准备 Measure Twice, Cut Once: Upstream Prerequisites
  • 第四章 关键的『构建』决策 Key Construction Decisions

第二部分 创建高质量的代码 Creating-High Quality Code

  • 第五章 软件构建中的设计 Design in Construction
  • 第六章 可以工作的类 Working Classes:抽象是以简化方式看待复杂操作的能力
  • 6.1类的基础:抽象数据类型 ADTs
    • ADT,abstract data type 抽象数据类型。它指的是一些数据及对这些数据的操作集合。这里的"数据",不仅仅是数学上或者软件工程中的数据,而是现实世界中可以操作的实体
    • ADT 的好处:
      • 隐藏实现细节(可能会有后续操作)
      • 容易改动(更改数据结构,优化提高性能)
      • 让接口提供更多信息(通过名称)
      • 可读性提高
      • 不需要多次传值(相关操作需要用到变量都放在ADT里面了)
    • 把常见的底层数据类型(栈 队列)创建为ADT并使用

      如出场演员名单(底层数据类型是列表)

    • 对于应用层面上ADT,最好在原有ADT的基础上创建一个针对现实世界问题的抽象层次。
    • 简单的事情可以抽取成ADT(方便扩展后续操作)
    • 在支持面向对象的语言,ADT可以用自己的class(类)实现。class=ADT+继承+多态
  • 6.2良好的类接口:用接口去展示抽象,确保细节隐藏在抽象背后
    • 接口中的每个子程序都朝着这个一致的目标而工作
    • 类的接口要展示一致的抽象层次,一个类只能实现一个ADT,不然就要拆分
    • 要理解类要抽象出什么功能,避免把使用的类库或者容器类暴露出来
    • 尽可能让接口可编程(programatic,编译器强制要求),而不是表达语义(sematic,通过方法名和注释)。

    比如多个类的初始化有先后顺序;一个类没有初始化调用会报错

    • 扩展的时候要注意新增公用方法的 抽象的一致性
    • 不要对类的使用者做任何假设,接口已经隐含了契约(接口已经提供了调用的条件说明)
    • 语义上的封装比语法上的封装要困难(公用接口不要暴露内部实现和数据)

      P142 很多例子

    • 封装和抽象要么两者皆有,要么全部没有
  • 6.3有关设计和实现的问题:包含/继承/成员函数/数据成员/类之间的耦合性
    • 包含(has a "有一个的关系"):数据成员的限制:7-+2

    数据成员都是基本数据类型,数据成员不超过9;数据成员都是复杂对象,数据成员不超过5

    • 继承(is a “是一个的关系”)(使用时会增加复杂度,有违软件的技术使命-管理复杂度的)
      • 要考虑方法和属性对派生类是否可见,方法是否要有默认的实现,是否可以覆盖?
      • 继承要符合里氏替换原则:对于基类定义的接口,在派生类的语义应该是相同的
      • 不要覆盖不可覆盖的方法(不要新建一个与基类的private相同的方法)
      • 只有一个派生类,可能犯了提前设计的毛病
      • 继承不要超过2-3层,派生类总数不超过该7+-2个;
      • 尽可能让数据让数据时private,因为继承会破坏封装
      • 如果多个类共享数据而非行为,创建这些类包含共用对象
      • 如果多个类共享行为而非数据,在基类定义接口,继承基类
      • 如果多个类共享行为和数据,在基类定义接口和数据成员,继承基类
      • 当你想由基类控制接口时,用继承,由自己控制接口,用包含
    • 成员函数和数据成员:
      • 减少以下数字的数量
        • 所实例化对象的种类
        • 调用实例化对象的子程序的数量
        • 调用由其他对象返回对象的子程序数量
        • 子程序的数量
      • 构造函数
        • 尽可能在构造函数中初始化全部数据成员
  • 6.4创建类的原因
    • 对现实对象的建模
    • 对抽象对象的建模(如shape就是抽象对象,得出恰当的抽象对象很重要
    • 降低复杂度(调用类的接口不用关心实现细节)
    • 隔离复杂度
    • 隐藏实现细节
    • 限制变化的影响范围
    • 隐藏全局数据
    • 让参数传递更流畅
    • 创建中心控制点
    • 让代码重用
    • 为程序族做规划
    • 把相关操作放在一起(子程序的组合)
    • 实现特定的重构
  • 6.5与具体编程语言有关的问题
  • 6.6超越类:包
  • 类的质量核对表P157-P158
  • 第七章 高质量的子程序 High-Quality Routines
    • 7.1 创建子程序(routines)的正当理由:提高程序管理能力,包括提高可读性、可靠性和可修改性,节省代码空间是次要,或者是副作用(side effect)。
      • 降低复杂度
      • 引入中间,易懂的抽象
      • 避免重复
      • 支持子类化(subclassing)
      • 隐藏顺序
      • 隐藏指针操作
      • 提高可移植性
      • 简化布尔判断
      • 改善性能
      • 确保所有程序都很小
    • 7.2 在子程序层上设计(Design at the Routine Level):抽象和封装理念适合类层次的设计,内聚性适合子程序设计
    • 内聚性(cohesion):指子程序中各种操作之间联系的紧密程度。(一个子程序就干一个活)
    • 功能内聚性:一个子程序只干一件事情
    • 以下是不够理想的内聚性,但是有作用的。
      • 顺序上的内聚性:子程序包含按特定顺序执行的操作,这些操作共享数据,而且只有在操作全部执行完毕的时候才达到一项完整的功能
      • 通信上的内聚性:指一个子程序内的不同操作用同样的数据,但不存在任何的联系
      • 临时的内聚性:一些因为需要同时操作而放在一起的操作

        startUp()

    • 以下是不可取的内聚性
      • 过程内聚性:把一组操作放在子程序中并按照特定顺序执行,除此之外没有其他彼此的联系
      • 逻辑上内聚性(其实是缺乏逻辑的内聚性):把若干操作放入同一子程序,通过传入的控制符执行不同的操作
      • 如果子程序仅有由一系列if else语句以及其他子程序语句组成,这样的逻辑上内聚性的子程序是可以的,就是这个子程序只发出各种指令,不进行任何的处理,那么他就是一个事件处理器(event handler)
    • 巧合的内聚性:子程序的操作之间没有任何的 联系
    • 7.3 好的子程序的名字 Good Routine Names
    • 描述子程序所做的所有的事
    • 避免使用无意义、模糊或者表达不清的动词 event handling事件处理除外

    handleCalculation()
    PerformServices()
    OutputUser()
    ProcessInput()
    DealwithOutPut

    • 不仅仅通过数字形成不同子程序名

    outPut1(); outPut2()

    • 根据需求确定子程序的名称长度
    • 函数命名时要对函数的返回值有所描述

    printer.isReady()
    customerId.next()

    • 给子程序命名时要使用语气强烈的动词加宾语的形式,在面向对象语言中,不用再过程(Procedure)名中不用加入对象名(宾语)

    document.print() orderInfo.check()

    • 准确使用对仗词

      add/remove insert/delete start/stop up/down
      begin/end increment/decrement open/close get/set
      first/last old/new min/max show/hide
      create/destory lock/unlock source/target get/put

    • 为常用操作确立命名规则
    • 7.4 子程序可以写多长 How long can a Routine be
    • 虽然有很多研究但是仍然没有一个公认的说法,一般是50-200行左右,可以从子程序的内聚性嵌套层次变量数量决策点(desicions points)和注释来决定子程序的长度,当超过200行,就要考虑在可读性上问题了。
    • 7.5 如何使用子程序参数 How to Use Routine Parameters
      • 按照输入-修改-输出的顺序排列参数:暗含了操作数据的顺序
      • 如果子程序用了类似的参数,参数的排列顺序应该一致
      • 使用所有的参数,没有用的要撇除
      • 把状态或者出错变量放在最后,因为它们只是程序的附属
      • 不要把子程序参数当做工作变量

      对输入参数进行操作,并将其作为返回值返回结果。
      * 在接口中对参数的假定加以说明,在接口文档说明
      * 参数是仅用于输入,要被修改,还是仅用于输出
      * 表示数量的参数单位(秒/分)
      * 没有用枚举类型的话,应该说明状态码和错误码的含义
      * 所能接受的数据范围
      * 不能接受的特定数值
      * 把子程序的参数个数限制在7个以内
      * 如何一直需要传递很多参数,说明子程序之间的耦合太过紧密。如果需要向很多不同的子程序传入相同的数据,就把这些子程序组成一个类,并把那些经常使用的数据作为类的内部数据
      * 考虑对参数使用某种输入、修改、输出的命名规则
      > i_xxx,m_xxx,o_xxx 或者 Input_xxx,Modify_xxx,Output_xxx
      * 为子程序传递用以维持其接口抽象的变量和对象
      * 假如经常需要修改子程序的参数表,且都是来自同一个对象,那就传递整个对象
      * 如果只是为传递几个特定的数据,把数据填入对象,再到子程序读取这些数据,那就只传递数据的值

    • 使用具名参数
    • 确保实际参数与形式参数相匹配
    • 7.6 使用函数时要特别考虑的问题
    • 函数有返回值的子程序,过程是指没有返回值的子程序
    • 设置函数的返回值
      • 检查所有可能的返回路径
      • 假如需要返回有关的数据,那就应该作为类的成员保存起来,而不是作为局部数据的引用或者指针返回。
    • 7.7 宏子程序和内联子程序 Macro Routine And Inline Routines
    • 把宏表达式整个都包含在括号内
    • 把包含多条语句的宏用大括号括起来
    • 用给子程序命名的方法给展开后代码形同子程序的宏命名,以便需要可以用子程序来替换宏。
    • 高质量的子程序核对表P185
  • 第八章 防御式编程 Defensive Programming:子程序应该不因传入错误的数据而被破坏,哪怕是由其他子程序产生的错误的数据。即核心思想是程序都是有问题,都是要被修改
    • 8.1 保护程序免遭非法输入数据破坏 Protecting your program from invalid inputs
      • 检查所有来源于外部数据的值:从文件网络用户或者其他外部接口获得的数据应该检查其有效性
      • 检查子程序所有输入参数的值
      • 决定如何处理错误的输入数据
    • 8.2 断言 Assertions
      • 在开发期间使用的,让程序运行时进行自检的代码,一个断言一般包含两个参数,一个布尔表达式,一个断言为假时显示的消息。断言程序执行的前条件和后条件
    • 用断言检查一下假定:
      • 输入参数或者输出参数在预定范围内
      • 子程序开始(结束)文件或者流处于开启或者关闭的状态
      • 子程序开始(结束)文件或者流读写的位置位于开头或者结尾
      • 指针不为空
      • 传入子程序的数组或者其他容器至少能存储X个数据元素
      • 表已经初始化,存储着真实的数据
      • 仅用于输入的变量的值没有被子程序改变
      • 子程序开始或者结束的时候,某个容器为满或者为空
      • 高度优化的子程序与运行缓慢但逻辑清晰的子程序运行结果一致
    • 对于高健壮性的代码,应该先断言再处理错误
    • 8.3 错误处理技术 Erro-Handling Technique:断言处理不应该发生的错误,错误处理技术处理预料中可能发生的错误。
      • 返回中立值
        • 数值计算返回0
        • 字符串操作返回空字符串
        • 指针操作返回空指针等
      • 换用下一个正确的数据

      温度计读取数值失败,可以等下一次如1/100秒读取
      * 返回前次相同的数据
      > 上面温度计的例子同样适用
      * 换用最接近的合法值
      >得到字符串长度小于0,那返回0
      * 把警告信息写到日志
      * 返回一个错误码
      * 调用处理错误的对象或者子程序
      * 健壮性 vs 正确性 robustness vs correctness
      * 高层次设计对错误处理方式的影响

    • 8.4 异常Exceptions:是把错误或者异常事件传递给调用方代码的特殊手段
      • 用异常通知其他的程序,发生了不可忽略的错误
      • 与断言相似,都是用来处理罕见甚至不可能出现的情况???
      • 是处理意外的有效途径,但是增加复杂度
      • 不能用异常来推卸责任,明确是由自身处理异常还是由调用方处理异常
      • 避免在解构函数和构造函数抛出异常,除非在同一地方进行捕获
      • 恰当的抽象层次抛出异常,就异常应于当前接口的抽象层次一致,避免暴露实现细节和内部信息
      • 避免使用空的catch语句,除非将catch的异常文档化
      • 了解函数库可能抛出的异常、
      • 考虑创造一个集中的异常报告机制,就是对于异常进行统一的格式化并记录和存储
      • 把项目中对异常的使用标准化
        • 可以定义项目特定的异常类,记录日志、报告错误的集中起来和标准化
        • 规定何种场合异常时需要局部处理
        • 规定何种场合异常只能抛出,不能局部处理
        • 考虑异常的替换方案
    • 8.5 隔离程序,使之包容由程序错误造成的伤害Barricade Your Program to Contain the Damage Caused by Errors
      • 隔栏是一种容错的策略 damage containment strategy
      • 可以在类的层面上采用这种方法,在类的公有方法假定输入的数据时不安全的,对数据进行检查并清理,之后再将数据传给私有方法,类的私有方法假定数据都是安全的
      • 隔栏外部的程序使用错误处理技术,在那里对数据的假定是不安全的。内部的程序使用断言技术,这样如果隔栏内的出现错误的数据,就是程序上的错误而非数据上的错误
    • 8.6 辅助调试的代码 Debugging aids
      • 不要把自动地把产品版的限制强加于开发版上:开发版可以花费更多的资源,允许运行缓慢,允许暴露不安全的操作
      • 尽早引入辅助代码
      • 采用攻击式编程 offensive programming
      • 确保断言使程序中止,及时修复错误
      • case语句的default分支或者else分支产生严重的错误以致不被忽视
      • 计划移除调试辅助的代码
    • 8.7 确定在产品中该保留多少防御式代码 Determine How much Defensive Programming to Leave in Production code
      • 保留那些检查重要错误的代码
      • 去掉检查细微错误的代码
      • 去掉可以导致程序硬性崩溃的代码:虽然便于调试,但是用户体验差
      • 为你的技术支持员记录错误信息
      • 确认留在代码中的错误信息是友好的(friendly):就是要严谨,正式
    • 8.8 对防御式编程采取防御姿态 Being Defensive about Defensive Programing
      • 防御式编程增加复杂度
      • 防御式编程因为要检查参数使程序运行缓慢
    • 核对表 P211
  • 第九章 伪代码编程过程 The Pseudocode Programming Process
    • 9.1 创建类和子程序的步骤概述 Summary steps of Building Classes and Routines
      • 图9.1 at p216:
      • 创建一个类的步骤 Steps in Creating a class
        • 创建类的总体设计 具体参考第六章 可以工作类
          • 定义类的职责
          • 定义类要隐藏的"秘密"
          • 定义类的接口所代表的抽象概念
          • 决定这个类是否要从其他类派生出来
          • 决定这个类是否可以被派生,即是否能被继承
          • 指出类的关键公用方法
          • 标识并设计出类所需要的重要数据成员
        • 创建类的子程序 Steps in Building a routine
          • 图9.2 at p217
          • 子程序的种类:成员访问子程序 (accessor routine ),转发到其他对象(pass-throughs)的子程序
          • 步骤:设计子程序->检查设计->编写子程序的代码->检查代码
        • 复审并测试整个类 :在子程序创建的同时经过测试,在整个类可以工作后,应该再对整体进行复查和测试,以便于发现在子程序独立测试层次上无法发现的问题
    • 9.2 伪代码 Pseudocode for Pros
      • 伪代码:描述 算法、子程序、类或者完整程序的工作逻辑、非正式的、类似英语的记法
      • 伪代码的注意事项:
        • 用类似英语的语句来精确描述特定的操作
        • 避免使用目标编程语言的元素,伪代码是比代码本身略高的设计层次,使用目标编程语言的元素会降低设计层次
        • 在本意 intent 层面编写伪代码
        • 在足够低的层次编写伪代码,以便可以近乎转化为代码
        • 好的伪代码能转换为注释
    • 伪代码的好处:
      • 伪代码使得评审更容易
      • 伪代码支持反复迭代精化思想:自顶向下,逐层拆解问题,解决问题
      • 伪代码使变更更加容易
      • 伪代码能使给代码作注释的工作量减少
      • 伪代码比其他设计形式的文档更容易维护
    • 9.3 通过伪代码编码过程创建子程序
    • 设计子程序 Design the Routine
      • 检查先决条件:子程序工作是否定义好,是不是与整体设计相匹配。是否项目必需的
      • 定义子程序的解决的问题:
        • 子程序要隐藏的信息
        • 子程序的输入
        • 子程序的输出
        • 调用子程序前确保有关的前条件成立(输入数据在特定范围内,流已经初始化等)
        • 在子程序将控制权交回调用程序前,确保后条件成立(输出数据在特定范围内,流已经关闭)
      • 为子程序命名
      • 决定如何测试子程序
      • 在标准库搜索可用的功能:重用好的代码,不重复造轮子
      • 考虑错误处理
      • 考虑效率问题:

        第一种情况绝大数系统而言,效率并不是十分紧要。另一种情况是对少数系统而言性能非常重要。在除了上述两种情况,在子程序效率的优化是白费功夫的,因为主要的优化是在于完善高层的设计
        * 研究算法和数据类型
        * 编写伪代码:先写头注释 head comment,再写伪代码
        * 考虑数据
        * 检查伪代码:确认很容易,很自然地理解子程序做些什么以及怎样做
        * 在伪代码中试验一些想法,留下最好的想法(迭代):伪代码试验想法成本比代码低。
        > 用伪代码反复描述这个子程序,直到伪代码写出句子已经足够简单,你可以把伪代码直接变成代码文档为止。最初伪代码层次太高,不断的精化和分解伪代码,直到再写伪代码实在浪费时间为止
        * 编写子程序代码 Code the Routine
        * 图9.3 at p225


        * 写出子程序的声明:把如果接口名称起得直接了当,就不需要接口假定(interface assumption)的事情
        * 把伪代码转变为高层次的注释
        * 在每条注释下填充代码: 伪代码相当于文章的提纲,每段伪代码注释描述类一段或者一句代码
        * 检查代码是否需要进一步分解
        * 如果一行伪代码下的代码过多,可以refactor重构成一个子程序
        * 递归recursively地应用伪代码编程过程。如果一行伪代码下的代码过
        多,可以把一行伪代码拆分为多行伪代码

    • 检查代码 Check the Code
      • 在脑海检查程序的错误:当子程序足够短小精悍,检查到程序所有可能的执行路径、端点和异常情况,不但要自己查(这叫桌面检查 desk checking),可以同行审查peer review,详查walk-through或者审查inspection

        从superstition迷信理解,调查显示只有5%的错误是由于编译器或者硬件造成的,所以遇到的错误大部分都是程序员自身造成的
        * 编译子程序:把编译器的警告级别调到最高,使用像lint的检查工具及时消除产生错误信息和警告的所有根源
        * 在调试器中逐行运行代码
        * 测试代码
        * 消除程序中的错误

    • 收尾工作 Clean Up LeftOvers
      • 检查子程序的接口
      • 检查子整体的设计质量:内聚性;子程序间松散耦合;防御式编程C7
      • 检查子程序的变量C10-13
      • 检查子程序的语句和逻辑:有无泄漏资源,错误,死循环,错误嵌套C14-19
      • 检查子程序的布局:格式化 C31
      • 检查子程序的文档
      • 除去冗余的注释
    • 9.4 伪代码编程过程的替代方案 Alternative to the PPP
      • 测试先行开发(测试驱动开发)Test-first Development:在任何代码之前先要写出测试用例,使得程序可测试
      • 重构refactoring C24
      • 契约式设计 design by contract 即每一段程序都具有前条件preconditions和后条件postconditions
      • 东拼西凑hacking
      • 核对表 P233

第三部分 变量 Variable

  • 第十章 使用变量的一般事项 General Issue in Using Variables
    • 10.1 数据认知: 列举的常见的数据类型
    • 10.2 轻松掌握变量定义:
      • 隐式声明:避免隐式声明,可能导致编译器的初始值不符合编程的要求,应该关闭隐式声明,并声明全部变量;
    • 10.3 变量初始化原则:
      • 初始化错误:
        • 从未对变量赋值
        • 变量值已经过期
        • 变量的一部分被赋值
      • 避免初始化方法:
        • 在声明时初始化变量
        • 在靠近变量第一次使用的地方初始化它
        • ****理想状态下****,在第一次使用的地方声明并初始化变量
        • 在可能的情况下,使用final或者const
        • 在计数累加器i j,再次使用时忘记重置是一个常见的错误
        • 在类的构造函数中初始化该类数据成员
        • 检查变量是否需要重新初始化
        • 一次性初始化具名常量,用可执行代码初始化变量。
    • 10.4 作用域 Scope
    • 使变量引用局部化:跨度span:变量引用点之间的距离;应该把变量的引用点集中起来
      、 * 尽可能缩短存活时间:就是变量最初引用点到最后引用点之间的距离
      • 减少作用域的一般原则:
        • 在循环开始前初始化该循环里使用的变量,而不是在该循环所属的子程序开始处初始化这些变量
        • 直到变量即将被使用的时候再为其赋值
        • 把相关的语句放在一起
        • 把相关的语句提出成单独的子程序
        • 开始时采用严格的作用域,然后根据需要扩展变量的作用域
          > private -> protected -> default- >public
    • 10.5 持续性:有可能数据发生了变化,引用了过期的变量导致错误
    • 在子程序加入调试代码或者断言检查关键数据的合理性
    • 准备抛弃变量时给它设置不合理的值 个人认为在at C++
    • 编写程序假定是没有持续性的,但不适合c++或者java中的static数据
    • 养成使用所有数据前声明和初始化的习惯
    • 10.6 绑定时间:绑定时间越早灵活性越差,其实跟上面的初始化变量的指导是一样的
    • 10.7 数据类型和控制结构之间的关系:
      • 序列型数据翻译为程序中的顺序语句
      • 选择型数据翻译为程序中的if else语句
      • 迭代型数据翻译为for repeat while等循环语句
    • 10.8 为变量指定单一用途
      • 每个变量只用于单一的用途
  • 第十一章 变量名的力量
    • 11.1 选择好变量名的注意事项
      • 最终重要的命名事项:
        • 名字要完全、准确地表达该事物
        • 容易阅读、不包含晦涩的缩写、同时无歧义
      • 以问题为导向:好的名字是表达”什么“(what)而不是”如何“(how),即反映问题而不是解决方案。变量应直指问题的领域而非计算机世界

        inputRecord 比 employeeData
        * 最适当的名字长度 Optimum Name Length:
        * 有研究是平均长度在10到16个字符
        * 有研究是平均长度在8到20个字符
        * 最重要是强调当自己代码中出现很多更短的名字,认真检查确保名字含义足够清晰
        * 变量名对作用域的影响 The Effect of Scope on Variable Names、
        * 对位于全局命名空间中的名字加入限定词
        * 如果编程语言不支持命名空间,就在对应的子系统加入前缀

    • 变量名中计算值限定空间 Computed-Value Qualifiers In Variable Names
      • 把总额 sum total、平均数 average 、最大值Max、最小值Min、记录Record、字符串String、Pointer指针加入名字的后面

        revenueTotal revenueAverage更具对称性,更容易维护
        Num放在开始位置代表总数,放在结束位置代表一个下标
        customerCount代表员工总数 customerIndex代表某个特定的员工

    • 变量名中的对仗词 Common Opposites In Variable Names
      • begin/end
        * first/last
      • locked/unlocked
      • min/max
      • next/previous
      • old/new
      • opened/closed
      • visible/invisible
      • source/target
      • source/destination
      • up/down
    • 11.2 为特定类型的数据命名 Naming Specific Types of Data
    • 为循环下标命名 Naming Loop Index:
      • 一般情况下使用i、j、k
      • 多层循环嵌套的情况或者循环长度超过一两行代码,应该给计数器赋予更长的名字以表达其含义
    • 为状态变量命名 Naming Status Variables
      • 为状态变量取一个比flag更好的名字
      • 标记应该用枚举类型具名常量或者作为具名常量的全局变量对其进行赋值,增加可读性
    • 为临时变量命名 Naming Temporary Variables:在使用temp等命名前最好思考是否有能表达其含义的名字
    • 为布尔变量命名 Naming Boolean Variables:
      • 谨记典型的布尔变量命名:
        • done 表示事情是否完成,未完成前是false,完成后是true
        • error 表示有错误发生,错误发生前是false,错误发生后是true
        • found 表示某个值已经找到,在未找到该值前是false,找到该值后是true
        • success或者ok 表示一项操作是否成功,失败是false,成功是true
      • 为布尔变量赋予隐含“真/假”含义的名字
        • status不是一个好的命名
        • isXXX优点是:不能用于哪些模糊的名字。缺点是可读性较差

          isStatus无意义 isFound 比found可读性差
          * 使用肯定布尔命名

    • 为枚举变量命名 Naming Enumerated Types:
      • 可以通过使用组前缀为明确表示该类型的成员都同属于一个组

        Color_XXX
        * 对于枚举的命名有不同的观点:有大小写混合Color_Blue;与常量类似的大写Color.BULE
        * 在处理枚举像类的编程语言里,处理枚举很像类,所以枚举成员总是冠以枚举名字前缀,就无需重复前缀了

    • 为常量命名 Naming Constans:应命名该常量代表的抽象事物而非数值
    • 11.3 命名规则的力量 The Power of Naming Covenstions
      • 为什么要有规则
        • 要求你更多按规矩办事,集中精力投入关注代码更重要的特征
        • 有助于项目之间传递知识
        • 有助于学习新项目
        • 有助于减少名字增生 name Proliferation
        • 弥补编程语言的不足
        • 强调相关变量之间的关系:把相关变量设置相同前缀将它们关联起来
      • 何时采用命名规则
      • 多人开发
      • 程序需要转交别人
      • 程序规模太大,无法同时了解全局,必需分而治之
      • 程序开发周期过长
      • 一个项目存在一些不常见的术语,在编写代码中使用术语或者缩写的时候
    • 11.4 非正式命名规则 Informal Naming Conventions
      • 与语言无关的命名规则指导原则
      • 与语言相关的命名规则指导原则:有C,C++,只列举Java
        • i,j是整数下标
        • 常量全部大写并用下划线分割
        • 类名和接口每个单词首字母大写
        • 变量名和方法名第一个单词首字母小写,后续单词首字母大写
        • 除用于全部大写的名字外,不使用下划线作为名字中的分隔符
        • 访问器子程序使用get和set前缀
      • 混合语言编程注意事项
      • 命名规则示例
        • 包含以下三类信息:
          • 变量的内容(是什么)
          • 数据的种类(具名常量,简单变量,用户自定义类型或者类)
          • 变量的作用域(局部,私用的,类的,包的或者全部的作用域)
        • 类成员数据:mXXX,全局变量:gXXX
    • 11.5 标准前缀 Standardized Prefixes:分用户自定义类型(UDT)的缩写语义前缀
    • 用户类型缩写 User-Defined Type Abbreviation:UDT缩写可以标识被命名对象或者变量的数据类型
    • 语义前缀 Semantic Prefixes:描述变量或者对象是如何被使用的
      • c:count 数量
      • first:数组需要处理的第一个元素
      • g:全局变量
      • i:数组的下标
      • last:数组中需要处理的最后一个元素
      • lim:数组中需要处理的元素上限,是非法的,不存在的上限,而last是合法的
      • m:类一级的变量
      • min:数组或者其他种类列表中绝对最前一个元素
      • max:数组或者其他种类列表中绝对最后一个元素
      • p:pointer 指针
    • 标准前缀的优点:使名字更紧凑、增加可读性
    • 11.6 创建具备可读性的短名字 Creating Short Names That Are Readable
      • 缩写的一般指导原则:
        • 使用标准缩写(参考词典)
        • 去掉所有非前置元音???

          computer->cmptr screen->scrn
          * 去掉虚词
          > andorthe
          * 使用每个单词的第一个或者前几个字母
          * 统一地使用单词的第一、第二或者第三(自行确定)字母后截断
          * 保留每个单词的第一和最后一个字母
          * 使用名字中每个重要单词,最多不超过3个
          * 去除无用的后缀
          > eding
          * 确保不要改变变量的含义

    • 11.7 应该避免的名字 Kind of Names To Avoid
      • 避免使用令人误解的名字或者缩写
      • 避免使用具有相似含义的名字
      • 避免使用具有不同含义但是相似名字的变量:

        一般是缩写很相似的情况如 clientReq 和 clientRes
        * 避免使用发音相似的名字
        * 避免在名字中使用数字
        * 避免拼错单词
        * 避免使用容易拼错的单词
        * 不要仅靠大小写来区分变量名
        * 避免使用多种自然语言:只有英语,不使用汉语等
        * 避免使用标准类型、变量和子程序名字:编程语言的关键词
        * 不要使用与变量含义无关的名字
        * 避免在名字中使用容易混淆的字符:数字1对于字母l或者字母i,数字2对应字母z,数字5对应字母s,数字6对应字母g,数字0对应字母o

  • 第十二章 基本数据类型
    • 12.1 数值概论
      • 避免神秘数值
      • 可以使用硬编码的1或者0
      • 预防除零错误
      • 使类型转换变得明显:不同的数据类型之间会发生转换时,利用显式转换而非隐式转换
      • 避免混合类型的比较:应该转换成相同类型再进行比较
      • 注意编译器警告
    • 12.2 整数 Integers
      • 检查整数除法
      • 检查整数溢出:在整数加法或者乘法的过程中,留心较大的整数。
    • 12.3 浮点数 Floating-Point Numbers
      • 避免数量级相差巨大之间加减运算:解决方案:对于一系列相差巨大的数进行运算,先进行从小到大排序从最小值开始把它们加起来,并不能消除舍入问题,但是能减少到最低限度。

        1000 000.00+0.1可能等于1000 000.00
        * 避免等量判断:确定数值在可接受的精度范围内
        > double类型变量,for循环中 加0.1,十次后不一定等于1.0
        * 处理舍入误差问题:
        * 换用精度更高的变量类型
        * 把浮点变量变成整数变量
        * 检查语言和函数库对特定数据类型的支持
        * 12.4 字符和字符串 Characters and Strings
        * 避免神秘字符和神秘字符串
        * 了解你的语言和开发环境是如何支持Unicode
        * 在程序生命周期中尽早决定国际化/本地化策略
        * 只支持一种文字语言,考虑使用ISO-8859字符集
        * 需要支持多语言,请使用unicode
        * 使用某种一致的字符串转换策略
        * 12.5 布尔变量
        * 用布尔变量对程序加以文档说明:对于表达式的结果赋予一个布尔变量,以提高可读性
        * 用布尔变量简化复杂判断:这是在上一条意见演化出来的,也是提高可读性
        * 如果需要的话,创建你自己的布尔类型
        * 12.6 枚举类型 Enumerated Types
        * 用枚举提高代码可读性
        * 用枚举提高代码可靠性
        * 用枚举是程序易于简化修改
        * 使用枚举作为布尔值的替代方案:有多种失败的类型
        * 检查非法数值
        * 定义枚举的第一项和最后一项以用于循环边界
        > Enum Country{
        Country_First = 0;
        Country_China = 0;
        Country_USA = 1;
        Country_Last = 1;
        }
        * 把枚举的第一个元素留作非法值
        > Enum Country{
        Country_InvalidFirst = 0;
        Country_First = 1;
        Country_China = 1;
        Country_USA = 2;
        Country_Last = 2;
        }
        * 明确定义项目代码编写标准中第一个和最后一个元素的使用规则
        * 警惕给枚举元素明确赋值而带来错误:
        > Enum Country{
        Country_InvalidFirst = 0;
        Country_First = 2;
        Country_China = 2;
        Country_UK = 4;
        Country_USA = 6;
        Country_Last = 6;
        }
        遍历的时候会遍历到1,3,5这些非法值
        * 如果你的语言没有枚举类型:p307
        * 12.7 具名常量 Named Constants
        * 12.8 数组 Arrays
        * 确保数组下标没有越界
        * 考虑用容器取代数组,或者将数组作为顺序化结构来处理
        * 检查数组的边界
        * 数组是多维,保证下标的使用顺序正确,防止下标串话
        * 在C中结合ARRAY_LENGTH()宏来使用数组
        * 12.9 创造自己的类型(类型别名)Creating Your Own Types(Type Aliasing)

  • 第十三章 不常见的数据类型
    • 13.1 结构体 Structure:指使用其他类型创建的数据,类似java中没有公用子程序,完全由公用数据成员组成的类,个人认为就是封装
      • 用结构体明确数据关系:归为一类,关联起来
      • 用结构体简化对数据块的操作
      • 用结构体简化参数列表
      • 用结构体减少维护
    • 13.2 指针 Pointers???未学,略
      • 用来理解指针的范例:指针:内存中的某个位置+如何解释该位置的内容
        • 内存中的位置:就是一个地址,以16进制数表示
        • 如何解释指针所指的内容:由指针的基类型 base type决定
      • 使用指针的一般技巧:略
    • 13.3 全局数据 Global Data
      • 与全局变量有关的常见问题:
        • 无意间修改了全局数据
        • 与全局数据有关的奇异的和令人激动的别名问题:就是出现两个或者以上的名字都是指同一个变量
        • 与全局数据有关的代码重入(re-entrant)问题:多线程情况下全局数据不仅是不同子程序共享,同时同一程序的不同拷贝之间也共享
        • 全局数据阻碍代码重用

子程序用到全局数据,不能直接将子程序复制到其他地方(其他类)里面,解决方法:上策是修改旧类将全局数据局部化;下策是在新类创建与旧类相同的全局数据,导致像病毒一样传染
* 与全局数据有关的非确定的初始化顺序事宜
> 在初始化一个类的变量时需要使用其他文件的初始化全局变量,所以需要采用明确手段保证两个变量按照正确顺序进行,不然将导致错误
* 全局数据破坏了模块化和智力上的可管理性
* 使用全局数据的理由:
* 保存全局数据:比如程序是否debug等
* 模拟具名常量
* 模拟枚举类型
* 简化对极其常用数据的使用
* 消除流浪数据(tramp data):

有时候传递数据给一个子程序或者类,只是想传递给另一个子程序或者类,如果调用链中间的子程序并不适用这一对象的时候,就称这些数据为流浪数据
* 只有万不得已才使用全局数据
* 按照"局部数据->private数据->protected数据->全局数据"顺序设置数据的作用域
* 区分全局变量和类变量
* 使用访问器子程序
* 用访问器子程序来取代全局数据
* 访问器子程序的优势
* 获得对数据的集中控制:如果要修改结构方法是需要修改子程序即可
* 确保变量的所有引用得到保护,避免出现异常
* 访问器子程序可以容易转变为抽象数据类型:即通过子程序名称实现抽象,提高代码可读性
* 如何使用访问器子程序
* 要求所有数据通过子程序访问
* 不要把全局数据放在一起,而是放在相应抽象水平的类里面
* 用锁来控制对全局数据的访问:在多线程下,子程序访问器加锁,保证数据正确性
* 使得对一项数据的所有访问都发生在同一抽象层上
> 如果有add(event),就会有remove(event)
* 如何降低使用全局数据的风险
* 创建一种命名规则来突出全局变量
> gXXX
* 为全局变量创建一份注释良好的清单
* 不要用全局变量存储中间结果
* 不要把全局变量都放在一个大对象中并到处传递,以说明你没有使用全局变量
> 全局变量应根据其抽象层次防到相应的类中

第三部分 语句 statement

  • 第十四章 组织直线型代码 Organizing Straight-Line Code
    • 14.1 必须有明确顺序的语句 statements That Must be in Specific Order
      • 设法组织代码,让依赖关系变得非常明显

      • 使子程序名能突显依赖关系

      • 利用子程序参数明确显示依赖关系

        参数
        init(expenseData);
        dayExpensse(expenseData);
        monthlyExpensse(expenseData);
        anunalExpensse(expenseData);

        带返回值
        expenseData = init(expenseData);
        expenseData = dayExpensse(expenseData);
        expenseData = monthlyExpensse(expenseData);
        expenseData = anunalExpensse(expenseData);
        用数据表明依赖关系不重要
        init(expenseData);
        dayExpenseData = dayExpensse(expenseData);
        monthlyExpenseData = monthlyExpensse(expenseData);
        anunalExpensseData = anunalExpensse(dayExpenseData ,monthlyExpenseData );
        * 用注释对不清晰的依赖关系进行说明
        * 用断言或者错误处理代码来检查依赖关系:但是增加类复杂度,采用的时候需要衡量利弊

    • 14.2 顺序无关的 语句 Statements Whose Order Don't Matter
      • 使代码易于自上而下地阅读 Making Code Read From Top to Bottom:跟把相关代码组织在一起时道理是一样的
      • 把相关代码组织在一起 Grouping Related Statements
  • 第十五章 使用条件语句 Using Conditionals
    • 15.1 if语句
      • 简单的if-then语句
        • 首先写正确代码路径,再处理不常见情况
        • 确保等量分支是正确的:不要漏掉特定情况
        • 把正常情况的处理放在if后面而不要放在else后面
        • if后面不要跟空语句:要不就改成 if(!XXX){};
        • 考虑else语句:如果需要可以配个空的else语句并加以说明
        • 测试else语句的正确性
        • 检查if else语句是不是弄反
      • if-then-else语句串 Chains of if-then-else statements:
        • 使用布尔值调用简化复杂的检测
        • 把最正确的情况放在最前面

        if(xxx){
        }else if(xxx){
        }
        else if(xxx){
        }
        * 如果语言支持把if-then-else语句串替换成其他结构:case语句,更清晰

    • 15.2 case语句 case Statements
      • 为case语句选择最有效的排序
        • 按字母顺序或者数字顺序排列各种情况:所有情况的重要性相同
        • 把正常的情况放在前面
        • 按执行频率排列case语句
      • 使用case语句的诀窍
        • 简化每种情况对应的操作:对于某种情况的操作过于复杂,应该变成一个子程序
        • 不要为了使用case语句而刻意制造一个变量
        • 把default语句只用于检查真正默认的情况

          还剩一个情况,用default去检查是不对的
          * 使用case穿越(穿透)需要注释说明情况
          * 核对表at p635

  • 第十六章 控制循环
    • 16.1 选择循环的种类 Selecting the Kind of Loop p367
      • 种类:
      • 计数循环 counted loop
      • 连续求值循环 continuously evaluated loop
      • 无限循环 endless loop
      • 迭代器循环 iterator loop

      对于C、C++、Java:for、foreach 、while、检查位置都是开始 do- while是结尾
      灵活度除了foreach 是严格之外其他均是灵活

do-while至少执行一次,其他可以不执行
* 什么时候使用while循环 When to Use a Loop-While-Exit Loop
* 什么时候用带退出的循环
* 正常带退出的循环
* 带退出的循环更容易理解
* 带退出的循环可能使退出的地方很多,可能导致在调试、修改或者测试时被忽略,如果可能尽可能把退出的代码写在一个地方
* 非正常带退出的循环
* 什么时候使用for循环 When to Use a for Loop:

你在循环头处写好后即把它忘掉,无须再循环中做任何事情去控制它,如果有一个必须使循环从循环退出的条件,就使用while循环
* 什么时候使用foreach循环 When to Use a foreach Loop:消除循环内务处理算数,防止off-by-one越界错误

  • 16.2 循环控制 Controlling the Loop p373
    • 防止出现错误的方法:
      • 减少能影响该循环各种原因的因素:说了跟没有一样——
      • 把循环内部当做子程序,把控制尽可能放在循环体外
        while(XXX && XXX && (XXX||XXX)){
        
         XXXXXXXXXX
         }
        
    • 进入循环 Entering Loop
      • 只从一个位置进入循环
      • 把初始化代码紧放在循环前面
      • 用while(true)代表无限循环
      • 在适当情况下多使用for循环

        因为for循环把循环控制代码集中了,while需要在循环顶部初始化循环条件,然后在底部修改循环的相关代码
        * 在while循环更适用的时候,不要使用for循环
        > 不是for(xx;xx;xx)中间代码不是简单对计数值进行判断,而是其他表达式则应当该为while循环
        * 处理好循环体 Processing The Middle of the Loop
        * 用{}将循环体重的语句包起来:个人:即使是一条语句也需要,因为扩展、修改程序可能会出现意料之外的错误
        * 避免空循环:不要出现循环体为空的情况,应改成do-while
        * 把循环体的内务操作要么放在循环开始处,要么放在结尾处:内务操作如i= i+1这样的表达式
        * 一个循环只做一件事
        * 退出循环 Exiting Loop
        * 设法确认循环能够终止:考虑正常情况、端点以及每一种异常情况
        * 不要为了终止循环混乱修改for循环下标
        * 避免出现依赖于下标最终取值的代码:下标值只在循环体内有效,可以说是缩小作用域的一种做法
        * 考虑使用安全计数器
        * 提前退出循环
        * 考虑在while循环中使用break而不是布尔标记:

就是循环体中前后操作有条件限制关系,当达到某个条件,不执行后续操作时,应当使用break直接退出
* 小心那些有很多break散布在循环中
* 在循环开始处使用continue:提高可读性
* 如果语言支持,请使用带标记号break结构:是break退出的目标一目标然
* 使用break和continue要小心谨慎
* 检查端点 Checking EndPoints
> 简单的循环:开始情况+任意选择的中间情况+最终情况,先脑海模拟,如果有复杂计算,手动检查计算是否正确
* 使用循环变量 Using Loop Variables
* 用整数或者枚举类型表示数组和循环的边界
* 嵌套循环中使用有意义的变量名提高其可读性
* 用有意义的名字防止循环下标串话
* 把循环下标变量限制在本循环内
* 循环应该多长 How Long Should a Loop Be
* 把循环代码的行数限制在50行以内
* 把嵌套限制在3层以内
* 把长循环的内容移到子程序内
* 要让长循环格外清晰

  • 16.3 轻松创建循环-由内而外 Creating Loop Easily- From the inside Out p385
    • 循环内部逻辑->循环控制语句
      *伪代码->代码
  • 16.4 循环和数组的关系 Corresponse Between Loop And Arrays p387
  • 第十七章 不常见的控制结构 Unusual Control Structures
    • 17.1 子程序中多处返回 Multiple Return From a Routine p391
    • 如果能增加可读性,就用return
    • 用防卫句子(guard clause)(早返回或早退出) 来简化复杂的错误处理
    • 17.2 递归 Recursion p393
    • 使用递归的技巧
      • 确认递归能够终止
      • 使用安全计数器防止无限循环
      • 把递归限制在一个子程序里面
      • 留心栈空间:防止栈溢出
      • 不要用递归去计算阶乘和斐波那契数列
    • 17.3 goto p398
      • 反对goto的观点 The Arguments Against gotos
      • 支持goto的观点 The Arguments for gotos
      • 关于goto的虚假辩论 The phony goto Debate
      • 错误处理和goto Erro Processing And goto
      • goto和在else字句中的共享代码 goto And The Sharing Code In an else Clause
      • goto使用原则总结 Summary of Guidlines For Using *gotos *
    • 17.4 针对不常见控制结构的观点 Perspective on Unusual Control Structures
  • 第十八章 表驱动法 Table-Driven Methods
    • 18.1 表驱动法使用总则 General Considerations in Using Table-Driven Methods p411
      • 查询表取代复杂的逻辑控制结构
        if(xxx|xxx){
      
            xxxxx
        }else if(xxx ||xxx && xxx){
        }else if(xxx ||xxx && xxx){
        }
      
      • 使用表驱动法的两个问题 Two Issues in Using Table-Driven Methods
        • 怎样从表中查询条目(查询
          • 直接访问 Direct access
          • 索引访问 Index access
          • 阶梯访问 Stair-step access
        • 在表中存些什么(数据
    • 18.2 直接访问表 Direct Access Tables p413
      • 示例 一个月中的天数 保险费率 灵活消息的格式
      • 灵活的消息格式:20种消息类型打印出来
        • 基于逻辑:根据消息的类型,一个类型对应一个子程序执行打印消息
        • 面向对象设计:定义一个消息基类,在其中定义一个打印消息的共有方法,派生出不同的消息子类,然后重载打印消息的方法,通过多态的方式打印消息
        • 表驱动法:根据不同的消息类型查找其对应字段信息和字段类型,并通过同一个子程序将基本数据信息打印出来
      • 构造键值对值 Fudging Lookup Keys
        • 复制信息从而能够直接使用该值:缺点是导致数据冗余,浪费空间
        • 转换键值对以使其能够直接使用
        • 把键值对转换提出成独立的子程序
    • 18.3 索引访问表 Indexed Access Table p425
      • 通过居间的索引进行访问
      • 优点:
        • 减少存储空间,主查询表每条记录都很大,索引表相对而言每条数据占用空间小很多
        • 即使使用索引访问,操作索引中记录也比操作主表中的记录来得简单
        • 表查询技术在可维护性上具有优点
    • 18.4 阶梯访问表 Stair-Step Access Table p426
      • 表中的数据对于不同数据范围有效,而不是对不同的数据点有效
      • 注意事项:
        • 留心端点:端点的情况是否被考虑到
        • 考虑用二分查找取代顺序查找
        • 考虑用索引访问来取代阶梯技术
    • 18.5 表查询的其他示例 Other Example of Table Lookups p429
  • 第十九章 一般控制问题 General Control Issue
    • 19.1 布尔表达式 Boolean Expressions p431
      • 用true或者false做布尔判断 Using true of false For Boolean Tests
        • 用true或者false做判断,而不用0和1等数值做判断
        • 隐式地比较布尔值与true和false:

          while(success){xx} 而不是while(success == true){xxx}
          * 简化复杂的表达式 Making Complicated Expression Simple
          * 拆分复杂判断并引入新的布尔变量
          * 把复杂的表达式做成布尔函数
          * 用决策表替代复杂的条件
          * 编写肯定形式的布尔表达式 Forming Boolean Expression Positively
          * 先肯定再否定语句
          > if(xx){xxx}else{xxx}而不是if(!xx){xxx}else{xxx}
          * 用狄摩根定理(逆反定理)简化否定的布尔判断
          * not A and not B = not(A or B) not A or not B = not (A and B)
          * 用括号使布尔表达式更清晰 Using Parentheses to Clarify Boolean Expressions
          * 用简单的技术技巧来使括号对称:开始为0,遇到一个左括号加1,遇到一个右括号减1
          * 把布尔值括在括号里面
          * 理解布尔表达式如何求值 Knowing How Boolean Expression Are Evaluated
          * 按照数轴顺序编写数值表达式 Writing Numeric Expressions in Number-Line Order
          * 从左到右,从大到小
          * 与0比较的知道原则 Guidelines for Comparisons to 0
          * 隐式地比较逻辑变量
          * 把数和()相比较
          * 在C中显式地比较字符和零终止符(‘\0’)
          * 把指针与NULL相比较
          * 布尔表达式的常见问题
          * java中 == 和equal的区别

    • 19.2 复合语句(语句块)Compound Statements (Blocks) p443
      *把括号对一起写出
      • 用括号把条件表达清楚
    • 19.3 空语句 Null Statements p444
      • 小心使用空语句
      • 为空语句创建一个DoNothing()预处理函数或者内联函数:强调空语句
      • 考虑如果换用一个非空的循环体,是否会让代码更清晰
    • 19.4 驯服危险的深层嵌套 Taming Dangerously Deep Nesting p445
      • 通过重复检测条件中某一部分来简化嵌套的if语句
      • 用break来简化嵌套if
      • 把嵌套if转换成一组if-then-else语句:即转换成if(xxx){xxx}elseif(xxx){xxx}elseif(xxx){xxxx}
      • 把嵌套if转换成case语句
      • 把深层嵌套的代码抽取出来放进单独的子程序
      • 使用一种更面向对象的方法:即多态
      • 重新设计深层嵌套代码
      • 争议 用状态变量重写代码(增加复杂度)17.3
      • 争议 用防卫字句退出子程序,从而使代码的主要路径更为清晰 17.1
      • 使用异常 8.4
    • 19.5 编程基础:结构化编程 p454
      • 核心思想:应用程序只采用一些单入口,单出口的控制结构。单入单出的控制结构就是一个代码块,只能从一个位置开始执行,并且只能结束于某个位置。(有且只有一个入口和出口
      • 结构化编程的三个组成部分 The Three Components of Structured Programming
        • 顺序 Sequence:赋值和调用子程序
        • 选择 Selection:if和case语句
        • 迭代 Iteration:
    • 19.6 控制结构与复杂度 Control Structure and Complexity p456
      • 复杂度的重要性 How Import is Complexity
      • 降低复杂度的一般性原则 General Guidelines For Reducing Complexity
        • 怎样去度量复杂度:

      程序一开始为1
      一旦遇到关键词或者其同类加1:if repeat,while,for,and,or
      给case语句中每一种情况加1
      * 如何处理复杂度的度量结果
      > 0-5子程序可能不错|
      6-10得想办法 简化子程序类
      10+把子程序 的某一部分拆分成另一个子程序并调用它

第五部分 代码改善 Code Improvement

  • 第二十章 软件质量描述 p463
    • 20.1 软件质量的特性
      • 外在特性:
      • 正确性 Correctness 指系统规范、设计和实现方面的错误的稀少程度
      • 可用性 Usability:指用户学习和使用的成本
      • 效率 Efficiency:指占用的内存,储存和执行时间
      • 可靠性 Reliability:在指定必需条件下,完成所需功能的能力:很长的无故障时间
      • 健壮性 Robustness:接受无效数据或者在压力环境下运行的能力
      • 完整性 Integrity:阻止对程序或者数据进行未经验证或者不正确访问的能力
      • 适应性 Adaptability:为特定应用或者环境参数设计的系统,能不修改的情况给其他应用或者环境使用
      • 精确性 :输出结果的误差程度
      • 内在特性:
      • 灵活性 Flexibility: 适应需求
      • 可维护性 Maintainability
      • 可移植性 Portability
      • 可重用性 ReuSability
      • 可读性 Readability
      • 可测试性 Testability
      • 可理解性 Understandability
    • 20.2 改善软件质量的技术
    • 20.3 不同质量保障技术的相对效能
    • 20.4 什么时候进行质量保证工作
    • 20.5 软件质量的普遍原理
  • 第二十一章 协同构建 p479
  • 第二十二章 开发者测试 p499
    • 22.1 开发者测试在软件质量中的角色 p500
      • 测试的种类:
        • 单元测试(Unit testing):测试的代码:一个程序员或者一个团队编写 代码规模:完整的类、子程序或者小程序
        • 组件测试(Component testing)测试代码:一个类、包小程序或者其他程序元素 代码规模:涉及到多个程序员或者多个团队
        • 集成测试(Integration testing):是对两个或更多的类、包、组件或者子系统进行联合测试。这些组件由多个程序员或者开发团队所创建。
      • 测试的作用:
        • 测试与其他开发活动背道而驰,测试的目标识找出错误,成功的测试是弄垮软件,而其他开发活动是避免程序错误和软件崩溃
        • 测试永远不可能彻底证明程序中没有错误
        • 测试并不能解决错误,改善软件质量
        • 程序员倾向于做出”干净“的测试
      • 构建中测试
        • 构建期间:先根据需求构想子程序->编写子程序或者类->先在脑海检查->进行复查或者测试
        • 每写一个子程序,对其进行独立测试,确实不容易,但是单独调试比继承之后再测试容易多。
        • 新加入的子程序发现新的错误,就知道这是子程序或者其接口引发的问题
    • 22.2 开发者测试的推荐方法 Recommend Approad to Developer Testing p503
    • 基础方法:
      • 对每一项相关的需求进行测试
      • 对每隔相关的设计关注点进行测试
      • 用基础测试basic testing来扩充针对需求和设计的详细测试用例:增加数据流测试 data-flow test,然后补充其他所需的测试用例
      • 使用一个检查表,其中记录着你在项目中所犯的错误
    • 测试先行还是测试后行:测试先行
      • 先写测试用例只是工作顺序问题:所花代价一样
      • 先编写测试用例,可以更早发现缺陷
    • 开发者测试的局限性
      • 开发者测试倾向于”干净“测试
      • 开发者测试对于代码覆盖率过于乐观
      • 开发者测试往往忽略一些更复杂的测试覆盖率类型
    • 22.3 测试技巧锦囊 p505
      • 不完整的测试Incomplete Testing:进行完整测试是不可能的事情
      • 结构化的基础测试 Structured Basic Testing
        • 测试程序中的每一条语句至少一次
        • 结构化基础测试最少数量计算:
          • 对于通过子程序的直路,开始的时候加1
          • 遇到每个关键词或者等价物加1,如:if、while、repeat、for、and以及or
          • 遇到每个case语句就加1
      • 数据流测试 Data-Flow Testing:数据流出错率不低于控制流
        • 数据的状态
          • 已定义:数据已经初始化了,但是没有被使用
          • 已使用:数据已经用于计算或者作为某子程序的参数
          • 已销毁:变量出了作用域或者指针已经被释放
        • 数据的状态的组合:正确的是已定义-已使用
      • 等价类划分 Equipment Partitioning
      • 猜测错误 Error Guessing
      • 边界值分析 Boundary Analysis
        • 常规边界值:小于、等于和大于
        • 复合边界值
      • 几类坏数据 Classes of Bad Data
        • 数据太少(没有数据)
        • 太多的数据
        • 错误(无效)的数据
        • 未初始化的数据
      • 几类好数据 Classes of Good Data
        • 正常的情况-中间或者期望值
        • 最小的正常局面
        • 最大的正常局面
        • 与旧数据的兼容性
    • 22.4 典型错误 p517
      • 错误不是均匀分布的
      • 绝大数错误与少数几个具有严重缺陷的子程序有关
      • 错误有几大类:结构方面的错误?,数据方面的错误,已实现的功能(可能是说随着迭代,新需求跟旧实现之间的错误)
      • 大多数错误的影响范围有限
      • 大多数构建错误是编程人员的错误造成的
      • 拼写错误是一个常见的问题
    • 22.5 测试支持工具 Test-Support Tools p523
      • 为测试各个类构造脚手架 Building Scaffolding to Test Individual Classes
        • 哑类(模仿对象/桩对象) dummy class(mock object/stub object):根据所需的真实性决定他们与现实的近似程度
        • 调用待测试的真实函数的伪造函数,称为“驱动函数”或者“测试夹具”
      • 测试数据生成器 Test-Data Generations
        • 产生程序员意想不到的数据组合
        • 比起手工构造测试数据,随机数据生成器能够更彻底地进行测试
      • 覆盖率监视器 Coverage Monitors
      • 数据记录器/日志记录器
      • 符号调试器
      • 系统干扰器
      • 错误数据库
    • 22.6 改善测试过程 Improving Your Testing p528
      • 有计划的测试
      • 重新测试(回归测试):在彻底检查代码的前提下,当相关代码发生改变之后,需要重新测试,可能产品迭代增加新的测试用例的同时应保留旧的测试用例
      • 自动化测试
    • 22.7 保留测试记录 Keeping Test Records p529
  • 第二十三章 调试 p535
  • 第二十四章 重构 p563
  • 第二十五章 代码调整策略 p587
  • 第二十六章 代码调整技术 p609

推荐阅读更多精彩内容