代码大全总结
发布时间:2025-12-10 11:47:54
浏览次数:20
第一部分 打好基础 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:子程序应该不因传入错误的数据而被破坏,哪怕是由其他子程序产生的错误的数据。即核心思想是程序都是有问题,都是要被修改。
- 第九章 伪代码编程过程 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
- 检查代码 Check the Code
- 收尾工作 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 选择好变量名的注意事项
- 变量名中计算值限定空间 Computed-Value Qualifiers In Variable Names
- 变量名中的对仗词 Common Opposites In Variable Names
- 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
- 为布尔变量赋予隐含“真/假”含义的名字
- 为枚举变量命名 Naming Enumerated Types:
- 为常量命名 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
- 11.7 应该避免的名字 Kind of Names To Avoid
- 第十二章 基本数据类型
- 12.1 数值概论
- 避免神秘数值
- 可以使用硬编码的1或者0
- 预防除零错误
- 使类型转换变得明显:不同的数据类型之间会发生转换时,利用显式转换而非隐式转换
- 避免混合类型的比较:应该转换成相同类型再进行比较
- 注意编译器警告
- 12.2 整数 Integers
- 检查整数除法
- 检查整数溢出:在整数加法或者乘法的过程中,留心较大的整数。
- 12.3 浮点数 Floating-Point Numbers
- 第十三章 不常见的数据类型
- 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语句
- 15.2 case语句 case Statements
- 为case语句选择最有效的排序
- 按字母顺序或者数字顺序排列各种情况:所有情况的重要性相同
- 把正常的情况放在前面
- 按执行频率排列case语句
- 使用case语句的诀窍
- 第十六章 控制循环
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
第五部分 代码改善 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 Inpidual Classes
- 哑类(模仿对象/桩对象) dummy class(mock object/stub object):根据所需的真实性决定他们与现实的近似程度
- 调用待测试的真实函数的伪造函数,称为“驱动函数”或者“测试夹具”
- 测试数据生成器 Test-Data Generations
- 产生程序员意想不到的数据组合
- 比起手工构造测试数据,随机数据生成器能够更彻底地进行测试
- 覆盖率监视器 Coverage Monitors
- 数据记录器/日志记录器
- 符号调试器
- 系统干扰器
- 错误数据库
- 22.6 改善测试过程 Improving Your Testing p528
- 有计划的测试
- 重新测试(回归测试):在彻底检查代码的前提下,当相关代码发生改变之后,需要重新测试,可能产品迭代增加新的测试用例的同时应保留旧的测试用例
- 自动化测试
- 22.7 保留测试记录 Keeping Test Records p529
作者:白桦叶
链接:https://www.jianshu.com/p/7b7228ebba55
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
转载于:https://www.cnblogs.com/valarchie/p/8870846.html