《代码大全2》 1-9

chapter1 软件开发过程中各种不同的活动

  1. 定义问题(problem definition)
  2. 需求分析(requirements development)
  3. 规划构建(construction planning)
  4. 软件架构(software architecture),或高层设计(high-level design)
  5. 详细设计(detailed design)
  6. 编码与调试(coding and debugging)
  7. 单元测试(unit testing)
  8. 集成测试(integration testing)
  9. 集成(integration)
  10. 系统测试(system testing)
  11. 保障维护(corrective maintenance)

chapter3 前期准备

迭代开发对前期准备的影响

  • 计划好预先对大约80%的需求做出详细说明,并给“稍后再进行详细说明的额外需求”分配一定的时间。然后在项目进行过程中,实施系统化的变更控制措施——只接受那些最具有价值的新需求
  • 预先只对最重要的20%的需求做出详细说明,并且计划以小幅度增量开发软件的部分,随着项目的进行,对额外的需求和设计做出详细说明。
image.png

准备

辨明你所从事的软件类型

  • 软件种类
  • 序列式开发或者迭代开发选择特征

问题定义(problem definition)

  • 产品设想(product vision)
  • 设想陈述(vision statement)
  • 任务陈述(mission statement)
  • 产品定义(product definition)

明确需求

  • 需求开发(requirements development)
  • 需求分析(requirements analysis)
  • 分析(analysis)
  • 需求定义(requirements definition)
  • 软件序曲(software requirements)
  • 规格书(specification)
  • 功能规格书(functional spec)
  • 规格(spec)

处理需求变更

  • 使用checklist检验需求
  • 确保每个人都知道需求变更的代价
  • 简历一套需求变更控制程序
  • 使用能适应变更的开发方法
  • 放弃这个项目
  • 注意项目的商业案例

架构

  • system architecture/high-level design/top-level-design
  • 架构规格书(architecture specification)

经典架构的组成本分

  • main class
  • ……

chapter5 软件构建中的设计

设计中的挑战

设计是一个险恶的问题

你必须首先把这个问题“解决”一遍以便能够明确定义它,然后再次解决该问题,从而形成一个可行的方案。(不断尝试)

设计是个了无章法的过程

设计无章法,很难判断设计是否“足够好”了

设计就是确定取舍和调整顺序的过程

衡量彼此冲突的各项设计特性

设计受到诸多限制

设计要点:一部分是在创造可能发生的事情,而另一部分又是在限制可能发生的事情。

设计师不确定的

设计师一个启发式过程

试错前进

设计是自然而然形成的

不断的设计评估、非正式讨论、写试验代码以及修改试验代码中演化和完善。

关键设计概念

管理复杂度

理想的设计特征

  • 最小的复杂度
  • 易于维护
  • 松散耦合
  • 可扩展性
  • 可重用性
  • 高扇入: utility classes
  • 低扇出
  • 可移植性
  • 精简性
  • 层次性
  • 标准技术:设计模式

层次设计

  • 软件系统
  • 分解为子系统和包,限制交互关系
  • 分解为包中的类
  • 分解为类中的数据和子程序
  • 子程序内部

设计

找出容易改变的区域,进行隔离,进行接口抽象

找出容易改变的区域

一些常见易变项目:

  • 业务规则
  • 对硬件的依赖
  • 非标准语言特性
  • 困难的设计区域和构建区域
  • 变量限制

预测不同程度的变化

耦合的种类:

  • 简单数据参数耦合
  • 简单对象耦合
  • 对象参数耦合
  • 语义上的耦合

查阅常用的设计模式

高内聚性

构造分层结构

严格描述类契约

分配职责

为测试而设计

避免失误,用失败的例子check设计

有意识地选择绑定时间

创建中央控制节点

画图

保持设计模块化

考虑使用蛮力突破

设计实践

迭代

不断尝试

分而治之

把程序分解关注不同区域

自上而下和自下而上的设计方法

  • 自上而下:大的事物分解成小部件
  • 自下而上:找到所需的功能,验证设计是否合理

建立试验性原型

原型试验

合作设计

结对编程,结对设计

要做多少的设计才够

尽可能的多,80%用在设计,文档上并不需要很精美

记录设计过程和成果

整理过程和成果

chapter6 可以工作的类

ADT(abstract data type)的好处

  • 可以隐藏实现细节
  • 改动不会影响到整个程序
  • 让接口提供更多的信息:命名可理解
  • 更容易提供性能:更容易迭代修改
  • 让程序的正确性显而易见:不会误调用
  • 程序更具自我说明性:可读性
  • 无须在程序内到处传递参数
  • 高层次操作,不用关心底层实现细节

建议

  • 把常见的底层数据类型封装成ADT并使用这些ADT,而不再使用底层数据类型
  • 把常用对象当成ADT
  • 简单的事物也可以当做ADT
  • 不要让ADT依赖于其存储介质

良好的抽象

类的接口应该展现一致的抽象层次

一定要理解类所实现的抽象是什么

保证精准定位,不做无用功

提供成对服务

判断是否需要成对服务

尽可能让接口可编程,而不是表达语义

每个接口都由一个可编程的部分和一个语义部分组成。可编程接口中的数据类型和其他组成部分,编译器能够强制要求它们。而语义部分,该接口将会怎样被使用,这些无法由编译器实施。“类似比如funcA必须在funcB前调用”,如果依赖于这种说明,就可能导致误用。尽量把语义接口转变为编程接口。

谨防在修改时,破坏接口的抽象

开小灶,为了临时方便写了个小函数

不要添加与接口抽象不一致的公用成员

同时考虑抽象性和内聚性

良好的封装

尽可能的限制类和成员的可访问性

不要公开暴露成员数据

不要因为一个子程序仅使用公共子程序,就把它放入到公开接口

让阅读代码比编写代码更方便

要警惕从语义上破坏封装

不要去调用A类的init_device, 在init中会调用它

留意过于紧密的耦合关系

  • 尽可能的限制类和成员的访问性
  • 避免使用友元类
  • 尽量使用private
  • 避免在公开接口中暴露数据
  • 要对语义上的封装保持警惕性

包含和集成

包含

通过包含实现“has a” 的关系

警惕超过约7个数据成员的类

继承

使用继承时考虑的决策:

  • 对于每个成员函数而言,它应该对派生类可见吗?它应该有默认的实现吗?这一默认的实现能被覆盖吗?
  • 对于每个数据成员而言(包括变量,具名常量,枚举),它应该对派生类可见吗?

用public继承来实现“是一个……”关系

继承方式创建新类时,表明新类是现有类一个特殊的版本。基类对派生类做什么设定了预期,也对派生类怎么运作提出了限制

要么使用继承并进行详细说明,要么就不要使用它

继承增加了复杂度

遵循Liskov替换原则

派生类必须能通过基类的接口被使用而被使用,且使用者无需了解两者之间的差异

确保只继承需要继承的部分

派生类可以继承成员函数的接口和实现,注意甄别该覆盖的函数。

不要覆盖一个不可覆盖的成员函数

注意哪些部分该重载,哪些部分不该重载。不希望被重载的部分,就要放到private里。

把共用的接口、数据及操作放到继承树中尽可能高的位置

只有一个实例的类是值得怀疑的

只有一个派生类的基类值得怀疑

派生类中覆盖了某个子程序,但在其中没做任何操作,这种情况也值得怀疑

避免让继承体系过深

尽量使用多态,避免大量类型检查

尽量让所有的数据都是private

不要使用多重继承

继承和包含的规则

  • 如果多个类共享数据并非行为,应该创建这些类可以包含的公用对象
  • 如果多个类共享行为并非数据,应该让它们从共同的基类继承而来,并在基类定义公用函数
  • 如果多个类共享行为和数据,应该让它们从共同的基类继承而来,并在基类定义公用函数和共用的数据
  • 当你想由基类控制接口时,使用继承;当你想自己控制接口时使用包含

成员函数和数据成员

  • 让类中函数的数量尽可能少
  • 禁止隐式地产生不需要的成员函数和运算符,某些函数定义为private
  • 减少类所调用的函数的数量(包括成员函数和其他函数,高扇入低扇出)
  • 减少函数的间接调用,例如:a.funca().funcb().call()
  • 减少类间的相互作用范围

构造函数

  • 尽可能初始化所有的数据成员
  • 优先使用深层复制

创建类的原因

创建的原因

对现实世界中的对象建模

对抽象对象建模

降低复杂度

隔离复杂度

隐藏实现细节

限制变化所影响的范围

隐藏全局数据

让参数传递更顺畅

创建中心控制点

为程序族做计划

实现特定的重构

应避免的类

万能类

消除无关紧要的类

只包含数据不包含行为

避免用动词命名的类

高质量的函数

创建类的理由

降低复杂度

引入中间的、易懂的抽象

避免代码重复

支持子类化:让函数保持简单,派生类减少犯错几率

隐藏顺序(隐藏细节)

隐藏指针操作

提高可移植性

简化复杂的布尔判断

改善性能

内聚性

功能的内聚性:最重要

顺序上的内聚性

通信上的内聚性

临时的内聚性

过程上的内聚性

逻辑上的内聚性

函数名称

描述函数所做的所有事情

避免使用无意义的、模糊或表述不清的动词:HandleCalculation()

不要使用数字

最好9-15个字符

给函数命名时使用语气强烈的动词加宾语的形式:printDocument(), checkOrderInfo()

准确使用对仗词

为常用操作确立命名规则

函数参数

按照“输入——修改——输出”顺序传参

多个函数使用了类似的参数,尽可能让这些参数的排列顺序保持一致

不传递多余参数

把状态或出错变量放在最后

不要把函数的参数用做工作变量

多取临时变量名

在函数中对参数的假定注释说明

  • 说明输入的,被修改的、还是输出的
  • 参数的单位
  • 状态码及含义
  • 参数的合法范围

尽量在7个参数内

为函数传递用以维持其接口抽象的变量或对象

传递参数还是对象?唯一的衡量标准是:维持其接口抽象

确保参数类型匹配,预防隐式转换

防范式编程

输入处理

检查所有来源于外部的数据的值

检查函数所有输入参数的值

决定如何处理错误的输入数据

断言

用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况

避免把需要执行的代码放到断言中

对于高健壮性的代码,应该先使用断言再处理错误

错误处理技术

程序的性能指标:健壮性和正确性

返回中立值

换用下一个正确的数据

返回与前次相同的数据

换用最接近的合法值

把警告信息记录到日志文件中

返回一个错误码

调用错误处理了程序或对象(全局处理错误)

当错误发生时显示出错消息

用最妥当的方式在局部处理错误

关闭程序

异常

用异常通知程序的其他部分,发生了不可忽略的错误

只在真正例外的情况下才抛出异常

分清什么是异常

不能用异常来推卸责任

避免在构造函数和析构函数中抛出异常,除非在同一地方把它们捕获

在恰当的抽象层次抛出异常

public TaxId GetTaxId()throws EmployeeDataNotAvailable {}: EmployeeDataNotAvailable 与函数功能相关

在异常消息中加入关于导致异常发生的全部消息

避免使用空的catch 语句

了解所用函数库可能抛出的异常

考虑创建一个集中的异常报告机制

把项目中对异常的使用标准化

Barricade

image.png

chapter9 伪代码编程过程

创建类的步骤

image.png

创建子程序的步骤

image.png

设计子程序 & 检查设计

检查先决条件

定义子程序要解决的问题

为子程序命名

决定如何测试子程序

在标准库中搜寻可用的功能

考虑错误处理

考虑效率问题

研究算法和数据类型

编写伪代码

考虑数据

检查伪代码

  • 自己检查
  • 他人检查

多次迭代,留下最好的想法

编写子程序的代码 & 复审并测试代码

image.png

检查代码

在脑海里检查程序中的错误

编译子程序

在调试器中逐行代码

测试代码

消除程序中的错误

你可能感兴趣的:(《代码大全2》 1-9)