第一章、软件的建设观点与质量目标
多维软件视图
软件构造这门课,我们首先接触的就是多维软件视图:
从时间分为:瞬时(Moment)、周期(Period)
从编码分为:构建(开发)(Build-time)、运行(Run-time)
从代码分为:代码(Code-level)、模块(Component)
这里每个层次关注的点是不一样的
Build-time:
- Code-level:代码逻辑组织
- Component-level:代码物理组织
- Moment:某一时刻的软件形态
- Period:软件形态随时间变化
Run-time:
- Code-level:逻辑实体再内存中的呈现
- Component-level:物理实体在物理硬件环境中的呈现
- Moment:逻辑/物理实体在内存/硬件环境中特定时刻的形态
- Period:逻辑/物理实体在内存/硬件环境中的形态随时间变化
软件形态的质量因素
软件质量(外部取决于内部):
- 外部:正确性、健壮(鲁棒)性、易扩展性、复用性、兼容性、效率、可移植性、易用性、功能性、时效性
- 内部因素:LOC、RUCS(可读、可理解、清晰、大小)
软件构造的5个主要目标:
- 代码容易理解
- 易用,开发便宜
- 低复杂度,易扩展
- 健壮性、正确性
- 效率
软件测试与测试优先编程
残留缺陷率:每1000行中bug的数目(1~10,0.1 ~ 1,0.01 ~0.1)
测试的主要目的:测试出错误
测试的层面:单元测试、集成测试、系统测试
改完之后再测试:回归测试
测试
{静态测试:看代码动态测试:边运行边看
{Test(测试):发现有没有错误Debug(调试):找到错误在哪
{白盒测试:按代码一步一步调试黑盒测试:结果正确即可
测试用例:输入+执行条件+期望结果(assertEquals)
测试有限编程:先写spec再写测试再写代码
{白盒测试:尽量所有的路径都走一次黑盒测试:划分等价类(对称自反传递),重点测边界
覆盖:路径覆盖>分支覆盖>语句覆盖
软件构建的过程与工具
软件生命周期与配置管理
生命周期:SDLC:重点:软件设计→编码→测试
2种基本模型
{线性过程迭代过程
模型:瀑布模型、增量模型、原型模型、V字模型、螺旋模型
敏捷开发:敏捷=增量+迭代
软件配置管理(SCM)
SCM:追踪和控制软件的变化
软件配置项(SCI):软件中发生变化的基本单元(多为文件)
基线(baseline):稳定的版本
配置管理数据库(CMOB):变化的信息
版本控制管理(VCS)
⎩⎨⎧本地版本控制系统:仓库在本地集中式版本控制系统:仓库在一个服务器分布式版本控制系统:服务器和本地都有仓库
Github
Github:{存变化的文件子节点指向父亲结点,可以有0/1/2个父亲节点


数据类型与类型检查
{静态类型检查:语法错误(类名,函数名,参数的数目/类型,返回值)动态类型检查:非法参数,非法返回值,null,数组越界
{基本数据类型:int,long,boolean,char,byte,short,float对象数据类型:String,BigIntegar,基本数据类型的包装(Integer,Boolean……)

重载overload:操作名称一样,输入参数不同
{静态类型语言:编译时检查动态类型语言:运行时检查
immutable与mutable
{可变数据类型:StringBuilder,Date,数组不可变数据类型:String,基本数据类型与它们的封装
不可变数据类型:
- 指向的空间所存的数据不可改变
- 关键字final:指向的地址不再改变
- 问题:垃圾回收
可变数据结构:
4. 防御使拷贝:return new
迭代器:Iterator
介绍:List,Set,Map
可变→不可变:Collections.unmodifiableList/Set/Map
注:只能看和读,可以改变封装之前的值改变它的值
spec
spec:
⎩⎨⎧输入输出,数据类型功能和正确性性能
行为等价:基于spec的,再某一soec条件下,效果一样
spec:
{前置条件:percondition(@param)后置条件:postcondition(@return,@throws)
fail fast
spec强度,强于S2>S1同时满足以下条件(其中一个相同):
- S2的前置条件更弱
- 在S1的前置条件下,S2的后置条件更强
此时S2可以替代S1(用户喜欢强的,程序员喜欢弱的,强的图更小)
ADT
⎩⎪⎪⎨⎪⎪⎧构造器(Creators):从无到有生产器(Producers):根据旧的返回新的观察器(Observers):返回参数变值器(Mutators):只有mutable才有,唯一可以是void的,更改数据
表示独立性:内部的参数独立
⎩⎨⎧spec:注释,类名Representation:具体的存储数据,成员变量Implementation:方法实现体
不变量(Invariants):任何时刻变量都要符合的要求
表示泄露→用private,final
接口(interface):只定义方法与spec
RI与AF

AF(Abstraction Function):映射:R(表示空间)→A(抽象空间)
- 满射
- 不一定单射
- R中的非法值没有映射
RI:映射:R→boolean(R中的表示是否合法)
Rep invartiant:条件
注释:RI+AF+Safety from rep exposure
OOP
接口与类
一个接口可以有多个实现类,一个类可以实现多个接口
静态工厂方法:
default方法:在接口中写实现体
接口中不能声明实现体:ArrayList × —— List √
⎩⎨⎧扩展:接口extends接口实现:类implements接口继承:子类extends父类
父类中没有用final修饰的可以重写:@overide;复用:super()
抽象类(abstract class):至少一种方法是抽象的,没有被实现的
多态
⎩⎨⎧重载(overload):同一个函数名,参数一定不同,返回值可以不同泛型:publicclass<E>;也可以用<?>:List<?extends接口>子类型多态:继承树,处理顶层即可
Object:equals,hashCode,toString
等价性
等价性:是基于AF的,要求的方法返回值相同
{引用等价性(==):对基本类型(对对象类型比较的是ID)对象等价性(equals):对对象类型
{equals():比较等价性,不能对数据修改,与null比较返回null,与hashCode()成对出现hashCode():查询的时候用,相同的hashCode一样即可,不一样的也可以相同
equals()=true → hashCode()相同,反之则不行
{观察等价性:observer的值相同行为等价性:observer+muator都一样
{观察等价性:observer的值相同行为等价性:observer+muator都一样
{immutable:一定要重写equals+hashCodemutable:不是必须的
复用度的度量、形态与外部表现
{ProgrammingForReuse:面向复用的编程ProgrammingWithReuse:基于复用的编程
复用:
⎩⎪⎪⎨⎪⎪⎧源代码级别:方法、语句模块级别:类、接口库级别:API(用来调用)构架级别:框架(自己填写内容)

{白盒复用:直到复用的东西的实现黑盒复用:不知道复用的东西的内部实现(尽量用黑盒)
{白盒框架:通过继承扩展黑盒框架:通过委派扩展
模块复用:
{继承(inheritance)委派(delegation)
面向复用的软件构造技术
LSP原则
LSP原则:子类型多态:用父类是可以用子类无条件的替换
为了实现LSP,我们要做到:
- 子类型的方法只增不减
- 子类型实现了抽象的父类中没有实现的方法
- 重写的方法,参数与返回值与父类的相同,不能抛出更多的异常
- 子类的spec:RI更强,前置条件pre相同或更弱,后置条件post相同或更强

{子类型参数:逆变(JAVA会当作重载)子类型返回值/异常:协变
{逆变:具体→抽象协变:抽象→具体
注:
- 泛型不能改变:类型擦除→用通配符解决<? extends 父类/接口>/<? super 子类>
- 数组可以:但敲定后就不可变了
委派(Comparator)
用另一个对象
继承与委派:
{继承(inheritance):要大部分的功能,类(class)层面的委派(delegation):要小部分的功能,对象(object)层面的

CRP原则:一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中的所有类。
⎩⎪⎪⎨⎪⎪⎧Dependency:临时性的委派,在用的时候以参数方式传入,依赖关系Association:永久性的委派,rep里保存,关联关系(在创建的时候传入)Composition:更强的Association,在创建时自动生成Aggregation:更弱的Association,在创建时传入,可以通过函数更改
框架(Framework)的复用
框架的复用:浏览器→写插件
{白盒框架:通过继承来扩展黑盒框架:通过委派来扩展

面向复用的设计模式
- Adapter(适配器模式):当新的客户端的输入或输出要求与老的方法不一致时,新建一个适配器,改变参数的样式,再委派给老的方法
- Decorator(装饰器模式):将某些功能通过委派的方式委派给同一个接口下的其他实现类
注:最后一行代码一共有4个Stack
- Facade(外观模式):当执行有固定逻辑顺序的几个方法是,通过外观模式一次性按固定的逻辑顺序调用这些方法(也可以一次性调用不同的ADT的方法)
- Strategy(策略模式):通过委派传入不同的ADT来以不同的实现方法完成目的
- Template method(模板模式):继承与重写→白盒框架
父类 a = new 子类()
- Iterator(迭代器模式):JAVA已写好

可维护性的度量与构造原则
维护:纠错性、适应性、完善性、预防性
目标:easy to change(Maintainability可维护性、Extensibility可扩展性、Flexibility灵活性、Adaptability可适应性、Manageability可管理性、Supportability支持性)
OO设计原则:SOLID、GRASP
OO设计模式:
⎩⎨⎧FactoryMethodPattern,BuilderPatternBridgePattern,ProxyPatternMementoPattern,StatePattern
高内聚低耦合
信息隐藏,分离关注点
可维护性指数

SOILD原则
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧SRP原则(单一责任原则):一个方法只实现一个功能,不是多于一个1个的原因改代码OCP原则(开放−封闭原则):模块可扩展,在比改变内部的情况下扩展LSP原则(Liskov替换原则):子类型可以无条件的替换父类型DIP原则(依赖转置原则):一个类依赖于抽象类而不是具体的类ISP原则(接口聚合原则):将大的接口拆分成小的接口(类从实现一个变成实现多个)
面型维护的设计模式
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧工厂方法:客户端尽量用接口,建一个专门用来构造的类→静态工厂方法抽象工厂方法:要一组有固定搭配的对象→多个工厂方法代理模式(Proxy):隔离ADT与client观察者模式(Observer):一个对多个,双方互相委派,永久委派访问者模式(Visitor):双方委派,临时委派
Observer与Visitor
1.Observer:
2.Visitor:
Visitor与Strategy的区别:

设计模式对比:
1.一颗继承树:
1.1适配器(Adaptor):
1.2代理模式(Proxy):
1.3模板模式(Template):
2.两颗继承树的设计模式:
2.1策略模式(Strategy):
2.2遍历器模式(Iterator):
2.3工厂方法:
2.4抽象工厂方法:
2.5观察者模式(Observer):
2.6访问者模式(Visitor):

面向可维护的构造技术
⎩⎨⎧基于自动机编程基于状态的编程:一个状态一个子类,FSM备忘录模式:Ctrl+Z
语法:正则表达式
语法成分语法继承树):终止(叶)+非终止(非叶)
⎩⎨⎧终止:没有“=”再解释非终止:还有“=”再解释递归:“=”左右两端都有
html:_ 斜体 _ ; 斜体 :
# 健壮性与正确性
健壮性:
⎩⎨⎧处理未期望的行为/错误终止执行也要返回错误信息信息要有助于debug
我们要注意:封闭细节+极端情况
{健壮性:容忍错误,使用户容易正确性:不满足前置条件的就可以做任何处理,使开发者容易
健壮性+正确性=可靠性
我们的接口要做到:
{对外:注重健壮性对内:注重正确性
术语:
⎩⎪⎪⎨⎪⎪⎧error:程序员犯的错误defect:缺陷,bug的根源fault:defect≈fault,bugfauilure:失效,运行时的外在表现
error导致→defect/fault/bug导致→failure
test:对合法和非法的都要测试
Code→Test→Debug
健壮性:错误与异常处理
Throwable:
{Error:我们无能为力,由于输入/设备/物理因素导致的Expection:可处理,用try−catch向上传

Error

Expection:
{RuntimeExpection:不能有的→代码不当导致的,验证后一定可以避免的其他:是我们要进行处理的→健壮性编程面向的对象,验证也不一定能避免
{RuntimeExpection:不能有的→代码不当导致的,验证后一定可以避免的其他:是我们要进行处理的→健壮性编程面向的对象,验证也不一定能避免
check与uncheck
{uncheck:error+RuntimeExpection不能恢复check:其他的异常,由编译器(静态)检查得出,能恢复

关键字
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧trycatchfinallythrow:扔出checked异常,不建议扔出unchecked(语法上允许)throws:自己的+下面传上来的

TWR:try(Resource res = ……){……}
throwable:栈结构
正确性:断言与防御式编程
目标:fail fast
断言
assert (期望的结果): “提示信息”;
AssertError→直接结束
判断的内容:RI(表示不变量)、内部不变量、控制流不变量、方法的pre、方法的post
不用assert外部的不受控制的
开发阶段使用,运行时注释掉→影响性能和健壮性
assert的开关:
{开:−ea关:−da
{assert:针对正确性,不可以发生的情况exception:针对健壮性,可以发生的不正常情况
防御式编程
对于非法输入:garbage in, garbage out
代码调试(debugging)
Debug的方式
{边写边debug写完再debug
诊断
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧Instrumentation:System.out.print,loggingDivideandConquer:一块一块运行Slicing:缩小查找的范围,找和错误相关的代码,倒退Focusondifference:找每次提交的差异SymbolicDebugging:用于学术上,输入符号值→符号表达式DebuggerLearnFromOther
工具
logging
并发
2种并行模式:
{共享内存消息传递
{进程(Process):私有空间,彼此隔离线程(Thread):程序内部的控制机制
进程
- 拥有计算机的所有资源
- 多进程间不共享内存→用消息传递
- 进程=程序=应用
- 一个应用可以有多个进程
- OS支持进程间通信:不仅限于本机,也可以是不同机器间
- JAVA虚拟机(JVM)通常为单一进程(也可以是多个:ProcessBuilder)
线程

- 进程内的小程序
- 进程=虚拟机,线程=虚拟CPU
- 一个线程可分出多个
- 共享进程资源:内存
- 难以获得现成的私有内存,但线程有自己的堆栈
- 线程的协调需要代价
- 每个应用至少一个线程(主线程,自动创建的)
- 主线程可以创建其他的线程:接口,Runnable

学过的-able:
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧Comparable/ComparatorIterable/IteratorObservable/ObesrverThrowableRunnable

Thread应该start而非run,不能start→Runnable
交错竞争
- 一个核的每一时刻只能执行一个线程
- 时间分片→多进程/多线程共享处理:OS自动调度
- 一般,多核的线程/进程数也多于核数
- 时间交错
- 外围的发布访问慢
- Thread.sleep():自己暂停
- Thread.interrupt():其他线程请求(不一定答应,sleep时才会答应)
- Thread.isinterrupt():检测是否暂停
- Thread.yield():自己放弃占用
- Thread.join()
线程安全
线程安全:ADT/方法在多线程中执行正确
4种线程安全的方法
⎩⎪⎪⎨⎪⎪⎧限制数据共享(Confinement):不共享数据共享不可变数据(Immutability):共享只读的数据共享线程安全的数据(ThreadsafeDataType)同步机制(synchronization)
说明
- List,Set,Map都是线程不安全的→解决方法:
Collection.unmodifiable;
Collection.synchronizedList/Set/Map(new ArraryList/HashSet/HashMap())
- 对于线程安全的数据的2种不安全
{迭代器遍历:iterator();for(Objectx:xList)多个操作放在一起(线程安全数据只针对一行代码)
- 编写注释:Thread safety agrument:
- 同步机制:
lock:List c = Collection.synchronizedList();
synchronized(Object lock){操作}→只锁{}内的
相同的锁对应相同的钥匙
应该互斥
可以用自己来加锁:synchronized(this)→Monitor模式
对于函数:public synchronized void ……(Creater不用)(尽量少用,影响性能)
{共享的mutable)→用锁多个mutable→要用同一个锁
死锁
2个地方都有的2把锁,顺序不同,一人拿了一个钥匙
解决方法: