目录
第1、3讲
1.软件构造的多维度视图
2.软件构造的阶段划分、各阶段的构造活动
3.内部/外部的质量指标
4.软件配置管理SCM与版本控制系统VCS
5.Git的结构、工作原理、基本指令
第4-8讲
1.基本数据类型、对象数据类型
2.静态/动态类型检查
3.Mutable/Immutable
4.值的改变、引用的改变、final
5.防御式拷贝
6.Snapshot diagram
7.Specification、前置/后置条件
8.行为等价性
9.规约的强度
10.ADT操作的四种类型
11.表示独立性、表示泄露
12.不变量、表示不变量RI
13.表示空间、抽象空间、AF
14.以注释的形式撰写AF、RI
15.接口、抽象类、具体类
16.继承、override
17.多态、overload
18.泛型
19.等价性equals()和==
20.equals()的自反、传递、对称
21.hashCode()
22.不可变对象的引用等价性、对象等价性
23.可变对象的观察等价性、行为等价性
第9讲
1.Programing for/with reuse
2.LSP
3.协变、反协变
4.数组的子类型化
5.泛型的子类型化
6.泛型中的通配符(?)
7.Delegation
8.Comparator和Comparable
9.CRP原则
10.接口的组合
11.白盒框架的原理与实现
12.黑盒框架的原理与实现
第10-11讲
1.可维护性的常见度量指标
2.聚合度与耦合度
3.SOLID
4.语法、正则表达式
5.设计模式adapter、decorator、strategy、template、iterator/iterable、factory method、visitor
第2、12讲
1.健壮性和正确性
2.Throwable
3.Error/Runtime异常、其他异常
4.Checked异常、Unchecked异常
5.Checked异常的处理机制:
-声明、抛出、捕获、处理、清理现场、释放资源等
6.自定义异常类
7.断言的作用、应用场合
8.防御式编程的基本思路
9.黑盒测试用例的设计
-等价类划分、边界值分析
10.以注释的形式撰写测试策略
11.JUnit测试用例写法
12.测试覆盖度
本文总结哈工大2022年春季学期软件构造课程内容,为作者期末复习时所写,详略以作者本人掌握程度为依据,相对课程本身来讲并不完善且有可能出错,如有错误以课程本身为准。
按阶段划分可以划分为 构造时/运行时视图
按动态性划分为 时刻/阶段视图
按构造对象的层次划分为 代码/构件视图
图表表示多维度视图下特定“坐标”的各自的处理方式
从无到有代码:编程,技术手段是ADT/OOP面向对象的编程
从代码到组件:设计,技术手段是ADT/OOP,面向可复用性和可维护性
从构建阶段到运行阶段:Debug,单元测试或者融合测试,面向健壮性和正确性
从时刻到时段:版本控制,动态链接
所谓开发,就是各视图的相应转换,排序,查找,各种计算,更多关注功能
内部质量指标:面向程序员,影响软件本身和开发者
代码行数;体系结构;可读性;提升相关性能
外部质量指标:用户能看见,针对终端用户的感受
正确性:保持正确性的技术手段有:测试和调试(不能提高程序能力)、防御式编程、形式化方法(通过形式化验证发现问题)
健壮性:例如播放器丢帧的处理机制,跳过之后继续播放要优于卡死程序
可扩展性:规约可不可以改
可复用性:产品已经开发可以多场合使用
兼容性:在不同的软件系统上都能够运行,跨操作系统/跨硬件
性能;可移植性:方便在不同技术环境之间移植,比如跨软硬件
功能性;及时性;可验证性;完整性:数据一致问题
可修复性;成本
git的结构分为三部分:
本地库,暂存区和工作区
它们之间的关系如下图所示
git的常用命令
产生冲突的原因:
合并分支时,两个分支在同一个文件的同一个位置有两套完全不同的修改。 Git 无法替我们决定使用哪一个。必须人为决定新代码内容。查看状态(检测到有文件有两处修改)
基本数据类型:例如int,long,boolean,double,char
只有值,没有ID,与其他值无法区分
不可变,在栈中分配内存,代价低
对象数据类型:例如String,BigInteger,Date
既有ID,也有值
java.util包提供了Date类来封装当前的日期和时间,提供两个构造函数来实例化Date对象
第一个构造函数使用当前日期和时间来初始化对象:
Date( )
第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数
Date(long millisec)
获取当前的日期和时间:
import java.util.Date;
public class DateDemo {
public static void main(String[] args) {
// 初始化 Date 对象
Date date = new Date();
// 使用 toString() 函数显示日期时间
System.out.println(date.toString());
}
}
System.out的println()方法只能在控制台输出字符串,而2Object toString()方法用于返回对象的字符串表示形式。
也就是说,下面两行代码效果相同:
System.out.println(Student);
System.out.println(Student.toString);
如果想要输出对象的描述,就需要重写Object toString()方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
I/O操作:
获得一个绑定到控制台的字符流,创建BufferedReader:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.read():从输入流读取一个字符并把该字符作为整数值返回,当流结束时返回-1。该方法抛出IOException
文件读写:
InputStream f = new FileInputStream("C:/java/hello");
OutputStream f = new FileOutputStream("C:/java/hello")
可变/不可变,在堆中分配内存,代价昂贵
注:容器类型要求操作的元素是对象类型,所以需要将基本数据类型包装转换为对象类型
根据定义变量的位置不同,将变量分为成员变量和局部变量
成员变量定义在类中方法外 局部变量定义在方法内部(存在于栈内存)
静态成员变量:JDK8后,在Java中类的成员变量被声明为static,意味着它为该类的所有实例所共享,可以使用“类名.静态成员名”访问此静态成员,它存储在元空间中,是类定义的一部分
非静态成员变量:随实例的属性存在而存在,存储在堆中
局部变量:不能加Java访问控制修饰符,保存在栈中,必须在声明时初始化
而成员变量可以不显式初始化,可以由系统设定默认值
Java是静态类型语言,所有变量的类型在编译时已知,因此编译器可以推导表达式类型
Collections.unmodifiableList:返回一个不可被修改的UnmodifiableList集合容器,该容器类重写了List容器中跟数据相关的方法,一旦调用,就会抛出UnsupportesOperationException异常
String.concat("xxx"),会新建一个引用,指向连接后的字符串
final修饰对象代表对象只能赋值一次
final int c=1;
static final b=1;
c和b的区别在于,b存放在静态空间,不会在程序运行时被释放,而c在程序用完它而不会再用到它时就会自动释放
当一个常熟或字符串需要在程序里反复使用的时候,可以定义为static final
为什么要用防御式拷贝
在ADT中,常常会有一些Observer方法,用户可以通过这样的方法查看ADT的一些相关属性。用户在获得相关属性后,可能会对这些属性做一些修改,如果我们直接把ADT中的rep返回给用户,那么用户的修改就有可能对ADT产生巨大影响,从而导致程序出现一些意想不到的错误。而使用防御性拷贝就是为了避免类似情况的出现。
其实,如果从更广义的角度看,不仅仅是出现返回值时有可能出现表示泄露,其实在构造函数里如果对用户输入的对象不进行有效的拷贝,用户之后的修改其实也可能对你设计的ADT产生影响,这时其实也需要进行防御性拷贝。
举例
以下面这个例子为例。
class Poem {
public String title;
public String author;
private List lines = new ArrayList<>();
private Date date;
// AF: 代表一首诗,包含四个属性:
// title为诗的题目,
// author为诗的作者,
// lines为诗的文本行,
// date为诗的发表日期
public Poem(String t, String a, List l, Date d) {
title = t;
author = a;
lines = l;
date = d;
}
public void addOneLine(String newLine) {
lines.add(newLine);
}
//..
public List getAllLines() {
return lines;
}
}
在这个ADT中实际上是存在着一些表示泄露的风险的。
注:这里强调是对象数据类型是由深拷贝和浅拷贝界定的,简单来说就是给个引用值还是复制一份副本传过去。基本数据类型在进行赋值操作时是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这时两个变量彼此互不影响,而对于对象或者引用数据来说在进行浅拷贝时(直接一个等号),只是将引用复制了一份,所以除非该变量本身是不可变类型,否则两者其中之一调用mutator都会导致同一个内存地址存储的值发生变化。
Java中用clone()方法实现深拷贝:
Person p = new Person(23, "zhang");
Person p1 = (Person) p.clone();
System.out.println(p);
System.out.println(p1)
clone本身是一个浅拷贝的方法,可以通过重写该方法来实现深拷贝:
static class Body implements Cloneable{
public Head head;
public Body() {}
public Body(Head head) {this.head = head;}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class Head /*implements Cloneable*/{
public Face face;
public Head() {}
public Head(Face face){this.face = face;}
}
public static void main(String[] args) throws CloneNotSupportedException {
Body body = new Body(new Head());
Body body1 = (Body) body.clone();
System.out.println("body == body1 : " + (body == body1) );
System.out.println("body.head == body1.head : " + (body.head == body1.head));
}
详见:(3条消息) 深拷贝和浅拷贝的区别_crystal_hhj的博客-CSDN博客_深拷贝和浅拷贝的区别
在构造函数里,直接使用了lines = l; 这样的语句,本意是想把输入的List
在构造函数里,还出现了date = d;语句。Date是对象数据类型,一旦客户端改变d中的值,date中的值也会跟着改变,这样是很危险的。
在getAllLines()方法中,直接把rep中的lines放回给了用户,这样用户可以随意修改返回的List,有可能会改变ADT中的rep,这也违反了表示独立性。
解决:防御式拷贝
关于防御式拷贝,主要的思路就是创建一个跟原来的对象一模一样的对象,并且这个新的对象不会受到原来对象的影响,也就是说它们是完全独立的。这里主要以List和Date这两个典型的mutable类型进行举例。
List
对于List,我们实际想要复制的是整个List里的所有对象,而不是指向这个List的指针。
我们可以遍历原始List,再将List里的所有元素拷贝到新的List中,代码如下。
class Test {
public static void main(String[] args) {
List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
List copyList = new ArrayList<>();
for(int i=0; i
最后的运行结果为
list : [b, c]
copylist : [a, b, c]
可以看出这里成功实现了元素的拷贝,对原来List的改变不会影响新的List。
但是这样的拷贝略显麻烦,下面的方法要简单一些。
class Test {
public static void main(String[] args) {
List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
List copyList = new ArrayList<>();
copyList.addAll(list);
list.remove(0);
System.out.println("list:"+list.toString());
System.out.println("copylist:"+copyList.toString());
}
}
通过List对象的addAll()方法,实现List中元素的拷贝(俗称“深拷贝”)。
Java中的一些其他的集合类(比如Set,Map等)也可以通过类似的方法实现防御式拷贝,进而避免外来的改变对ADT造成影响。
而在Observer方法中,实际上也需要进行防御式拷贝,这里可以使用上面的方法将rep的拷贝返回给用户,也可以使用Collections.unmodifiableLis()对List对象进行修饰,修饰过的对象是不可变的。
Date
Date与List相比就要简单一些了,我们只需要创建一个新的Date对象,保证这个对象的值和原来的对象一样即可。
下面是具体的代码实现。
class Test {
public static void main(String[] args) {
Date date = new Date();
Date copyDate = new Date(date.getTime());
date.setTime(2000000000);
System.out.println("date:"+date.toString());
System.out.println("copydate:"+copyDate.toString());
}
}
最后的运行结果为
date : Sat Jan 24 11:33:20 CST 1970
copydate : Tue Jul 06 16:57:15 CST 2021
我们成功创建了两个完全独立的Date对象,这正是我们想要的。
对于一些其他的对象,或是通过构造函数,或是通过一些set方法,我们都可以创建一个与原先对象一模一样的对象。
如果返回值本身的就是immutable类型的对象,实际上就不需要防御式拷贝了,因为这样的对象是不允许被改变的。所以,在编程中,如果可以使用immutable类型的对象,就尽量使用,这样可以避免许多与表示泄露有关的麻烦。
详见:https://blog.csdn.net/Gravitas/article/details/118523519
用于描述程序运行时的内部状态
基本类型的值
对象类型的值
不可变对象用双线椭圆
例子:针对可变值的不可变引用
针对不可变值的可变引用
List:需要将下标按照引用的标注形式写出来
set:直接从set对象伸出箭头指向set中的元素。
map:需要将键值对用方框框起来
详见:关于SnapShot不得不知的知识点 - 知乎 (zhihu.com)
Specification diagram:
规约定义一个区域,该区域包含所有可能的实现方式。
空间中的每个点表示一种方法的实现。
对于某个具体实现,若满足规约,则落在其区域内。
更强的规约表达为更小的区域。
spec变强意味着 更放松的前置条件+更严格的后置条件
构造函数:
(1)构造方法的方法名必须与类名相同。
(2)构造方法没有返回值类型,可以有return,但是不能有返回值,在这里只是表示结束,并不是返回的表示,一般也不使用return。
(3)构造方法的主要作用是完成对象的初始化工作,它能够把定义对象时的参数传给对象的域。
(4)一个类可以定义多个构造方法,如果在定义类时没有定义构造方法,则编译系统会自动插入一个无参数的默认构造方法,这个构造方法不执行任何代码。
(5)构造方法可以重载,以参数的个数,类型,顺序。
构造函数的调用问题:
没有参数的构造函数称为默认构造函数
(1)子类只调用父类的默认构造函数,即无参数构造函数。如果父类没有默认构造函数,那子类不能从父类调用默认构造函数。
(2)子类从父类处调用默认构造函数,不能成为子类的默认构造函数。
(3)在创建对象时,先调用父类默认构造函数对对象进行初始化,然后调用子类自身自己定义的构造函数。
(4)如果子类想调用父类的非默认构造函数,则必须使用super来实现。
(5)子类必须调用父类的构造函数。可以通过系统自动调用父类的默认构造函数,如果父类没有默认构造函数时,子类构造函数必须通过super调用父类的构造函数。
String.concat():prducer
表示独立性:client使用ADT时无需考虑其内部如何实现,ADT内部的变化不应影响外部spec和客户端。
如何避免表示泄露:
确保所有的变量都是private
变量是否加final没有关系
确定可变类型的属性
构造函数传递可变类型的类——防御式拷贝
返回值为可变类型的get函数——防御式拷贝
注释编写安全策略示例
1.All fields are private( final)------即将类中所有的属性(变量)定义为private类型,目的是不让用户得到你的内部属性
2.尽量使用immutable数据类型,比如能使用String就不使用StringBuilder,能使用Instance或LocalDateTime就不使用Data
3.如果我们使用了mutable类型的数据,为了防止对外泄露其内部表示,需要使用defensive copy,即防御式拷贝。如果是Set、Map等数据类型,还可以使用Collections.unmodifiableSet,Collections.unmodifiableMap等方法。
4.随时检查RI是否满足,自写checkRep()方法(通常在其中使用assert来进行判断),在创造和改变表示的方法中(creators,producers,mutators),进行调用,来确保不变性。
不变量(Invariant):程序的一个属性,对于程序的每一个可能的运行时状态,它总是为真。例如:immutability就是一个典型的“不变量”。
不变量属于ADT本身,是在程序运行过程中不变的量
表示不变量RI指定某个具体的表示是否是合法的,是所有表示值的一个子集,也包含了所有合法的表示值,是一个条件,描述了什么是合法的表示值
表示值空间:R空间由实际实现实体的值组成 确定哪些值合法即是表示不变量
表示空间是ADT内部可见的成员变量取值
抽象空间是client可见的部分
AF抽象函数表示了R和A之间的映射关系
写在注释中,所以不会被除自己以外的人看到,涉及内部实现
目的:在编程过程中为我们提供指导和约束,在设计好后,我们的所有工作都必须在这个前提下进行
// Rep invariant:
// s contains no repeated characters
// Abstraction function
// AF(s) = {s[i] | 0 <= i < s.length() }
传统接口只有方法定义没有实现
但现在:
接口内可以实现静态工厂方法
相比于通过构造器(new)构建对象:
1.静态工厂方法可具有指定的更有意义的名称
2.不必在每次调用的时候都创建新的工厂对象
3.可以返回原返回类型的任意子类型
还可以用default方法在接口中统一实现某些功能,以增量式的方法为接口增加额外的功能而不破坏已实现的类
接口中的成员变量:
(1)接口中无法定义普通的成员变量
(2)接口中定义的变量,必须有固定的修饰符修饰public static final ,所以接口中的变量也称之为常量,其值不能改变。
static 可以被类名、接口名直接调用
final 引用不可改变
(3)public static final 在接口中可以省略不写,也可以选择性写,但是不代表没有.
(4)接口中的成员变量必须显式初始化
重写的规则:
1-规则一:重写方法不能比被重写方法限制有更严格的访问级别。
(但是可以更广泛,比如父类方法是包访问权限,子类的重写方法是public访问权限。)
2-规则二:参数列表必须与被重写方法的相同。
(需要注意的是如果子类方法的参数和父类对应的方法不一样,那就不是重写,而是重载)
3-规则三:返回类型必须与被重写方法的返回类型相同或是返回类型的子类型
4-规则四:重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常。但是可以抛出更少,更有限或者不抛出异常。
5-规则五:不能重写被标识为final的方法。
6-规则六:如果一个方法不能被继承,则不能重写它
无论重载、泛型,还是继承关系都是多态的一个具体表现,也被归属为不同的多态
多态分为:
特定多态:函数重载,操作符重载(Java只有内建的操作符重载)
参数化多态:泛型
子类型多态:表示有替代关系,有子类型关系,可以替换,由继承决定
重载:指的是函数有多个不同实现,依赖参数类型而选择具体的实现
参数化多态机制:指在程序设计语言中声明与定义函数、复合类型、变量时不指定其具体的类型,而把这部分类型作为参数使用,使得该定义对各种具体类型都适用。被称为泛型函数、泛型数据类型、泛型变量、形成了泛型编程的基础。
equals重写准则:
自反,对称,传递
除非对象被改,否则多次调用equals应该是同样的结果
任何值与null值比较都返回false
相等对象的hashCode结果必须一致
对类所有对象都生效
instanceof是Java的一个二元操作符
返回一个布尔值,判断左边的对象是否是它右边的类的实例
if (o instanceof Vector)
System.out.println("对象是 java.util.Vector 类的实例");
else if (o instanceof ArrayList)
System.out.println("对象是 java.util.ArrayList 类的实例");
else
System.out.println("对象是 " + o.getClass() + " 类的实例");
该方法还可以用来判空:如果操作数为空,返回值为false
getClass()方法是获得调用该方法的对象的类;getClass().getName()可以得到该类的路径
以上两种方法都可用于equals方法的重写来判断二者类型是否相等。
equals方法重写步骤:
如果该对象的引用和参数对象的引用相同,返回true。这项测试在成立时能够勉去其他所有测试工作。
如果参数为空(null),根据约定返回false(还可以在下面的代码中避免空引用)。
如果两个对象的类不同,返回false。要得的一个对象的类,可以使用getClass()方法。请注意是使用==来判断Class类型的对象是否相等,因为同一种类型的所有对象的getClass()方法一定能够返回相同的类型。
将参数对象的类型从Object转换到当前数据类型(因为前一项测试已经通过,这种转换必要成功)。
如果任意实例变量的值不相同,返回false。
返回当对象放入hash容器中后产生的哈希值(一个整数),用于判断是否是同一个对象
要求与equals逻辑一致
可以直接return一个整数
没有mutator,只能观察
比的是抽象的东西,一般为了满足需求要重写equals和hashcode
观察等价性:不改变状态的情况下看起来一致
行为等价性:调用对象任何方法得到的结果一致
不同mutable类有不同的等价性标准
Data:观察等价性,识别数据长度
List:元素个数顺序一样
StringBuilder:指向同一个地址空间,其中的equals用的是object继承来的
可变类型的equals方法不用重写:行为等价性
观察等价性:直接用observer
子类型可以增加方法,但不可删
子类型需要实现抽象类型(接口、抽象类)中所有未实现的方法
子类型中重写的方法必须有相同或子类型的返回值或者符合co-variant(协变)的参数(此种情况Java按照重载overload处理)
子类型中重写的方法不能抛出额外的异常
更强的不变量:不变量要保持不变性,也可以增加新的不变量
更弱的前置条件
更强的后置条件
子类型:方法参数逆变;方法返回值协变;异常类型协变
无限定通配符>:一般用于定义一个引用变量,其可以指向多个不同类型的变量
super A>:下限通配符
extends A>:上限通配符,这里既可以表示类的extends,也可以代表接口的implements
如果有多个限定:
限定的类型参数允许调用限定类型中的方法
简单理解:
Comparator是一个外部接口,可以用来委托
Comparable是需要比较的类本身实现的一个接口:
public class student implements Comparable
在类中直接重写compareTo方法:
@Override
public int compareTo(student o) {
return this.getAge()-o.getAge();
}
接口之间可以继承和扩展
通过代码层面的继承进行框架扩展
通过实现特定接口/delegation进行框架扩展
边界匹配器:^ 代表以某些内容开头
$ 代表以某些内容结尾
eg: "^[a-z][0-9]$"
数量词:所有数量词都是放在元素之后的
X? 代表出现0或1次
X* 代表出现次数大于等于0次
X+ 大于等于1
X{n} 代表出现n次
X{n,} 代表至少出现n次
X{n,m} 代表次数在之间
逻辑运算符:
XY:连接
X|Y:或
特别的:
\\d:相当于[0-9]
\\t:制表符
\\\\:斜线字符\\
\\.:.
\\n:换行符
\\r:回车符
.:任意一个字符
[xxx]:中括号表示或者
[^abc]:^表示除所修饰字符以外的其他字符
注:设计模式强烈建议看代码例子,图量很大就不粘了,这里只简单给出了结构图
UML图:
类(Class):使用三层矩形框表示。 第一层显示类的名称,如果是抽象类,则就用斜体显示。 第二层是字段和属性。 第三层是类的方法。 注意前面的符号,‘+’表示public,‘-’表示private,‘#’表示protected。
接口:与类图的区别是顶端有<>显示 。
继承类(extends) :用空心三角形+实线来表示。
实现接口(implements) :用空心三角形+虚线来表示
关联(Association) :用实线箭头来表示,例如:燕子与气候
聚合(Aggregation) :用空心的菱形+实线箭头来表示 has a 聚合:表示一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分,例如: 公司和员工
组合(Composition):用实心的菱形+实线箭头来表示 contains a 组合:部分和整体的关系,并且生命周期是相同的。例如:人与头
依赖(Dependency) :一个类需要另一个类的协助 用虚线箭头来表示,例如:动物与氧气
类之间的关系 UML把类之间的关系分为以下5种.
- 关联:类A与类B的实例之间存在特定的对应关系
- 依赖:类A访问类B提供的服务
- 聚集:类A为整体类,类B为局部类,类A的对象由类B的对象组合而成
- 泛化:类A继承类B
- 实现:类A实现了B接口 关联(Association)
关联指的是类之间的特定对应关系,在UML中用带实线的箭头表示。按照类之间的数量对比,关联 可以分为以下三种:
一对一关联
一对多关联
多对多关联
注意:关联还要以分为单向关联和双向关联
泛化(Generalization)泛化指的是类之间的继承关系,在UML中用带实线的三角形箭头表示。
实现(Realization) 实现指的是类与接口之间的关系,在UML中用带虚线的三角形箭头表示。
详见:https://blog.csdn.net/huanghuang9426/article/details/114106162
adapter:适配器实现一个接口,客户端调用这个接口,调用适配器,同时,适配器中的实现来自于适配器内部,而适配器本身作为一个类,它的方法可以继承复用一个父类的方法或者委托复用一个其他类的方法
decorator:
将通用实现委托给已有实现
strategy:
算法的不同实现
templete method
做事步骤一样但具体方法不同
abstract作为修饰符,声明抽象方法,方法只有方法头的声明,而用一个分号来代替方法体的定义;至于方法体的具体实现,那是由当前类的不同子类在他们各自的类定义中完成的。
Iterator:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
factory method:
visitor:
软件测试与测试优先的编程
错误和异常的共同祖先
捕获异常指的是进行了处理,而抛出异常指的是抛出异常等待调用者处理
要么捕获(catch)
要么抛(throws):要在方法后用throws xxx来声明
//声明
public static void readFile()throws IOException {
...
}
//调用
public static void main(String[] args) {
try{
readFile();
}catch(IOException e) {
System.out.println(e);
}
}
class DataHouseException extends Exception {
//构造方法
public DataHouseException(String message) {
super(message);
}
}
异常类的调用
class BankATM {
public static void GetBalanceInfo(long ID)throws MyAppException
{
try {
DataHouseException.FindData(ID)''
}catch(DataHouseException e){
throw new MyAppException("invalid id", e);
}
}
}
开启断言: VM Arguments文本框中输入:-ea
如果不满足条件就发出异常,后面的代码也不会运行
格式:
assert 表达式
assert 表达式 : 信息;
Notice:编译器需要手动开启assert
使用assert的情况:
在私有方法中放置assert校验输入参数
流程控制中不可能达到的区域,终止执行 assert false
断言表达式:
assertEquals(2,Math.max(1,2))
assert if then else
约定俗成:参数顺序为(期望值,实际处理结果)
用@Test
测试方法之间的执行互不影响
尽量使用不变量
尽量避免表示泄露
等价类划分:等价类满足自反,对称,传递
输入有效/无效等价类,还要测试约束条件,特殊情况
每个等价类里取一个数据出来作为测试用例,划分必须完备且无交集
eg:根号x 分正值和负值
输入长度为10的学号:10,>10,<10
每种划分测试一种特性:分奇数偶数
边界值分析:假设大量错误发生在输入域的边界而非中央,作为等价类划分的一种补充
eg:选最小整数和最大整数