1 如果能认识到设计是一项明确的活动, 你就会获益匪浅,也就是说无论是大项目还是小项目,设计都是软件构建的一部分
5.2 关键的设计概念:
最小复杂度: 应该做出简单易于理解的设计,如果你的设计方案不能让你在专注于程序的一部分时安心的忽略其他部分的话,这一设计就没有什么作用了
易于维护 : 设计出自明的系统(self-explanatory)
松散耦合 : 就是让程序的各个组成部分关联最小
可扩展性 : 增强系统的功能二无须破坏它的底层结构
可重用性 : 在其他系统中课重复使用
高扇入 : 就是让大量的类使用某个给定的类
低扇入 : 就是让一个类 里面少量或适中的使用其他的类
可移植性 : 就是可以改变环境
精简性 : 就是设计出的系统没有多余的部分,一本书的完成不在它不能在增加任何内容的时候,而是在于不能再删除任何内容的时候
层次性 : 暂时还不能理解
标准技术 : 尽量使用标准的语言利于共享
7.1
1 什么是高质量的子程序,是从分析低质量的子程序开始的,书中有例子分析,分析的几条都需要细化概念
2 创建子程序的正当理由:
降低复杂度:
1)创建子程序最主要的一个原因就是为了降低程序的复杂度,可以通过创建子程序隐藏一些信息,即,使这些信息只在子程序中考虑,而在子程序之外的地方是不考虑的,当要编写子程序的时候要考虑他们,不过一旦程序写好了,就应该可以忘掉这些细节,可以直接调用这个子程序而无需了解其内部工作的细节,这样就达到了只专注当前考虑的程序而不被其他内容所影响,并降低了整个程序的复杂度
2)不需要了解细节,缩小规模,易于维护,提高正确性,当循环的嵌套过多时,就需要从子程序中提取出新的子程序来形成一个独立的子程序,这样可以降低外围子 程序的复杂度,解决嵌套的问题是看有没有php语句的高级用法可以解决嵌套的问题比如continue和break
引入简单易懂的抽象:
就是为子程序命名一个好的变量名称,可以直观的了解你的这段程序的意图,所以说把一些功能逻辑比较复杂难于理解的代码写成子程序并赋予一个好的名字可以 直观的了解判断你实现的是一个什么功能,而暂时不用去管具体的是如何实现的,这样就有利于代码和结构上面的逻辑清晰了。
避免代码重复:
如果在两个子程序中有类似的代码,就需要把类似的重复的代码提取出来,将其中相同的部分放在一基类,差异的放在派生类里面
支持子类化 :
隐藏顺序:
把处理事件的顺序隐藏起来是一个很好的解决方式,一个子程序的执行不应该依赖另一个子程序是否执行,把具有相互依赖关系的语句集合起来组成子程序,这样就把必须执行的顺序隐藏在子程序中了,这样要比在系统内到处散布要好的多
隐藏指针操作 :
提高可移植性 :
简化复杂的布尔判断 :
要把复杂的布尔判断放在函数中,这样可以隐藏复杂的细节,另外一个好的名字可以使程序逻辑清晰
改善性能 :
如果子程序在多处得到引用,并且此子程序可以优化,那么整个系统就会多处得到优化
确保所有的子程序都很小:
这是没有必要的
3 简单但很有必要写成子程序 166
当多处引用该子程序的时候是有必要的,或者该子程序有可能随时修改的情况下,这是需要根据具体的情况而言的
7.2在子程序层上设计
1功能的内聚性 :是最强也是最好的一种内聚性,也就是说让一个子程序仅执行一次操作,名字和功能要相对应
2 需要改变调整的其他不理想的内聚性,具体的如何修改参考书上面有例子都挺好的 168
3 编写具有功能内聚性的子程序总是可能的 ,因此要把注意力放在这个上面
7.3好的子程序的名字
1 描述子程序所作的所有事情,如果不能描述清除就不要含有副作用
2 避免使用无意义的,模糊或表述不清的动词
3 不要仅通过数字来形成不同的子程序的名字
4 根据需要确定子程序名字的长度 9--15个字符
5 给函数命名时要对返回值有所描述
6 给过程起名时要用语气强烈的动词加上宾语的形式(名词等)
7 准确使用对仗词
add/remove increment/decrement open/close begin/end insert/delete show/hide create/destroy lock/unlock source/target first/last min/max start/stop get/put next/previous up/down get/set
old/new
8 为常用操作确立命名规则 如id
7.4 决定子程序长度的因素:
复杂的算法可以增加子程序的长度
子程序的内聚性,嵌套的层次,变量的数量,决策点的数量,注释数量及一些跟复杂度相关的考虑事项等
7.5 如何使用子程序参数
1 按照输入-修改-输出的顺序排列参数 : 修改就是作为输入输出的参数
2 如果几个子程序都用到一些类似的参数,应该让这些参数的排列顺序保持一致 3 使用所有的参数,去掉不使用的参数 4 把状态变量和指示发生错误的变量放在参数表的最后,因为这两个都是输出变量 5 不要把子程序的参数用作工作变量,最好是使用局部变量,就是不要用参数存储数据值,不要改变参数的值 6 子接口中对参数的假定加以说明 7 子程序的参数不应超过7个,如果超过则说明子程序之间耦合太过紧密了,应该重新设计这个子程序或这组子程序,如果你向很对不同的子程序传递相同的数据,那 就把这些子程序组成一个类,把这些经常使用的数据用作类的内部数据 8 如果你认为把输入,修改,输出参数区分开很重要那可以使用前缀 i_,m_,o_命名规则加以区分 9 为子程序传递用以维持接口抽象的变量或对象,传递的参数要足够维持子程序的稳定性,如果你经常修改参数列表,而每次修改的参数都是来自同一个对象,那就说 明你应该传递整个对象而不是个别的数据项了 10 把形式参数和实际参数对应起来避免参数放错位置 11 确保实际参数和形式参数的数据类型相一致 7.6使用函数时需要特别考虑的问题 1 什么时候使用函数什么时候使用过程 如果一个子程序的主语用途就是返回由其名字所指明的返回值,就应该是用函数,否则应该是用过程 2 设置函数的返回值 7.7 宏子程序和内联子程序 没看
伪代码编程过程的价值重大,却很少有程序员真正挖掘出该过程的全部能量
9.1 创建类和子程序的步骤概述
1 创建类的步骤 没看
2 创建子程序的步骤 参见书 设计子程序,检查设计,编写子程序代码,检查代码
9.2 伪代码
1 项目成功的关键之一就是在代码最小的阶段扑获到错误
2 有效使用伪代码的指导原则
1)使用语句来描述特定的操作(英语和中文都可)
2)避免使用编程语言,在比代码略高的层次上设计
3)用伪代码编写你解决问题的意图,思路
4)伪代码的层次应该是仅靠在编写代码的层次之上,以便可以近乎自动地从它生成代码,应该不断的精化伪代码,加入越来越多的细节,直到看起来已经很容易直接写出代码为止
3 两个伪代码的比较 218-219
4 伪代码可以直接转化为注释,程序比较小的时候直接编程程序
5 伪代码的优势
1)使底层设计评审工作更容易,同时减少了对代码本身的进行评审的需要
2)伪代码支持反复迭代精华的思想,在不同的层次就可以发现和解决,不会产生威胁
3)伪代码使变更更加容易,在设计的最初阶段就可以发现错误和问题
4)可以使注释量减少到最小
5)伪代码比其他的设计文档更容易维护
9.3 通过伪代码编程过程创建子程序
1 设计子程序
1)检查先决条件 :检查子程序与整体设计是否相匹配,是否是必须的需求。
2)定义子程序要解决的问题:具体的实例参见书
陈述出改子程序需要解决的问题,叙述要足够的详细以便能去创建这个子程序,需要如下条件
a 这一子程序需要隐藏的信息
b 传给这个子程序的各项输入
c 从该子程序得到的输出
d 在调用程序之前确保有关的前条件成立(初始化已经完成,输入数据的取值位于特定的范围之内)
e 在子程序将控制权交回调用方之前,确保其后条件的成立
看看显示错误的实例reportErrorMessage()
. 该子程序隐藏了两项信息:错误信息的文本和当前的错误处理方式
. 对于该子程序没有任何可保证的前条件
. 给该子程序的输入数据是一个错误码
. 存在两种输出:首先是错误信息,其次是reportErrorMessage()返回给调用方程序的状态值
. 该子程序保证状态值或者为success或者为failure
3)位子程序命名
4)决定如何测试子程序,就是伪造一些合法的和不合法的参数来测试(属于单元测试,如果想了解通过先编写测试来构建程序的另一种完全不同的方法,请阅读《测试驱动开发》)
5)在标准库中搜索可用的功能:就是说在一个复杂的设计时,先看手册上面是否有合适的函数来简化你的设计,然后查看公司维护的代码库中是否有已经写好的可以提供该功能的函数,这样可以提高代码的质量和生产率
6)考虑错误处理:输入的数据和其他子程序中返回的数据
7)考虑效率问题:程序的接口是否有很好的抽象,封装是否好,算法,性能、资源与速度,记住不要在子程序上为效率白费功夫,这是需要在整体设计上考虑的事情。
8)研究算法和数据类型:在程序库中无法找到相应的内容的时候,看算法书
9)编写伪代码 :参加书上的实例,这段实例是在很高的层次上编写的
10)考虑数据:如果对数据的操作是某子程序的重点,那么就要考虑好数据的类型,清楚主要的数据部分
11)检查伪代码:复查伪代码、让别人能够理解
12)在伪代码中试验不同的想法,留下最好的,不断的精化和分解伪代码,参见书
2 编写子程序的代码步骤
1)写出子程序的说明:首先写出子程序的接口的说明,就是建立接口,变量
2)把伪代码转化为给高层次的注释 参见书,编写第一条和最后一条语句,然后转化为注释
3)在每条注释下面填充代码
221页5句话的规格说明到224页的15行伪代码到现在的长达一页的子程序,说明编码并不是一项简单的工作
4)检查代码是否需要进一步的分解:代码行过多,重构成子程序和继续递归的使用伪代码,就是说再次使用伪代码编程
3 检查代码
尽可能早的发现所有的错误
1)在脑海里检查程序的错误:就是在编译之前查看代码,在脑海里执行每一条代码路径,如果出现错误不要迷信是系统或其他原因,大部分是自己的错误
2)编译子程序:在完成以上步骤后才可以编译子程序是有效的,那种“只要在编译一次就可以搞定啦”的毛病会导致你对代码做出草率而可能带来错误的更改,从长远看这可能会花掉你更多的时间,要脱离“并凑,编译然后修改”的毛病
3)如何发挥编译子程序的功效
A 把编译器的警告级别跳到最高,通过编译器来检测错误
B 使用验证工具,FIRFOX
C 消除产生错误信息和警告的所有根源
4)在调试器中逐行执行代码:查看代码是否按照期望执行
5)测试代码:用以前编写的测试用例,参见22章和23章
6) 消除程序中的错误
4 收尾工作按照本书说描述的优秀代码的特性来检查
具体参见书
5 根据需要重复上述的步骤高质量的编程是一个迭代的过程,所以在程序质量不佳时,不要犹豫,把构建的工作再做一边
9.4 伪代码编程过程的替代方案
第十章 使用变量的一般事项
10.1 数据认知
1 数据认知的测试
10.2 轻松掌握变量定义
1 声明全部的变量
10.3 变量初始化原则
1 在声明变量的时候初始化
2 在靠近变量第一次使用的位置初始化,符合就近原则
3 在靠近变量第一次使用的位置声明和定义该变量
4 在类的构造函数里初始化该类的数据成员
5 检查是否需要重新初始化
6 利用警告信息
10.4 作用域
1 是变量引用局部化:就是把使用该变量的地方都集中在一起,有利于提高程序的可读性
2 尽可能的缩短变量的存活时间:变量初始化的语句和最后引用到该变量的语句之间的语句的行数应该尽可能的少
3 减少作用域的一般原则
1)在循环开始之前初始化循环当中需要用到的变量
2)知道变量即将被使用时在对其进行赋值
3)把相关的语句放在一起:如 oldnum,lodpage,...放在一起 newnum,newpage,...放在一起
4)把相关的语句组提取成单独的子程序
5)开始的时候采用最小的变量作用域,然后根据需要扩展变量的作用域
6)隐藏的信息越多,在同一时间内你需要考虑的信息就越多,犯错误的几率就越小
10.5 持续性
避免使用错误变量的步骤
1 加入调试代码或断言
2 在准备抛弃变量的时候给他们赋上“不合理的数值”
3 养成在使用所有数据之前声明和初始化的习惯
10.6 绑定时间
1 主要是考虑,变量的可替换性,当在多处都要使用某种数值的时候,最好是把这数组赋予变量,这样只要更改一处则使用这个变量的其他的地方就会都更改过来,比如,$color=#000;这是最好是把#000这个硬编码,付给一个常量。
10.8 为变量指定单一的用途
1 每个变量只用于单一用途:就是说定义变量不要重名
2 避免让代码具有隐含含义:使用两个变量来保存两种信息
3 确保使用了所有已声明的变量
12.1 数值概论
1 预防除零错误
2 是类型转换变的明显
3 避免混合类型的比较
4 注意编译器的警告
12.2 整数
12.3 浮点数
1 处理舍入问题:把浮点变量变成整型变量,比如用100表示美元,则0 到 99就表示美分,用函数把这个封装起来,进行换算
2 检查函数库
12.4 字符和字符串
1 突然出现的常量,最好是使用具名常量的形式,即赋值给常量,然后常量的命名带有某种含义
2 单一语言和多种语言需要考虑的字符集是ISO 8859 和unicode
12.5 布尔变量
1 用布尔变量来简化复杂的判断
如 $finished = (( elementIndex<0 ) || (MAX_ELEMENTS < elementIndex));
$repeatedEntry = (elementIndex == lastElementIndex );
if( finished || repeatedEntry ){ ...}
就是把相同的部分组合在一起
12.6 枚举类型
1 如果你的语言当中没有枚举类型,就使用类来代替,具体的还没有研究
12.7 具名常量
1 使用具名常量是将程序“参数化”的方法,把程序中需要变化的一方面写成参数,当需要对其进行修改的时候,只改动一处就可以了
2 任何无缘无故出现并且多处用到的字符串或数字,在使用的时候最好是都要写成具名常量的形式,易于维护
12.8 数组
12.9 创建你自己的类型
1 当在一个程序中对变量的类型具有不同的要求的时候,最好是把数据类型也定义成具名常量!
第十三章 不常见的数据类型
13.1 结构体
1 在函数中传递参数,如果参数有很多并且都是同类型的那么就把参数组合成一个函数体
13.2 指针
13.3 全局变量
1 与全局数据有关的常见问题
2 使用全局数据的理由
3 只有万不得已时才使用全局变量
4 用访问子程序来取代全局数据
1)优势:易维护,易理解,易修改
5 如何降低使用全局数据的风险
1)使用一个命名规则来突出全局变量,如在全局变量的前面加上g_
17.1 子程序中的多个返回 多个return可以增强子程序的可读性和可维护性,同时可以避免产生很深的嵌套逻辑,但是使用的时候要多加小心
在这里衔接
例子灵活的消息格式 1 表驱动法是把逻辑和数据分开构造查询键值,可以将数据作为键值直接访问表 构造键值的方法 1 复制信息从而能够直接使用键值 0-17岁使用的是相同的费率,18-66使用的是不同的费率,66岁以上使用的是相同的费率,这样如果把age用作键值的话,那么0-17岁的费率就要 复制17边,在数组中这是不可避免的 2 转换键值以使其能够直接使用 例如max(min(66,age),17)这样就把所有小于17岁的键值转换成数组中是17的键值,把所有大于66的键值转化成数据为66创建这样的函 数,要求你能够从打算作为键值的数据中识别出某种模式来
3 把键值转换提取成独立的子程序 18.3 索引访问表 优点参见书 18.4 阶梯访问表 1 就是重新设计一种键值的读取方式,在什么范围设置什么样的键值。 2 无规则的分布的数据是不可能使用一个函数把它们整齐的转换成表键值的,这就需要是用阶梯方法 3 需要注意的细节 参见书 18.5 表查询的其他示例
第十九章 一般控制问题
19.1 布尔表达式
1 使用ture和false做布尔判断
2 简化复杂的表达式
1)拆分复杂的判断并引入新的布尔变量
2)把复杂的表达式做成布尔函数,就是返回值是return false或return true
3)用决策表代替复杂的条件 --参见表驱动法
3 编写肯定形式的布尔表达式
1) 在if语句中,把判断条件从否定形式转化成肯定形式,并呼唤if和else字句中的代码
2) 狄摩根定理 if(!a || !b) =》 if(!(a && b))
4 用括号使布尔表达式更清晰
5 理解布尔表达式是如何求值的 最好不要利用布尔表达式的逻辑顺序,如while($aa!=0 && $bb/$aa>1),在这里会先判断$aa是否等于零,如果为零则退出,如果不为零则进入下一个判断,这样的判断是不好的,最好是采用嵌套的判断 语句来说明你的意图
6 按照数轴的顺序编写数值表达式 应该按照这样的形式 min<i and i<max 或者 i<min or max<i 这种形式可以使代码阅读者清晰的判断出你的逻辑,而i>min and i<max 就不好
7 与零比较的指导原则是:逻辑判读是可以隐藏零,数值和字符判断是不能隐藏零,如if(!$idn){} 而 while($balance= 0){} while(charset != "0"){}
8 布尔表达式常见的问题:在布尔判读是总是把 == 写成 = 这样在一些语言当中是不会报错的,执行的就会是赋值操作,所以为了避免这种情况,可以把变量放在右边,常量放在左边 if(CONFIND == $i),如果是老程序员就不用这样了
19.2 复合语句(语句块)
1 先写{} 2 用括号来把条件表达清楚
19.3 空语句 最好是避免使用空语句
19.4 驯服危险的深层嵌套
1 避免使用超过3到4层的嵌套
2 通过重复检测条件中的某一部分来简化嵌套的if语句,在不能无偿的减少嵌套的层次时 例如:if($a="b"){if($c="d"){if($e=f){if($g=i){//lost of code}}}}可以写成 if($a="b"){if($c="d"){}}这个执行完后if($a='b' && $c='d' && $e=f){if($g=i){//lost of code}}
3 用break块来简化嵌套语句,上面的例子可以使用break来简化,见书,php可以用do{}while(0);来执行一次代码块
4 把嵌套的if语句使用if then else代替 一般的情况是不会出现这种深层嵌套的
5 使用case语句
6 把深层嵌套的代码抽取出来放进单独的子程序 例子参见书
7 使用一种更面向对象的方法 这种方法没有更好的理解,找时间好好把面向对象的方式看看
8 重新设计深层嵌套的代码 复杂的代码说明你没有充分的理解你的程序,所以无法简化它,在面向对象的程序中如果有case语句就说明你没有做好分解
9 减少嵌套层次的总结
1) 重复判断一部分条件
2) 转换成if-then-else
3) 转换成case语句
4) 把深层嵌套的代码提取成单独的子程序
5) 适用对象和多态派分
6) 用状态变量重写代码(17.3节)
7) 用防卫子句来退出子程序,从而是代码的主要路径更加清晰(17.1)
8) 使用异常(8.4节)
9) 完全重新设计深层嵌套的代码
19.5 结构化编程
1 结构化编程的核心思想是一个应用程序应该只采用一些单入单出的控制结构(见书)
2 一个结构化的程序是按照一种有序的且有规则的方式执行,不会做不可预知的跳转,可读性差意味着不容易理解,最终导致应用程序的低质量
3 结构化编程的三个组成部分
1)顺序:指一组按照先后顺序执行的语句
2) 选择:是一种有选择的执行语句的控制结构
3)迭代:是使一组语句多次执行的控制结构,常常成为循环
19.6 控制结构与复杂度1 程序的复杂度也就是理解程序所要花费的时间,控制流程是影响复杂度的最大因素之一
2 复杂度的重要性:人的大脑是有限的不可能承受巨大的复杂度,所以在软件的编程上的错误率就会增加,所以低复杂度也是保证程序质量的重要因素之一
3 降低复杂度的一般性原则
1) 度量复杂度:在子程序中统计决策点,从1开始,遇到if、while、and、for、or的就加1,case语句中的每一种情况都加1。
2) 如何处理复杂度的度量结果 :根本的是要减少在头脑中同时考虑的项目的数量
参见书
4 其他类型的复杂度
所用的数据量,控制结构中的嵌套层数,代码行数,对用以变量的先后引用之间的代码行数(跨度),变量生存的代码行数(生存期),以及输入输出的量
第三十一章 布局与风格
31.1 基本原则
1 空白有助于人的阅读
2 着重逻辑表现
3 人看程序总是倾向于从代码的视觉外观获取提示
4 良好布局的目标
1)准确表现代码的逻辑结构
2)始终如一的表现代码的逻辑结构
3)改善可读性:合乎逻辑却很难看懂是没有任何作用的
4)经得起修改:修改某行时不必连带修改其他行代码
31.2 布局技术
1 空白
1)分组
2)空行
3)缩进
2 括号
31.3 布局风格
1 纯块结构
2 对齐结构
31.4 控制结构的布局
1 控制块结构,正确使用缩进
2 其他考虑
1)段落之间要使用空行
2)对于复杂布尔表达式,将条件分隔放在几行上
31.5 单条语句的布局
1 用空格使语句显得清楚:使逻辑表达式更容易读,使数组更容易读,使子程序参数更容易读
2 格式化后续行,就是说一行放不下代码
1)使续行明显,使用看起来是语句有明显错误的方式
如 $aaa=aaa
+bbb+dddd
+cccc+gggg
+eeee+koko
-ooo;
或使用标点符号,逻辑判断符号等等最好是放在左边
2)使逻辑更清晰,突出想要表达的内容,如变量,函数等等,常常使用的就是缩进,对齐等
3 每行只写一条语句
4 数据声明的布局
1)每行只声明一个数据
2)变量的声明应该接近首次存放的位置
3)合理组织声明的顺序,按照变量的类型组织
31.6 注释的布局
1 注释的缩进要与相应的代码保持一致
2 每行注释至少使用一个空行分开
31.7 子程序的布局
1 用空行分隔子程序的各个部分
2 将子程序的多个参数按标准缩进,分行对齐
31.8 类的布局 :没看
第三十二章 自说明代码
32.1 外部文档
1单元开发文件夹
2详细设计文档
32.2 编程风格做文档:保持好的逻辑性
32.3 注释与不注释
32.4 高校注释之关键
32.5 注释技术
1 注释单行
2 注释代码段
3 注释数据声明
4 注释控制结构
5 注释子程序
6 注释类,文件和程序
32.6 IEE标准