目录
第3讲 软件构造过程和配置管理
*3.1 软件开发模型
不在考试范围内。
3.2 软件配置管理与版本控制系统
3.2.1 SCM和VCS
软件配置管理(SCM)的目标是追踪和控制软件的变化,SCM中发生变化的基本单元称为软件配置项(SCI),在Git中,SCI指的是文件。将各个文件的特定版本串联起来,即软件持续变化过程中的“稳定时刻”,称为基线(例如:对外发布的版本),如下图所示:
配置管理数据库(CMDB)存储软件的各配置项随时间发生变化的信息和基线。版本是软件的任一特定时刻的形态的唯一的编号,作为“身份标识”。
版本控制系统(VCS)可以用于控制软件版本,具有回滚版本、比较差异、备份软件版本历史、获取备份、合并等多种功能。VCS的分类如下:
- 本地版本控制系统:仓库存储于开发者本地机器,无法共享和协作;
- 集中式版本控制系统:仓库存储于独立的服务器,支持多开发者之间的协作;
- 分布式版本控制系统:仓库存储于独立的服务器和每个开发者的本地机器上。
VCS中的术语如下:
- 仓库:在SCM中的CMDB(配置管理数据库);
- 工作拷贝:在开发者机器上的项目拷贝;
- 文件:一个独立的SCI;
- 版本:在某个特定时间点的所有文件的共同状态;
- 变化(Code Churn):两个版本之间的差异;
- HEAD :程序员正在其上工作的版本。
3.2.2 SCM工具的代表:Git
每个Git仓库由以下三部分构成:
- .git 文件夹:本地的CMDB;
- 工作目录:本地文件系统;
- 暂存区:隔离工作目录和Git仓库。
每个文件属于三个状态之一:已修改、已暂存、已提交。Git仓库的常用命令如下:
对象图是版本之间的演化关系图,一条边A->B 表征了“在版本B的基础上作出变化,形成了版本A。对象图存储了项目中文件的所有版本,以及描述这些更改的所有日志条目。在Git中,对象图保存在.git文件夹内,克隆一个仓库也就是克隆了完整的对象图。下面是对象图的两种表示:
在对象图中,较新的版本指向较旧的的版本。如果存在多个节点指向一个节点,则发生了分支,如果存在一个节点指向多个节点,则发生了合并。
Git中的每个Commit以树形结构存储,存储了文件目录结构、指向具体文件的指针和提交信息。通过这种方式,可以避免存储相同的文件。
在传统的VCS中,存储版本之间的行变化,而Git存储变化的文件。
分支可以沿两个方向并行进行修改,可以进行以下操作:
- git checkout [Name]: 切换到指定分支;
- git checkout -b [Name]: 以当前分支为起点,创建一个新的分支;
- git merge [Name]: 将指定分支合并到当前分支。
*3.3 软件构造的总体过程
不在考试范围内。
*3.4 软件的构建过程
不在考试范围内。
第4讲 数据类型和类型检验
4.1 数据类型
数据类型是一组值,以及可以对这些值执行的操作。用特定数据类型定义,可存储满足类型约束的值称为变量。
在Java中,数据类型可分为基本数据类型和对象数据类型。两者比较如下:
基本类型 | 对象类型 | |
---|---|---|
包含 | int, double, float, char, boolean, byte, short, long | 其他所有类型(包含数组) |
ID | 无 | 有 |
值 | 有 | 有 |
可变性 | 不可变 | 可变或不可变 |
内存分配 | 栈 | 堆 |
代价 | 低 | 高 |
所有的对象类型都继承或者间接继承自Object类。
基本类型有其对应的包装类型,如int -> Integer等,一般在定义集合类型时使用,其他情况下避免使用。这两种类型一般可以进行自动转换。
操作符是执行简单计算的符号,操作是接受输入并产生输出的函数(有时还会更改值本身)。
4.2 静态类型检查与动态类型检查
语言可分为静态类型语言和动态类型语言,每个语言可以提供静态类型检查、动态类型检查或者无检查,从发现bug的角度讲,静态类型检查>>动态类型检查>>无检查。
静态类型检查可在编译阶段发现错误,避免了将错误带入到运行阶段,可提高程序正确性、健壮性。静态类型检查可以发现以下错误:
- 语法错误:大多数语言,包括Python都可以进行这类检查;
- 类名、函数名错误:例如Math.sine(2);
- 参数数目、类型错误:检查函数的参数信息;
- 返回值类型错误:检查返回的值是否和声明的相同。
动态类型检查可以在运行时发现错误,例如:
- 非法的参数值:例如x/y,其中y==0,在运行时发现错误并抛出异常;
- 非法的返回值:特定的返回值不能在给定的类型中表示;
- 越界:例如负值数组下标和过大的数组下标;
- 空指针:例如在空的指针上调用函数。
静态检查是关于“类型”的检查,不考虑值;而动态检查是关于“值”的检查。
4.3 类型的可变性
改变一个变量是指改变一个指针,而改变变量的值则是改变指针指向的内容。为了安全性,需要尽量减少变化。
4.3.1 可变类型和不可变类型
不可变类型是指一旦创建,其值不能改变的类型。
对于引用类型,可以使用final关键字修饰,使这个指针不能变化。试图改变final的引用会导致边缘错误,这也是静态类型检查的一部分。final关键字的其他的用法如下:
- final修饰的方法不可以被重写;
- final修饰的类不可以被继承。
可变与不可变类型在多个引用指向同一个值会产生区别。
可变类型拥有方法可以修改自己的值或引用。不同于不可变类型,可变类型不需要产生大量的拷贝,效率较高。此外,可变类型还可以用于在多个模块之间共享数据。
不可变类型更加安全,在其他的质量指标上表现更好,因此需要折中。
注意:不要使用Date类型,因为它是一个可变的类型,使用可能导致难以调试的bug。
为了防止发生意外的问题,可以在返回可变类型时进行防御式拷贝,但可能导致内存浪费,不可变类型没有这种问题。因此,可变类型应尽量只在局部安全使用。
4.3.2 容器和数组
数组是固定长度的类型序列,List<T>是一个接口,用于表示变长类型序列。一般情况下,应尽量使用List<T>,避免使用数组。容器中只能放入引用类型。
Set<T>是代表一个无序集合的接口,Map<K, V>代表一个从K到V的映射。
迭代器是一个遍历元素集合并逐个返回元素的对象,是一个可变类型。在使用范围for语句时,会隐式使用迭代器。
在使用迭代器或者范围for时,不要改变目标类型的值,可能会对迭代器造成破坏。
4.3.3 不可变包装器
工具类Collections提供了一组方法,用于将给定的集合转换为一个不可修改的集合,如Collections.unmodifiableList、Collections.unmodifiableSet、Collections.unmodifiableMap等。
但是这样得到的结果的不可变性只能是运行时检查的,即试图修改时抛出异常,编译器无法进行静态检查。
List.of(T t1, ...)等提供一个不可修改的集合。要创建浅拷贝的集合,使用List.copyOf()等,但是这个方法需要Java 10+。
4.4 快照图
快照图用于用于描述程序运行时的内部状态,便于程序员之间的交流、刻画各类变量随时间变化、解释设计思路。
1. 基本类型的值:
2. 对象类型的值:
3. 不可变类型:使用双线椭圆
4. 不可变的引用:使用双线箭头
5. 修改可变对象的值: