1、软件构造的多维度视图:
2、内部/外部的质量指标:
内部质量属性:
- 可读性(Readability)
- 可理解性(Understandability)
- 清晰(Clearness)
- 大小(Size)
- 源代码相关因素,如代码行数 (LOC)、圈复杂度等
外部质量属性:
- 正确性:最重要的质量指标
- 健壮性(即出现异常的时候不要崩溃)
- 可拓展性
- 可复用性
- 兼容性
- 性能
- 可移植性
- 易用性
- 功能性
- 及时性
外部质量因素 影响 用户
内部质量因素 影响 软件本身和它的开发者
外部质量取决于内部质量
3、白盒/黑盒测试
白盒测试:对程序内部代码 结构的测试
白盒测试的目的是验证程序的内部逻辑是否正确,以及代码是否按照规范执行
黑盒测试:对程序外部表现出来的行 为的测试
黑盒测试的目的是验证软件的功能是否符合用户需求和预期,并检查软件是否能够正确地处理各种输入情况。
4、SCM/VCS
Git版本之间的演化关系图,一条边A->B表示“在版本B的基础上作出变化,形成了版本A”
软件配置项:软件中发生变化的基本单元(例如:文件)
baseline基线:软件持续变化过程中的“稳定时刻”(例如:对外发布的版本)
CMDB :配置管理数据库 存储软件的各配置项随时间发生变化的信息+基线
5、Git的结构、工作原理、基本指令
(3-P49)传统 VCS 存储 版本之间的变 化(行)
Git 存储发生变 化的文件(而 非代码行), 不变化的文件 不重复存储
即Git 管理的基本单元是文件
(3-P50)文件未发生变化,则后 续多个版本始终指向同 一个文件
文件发生变化了,存储 两份不同的文件,两个 版本指向不同的文件
`git commit -m "msg"` 命令用于将修改后的文件提交至当前所在的本地仓库。它会将本地工作目录中的修改提交到当前所在的本地 Git 仓库,而不是远程仓库。
要将本地仓库的修改推送到远程仓库,需要使用 `git push` 命令。`git push` 命令将本地分支中的提交推送到远程仓库中对应的分支。
因为 git commit -m "msg"
只是将已暂存(staged)的更改提交到本地仓库,而不是将工作目录中所有修改过的文件提交到本地仓库。要提交工作目录中的修改,首先需要使用 git add
命令将修改添加到暂存区。
Git是一个分布式版本控制系统,广泛用于软件开发中的源代码管理。它能够高效地管理项目的变更历史,并支持多个开发者同时进行协作。了解Git的结构、工作原理和基本指令是使用Git的基础。
### Git的结构
1. **工作区(Working Directory)**:
- 本地项目的当前状态,包括所有文件和文件夹。
2. **暂存区(Staging Area)**:
- 一个临时区域,用于保存即将提交到本地仓库的文件快照。也称为索引(Index)。
3. **本地仓库(Local Repository)**:
- 存储在本地磁盘上的代码仓库,包括所有的提交历史记录。
4. **远程仓库(Remote Repository)**:
- 存储在远程服务器上的代码仓库,多个开发者可以通过它进行协作。
### Git的工作原理
1. **修改文件**:
- 在工作区中进行代码修改。
2. **暂存更改**:
- 使用 `git add` 命令将修改的文件添加到暂存区。
3. **提交更改**:
- 使用 `git commit` 命令将暂存区中的更改提交到本地仓库。
4. **推送更改**:
- 使用 `git push` 命令将本地仓库中的提交推送到远程仓库。
5. **拉取更改**:
- 使用 `git pull` 命令从远程仓库获取最新的提交并合并到本地工作区。
### Git的基本指令
1. **配置用户信息**:
```bash
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
```
2. **初始化仓库**:
```bash
git init
```
3. **克隆远程仓库**:
```bash
git clone <repository_url>
```
4. **查看当前状态**:
```bash
git status
```
5. **添加文件到暂存区**:
```bash
git add <file_name>
git add .
```
6. **提交更改到本地仓库**:
```bash
git commit -m "Commit message"
```
7. **查看提交历史**:
```bash
git log
```
8. **推送更改到远程仓库**:
```bash
git push origin <branch_name>
```
9. **拉取远程仓库的更改并合并**:
```bash
git pull origin <branch_name>
```
10. **创建新分支**:
```bash
git branch <new_branch_name>
```
11. **切换分支**:
```bash
git checkout <branch_name>
```
12. **合并分支**:
```bash
git merge <branch_name>
```
13. **解决合并冲突**:
- 手动编辑冲突文件,解决冲突后添加到暂存区,然后提交。
```bash
git add <resolved_file>
git commit -m "Resolve merge conflict"
```
14. **删除分支**:
```bash
git branch -d <branch_name>
```
15. **查看分支**:
```bash
git branch
```
### 结论
Git的核心在于它能够高效地管理项目的变更历史,并支持多个开发者同时进行协作。掌握Git的结构、工作原理和基本指令,能够让你在开发过程中更加高效和协同工作。
6、基本数据类型和对象数据类型
基本数据类型:int,char,float等
对象数据类型:自己定义一个class,其中包括能执行的函数,实例化后即可调用
7、静态类型检查/动态类型检查
静态类型检查在编译时进行:
- 类型不匹配:当变量被赋予不兼容的类型时
- 函数参数类型错误:传入函数的参数类型与函数要求的不匹配时
- 类型转换错误:非法的强制类型转换在编译时可以被检测出来
- 访问不存在的成员:访问对象不存在的字段或方法
8、specification、前置/后置条件
1、spec只讲能做什么不讲怎么实现
Spec包含以下方面:
- 方法描述
- 参数(@param)
- 返回值(@return)
2、对于两个函数,当它们都符合同一个归约到时候,等价。
AF映射到同样的结果,则等价
3、单纯的看实现代码, 并不足以判定不同的 implmentation是否 是“行为等价的”, 需要根据代码的spec (开发者与client之 间形成的contract) 判定行为等价性
站在外部观察者角度:对两个 对象调用任何相同的操作,都 会得到相同的结果,则认为这 两个对象是等价的。
4、前置条件:对客户端的约束,在使用方法时必须满足的条件
后置条件:对开发者的约束,方法结束时必须满足的条件
- 前置条件满足,则后置条件必须满足
前置条件不满足,则方法可以做任何事情
- spec强弱比较:
- 更强的不变量
- 更弱的前置条件
- 更强的后置条件
9、snapshot图
java snapshot图史上最详细手把手教学-CSDN博客
数据类型分为:基本数据类型和对象数据类型。
区分两者的技巧就是首字母,首字母大写的就是对象数据类型
如int,long,char等等基本数据类型,全都是不可变的数据类型;
如Double,Integer,String等等对象数据类型,是不可变的数据类型;
如Date,StringBuilder,List等等对象数据类型,是可变的数据类型
在Java中,可以判断一个变量是否是基本数据类型(即原始类型),如果不是,则它就是引用类型。
10、ADT
(1)ADT 的特性:表示泄漏、抽象函数 AF 、 基于数学的形式对 ADT 、表示不变量 RI
(2)ADT是由操作定义的,与其内部 如何实现无关
(3)四种ADT类型:
Creators 构造器: 用于创建新的实例或对象。
Producers 生产器: 用于从现有实例生成新的实例,通常不会修改原实例,而是返回一个新的实例。(e.g. 合并两个集合返回一个新的集合,将列表转化成一个不可变列表)(本质上为拷贝数据而非改值)
Observers 观察器:用于检查或查询对象的状态,返回有关对象的信息,而不修改对象(e.g.获取栈的大小,检查队列是否为空,获取集合中元素的数量)
Mutators 变值器,改变对象属性的方法(可变性的关键,即只有mutable的对象才能够有Mutators):用于修改对象的状态,直接改变对象的数据或结构
变值器通常返回void,这意味着它改变了对象的某些内部状态
变值器也可返回非空类型
(4)ADT的内部表示 ( 私有属性 ) 对外部都应严格不可见
(5)ADT的规约里只能使用client可见的内容来撰写,包括参数、返回值、异常等。
(6)故在代码中以注释的形式写出AF和RI而不能在 Javadoc文档中,防止被外部看到而破坏表示独立性 / 信息隐藏
(7)ADT类型的注意事项:
构造器和生产器在创建对象时要确保不变量为true
变值器和观察器在执 行时必须保持不变性
在每个return之前,用checkRep()检查不变量是否得到保持
11、Rep,RI,AF
(1)放置位置:
Rep: 表示在类的私有字段中,并在字段声明处注明。
子类型的规约不能弱化超类型的规约
RI: 作为私有方法或直接在注释中说明。
AF: 作为注释描述表示到抽象值的映射,通常放在类的开头或字段声明处。
Spec: 在每个方法的注释中详细描述方法的行为规范。
(2)AF: 满射、非单射、 未必双射,即A中每个值在R中都有映射
(3)即使是同样的R、同样的RI,也 可能有不同的AF,即“解释不同”。
示例:区间集合
假设我们有一个类,它使用两个整数表示一个区间 [a, b]。
AF1:闭区间
一个可能的AF是将 [a, b]
解释为闭区间,表示区间中的所有整数,包括边界 a
和 b
。
在这个解释下,[a, b]
包含所有从 a
到 b
的整数。例如,如果 a=1
和 b=3
,那么这个区间表示的集合是 {1, 2, 3}
。
AF2:左闭右开区间
另一个可能的AF是将 [a, b]
解释为左闭右开区间,表示区间中的所有整数,包括 a
但不包括 b
。
在这个解释下,[a, b]
包含所有从 a
到 b-1
的整数。例如,如果 a=1
和 b=3
,那么这个区间表示的集合是 {1, 2}
。
(4)哪些可以暴露给客户端?
(5)总结:
12、OOP
(1)接口、抽象类、具体类
①接口(interface):
声明:使用“interface”关键字
方法:默认为“public abstract”可以包含默认方法“default”和静态方法(“static”)
具体见下图:
一个类可以实现多个接口,使用 implements
关键字
②抽象类(abstract)->不能被实例化的类
声明:使用“abstract”关键字
方法:可以包含抽象方法和具体方法
Tigs:
抽象方法:只有方法签名,没有方法实现,子类必须重写(实现)这些抽象方法
使用 extends
关键字,一个类只能继承一个抽象类。
③具体类
声明:不需要任何的关键字
方法:可以包含具体方法
可以继承抽象类并实现接口。
(2)Inheritance:严格继承->子类只能添加新方法,无法重写超类中的方法
(3)Override覆盖/重写
13、多态
(1)分为运行时多态和编译时多态
运行时多态:运行时多态是指通过父类引用调用子类重写的方法。它在程序运行时决定调用哪个方法。
通过Overriding 实现,即为动态多态性
编译时多态:
通过Overload实现,即为静态多态性
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型
(2)Override和Overload的关系
(3)泛型类:
当你需要一个能够装不同类型东西的盒子,但你又不确定里面会装什么,那么就可以用泛型类来创建这样的盒子。泛型类就像一个万能盒子,可以装入任何类型的东西,比如整数、字符串或者其他自定义类型。这个盒子的大小和形状都是一样的,但装入的东西可以是各种各样的,就像装入不同的宝藏一样。
举个例子,假设你有一个名为`Box`的盒子,你可以在里面放入一个整数、一个字符串或者其他任何你想放入的东西。这个`Box`就是一个泛型类。然后你可以从盒子里取出你放入的东西,并且知道它是什么类型的。这就是泛型类的基本思想。
(4)Subtyping Polymorphism:
举个例子来说明 Subtyping Polymorphism:
假设我们有一个动物园,里面有各种动物,比如狗、猫和鸟。我们可以定义一个 Animal 类作为父类,然后定义具体的动物类作为子类。每个动物类都可以重写父类的方法,并且具有自己的特性。
现在,我们可以编写一个通用的方法来处理动物,并且可以处理不同种类的动物,这就是 Subtyping Polymorphism 的体现。
在这个例子中,我们创建了一个动物数组,包含狗、猫和鸟的实例。通过循环遍历数组,并调用每个动物的 makeSound() 方法,我们可以看到不同类型的动物会发出不同的声音,这就是 Subtyping Polymorphism 的效果。
14、等价性equals()和==
(1)equals()和==的区别:
“==” 运算符用于比较两个对象的引用是否指向相同的内存地址,也就是比较它们是否是同一个对象的实例。而 equals()
方法是用来比较对象的内容是否相等,通常是在类中进行重写以实现自定义的相等性比较逻辑。
总的来说,==
比较的是对象的引用,而 equals()
比较的是对象的内容。
默认的equals()
方法是用来比较对象的引用,也就是说它的行为与==
运算符是一样的。
故需要对equals()进行重写
即 “==”à引用等价性(判断地址),equals()à对象等价性(判断内容)
同时,在自定义ADT时,需要重写Object的equals()
对基本数据类型,使用==判定相等;对对象类型,使用equals()判断相等
(2)重写equals()函数
解读:
第一个if首先检查当前对象 (this
) 是否与传入的对象 (obj
) 是同一个对象(引用相同),若相同则返回true。
第二个if,首先检查传入的对象 obj
是否为 null
。然后
检查当前对象的类是否与传入对象的类相同,这里使用 getClass()
方法而不是 instanceof
关键字,因为 instanceof
检查的是继承关系,而 getClass()
检查的是对象是否是确切的同一个类。这意味着即使子类对象传入,比较也会返回 false
最后对字符进行比较,
将 obj
强制类型转换为 MyClass
类型,因为在前面已经确保了 obj
和当前对象是同一个类,比较两个对象的 value
字段。如果它们的 value
相等,返回 true
,否则返回 false
。
或者
(3)重写hashCode()函数
①为什么要重写equals()函数后必须重写hashCode()函数?
如果只重写equals()
而不重写hashCode()
,会导致在基于哈希的数据结构中(如HashSet
、HashMap
)出现意想不到的行为。即使两个对象是相等的(根据equals()
),它们可能会被存储在不同的桶中,因为默认的hashCode()
方法是基于对象的内存地址生成的。
- 可变类型/不可变类型
观察等价性:在不改变状态的情况下, 两个 mutable 对象是否看起来一致 (某一时刻两者是否相同)
行为等价性:调用对象的任何方法都展示出一致的结果
15、复用
(1)白盒复用:源代码可见,可修改和扩展
黑盒复用:源代码不可见,不能修改
(2)继承 + 委派 = 灵活的复用
16、LSP
(1)主要原则:
1.行为保持一致性
2.不增加子类型约束
17、CRP
组合复用原则的核心思想是通过组合对象来实现新功能,而不是通过继承。
Delegation发生在object层面,inheritance发生在class层面
白盒框架,通过代码层面的继承进行框架扩展
黑盒框架,通过实现特定接口 delegation 进行框架拓展
18、SOLID
单一责任原则:一个类应该只有一个引起它变化的原因,即一个类应该只有一个职责
开放-封闭原则:对扩展性的开放,对修改的封闭
Liskov替换原则
依赖转置原则
接口聚合原则
19、设计模式
(a)创建型模式(Creational patterns)
(1)Factory method:
(b) 结构型模式(Structural patterns)
(1)Adapter: 允许将一个类的接口转换成客户期望的另一个接口。适配器使得原本由于接口不兼容而不能在一起工作的类可以一起工作。(两棵“继承树”,两个层次的“delegation”)
委派
(2)Decorator: 对每一个特性构造子类,通过委派机制增加到对象上
装饰器和继承的对比:
装饰器在运行时组合功能 继承在编译时组合功能
装饰器由多个协作对象组成 继承生成单个、类型明确的对象
可以混合和匹配键入的多个装饰 多重继承在概念上是困难的
(3)行为类模式(Behavioral patterns)
(1)Strategy: 为不同的实现算法构造抽象接口,利用delegation,运行时动态传入client倾向的算法类实例(两棵“继承树”,两个层次的“delegation”)
(2)Template method: 使用继承和重写实现模板模式(只使用继承,不使用delegation)
Iterator/iterable: :让自己的集合类实现Iterable接口,并实现自己的 独特Iterator迭代器(hasNext, next, remove),允许客户端利用这 个迭代器进行显式或隐式的迭代遍历
Visitor: 为 ADT 预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变本身的情况下通过delegation 接入 ADT
20、异常处理
(1)Error和Exception
Error
表示系统级的错误,通常是程序无法恢复的严重错误,例如虚拟机错误、内存溢出等。一般情况下,程序不应该捕获或处理Error
。
e.g. OutOfMemoryError,StackOverflowError
Exception
是程序正常操作中可以预料到的异常,可以被程序捕获并处理
同时Exception分为Checked Exception和Unchecked Exception
如何区分呢?
如下图,所有继承自Exception
但不继承自RuntimeException
的异常均为Checked Exception
所有继承自RuntimeException的异常均为Unchecked Exception
同时Checked Exception->需要在方法签名中声明或者在方法内部捕获处理
Unchecked Exception 则不强制处理
(2)throw和throws的区别
①throw->用于显式地抛出异常->在方法体内使用
②throws->用于声明一个方法可能的异常à在方法签名中使用
如下图,上行为throws,下行为throw
(3)try、catch和finally(这种类型为捕获异常的处理方式)
try: try
块内的代码段是程序可能抛出异常的地方,用于包围可能抛出异常的代码。
catch: 用于捕获和处理在try
块中抛出的异常, 紧跟在try
块之后,可以有一个或多个catch
块,来捕获不同类型的异常。
final: 用于执行无论是否抛出异常都需要执行的代码。通常用于关闭资源,如文件或数据库连接。
对于throws: 如果不打算在方法内处理异常,可以在方法签名中使用throws
关键字声明该方法可能抛出的checked异常,让调用该方法的代码来处理这些异常(这种为声明异常的处理方式)
总结:checked异常->在编译时被检查,必须处理(捕获或声明)
try-catch异常->用于捕获和处理异常
throws声明->用于将异常传播给调用者
- 子类型和父类型抛出异常的关系
- 如果子类型中 override 了父类型中的函数,那么子类型中方法抛出的异常不能比父类型抛出的异常类型 更宽泛->
- 如何理解更宽泛?
- 如果子类型中 override 了父类型中的函数,那么子类型中方法抛出的异常不能比父类型抛出的异常类型 更宽泛->
一个异常类是另一个异常类的超类时,我们说这个异常类更宽泛。比如,Exception
是IOException
的超类,所以Exception
比IOException
更宽泛。
例如:
在这个例子中,Child
类中的method
方法重写了Parent
类中的method
方法。父类方法声明抛出IOException
,而子类方法声明抛出FileNotFoundException
。这在Java中是允许的,因为FileNotFoundException
是IOException
的子类,属于更具体(更窄)的异常类型。
总之一句话,子类型抛子类型错误,至多原封不动或不抛出异常
不抛出异常可以这样理解:原本有抛出异常的时候,程序中可以选择正常return或者抛出异常的return,有多于一个的选项且连续性得到保证;若无抛出异常,则只能有正常return的方法,否则就报错,后置条件更加苛刻,由LSP原则故不抛出异常可理解为抛出异常的子类。
-
- 子类型方法可以抛出更具体的异常,也可以不抛出任何异常
- 如果父类型的方法未抛出 异常,那么子类型的方法也不能抛出异常。
21、防御式编程
(1)防御式编程的方式
a) 检查前置条件是防御式编程的一种典型形式
使用断言进行检测(assertion)->便于快速发现错误所在
使用断言的时机:
①验证不变量(assert array.length > 0)
②验证前置条件和后置条件
③对关键节点进行验证,比如让某个值必须为true才继续运行
22、测试类撰写
(1)等价类划分/边界值分析
将输入条件划分为有效等价类和无效等价类。
边界值分析是对等价类划分的补充,对边界值进行验证。
最后小建议:如果遇到不懂的问题,记得多用用大语言模型哈!
纵有疾风起,人生不言弃!祝大家期末考加油!💪