哈工大软件构造 期末复习考点总结

哈工大 软件构造课程 考点复习总结(第一、二章)

  1. 软件构造的多维视图

  2. Build-time
    Code-level: 源代码:代码的逻辑组织:AST抽象语法树、函数、方法、类、接口等具体的逻辑实现List
    item
    (class diagram类图)
    Component-level:结构:代码的物理组织:源文件、包、库、静态链接、测试用例
    (component diagram构建/组件图),编译
    Moment view : 特定时刻的源码或组件形态
    Period view:软件形态随时间的变化

Build-moment-code:词汇(源码)、语法(抽象语法树)、语义(类图)
2.
Build-period-code:code churn代码变化:增减修改

Build-moment-component:文件目录,静态链接、包、库

Build-period-component:files/packages/components/libraries
change

SCI(software configuration item 配置项)、VCS

Runtime Views

Code-level : 可执行程序的内存状态,程序之间的互动、调用

Component-level:软件包部署到物理环境(OS、network、硬件等),及其互动

Moment view:特定时刻的程序行为

Period view:随着时间的行为

Run-moment-code:snapshot 快照 描述程序运行时内存里变量层面的状态 ,Memory dump(内存转储/导出)

Run-period-code:sequence diagram序列图(时序交互),Execution tracing 执行跟踪(代码层面,用日志方式记录程序执行的调用次序)

Run-moment-component:Deployment diagram(部署图:设备、操作系统),

Run-period-component:Event logging 事件日志(系统层面)

Event logging:系统管理员使用,高层次信息(如安装程序失败),有一定输出格式,不必敏捷

Execution tracing:开发者使用,低层次信息(如抛出异常),较低格式限制,必须敏捷

  1. 软件构造的阶段划分,各阶段的活动

Software development lifecycle SDLC 软件开发生命周期

0->1 从无到有

计划-需求-设计-构造(实现)-测试-部署-运维

1->n 从有到好

版本更新

传统软件开发模型

基本类型:Linear 线性过程;Iterative 迭代过程

模型:

  1. Waterfall 瀑布过程(线性非迭代)

阶段清楚,管理简单,无法适应需求变化

  1. Incremental 增量过程(非迭代)

多个瀑布串行,比较容易适应需求的增加(有需求就增加一个瀑布)

  1. V-Model V字模型(验证,确认)

  2. Prototyping 原型过程(迭代)

在原型上持续不断的迭代,发现用户变化的需求

迭代:开发出来后由用户试用/评审,发现问题反馈给卡发着,开发者修改原有的实现,继续交给用户评审

时间代价高,但开发质量也高

  1. Spiral 螺旋过程(迭代)

多轮迭代基本遵循瀑布模式

每轮迭代有明确的目标,遵循"原型"过程,进行严格的风险分析,方可进入下一轮迭代

Agile development 敏捷开发

通过快速迭代和小规模的持续改进,以快速适应变化

用户高度参与,小步骤迭代确认验证。强调人的作用

  1. 内部/外部质量指标

外部质量因素影响用户,内部质量因素影响软件本身和它的开发者,外部质量取决于内部质量。

External quality factors 外部质量

Correctness 正确性:按照spec执行,得到正确的结果,软件的行为要严格符合规约中定义的行为

保证正确性:测试和调适、防御式编程,形式化方法(形式化语言)

encapsulation,
decentralization 封装、分散化

Robustness 健壮性:针对异常情况的处理:出现规约定义之外的情形,软件做出恰当的反应(出现异常时不要崩溃),未被spec覆盖的情况即为"异常情况"

encapsulation,
error handling封装、异常处理

Extendibility 可扩展性:是否容易使软件适应规约的变化

提升可扩展性的两个原则:简约主义设计,分离主义设计

encapsulation,
information hiding封装,信息隐蔽(结构良好的对象有简单的接口,并且不向外界显漏任何内部机制。)

Reusability 可复用性:一次开发,多次使用,发现共性

modularity,
component, models, patterns模块化、组件、模型、模式

Compatibility 兼容性:不同软件系统之间相互可容易的集成

保持设计的同构性:标准化文件格式,标准化数据结构,标准化用户接口

Efficient 性能

Portability 可移植性:软件可方便的在不同的技术环境之间移植:硬件、操作系统

Ease of use 易用性:易学、安装、操作、监控

给用户提供详细的指南,结构简单

Functionality 功能

Timeliness 及时性:及时发布等

其他质量:verifiability 可验证性、integrity完整性,repairability可修复性,economy经济型

Internal quality factors 内部质量

代码相关:lines of code(LOC)、cyclomatic complexity 循环复杂性

结构相关:coupling耦合度(多个模块间联系),cohesion聚合度(一个模块;高内聚,一个程序只执行一种功能) (应当 高内聚低耦合,单一责任原则)

Readability 可读性

Understandability 可理解性

Clearness

Size

  1. 最重要的几个质量因素

Correctness and robustness:
reliability(可靠性)

Extendibility and reusability:
modularity(模块化)

软件构造的五个关键质量目标

Easy to understand: elegant and beautiful code /
understandability 便于理解

Understandability:注释、命名、日志等

Cheap for develop: design for/with reuse: reusability 发展

Reusability:ADT、OOP、设计模式等以及复用外部文件

Ready for change: maintainability and adaptability 变化

Maintainability
and Adaptability

Safe from bugs: robustness 安全

Robustness 异常处理、断言、防御式变成、测试优先原则、日志追踪、debug、内存导出

Efficient to run: performance 高效

Performance 代码调优、空间复杂性(内存管理)、时间复杂性(I/O性能等),分布式系统,多线程

  1. 软件配置管理SCM与版本控制系统VCS

VCS version control system

Local VCS 本地:仓库在开发者本地机器

Centralized VCS 集中式:仓库在独立的服务器

Distributed VCS 分布式:仓库在独立的服务器+开发者本地机器

SCM 软件配置管理:追踪和控制软件的变化

SCI 软件配置项:软件变化的基本单元(如一个文件)

Baseline 基线:软件持续变化过程中的"稳定时刻"(如 对外发布的版本)

CMDB 配置管理数据库:存储软件的各配置项随时间发生变化的信息+基线(git的本地repository?)

Versioning 版本控制

  1. Git的结构、工作原理、基本指令

结构

Working directory 工作目录:本地文件系统 (已修改,未暂存)

Staging area 暂存区:隔离工作目录和git仓库 (已暂存)

.git directory 本地仓库(本地CMDB) (已提交)

Object Graph

指令

git branch branchname创建新分支

git checkout branchname切换到分支

git checkout -b branchname 创建新分支并切换到新分支

git merge branchname 合并当前分支与目标分支

pull=fetch + merge

fetch 从远程仓库获取最新版本到本地,不会自动merge

fork 复制别人的仓库到自己的仓库(自己不是contributor,不能提交修改给别人)

clone 复制仓库到自己的本地仓库

哈工大 软件构造课程 考点复习总结(第三章)

  1. 数据类型

Primitive types 基本数据类型(8种)

  1. Object types 对象数据类型(reference types 引用数据类型)

Short、int 、long、float、double、boolean、char、byte

如:String、BigInteger

只有值,没有ID(无法与其他值区分),不能赋值为null;
immutable

有值,也有ID;
部分mutable,部分immutable

在栈中分配内存,代价低

在堆中分配内存,代价高

  1. 静态类型检查&动态类型检查

静态类型检查

动态类型检查

(静态类型语言 如java)

(动态类型语言 如python)

提高程序的正确性和健壮性

关于"类型"的检查,不考虑值(不知道运行时会是什么值)

关于"值"的检查

  1. Mutable &
    Immutable

Immutable 不可变数据类型

mutable 可变数据类型

优点

优点:安全

优点:最少化拷贝以提高效率获得更好的性能,适合于在多个模块之间共享数据

缺点

缺点:频繁修改产生大量临时拷贝,需要垃圾回收·

缺点:不安全

其他

一旦被创建,其值不能改变
对于引用类型,加final限制不能改变引用

安全地使用可变类型:局部变量(不涉及共享,且只有一个引用)
如果有多个引用(别名),不安全
Defensively copy 防御式拷贝:返回全新的对象

尽可能用immutable!

Snapshot Diagram 画法:

基本类型:单独一个常量

引用类型:圈住!

重分配:

不可变类型(用双线椭圆),修改引用

可变类型:修改值

引用:

可变引用:单线箭头

不可变引用:双线箭头

  1. Specification

作用

规约可以隔离"变化",无需通知客户端

规约可以提高代码效率

规约扮演"防火墙"角色

解耦,不需要了解具体实现

内容:只讲"能做什么",而不讲"怎么实现"

Behavior equivalence 行为等价性

是否可以相互替换

站在客户端的视角看行为等价性,不同的行为,对用户来说(根据用户需求)可能等价!

根据规约判断行为等价,两个方法符合同一个规约,则等价

规约的结构:

Pre-condition

Post-condition

Exceptional behavior 异常行为,如果违背了前置条件,会发生什么

规约的强度与替换

Spec变强:更放松的前置条件(前置条件更弱)+更严格的后置条件(后置条件你更强),

两条件同时变强或变弱则无法比较。

若规约强度S2>=S1,则可以用S2替换S1。

deterministic spec & undetermined spec 确定的规约和欠定的规约

确定的规约:给定一个满足前置条件的输入,其输出唯一、明确

欠定的规约:同一个输入可以有多个输出(多次执行输出可能不同)

Declarative spec & operational spec 声明式规约和操作式规约

操作式规约:如 伪代码

声明式规约:没有内部实现的描述,只有"初-终"状态

声明式规约更有价值!

内部实现的细节不在规约里呈现,而放在代码实现体内部注释里呈现。

Diagraming specification

规约定义一个区域,该区域包含所有可能的实现方式。

空间中的每个点表示一种方法的实现。

对于某个具体实现,若满足规约,则落在其区域内。

更强的规约表达为更小的区域。

Quality of specification 规约质量

内聚性:spec描述的功能应单一、简单、易理解

运行结果信息丰富(可能的改变,以及返回值等),不能让客户端产生理解上的歧义

足够强(如postcondition中充分阐述各种情况)

适当弱(太强的规约,在很多特殊情况下难以达到)

在规约里使用抽象类型(在java中,经常使用interface,如Map、List,而不是HashMap、ArrayList),可以给方法的实现体和客户端更大的自由度

使用前置条件和后置条件?

客户端不喜欢太强的pre-condition,不满足precondition的输入会导致失败

So:不限定太强的precondition,而在postcondition中抛出异常:输入不合法,

fail fast,避免fail大规模扩散

    是否使用前置条件取决于:

check(检查参数合法性)的代价

方法的使用范围:

如果只在类内部使用(private),则可以不使用precondition,在使用该方法的各个位置进行check

如果在其他地方使用(public),则必须使用precondition,若client不满足则抛出异常

  1. Pre-condition
    and post-condition 前置条件和后置条件

Pre-condition 前置条件(requires)

Post-condition 后置条件(effects)

@param

@return @throws

对客户端的约束
在使用方法时必须满足的条件

对开发者的约束
方法结束时必须满足的条件

契约:如果前置条件满足了,后置条件必须满足

除非在后置条件中声明,否则方法内部不应该改变输入参数。
尽量不设计mutating的spec,否则容易引发bugs。
尽量避免使用mutable对象。
避免使用可变的全局变量。

ADT

  1. ADT及其四种操作

抽象类型:强调"作用于数据上的操作",程序员和client无需关心数据如何具体存储,只需设计/使用操作即可。

ADT由操作定义,与其内部实现无关。

可变数据类型:提供了可改变其内部数据值的操作;

不可变数据类型:其操作不改变内部值,而构造新的对象。(没有mutators)

ADT操作分类:

Creators 构造器:

不利用该类型对象产生一个新的对象

可能实现为构造函数或静态函数(factory method)

Producers 生产器:

用已有该类型对象产生新对象

如string.concat()(连接两个字符串,产生一个新的字符串)

Observers 观察器

如list.size()返回int(不同于原类型)

Mutators 变值器(改变对象属性的方法)

通常范围void,如果返回void,则必然意味着它改变了某些对象的内部状态

也可能范围非空类型(如容器类的put、add方法)

  1. Representation
    Independence 表示独立性

表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表现的变化不应该影响外部spec和客户端。

  1. Representation
    exposure 表示泄漏

如client能直接接触类成员变量。

表示泄漏影响表示不变量,也影响表示独立性:无法在不影响客户端的情况下改变其内部表示。

避免方法:private、final、defensive copy

  1. Invariants 不变量 & Representation Invariant 表示不变量

ADT应保持其不变量在任何时候总是true;

ADT负责其不变量,与client的任何行为无关。

作用:保持程序的"正确性",容易发现错误。

  1. Abstraction
    Function 抽象函数

表示空间R

抽象空间A

值的实际实现本质

抽象表示(client看到和使用的值)

ADT实现者关注表示空间R

用户关注抽象空间A

R到A的映射

一定是满射:A中元素总有R中具体的实现

未必是单射:A中一个元素在R中可能有多重实现方式

未必是双射:R中表示不符合A中需求(如图中"abbc")

抽象函数AF:R和A之间映射关系的函数

AF:R->A

对于RI :R-> Boolean

RI:某个具体的"表示"是否合法;表示值的一个子集,包含所有合法的表示值;一个条件,描述了什么是"合法"表示值。

Documenting AF 、RI、Safety from Rep
Exposure

选择某种特定的表示方式R

进而指定某个子集是"合法"的(RI)

并为该子集中的每个值做出"解释"(AF)

即 如何映射

Safety from Rep Exposure

证明代码并未对外泄露其内部表示

保证不变量为true,不变量:

通过creators和producers创建

受mutators和observers保护

无表示泄漏发生

OOP

  1. Interface 接口

接口的成员变量默认用final关键字修饰,故必须有初值,可用public,default修饰,可用static修饰。

接口的方法只能被public、default、abstract、static、strictfp(严格浮点运算)修饰。

  1. Inheritance、override 继承和重写

Strict inheritance 严格继承:子类只能添加新方法,无法重写超类(父类)中的方法(final限制)。

考虑final修饰类、方法、属性时的不同作用。

Override 方法:具有一致的signature,复用的基本机制。

  1. Polymorphism ,subtyping and overloading 多态,子类型化,重载

三种多态:

  1. Ad hoc
    polymorphism (特殊多态)

用于function overloading(功能重载),即重载

  1. Parametric
    polymorphism (参数化多态)

泛型

  1. Subtyping (subtype polymorphism / inclusion polymorphism )(子类型多态、包含多态)

Overloading 重载

重载条件:

方法名相同

参数列表不同,即参数类型、个数、类型顺序至少有一项不相同

返回值类型可以不同

方法的修饰符可以不同

可以抛出不同的异常

可以在类内重载,也可以在子类重载

重载是一种静态多态,静态类型检查

(static dispatch 静态分派)并在编译阶段决定具体执行哪个方法(即对方法的调用取决于编译时声明的引用的类型)

而重写(dynamic dispatch 动态分派)则进行动态类型检查,根据运行时堆中的实例类型选择方法。

Generic 泛型

通配符 :只有使用泛型的时候出现,不能在定义中出现。

类型擦除:编译后、运行时类型擦除

List
-> List

注意可能引起重载编译错误。

运行时不能用 instanceof 检查泛型。

不能创建泛型数组

不能用在静态变量

不能创建对象(不能new)

Subtypes

超类的子类型,如:ArrayList和LinkedList是List的子类型。

子类型的规约不能弱化超类型的规约。

子类型多态:不同类型的对象可以统一处理而无需区分(不加区分地调用同样的方法等),从而隔离变化

LSP(Liskov Substitution Principle) 如果S是T的子类型,那么T的对象可以被S的对象替换。

Type casting 类型转换

避免向下类型转换。

Dispatch 分派

Static dispatch 静态分派

Dynamic dispatch 动态分派

将调用的名字与实际方法的名字联系起来(可能有多个)

决定具体执行哪一个操作

重载,在编译阶段即可确定执行哪个具体操作

重写,在运行时决定

Early/static binding

Lade/dynamic binding

绑定static、private、final方法时发生

重写父类子类的同样方法

  1. equals()

引用等价性 ==

比较内存地址ID

用于比较基本数据类型

对象等价性 equals()

验证正确性:reflexive 自反性、symmetric 对称性、transitive 传递性、非空(a.equals(null) return false)

  1. hashCode()

等价的对象必须有相同的hashCode

Rule:重写equals时重写hashcode

  1. Equality of
    Mutable Types 可变对象的等价性

Observational equality 观察等价性

Behavioral equality 行为等价性

在不改变状态的形况下,两个mutable看起来是否一致

调用对象的任何方法都展示出一致的结果

调用observer,producer,creator

调用任何方法,包括mutator

当前情况下,看起来(如成员变量)相同

经过改变后,依然相同(只是别名引用)

对不可变类型,观察等价性和行为等价性完全等价。

对可变类型,往往倾向于实现严格的观察等价性。(但有时观察等价性可能导致bug,甚至破坏RI)

对可变类型,应当实现行为等价性,即只有指向内从空间中同样的objects才相等(即equals比较引用,如==而hashcode把引用映射为一个值)。

所以对可变类型,无需重写equals和hashcode,直接继承object。(比较引用)

若一定要判断两个可变对象是否一致,最好定义一个新的方法。

哈工大 软件构造课程 考点复习总结(第四、五章)

  1. 代码可理解性

度量

标识符的平局长度

命名独特性比例

代码复杂度

LoC 代码行数

注释密度(百分比)

如何书写可理解性代码

命名规范

代码行最大长度,文件最大LoC

注释

布局:缩进、对齐、空行、分块等

避免多层嵌套(增加复杂度)

文件和包的组织

包的相关原则

REP 复用/发布等价原则

复用的粒度等价于发布的粒度(发布的都是应被复用的,未发布的不应被复用)

CCP Common Closure Principle 共同封闭原则

一个包中的所有类针对同一种变化是封闭的

一个包的变化将会影响到包里所有的类,而不会影响到其他的包

如果两个类紧密耦合在一起,即二者总是同时发生变化,那么它们应属于同一个包

CRP Common Reuse Principle 共同复用原则

一个包里的类应被一起复用

如果复用了其中一个类,那么就应复用所有的类

总之,尽量把相关性高的类放在一起,对不同类进行合理划分。

  1. 代码复用

可复用组建的形态和层级

源代码:方法、声明等

模块:类、接口

库:API

系统(结构):framework 框架:一组具体类、抽象类及其之间的连接关系

代码复用的类型:

White box 白盒复用:源代码可见,可修改和扩展

即,直接复制修改代码。可定制化程度高,但需要充分了解代码。

Black box 黑盒复用:源代码不可见,不能修改

只能通过API接口使用,无法修改代码。简单、清晰,但适应性差。

Frameworks :domain-level reuse
领域复用

框架:一组具体类、抽象类及其之间的连接关系

开发者根据framework的规约,填充自己的代码进去,形成完整的系统;

开发者 增加新代码、对抽象类机型具体化、实现接口;

Framework作为主程序加以执行,执行过程中调用开发者所写的程序。

控制反转(inverse of control),控制权由代码转到了外部容器(framework),好处是降低了对象之间的依赖程度,提高灵活性和可维护性。

External observations of reusability 可复用的外部观察特性

Type variation 类型可变:泛型,且满足LSP

Implementation variation 实现可变:

ADT有多种不同的实现,提供不同的representations和abstract funtion,但具有同样的specification (pre-condition, post-condition, invariants),从而可以适应不同的应用场景

Routine grouping 功能分组:

Representation independence 表示独立

内部实现可能会经常变化,但客户端不应受到影响(不变量为true)

Factoring out common behaviors 抽取共性行为

  1. LSP (Liskov substitution principle) liskov 替换原则

子类型可以增加,但不可删除方法

子类型需要实现抽象类中所有未实现的方法

子类型中重写的方法必须有相同或子类型的返回值
co-variance:协变

子类型中重写的方法必须使用同样类型的参数
contra-variance 反协变(逆变)

子类型中重写的方法不能抛出额外的异常
协变

同样或更强的不变量

同样或更弱的前置条件

同样或更强的后置条件

  1. Covariance 协变、Contravariance 反协变,逆变

协变

父类型->子类型

Spec越来越具体

返回值类型

不变,或更具体

异常类型

不变,或更具体

反协变

父类型->子类型

Spec越来越具体

参数类型

不变,或更抽象(由于java语法,不能更抽象)

注意

数组( T [ ] )是协变的

T[ ]中元素必须是T或其子类,否则编译时通过,但运行时会错误。

泛型 不变(类型擦除)

通配符

调用时使用。

  1. Comparator and
    Comparator

对自定义ADT(Edge为例)比较大小,或要放入collections或arrays进行排序

创建比较类EdgeCompare实现comparator接口

Collections . sort
( Lsit , EdgeCompare)

Edge类实现Comparable接口

类内ouvrride compareTo 方法

  1. Delegation 委派

一个对象请求另一个对象的功能

如果子类只需要复用父类中的一小部分方法,可以不需要使用继承,而是通过委派机制实现。

即,一个类不需要继承另一个类的全部方法,通过委派机制调用部分方法。

"委派"发生在object层面,"继承"发生在class层面

委派的类型

Dependency 依赖 临时的委派 (B uses A)

类内调用

A类是B类中的(某中方法的)局部变量;

A类是B类方法当中的一个参数;

A类向B类发送消息,从而影响B类发生变化;

Association 关联 永久的委派 (B owns A) (关联关系可以是双向的)

A类为B的成员变量(或者对象数组)。

也可能自关联(如,自身的成员变量有自身的实例,如父节点有子节点)

Composition 组合 更强的委派 (A is part of B )

A是B的一部分,B不能脱离A存在,A不能单独存在。

Aggregation 聚合

整体和部分(可有可无)的关系。

  1. CRP (Composite Reuse Principle)组合复用原则

对于某一具体行为,用委派代替继承。

使用接口定义不同方面的行为,

接口之间通过extends实现行为的组合(组合多用接口,则组合接口汇集了其他接口的行为)。

用具体类实现组合接口。

  1. White box and
    black box framework 白盒框架和黑盒框架

白盒框架

黑盒框架

实现方式

通过 子类 和 重写方法 实现扩展
继承

通过实现 插件接口 实现扩展
委派/组合

常用设计模式

模板方法

策略模式 观察者模式

调用机制

子类型有main方法,但framework拥有控制权

插件加载机制加载插件(委派),framework拥有控制权

另一种实现

在抽象父类中添加abstract方法
(需修改framework代码)
子类实现abstract方法

实现预留接口
(不需修改framework代码,framework中有预留接口)
实现类实现接口

例如
Class WhiteRun extends Thread{
(override run) }
New WhiteRun().strat

例如
Class BlackRun implements Runnable
New Thread( new BlackRun() ).start()

设计模式

Structural patterns 结构型设计模式

Adapter 适配器模式

将某个类/接口 转换为client期望的其他形式(client的调用传参与类/接口 方法的参数列表不一致,不兼容)

增加一个接口,将已存在的子类封装

Client面向接口编程,从而隐藏具体子类。

适配器类实现接口或继承实现接口的父类,

适配器中调用子类方法实现功能(委派)。

可能client调用子类方式(如参数形式等)与子类不匹配,

通过适配器进行调整调用,适配子类,从而实现原本不兼容的调用。

Decorator 修饰器

为对象增加不同侧面的特性。

对每一个特性构造子类,通过委派机制增加到对象上。

例如:Collections.synchronizedList( List list)

即返回一个在list增加了新特性的对象。

即为对象本身和其需要增加的特性进行封装,

基础功能通过委派(父类)实现,并额外增加新特性,成为一个新的类。

Client可以通过层层修饰获得具有多种特性的对象。

Façade 外观模式

客户端需要通过一个简化的接口来访问复杂系统内的功能。

提供一个统一的接口来取代一系列小接口调用,相当于对复杂系统进行封装,简化客户端使用。

Behavioral patterns 行为类设计模式

Strategy 策略模式

对一个任务,有多用算法实现。

为该任务创建一个接口,

实现类实现同一接口中的方法。

  1. Template method 模板模式

做事情的步骤一样,但具体方法不同。

共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。

使用继承和重写实现模板模式。

(实验中的 GoncreteGraph)

模板模式广泛应用于framework。

Iterator 迭代器

独立于元素类型,访问容器内的所有元素。

Iterator pattern:让自己的集合类实现Iterable接口,并实现自己独特的iterator迭代器(hasNext,next,remove),允许客户端利用这个迭代器进行显示或隐式的迭代遍历。

(实现了iterable的对象才可用foreach遍历)

for(E e : collection) { …}

Iterator iter =
collection.iterator();

While(iter.hasNext())
{…}

哈工大 软件构造课程 复习考点总结(第六、七章)

  1. 可维护性的常见度量指标

Cyclomatic complexity 圈复杂度

Lines of Code LoC 代码行数

Maintainability Index (MI) 可维护性指数

Depth of Inheritance 继承的层次数

Class Coupling 类之间的耦合度

Unit test coverage 测试代码覆盖率

  1. Coupling 耦合度 and Cohesion 聚合度

Coupling 耦合度:

模块之间的依赖性。

Conhesion 聚合度

功能专一性。高聚合度:模块的所有元素作用于同一目标。

  1. SOLID 设计原则

SRP The Single
Responsibility Principle 单一责任原则

OCP The Open-Closed
Principle 开放-封闭原则

LSP The Liskov
Substitution Principle Liskov替换原则

DIP The Dependency
Inversion Principle 依赖转置原则

ISP The Interface
Segregation Principle 接口聚合原则

SRP 单一责任原则

一个类仅有一个责任;

至多有1个原因使得一个类发生变化。

OCP 开放-封闭原则

对扩展性开放

模块的行为可扩展,从而该模块可表现出新的行为以满足需求的变化。

对修改封闭

模块自身原本的代码不应被修改。

扩展模块行为的一般途径是修改模块的内部实现。

如果一个模块不能被修改,那么通常认为其具有固定的行为。

实现方案:抽象:继承、组合

注意:

避免使用 if-else / switch-case 语句。

LSP liskov替换原则

DIP 依赖转置原则

低层模块依赖于高层模块,具体依赖于抽象(抽象类、接口)。

ISP 接口聚合原则

客户端不应被迫依赖于它们不需要的方法。

客户端不需要实现(接口中)不需要的方法。

实现方案:将"胖"接口分解为若干个小接口,不同的接口向不同客户端提供服务。

  1. 语法、正则表达式

Productions 产生式节点,也称nonterminal非终止节点。(中间变量)

Terminal 终止节点。(终止字符)

Root 根节点。(起始字符)

正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点。

Java中:

匹配:

Pattern . matcher ( regex , string)

或 Pattern pattern =
Pattern.compile( regex ) ;

Matcher m = pattern.matcher (string) ;

m.matcher().

捕获:

Pattern parse = Pattern . compile (regex)
;

Matcher match = parse.matcher(string) ;

match.find() ;

match.group(index)

设计模式

Creational patterns

Factory method pattern 工厂方法模式

当client不知道要创建哪个具体类的实例,或不想在client代码中指明要具体创建的实例时,采用工厂方法。

定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

有新的子类时,只需添加新的工厂方法。(OCP)

  1. Abstract factory
    pattern 抽象工厂模式

提供接口来创建一组相互关联/相互依赖的对象,但不需要指明其具体类。

抽象工厂接口的每个具体实现类实现一种具体的产品生产搭配。

抽象工厂创建的不是一个完整产品,而是"产品族"(遵循固定搭配规则的多类产品的实例),

得到的结果是:多个不同产品的对象,各产品创建过程对client可见(client或辅助类中调用工厂实现类的相关方法组装具体部件,但每块部件到底选择哪种类型实现,取决于工厂类的搭配规则。),但"搭配"不能改变。

本质上,Abstract Factory 是把多类产品的factory method组合到一起,抽象出配置每块部件的方法。

  1. Builder pattern 构造器模式

创建复杂对象,包含多个组成部分。

创建一个完整的产品,有多个部分组成,client不了解各部分如何创建、各部分如何组合,最终得到一个完整产品对象。

Structural patterns

Bridge 桥接模式

通过delegation + inheritance 建立两个具体类之间的关系。(DIP 依赖转置,抽象依赖于抽象)

实例化abstraction时,new
RefinedAbstraction(左侧继承树的子类)并将接口实现类传入构造函数(右侧继承树的子类)。

  1. Proxy Pattern 代理模式

某个对象比较具有高隐私性,不希望client直接访问,设置proxy,在二者间建立防火墙。

隔离对复杂对象的访问,降低难度/代价,定位在"访问/使用"行为。

Composite 组合模式

递归组合,组合对象生成树结构,来体现层级关系(如职位等级图)。

在同类型的对象之间建立起树型层次结构,一个上层对象可包含多个下层对象。

Behavioral patterns

Observer

“粉丝”(observer)对"偶像"(subject)感兴趣,希望随时得知偶像的一举一动;

粉丝到偶像那里注册(obsever关联subject , 并调用subject的相关方法将自身加入其"粉丝"容器),

偶像一但有新闻发生,就推送给已注册(容器中的对象)的"粉丝"(回调observer的特定功能)。

在java中:

Observer 接口,实现该接口,构造"粉丝"。

Observable抽象类,派生子类,构造"偶像。"

  1. Visitor pattern

对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。

本质上:将数据和作用于数据上的某种/某些特定操作分离开来。

类似strategy,不过visitor对ADT本身进行操作。

  1. State pattern 状态模式

具体状态实现state接口,根据操作state不同实现之间转换。

  1. Memento pattern 备忘录模式

记录originator对象的历史状态,并可恢复记录或删除。

  1. Robustness 健壮性 和 Correctness 正确性

健壮性

正确性

系统在不正常输入或不正常外部环境下仍能够表现正常的程度。

程序按照spec加以执行的能力,是最重要的质量指标。

出错后:·1、退出(并提示信息) 2、容错(转为正常)

Fail fast

让用户变得更容易:出错也可以容忍,程序内部已有容错机制。

让开发者变得更容易:用户输入错误(不满足precondition的调用),直接结束。

对外部接口,开放(倾向于健壮)

对内部逻辑:保守(倾向于正确)

可靠性reliability = robustness + correctness

Error
–> defeat/fault
/ bug
–>failure

Mistake 程序员犯的错误 缺陷 失效,运行时程序的外部表现

对程序运行结果(Throwable):

Error 程序员无能为力(设备、I/O、网络、JVM等)

Exception 程序员自身引入:捕获、处理

Return (正常)

Throws (非正常)

  1. Thorwable

Error 内部错误:java运行时系统内部错误或资源不足

程序员无能为力,一旦发生,想办法让程序优雅的结束。

分类:用户输入错误、设备错误、物理限制等

Exception 异常:程序捕捉到的error(不是Error)

程序导致的问题,可以捕获、可以处理。

  1. Exception

异常:程序执行中的非正常事件,程序无法再按预想的流程执行。

程序若找不到异常处理程序,则退出,在控制台打印出statck trace。

Runtime Exception 运行时异常,有程序代码、或I/O等外部原因造成,不需要捕获/处理。

  1. Checked and
    Unchecked exceptions

从异常处理机制的角度进行分类:异常被谁check? 编译器/程序员

Unchecked异常:Error +
RuntimeException

程序代码产生,编译正常,可以不处理,抛出则程序失败。(类似 dynamic type checking)

Checked 异常:

必须捕获并制定错误处理机制,否则编译无法通过。(类似 static type checking)

如何选择? 对于一个异常:

如果客户端可以通过其他的方法恢复异常,则采用checked exception;

如果客户端对出现的异常无能为力,则采用unchecked exception。

  1. 异常处理

根据LSP:

父类抛出异常与子类协变,或子类不抛出异常;

若父类不抛出异常,则子类不抛出并自行处理异常。

Try-catch-finally :

无论程序是否碰到异常(无论Exception还是Error(包括assertion)),finally均会被执行。

(try 中 return 后,finally中若有return则,则依然执行,若无则在try执行return前执行)

(若 try或catch中强制退出(exit),则finally无法执行)

Try-with-resource:

try-with-resources这种声明方式指定了一个或多个资源,而且这些资源需要在程序结束的时候进行关闭。这种方式可以确保每个指定的资源都可以在声明结束的时候进行关闭(就是在try(){}结束的时候)。但是前提是这些资源必须实现接口java.lang.AutoCloseable(其中包括实现了java.io.Closeable接口的所有对象),原因是java.io.Closeable接口继承了java.lang.AutoCloseable接口。

如上图,BufferedReader 在try结束后自动关闭资源(br.close()).

  1. Assertion 断言机制

Assert (布尔值) : (输出字符串) ;

Assert false 则打印输出字符串,并throw AssertionError

断言主要用于开发阶段,为了调试程序、尽快避免错误。

什么时候用断言:

Inernal invariants 内部不变量

Rep invariants 表示不变量

如,验证private Boolean checkRep();

Control-Flow Invariante 控制流不变量

判定不能到达的位置,如switch-case中的default语句

方法的前置条件

对public方法:使用异常处理(因为是不受控制的外部条件)

对private方法:使用断言(对内严格)

方法的后置条件

断言 维护正确性,使用断言处理"绝不应该发生"的情况;

错误/异常处理 维护健壮性,处理"预料到可以发生"的不正常情况。

  1. Debug 调试

识别错误根源,消除错误

预防:防御式编程、assertion、exception

Debug: test -> debug ->

    重现    ->    诊断(假设检验,分治法)    ->    修复    ->    反思(是否存在相似的bug等)

Git bisect (诊断方法) 在输入的正确commit和错误commit之间二分查找,定位出错的commit。

常用手段:

Print(到处print)、stack tracing(输出调用栈),memory dump(内存导出分析),logging(记录日志),

断点调试。

Testing 测试

探测是否存在错误。

测试的等级:单元测试、集成测试、系统测试、验收测试,回归测试。

白盒测试:对程序内部代码结构的测试。

黑盒测试:对程序外部表现出来的行为的测试。

测试优先编程:先写spec,再写符合spec的测试用例,写代码、执行测试、发现问题并修改、再测试直到通过。

Code coverage 代码覆盖度: 测试效果/测试难度:路径覆盖(所有可能的分支、路径)>分支覆盖>语句覆盖

黑盒测试

检查程序是否符合规约。

用尽可能少的测试用例,尽快运行,并尽可能大的发现程序的错误。

哈工大 软件构造课程 考点复习总结(第一、二章)

  1. 软件构造的多维视图

Build-time

Code-level: 源代码:代码的逻辑组织:AST抽象语法树、函数、方法、类、接口等具体的逻辑实现

(class diagram类图)

Component-level:结构:代码的物理组织:源文件、包、库、静态链接、测试用例

(component diagram构建/组件图),编译

Moment view : 特定时刻的源码或组件形态

Period view:软件形态随时间的变化

Build-moment-code:词汇(源码)、语法(抽象语法树)、语义(类图)

Build-period-code:code churn代码变化:增减修改

Build-moment-component:文件目录,静态链接、包、库

Build-period-component:files/packages/components/libraries
change

SCI(software configuration item 配置项)、VCS

Runtime Views

Code-level : 可执行程序的内存状态,程序之间的互动、调用

Component-level:软件包部署到物理环境(OS、network、硬件等),及其互动

Moment view:特定时刻的程序行为

Period view:随着时间的行为

Run-moment-code:snapshot 快照 描述程序运行时内存里变量层面的状态 ,Memory dump(内存转储/导出)

Run-period-code:sequence diagram序列图(时序交互),Execution tracing 执行跟踪(代码层面,用日志方式记录程序执行的调用次序)

Run-moment-component:Deployment diagram(部署图:设备、操作系统),

Run-period-component:Event logging 事件日志(系统层面)

Event logging:系统管理员使用,高层次信息(如安装程序失败),有一定输出格式,不必敏捷

Execution tracing:开发者使用,低层次信息(如抛出异常),较低格式限制,必须敏捷

  1. 软件构造的阶段划分,各阶段的活动

Software development lifecycle SDLC 软件开发生命周期

0->1 从无到有

计划-需求-设计-构造(实现)-测试-部署-运维

1->n 从有到好

版本更新

传统软件开发模型

基本类型:Linear 线性过程;Iterative 迭代过程

模型:

  1. Waterfall 瀑布过程(线性非迭代)

阶段清楚,管理简单,无法适应需求变化

  1. Incremental 增量过程(非迭代)

多个瀑布串行,比较容易适应需求的增加(有需求就增加一个瀑布)

  1. V-Model V字模型(验证,确认)

  2. Prototyping 原型过程(迭代)

在原型上持续不断的迭代,发现用户变化的需求

迭代:开发出来后由用户试用/评审,发现问题反馈给卡发着,开发者修改原有的实现,继续交给用户评审

时间代价高,但开发质量也高

  1. Spiral 螺旋过程(迭代)

多轮迭代基本遵循瀑布模式

每轮迭代有明确的目标,遵循"原型"过程,进行严格的风险分析,方可进入下一轮迭代

Agile development 敏捷开发

通过快速迭代和小规模的持续改进,以快速适应变化

用户高度参与,小步骤迭代确认验证。强调人的作用

  1. 内部/外部质量指标

外部质量因素影响用户,内部质量因素影响软件本身和它的开发者,外部质量取决于内部质量。

External quality factors 外部质量

Correctness 正确性:按照spec执行,得到正确的结果,软件的行为要严格符合规约中定义的行为

保证正确性:测试和调适、防御式编程,形式化方法(形式化语言)

encapsulation,
decentralization 封装、分散化

Robustness 健壮性:针对异常情况的处理:出现规约定义之外的情形,软件做出恰当的反应(出现异常时不要崩溃),未被spec覆盖的情况即为"异常情况"

encapsulation,
error handling封装、异常处理

Extendibility 可扩展性:是否容易使软件适应规约的变化

提升可扩展性的两个原则:简约主义设计,分离主义设计

encapsulation,
information hiding封装,信息隐蔽(结构良好的对象有简单的接口,并且不向外界显漏任何内部机制。)

Reusability 可复用性:一次开发,多次使用,发现共性

modularity,
component, models, patterns模块化、组件、模型、模式

Compatibility 兼容性:不同软件系统之间相互可容易的集成

保持设计的同构性:标准化文件格式,标准化数据结构,标准化用户接口

Efficient 性能

Portability 可移植性:软件可方便的在不同的技术环境之间移植:硬件、操作系统

Ease of use 易用性:易学、安装、操作、监控

给用户提供详细的指南,结构简单

Functionality 功能

Timeliness 及时性:及时发布等

其他质量:verifiability 可验证性、integrity完整性,repairability可修复性,economy经济型

Internal quality factors 内部质量

代码相关:lines of code(LOC)、cyclomatic complexity 循环复杂性

结构相关:coupling耦合度(多个模块间联系),cohesion聚合度(一个模块;高内聚,一个程序只执行一种功能) (应当 高内聚低耦合,单一责任原则)

Readability 可读性

Understandability 可理解性

Clearness

Size

最重要的几个质量因素

Correctness and robustness:
reliability(可靠性)

Extendibility and reusability:
modularity(模块化)

软件构造的五个关键质量目标

Easy to understand: elegant and beautiful code /
understandability 便于理解

Understandability:注释、命名、日志等

Cheap for develop: design for/with reuse: reusability 发展

Reusability:ADT、OOP、设计模式等以及复用外部文件

Ready for change: maintainability and adaptability 变化

Maintainability
and Adaptability

Safe from bugs: robustness 安全

Robustness 异常处理、断言、防御式变成、测试优先原则、日志追踪、debug、内存导出

Efficient to run: performance 高效

Performance 代码调优、空间复杂性(内存管理)、时间复杂性(I/O性能等),分布式系统,多线程

  1. 软件配置管理SCM与版本控制系统VCS

VCS version control system

Local VCS 本地:仓库在开发者本地机器

Centralized VCS 集中式:仓库在独立的服务器

Distributed VCS 分布式:仓库在独立的服务器+开发者本地机器

SCM 软件配置管理:追踪和控制软件的变化

SCI 软件配置项:软件变化的基本单元(如一个文件)

Baseline 基线:软件持续变化过程中的"稳定时刻"(如 对外发布的版本)

CMDB 配置管理数据库:存储软件的各配置项随时间发生变化的信息+基线(git的本地repository?)

Versioning 版本控制

  1. Git的结构、工作原理、基本指令

结构

Working directory 工作目录:本地文件系统 (已修改,未暂存)

Staging area 暂存区:隔离工作目录和git仓库 (已暂存)

.git directory 本地仓库(本地CMDB) (已提交)

Object Graph

指令

git branch branchname创建新分支

git checkout branchname切换到分支

git checkout -b branchname 创建新分支并切换到新分支

git merge branchname 合并当前分支与目标分支

pull=fetch + merge

fetch 从远程仓库获取最新版本到本地,不会自动merge

fork 复制别人的仓库到自己的仓库(自己不是contributor,不能提交修改给别人)

clone 复制仓库到自己的本地仓库

哈工大 软件构造课程 考点复习总结(第三章)

  1. 数据类型

Primitive types 基本数据类型(8种)

  1. Object types 对象数据类型(reference types 引用数据类型)

Short、int 、long、float、double、boolean、char、byte

如:String、BigInteger

只有值,没有ID(无法与其他值区分),不能赋值为null;
immutable

有值,也有ID;
部分mutable,部分immutable

在栈中分配内存,代价低

在堆中分配内存,代价高

  1. 静态类型检查&动态类型检查

静态类型检查

动态类型检查

(静态类型语言 如java)

(动态类型语言 如python)

提高程序的正确性和健壮性

关于"类型"的检查,不考虑值(不知道运行时会是什么值)

关于"值"的检查

  1. Mutable &
    Immutable

Immutable 不可变数据类型

mutable 可变数据类型

优点

优点:安全

优点:最少化拷贝以提高效率获得更好的性能,适合于在多个模块之间共享数据

缺点

缺点:频繁修改产生大量临时拷贝,需要垃圾回收·

缺点:不安全

其他

一旦被创建,其值不能改变
对于引用类型,加final限制不能改变引用

安全地使用可变类型:局部变量(不涉及共享,且只有一个引用)
如果有多个引用(别名),不安全
Defensively copy 防御式拷贝:返回全新的对象

尽可能用immutable!

Snapshot Diagram 画法:

基本类型:单独一个常量

引用类型:圈住!

重分配:

不可变类型(用双线椭圆),修改引用

可变类型:修改值

引用:

可变引用:单线箭头

不可变引用:双线箭头

  1. Specification

作用

规约可以隔离"变化",无需通知客户端

规约可以提高代码效率

规约扮演"防火墙"角色

解耦,不需要了解具体实现

内容:只讲"能做什么",而不讲"怎么实现"

Behavior equivalence 行为等价性

是否可以相互替换

站在客户端的视角看行为等价性,不同的行为,对用户来说(根据用户需求)可能等价!

根据规约判断行为等价,两个方法符合同一个规约,则等价

规约的结构:

Pre-condition

Post-condition

Exceptional behavior 异常行为,如果违背了前置条件,会发生什么

规约的强度与替换

Spec变强:更放松的前置条件(前置条件更弱)+更严格的后置条件(后置条件你更强),

两条件同时变强或变弱则无法比较。

若规约强度S2>=S1,则可以用S2替换S1。

deterministic spec & undetermined spec 确定的规约和欠定的规约

确定的规约:给定一个满足前置条件的输入,其输出唯一、明确

欠定的规约:同一个输入可以有多个输出(多次执行输出可能不同)

Declarative spec & operational spec 声明式规约和操作式规约

操作式规约:如 伪代码

声明式规约:没有内部实现的描述,只有"初-终"状态

声明式规约更有价值!

内部实现的细节不在规约里呈现,而放在代码实现体内部注释里呈现。

Diagraming specification

规约定义一个区域,该区域包含所有可能的实现方式。

空间中的每个点表示一种方法的实现。

对于某个具体实现,若满足规约,则落在其区域内。

更强的规约表达为更小的区域。

Quality of specification 规约质量

内聚性:spec描述的功能应单一、简单、易理解

运行结果信息丰富(可能的改变,以及返回值等),不能让客户端产生理解上的歧义

足够强(如postcondition中充分阐述各种情况)

适当弱(太强的规约,在很多特殊情况下难以达到)

在规约里使用抽象类型(在java中,经常使用interface,如Map、List,而不是HashMap、ArrayList),可以给方法的实现体和客户端更大的自由度

使用前置条件和后置条件?

客户端不喜欢太强的pre-condition,不满足precondition的输入会导致失败

So:不限定太强的precondition,而在postcondition中抛出异常:输入不合法,

fail fast,避免fail大规模扩散

    是否使用前置条件取决于:

check(检查参数合法性)的代价

方法的使用范围:

如果只在类内部使用(private),则可以不使用precondition,在使用该方法的各个位置进行check

如果在其他地方使用(public),则必须使用precondition,若client不满足则抛出异常

  1. Pre-condition
    and post-condition 前置条件和后置条件

Pre-condition 前置条件(requires)

Post-condition 后置条件(effects)

@param

@return @throws

对客户端的约束
在使用方法时必须满足的条件

对开发者的约束
方法结束时必须满足的条件

契约:如果前置条件满足了,后置条件必须满足

除非在后置条件中声明,否则方法内部不应该改变输入参数。
尽量不设计mutating的spec,否则容易引发bugs。
尽量避免使用mutable对象。
避免使用可变的全局变量。

ADT

  1. ADT及其四种操作

抽象类型:强调"作用于数据上的操作",程序员和client无需关心数据如何具体存储,只需设计/使用操作即可。

ADT由操作定义,与其内部实现无关。

可变数据类型:提供了可改变其内部数据值的操作;

不可变数据类型:其操作不改变内部值,而构造新的对象。(没有mutators)

ADT操作分类:

Creators 构造器:

不利用该类型对象产生一个新的对象

可能实现为构造函数或静态函数(factory method)

Producers 生产器:

用已有该类型对象产生新对象

如string.concat()(连接两个字符串,产生一个新的字符串)

Observers 观察器

如list.size()返回int(不同于原类型)

Mutators 变值器(改变对象属性的方法)

通常范围void,如果返回void,则必然意味着它改变了某些对象的内部状态

也可能范围非空类型(如容器类的put、add方法)

  1. Representation
    Independence 表示独立性

表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部表现的变化不应该影响外部spec和客户端。

  1. Representation
    exposure 表示泄漏

如client能直接接触类成员变量。

表示泄漏影响表示不变量,也影响表示独立性:无法在不影响客户端的情况下改变其内部表示。

避免方法:private、final、defensive copy

  1. Invariants 不变量 & Representation Invariant 表示不变量

ADT应保持其不变量在任何时候总是true;

ADT负责其不变量,与client的任何行为无关。

作用:保持程序的"正确性",容易发现错误。

  1. Abstraction
    Function 抽象函数

表示空间R

抽象空间A

值的实际实现本质

抽象表示(client看到和使用的值)

ADT实现者关注表示空间R

用户关注抽象空间A

R到A的映射

一定是满射:A中元素总有R中具体的实现

未必是单射:A中一个元素在R中可能有多重实现方式

未必是双射:R中表示不符合A中需求(如图中"abbc")

抽象函数AF:R和A之间映射关系的函数

AF:R->A

对于RI :R-> Boolean

RI:某个具体的"表示"是否合法;表示值的一个子集,包含所有合法的表示值;一个条件,描述了什么是"合法"表示值。

Documenting AF 、RI、Safety from Rep
Exposure

选择某种特定的表示方式R

进而指定某个子集是"合法"的(RI)

并为该子集中的每个值做出"解释"(AF)

即 如何映射

Safety from Rep Exposure

证明代码并未对外泄露其内部表示

保证不变量为true,不变量:

通过creators和producers创建

受mutators和observers保护

无表示泄漏发生

OOP

  1. Interface 接口

接口的成员变量默认用final关键字修饰,故必须有初值,可用public,default修饰,可用static修饰。

接口的方法只能被public、default、abstract、static、strictfp(严格浮点运算)修饰。

  1. Inheritance、override 继承和重写

Strict inheritance 严格继承:子类只能添加新方法,无法重写超类(父类)中的方法(final限制)。

考虑final修饰类、方法、属性时的不同作用。

Override 方法:具有一致的signature,复用的基本机制。

  1. Polymorphism ,subtyping and overloading 多态,子类型化,重载

三种多态:

  1. Ad hoc
    polymorphism (特殊多态)

用于function overloading(功能重载),即重载

  1. Parametric
    polymorphism (参数化多态)

泛型

  1. Subtyping (subtype polymorphism / inclusion polymorphism )(子类型多态、包含多态)

Overloading 重载

重载条件:

方法名相同

参数列表不同,即参数类型、个数、类型顺序至少有一项不相同

返回值类型可以不同

方法的修饰符可以不同

可以抛出不同的异常

可以在类内重载,也可以在子类重载

重载是一种静态多态,静态类型检查

(static dispatch 静态分派)并在编译阶段决定具体执行哪个方法(即对方法的调用取决于编译时声明的引用的类型)

而重写(dynamic dispatch 动态分派)则进行动态类型检查,根据运行时堆中的实例类型选择方法。

Generic 泛型

通配符 :只有使用泛型的时候出现,不能在定义中出现。

类型擦除:编译后、运行时类型擦除

List
-> List

注意可能引起重载编译错误。

运行时不能用 instanceof 检查泛型。

不能创建泛型数组

不能用在静态变量

不能创建对象(不能new)

Subtypes

超类的子类型,如:ArrayList和LinkedList是List的子类型。

子类型的规约不能弱化超类型的规约。

子类型多态:不同类型的对象可以统一处理而无需区分(不加区分地调用同样的方法等),从而隔离变化

LSP(Liskov Substitution Principle) 如果S是T的子类型,那么T的对象可以被S的对象替换。

Type casting 类型转换

避免向下类型转换。

Dispatch 分派

Static dispatch 静态分派

Dynamic dispatch 动态分派

将调用的名字与实际方法的名字联系起来(可能有多个)

决定具体执行哪一个操作

重载,在编译阶段即可确定执行哪个具体操作

重写,在运行时决定

Early/static binding

Lade/dynamic binding

绑定static、private、final方法时发生

重写父类子类的同样方法

  1. equals()

引用等价性 ==

比较内存地址ID

用于比较基本数据类型

对象等价性 equals()

验证正确性:reflexive 自反性、symmetric 对称性、transitive 传递性、非空(a.equals(null) return false)

  1. hashCode()

等价的对象必须有相同的hashCode

Rule:重写equals时重写hashcode

  1. Equality of
    Mutable Types 可变对象的等价性

Observational equality 观察等价性

Behavioral equality 行为等价性

在不改变状态的形况下,两个mutable看起来是否一致

调用对象的任何方法都展示出一致的结果

调用observer,producer,creator

调用任何方法,包括mutator

当前情况下,看起来(如成员变量)相同

经过改变后,依然相同(只是别名引用)

对不可变类型,观察等价性和行为等价性完全等价。

对可变类型,往往倾向于实现严格的观察等价性。(但有时观察等价性可能导致bug,甚至破坏RI)

对可变类型,应当实现行为等价性,即只有指向内从空间中同样的objects才相等(即equals比较引用,如==而hashcode把引用映射为一个值)。

所以对可变类型,无需重写equals和hashcode,直接继承object。(比较引用)

若一定要判断两个可变对象是否一致,最好定义一个新的方法。

哈工大 软件构造课程 考点复习总结(第四、五章)

  1. 代码可理解性

度量

标识符的平局长度

命名独特性比例

代码复杂度

LoC 代码行数

注释密度(百分比)

如何书写可理解性代码

命名规范

代码行最大长度,文件最大LoC

注释

布局:缩进、对齐、空行、分块等

避免多层嵌套(增加复杂度)

文件和包的组织

包的相关原则

REP 复用/发布等价原则

复用的粒度等价于发布的粒度(发布的都是应被复用的,未发布的不应被复用)

CCP Common Closure Principle 共同封闭原则

一个包中的所有类针对同一种变化是封闭的

一个包的变化将会影响到包里所有的类,而不会影响到其他的包

如果两个类紧密耦合在一起,即二者总是同时发生变化,那么它们应属于同一个包

CRP Common Reuse Principle 共同复用原则

一个包里的类应被一起复用

如果复用了其中一个类,那么就应复用所有的类

总之,尽量把相关性高的类放在一起,对不同类进行合理划分。

  1. 代码复用

可复用组建的形态和层级

源代码:方法、声明等

模块:类、接口

库:API

系统(结构):framework 框架:一组具体类、抽象类及其之间的连接关系

代码复用的类型:

White box 白盒复用:源代码可见,可修改和扩展

即,直接复制修改代码。可定制化程度高,但需要充分了解代码。

Black box 黑盒复用:源代码不可见,不能修改

只能通过API接口使用,无法修改代码。简单、清晰,但适应性差。

Frameworks :domain-level reuse
领域复用

框架:一组具体类、抽象类及其之间的连接关系

开发者根据framework的规约,填充自己的代码进去,形成完整的系统;

开发者 增加新代码、对抽象类机型具体化、实现接口;

Framework作为主程序加以执行,执行过程中调用开发者所写的程序。

控制反转(inverse of control),控制权由代码转到了外部容器(framework),好处是降低了对象之间的依赖程度,提高灵活性和可维护性。

External observations of reusability 可复用的外部观察特性

Type variation 类型可变:泛型,且满足LSP

Implementation variation 实现可变:

ADT有多种不同的实现,提供不同的representations和abstract funtion,但具有同样的specification (pre-condition, post-condition, invariants),从而可以适应不同的应用场景

Routine grouping 功能分组:

Representation independence 表示独立

内部实现可能会经常变化,但客户端不应受到影响(不变量为true)

Factoring out common behaviors 抽取共性行为

  1. LSP (Liskov substitution principle) liskov 替换原则

子类型可以增加,但不可删除方法

子类型需要实现抽象类中所有未实现的方法

子类型中重写的方法必须有相同或子类型的返回值
co-variance:协变

子类型中重写的方法必须使用同样类型的参数
contra-variance 反协变(逆变)

子类型中重写的方法不能抛出额外的异常
协变

同样或更强的不变量

同样或更弱的前置条件

同样或更强的后置条件

  1. Covariance 协变、Contravariance 反协变,逆变

协变

父类型->子类型

Spec越来越具体

返回值类型

不变,或更具体

异常类型

不变,或更具体

反协变

父类型->子类型

Spec越来越具体

参数类型

不变,或更抽象(由于java语法,不能更抽象)

注意

数组( T [ ] )是协变的

T[ ]中元素必须是T或其子类,否则编译时通过,但运行时会错误。

泛型 不变(类型擦除)

通配符

调用时使用。

  1. Comparator and
    Comparator

对自定义ADT(Edge为例)比较大小,或要放入collections或arrays进行排序

创建比较类EdgeCompare实现comparator接口

Collections . sort
( Lsit , EdgeCompare)

Edge类实现Comparable接口

类内ouvrride compareTo 方法

  1. Delegation 委派

一个对象请求另一个对象的功能

如果子类只需要复用父类中的一小部分方法,可以不需要使用继承,而是通过委派机制实现。

即,一个类不需要继承另一个类的全部方法,通过委派机制调用部分方法。

"委派"发生在object层面,"继承"发生在class层面

委派的类型

Dependency 依赖 临时的委派 (B uses A)

类内调用

A类是B类中的(某中方法的)局部变量;

A类是B类方法当中的一个参数;

A类向B类发送消息,从而影响B类发生变化;

Association 关联 永久的委派 (B owns A) (关联关系可以是双向的)

A类为B的成员变量(或者对象数组)。

也可能自关联(如,自身的成员变量有自身的实例,如父节点有子节点)

Composition 组合 更强的委派 (A is part of B )

A是B的一部分,B不能脱离A存在,A不能单独存在。

Aggregation 聚合

整体和部分(可有可无)的关系。

  1. CRP (Composite Reuse Principle)组合复用原则

对于某一具体行为,用委派代替继承。

使用接口定义不同方面的行为,

接口之间通过extends实现行为的组合(组合多用接口,则组合接口汇集了其他接口的行为)。

用具体类实现组合接口。

  1. White box and
    black box framework 白盒框架和黑盒框架

白盒框架

黑盒框架

实现方式

通过 子类 和 重写方法 实现扩展
继承

通过实现 插件接口 实现扩展
委派/组合

常用设计模式

模板方法

策略模式 观察者模式

调用机制

子类型有main方法,但framework拥有控制权

插件加载机制加载插件(委派),framework拥有控制权

另一种实现

在抽象父类中添加abstract方法
(需修改framework代码)
子类实现abstract方法

实现预留接口
(不需修改framework代码,framework中有预留接口)
实现类实现接口

例如
Class WhiteRun extends Thread{
(override run) }
New WhiteRun().strat

例如
Class BlackRun implements Runnable
New Thread( new BlackRun() ).start()

设计模式

Structural patterns 结构型设计模式

Adapter 适配器模式

将某个类/接口 转换为client期望的其他形式(client的调用传参与类/接口 方法的参数列表不一致,不兼容)

增加一个接口,将已存在的子类封装

Client面向接口编程,从而隐藏具体子类。

适配器类实现接口或继承实现接口的父类,

适配器中调用子类方法实现功能(委派)。

可能client调用子类方式(如参数形式等)与子类不匹配,

通过适配器进行调整调用,适配子类,从而实现原本不兼容的调用。

Decorator 修饰器

为对象增加不同侧面的特性。

对每一个特性构造子类,通过委派机制增加到对象上。

例如:Collections.synchronizedList( List list)

即返回一个在list增加了新特性的对象。

即为对象本身和其需要增加的特性进行封装,

基础功能通过委派(父类)实现,并额外增加新特性,成为一个新的类。

Client可以通过层层修饰获得具有多种特性的对象。

Façade 外观模式

客户端需要通过一个简化的接口来访问复杂系统内的功能。

提供一个统一的接口来取代一系列小接口调用,相当于对复杂系统进行封装,简化客户端使用。

Behavioral patterns 行为类设计模式

Strategy 策略模式

对一个任务,有多用算法实现。

为该任务创建一个接口,

实现类实现同一接口中的方法。

  1. Template method 模板模式

做事情的步骤一样,但具体方法不同。

共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现。

使用继承和重写实现模板模式。

(实验中的 GoncreteGraph)

模板模式广泛应用于framework。

Iterator 迭代器

独立于元素类型,访问容器内的所有元素。

Iterator pattern:让自己的集合类实现Iterable接口,并实现自己独特的iterator迭代器(hasNext,next,remove),允许客户端利用这个迭代器进行显示或隐式的迭代遍历。

(实现了iterable的对象才可用foreach遍历)

for(E e : collection) { …}

Iterator iter =
collection.iterator();

While(iter.hasNext())
{…}

哈工大 软件构造课程 复习考点总结(第六、七章)

  1. 可维护性的常见度量指标

Cyclomatic complexity 圈复杂度

Lines of Code LoC 代码行数

Maintainability Index (MI) 可维护性指数

Depth of Inheritance 继承的层次数

Class Coupling 类之间的耦合度

Unit test coverage 测试代码覆盖率

  1. Coupling 耦合度 and Cohesion 聚合度

Coupling 耦合度:

模块之间的依赖性。

Conhesion 聚合度

功能专一性。高聚合度:模块的所有元素作用于同一目标。

  1. SOLID 设计原则

SRP The Single
Responsibility Principle 单一责任原则

OCP The Open-Closed
Principle 开放-封闭原则

LSP The Liskov
Substitution Principle Liskov替换原则

DIP The Dependency
Inversion Principle 依赖转置原则

ISP The Interface
Segregation Principle 接口聚合原则

SRP 单一责任原则

一个类仅有一个责任;

至多有1个原因使得一个类发生变化。

OCP 开放-封闭原则

对扩展性开放

模块的行为可扩展,从而该模块可表现出新的行为以满足需求的变化。

对修改封闭

模块自身原本的代码不应被修改。

扩展模块行为的一般途径是修改模块的内部实现。

如果一个模块不能被修改,那么通常认为其具有固定的行为。

实现方案:抽象:继承、组合

注意:

避免使用 if-else / switch-case 语句。

LSP liskov替换原则

DIP 依赖转置原则

低层模块依赖于高层模块,具体依赖于抽象(抽象类、接口)。

ISP 接口聚合原则

客户端不应被迫依赖于它们不需要的方法。

客户端不需要实现(接口中)不需要的方法。

实现方案:将"胖"接口分解为若干个小接口,不同的接口向不同客户端提供服务。

  1. 语法、正则表达式

Productions 产生式节点,也称nonterminal非终止节点。(中间变量)

Terminal 终止节点。(终止字符)

Root 根节点。(起始字符)

正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点。

Java中:

匹配:

Pattern . matcher ( regex , string)

或 Pattern pattern =
Pattern.compile( regex ) ;

Matcher m = pattern.matcher (string) ;

m.matcher().

捕获:

Pattern parse = Pattern . compile (regex)
;

Matcher match = parse.matcher(string) ;

match.find() ;

match.group(index)

设计模式

Creational patterns

Factory method pattern 工厂方法模式

当client不知道要创建哪个具体类的实例,或不想在client代码中指明要具体创建的实例时,采用工厂方法。

定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

有新的子类时,只需添加新的工厂方法。(OCP)

  1. Abstract factory
    pattern 抽象工厂模式

提供接口来创建一组相互关联/相互依赖的对象,但不需要指明其具体类。

抽象工厂接口的每个具体实现类实现一种具体的产品生产搭配。

抽象工厂创建的不是一个完整产品,而是"产品族"(遵循固定搭配规则的多类产品的实例),

得到的结果是:多个不同产品的对象,各产品创建过程对client可见(client或辅助类中调用工厂实现类的相关方法组装具体部件,但每块部件到底选择哪种类型实现,取决于工厂类的搭配规则。),但"搭配"不能改变。

本质上,Abstract Factory 是把多类产品的factory method组合到一起,抽象出配置每块部件的方法。

  1. Builder pattern 构造器模式

创建复杂对象,包含多个组成部分。

创建一个完整的产品,有多个部分组成,client不了解各部分如何创建、各部分如何组合,最终得到一个完整产品对象。

Structural patterns

Bridge 桥接模式

通过delegation + inheritance 建立两个具体类之间的关系。(DIP 依赖转置,抽象依赖于抽象)

实例化abstraction时,new
RefinedAbstraction(左侧继承树的子类)并将接口实现类传入构造函数(右侧继承树的子类)。

  1. Proxy Pattern 代理模式

某个对象比较具有高隐私性,不希望client直接访问,设置proxy,在二者间建立防火墙。

隔离对复杂对象的访问,降低难度/代价,定位在"访问/使用"行为。

Composite 组合模式

递归组合,组合对象生成树结构,来体现层级关系(如职位等级图)。

在同类型的对象之间建立起树型层次结构,一个上层对象可包含多个下层对象。

Behavioral patterns

Observer

“粉丝”(observer)对"偶像"(subject)感兴趣,希望随时得知偶像的一举一动;

粉丝到偶像那里注册(obsever关联subject , 并调用subject的相关方法将自身加入其"粉丝"容器),

偶像一但有新闻发生,就推送给已注册(容器中的对象)的"粉丝"(回调observer的特定功能)。

在java中:

Observer 接口,实现该接口,构造"粉丝"。

Observable抽象类,派生子类,构造"偶像。"

  1. Visitor pattern

对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。

本质上:将数据和作用于数据上的某种/某些特定操作分离开来。

类似strategy,不过visitor对ADT本身进行操作。

  1. State pattern 状态模式

具体状态实现state接口,根据操作state不同实现之间转换。

  1. Memento pattern 备忘录模式

记录originator对象的历史状态,并可恢复记录或删除。

  1. Robustness 健壮性 和 Correctness 正确性

健壮性

正确性

系统在不正常输入或不正常外部环境下仍能够表现正常的程度。

程序按照spec加以执行的能力,是最重要的质量指标。

出错后:·1、退出(并提示信息) 2、容错(转为正常)

Fail fast

让用户变得更容易:出错也可以容忍,程序内部已有容错机制。

让开发者变得更容易:用户输入错误(不满足precondition的调用),直接结束。

对外部接口,开放(倾向于健壮)

对内部逻辑:保守(倾向于正确)

可靠性reliability = robustness + correctness

Error
–> defeat/fault
/ bug
–>failure

Mistake 程序员犯的错误 缺陷 失效,运行时程序的外部表现

对程序运行结果(Throwable):

Error 程序员无能为力(设备、I/O、网络、JVM等)

Exception 程序员自身引入:捕获、处理

Return (正常)

Throws (非正常)

  1. Thorwable

Error 内部错误:java运行时系统内部错误或资源不足

程序员无能为力,一旦发生,想办法让程序优雅的结束。

分类:用户输入错误、设备错误、物理限制等

Exception 异常:程序捕捉到的error(不是Error)

程序导致的问题,可以捕获、可以处理。

  1. Exception

异常:程序执行中的非正常事件,程序无法再按预想的流程执行。

程序若找不到异常处理程序,则退出,在控制台打印出statck trace。

Runtime Exception 运行时异常,有程序代码、或I/O等外部原因造成,不需要捕获/处理。

  1. Checked and
    Unchecked exceptions

从异常处理机制的角度进行分类:异常被谁check? 编译器/程序员

Unchecked异常:Error +
RuntimeException

程序代码产生,编译正常,可以不处理,抛出则程序失败。(类似 dynamic type checking)

Checked 异常:

必须捕获并制定错误处理机制,否则编译无法通过。(类似 static type checking)

如何选择? 对于一个异常:

如果客户端可以通过其他的方法恢复异常,则采用checked exception;

如果客户端对出现的异常无能为力,则采用unchecked exception。

  1. 异常处理

根据LSP:

父类抛出异常与子类协变,或子类不抛出异常;

若父类不抛出异常,则子类不抛出并自行处理异常。

Try-catch-finally :

无论程序是否碰到异常(无论Exception还是Error(包括assertion)),finally均会被执行。

(try 中 return 后,finally中若有return则,则依然执行,若无则在try执行return前执行)

(若 try或catch中强制退出(exit),则finally无法执行)

Try-with-resource:

try-with-resources这种声明方式指定了一个或多个资源,而且这些资源需要在程序结束的时候进行关闭。这种方式可以确保每个指定的资源都可以在声明结束的时候进行关闭(就是在try(){}结束的时候)。但是前提是这些资源必须实现接口java.lang.AutoCloseable(其中包括实现了java.io.Closeable接口的所有对象),原因是java.io.Closeable接口继承了java.lang.AutoCloseable接口。

如上图,BufferedReader 在try结束后自动关闭资源(br.close()).

  1. Assertion 断言机制

Assert (布尔值) : (输出字符串) ;

Assert false 则打印输出字符串,并throw AssertionError

断言主要用于开发阶段,为了调试程序、尽快避免错误。

什么时候用断言:

Inernal invariants 内部不变量

Rep invariants 表示不变量

如,验证private Boolean checkRep();

Control-Flow Invariante 控制流不变量

判定不能到达的位置,如switch-case中的default语句

方法的前置条件

对public方法:使用异常处理(因为是不受控制的外部条件)

对private方法:使用断言(对内严格)

方法的后置条件

断言 维护正确性,使用断言处理"绝不应该发生"的情况;

错误/异常处理 维护健壮性,处理"预料到可以发生"的不正常情况。

  1. Debug 调试

识别错误根源,消除错误

预防:防御式编程、assertion、exception

Debug: test -> debug ->

    重现    ->    诊断(假设检验,分治法)    ->    修复    ->    反思(是否存在相似的bug等)

Git bisect (诊断方法) 在输入的正确commit和错误commit之间二分查找,定位出错的commit。

常用手段:

Print(到处print)、stack tracing(输出调用栈),memory dump(内存导出分析),logging(记录日志),

断点调试。

Testing 测试

探测是否存在错误。

测试的等级:单元测试、集成测试、系统测试、验收测试,回归测试。

白盒测试:对程序内部代码结构的测试。

黑盒测试:对程序外部表现出来的行为的测试。

测试优先编程:先写spec,再写符合spec的测试用例,写代码、执行测试、发现问题并修改、再测试直到通过。

Code coverage 代码覆盖度: 测试效果/测试难度:路径覆盖(所有可能的分支、路径)>分支覆盖>语句覆盖

黑盒测试

检查程序是否符合规约。

用尽可能少的测试用例,尽快运行,并尽可能大的发现程序的错误。

等价类划分

将被测函数的输入域划分为等价类,从等价类中导出测试用例。

针对每个输入数据需要满足的约束条件,划分等价类。

每个等价类代表着对输入约束加以满足/违反的有效/无效数据的集合。

边界值分析 BVA(Boundary Value
Analysis)

在等价类划分时,将边界作为等价类之一加入考虑。不仅考虑边界,还要考虑边界两侧(稍微偏离边界)。

等价类划分

将被测函数的输入域划分为等价类,从等价类中导出测试用例。

针对每个输入数据需要满足的约束条件,划分等价类。

每个等价类代表着对输入约束加以满足/违反的有效/无效数据的集合。

边界值分析 BVA(Boundary Value
Analysis)

在等价类划分时,将边界作为等价类之一加入考虑。不仅考虑边界,还要考虑边界两侧(稍微偏离边界)。

你可能感兴趣的:(哈工大软件构造 期末复习考点总结)