本次实验重点训练面向健壮性和正确性的编程技能,利用错误和异常处理、断言与防御式编程技术、日志/断点等调试技术、黑盒测试编程技术,使程序 可在不同的健壮性/正确性需求下能恰当的处理各种例外与错误情况,在出错后可优雅的退出或继续执行,发现错误之后可有效的定位错误并做出修改。 实验针对 Lab 3 中写好的 ADT 代码和基于该 ADT 的三个应用的代码,使用以下技术进行改造,提高其健壮性和正确性:
⚫ 错误处理
⚫ 异常处理
⚫ Assertion 和防御式编程
⚫ 日志
⚫ 调试技术
⚫ 黑盒测试及代码覆盖度
需要下载spotBugs寻找错误,下载完成后会出现以下界面:
考虑到文件内容较多,为了加快读文件以及建立轨道的速度,采用读一行匹配一行的策略,对于多数异常的处理一边匹配一边进行,对于某些特定异常需要在匹配完成之后再进行处理
种类1. 输入文件中存在不符合语法规则的语句
对于田径比赛轨道系统,异常情况以及处理代码如下:
1.运动员国籍不是由三位字母构成
2运动员.国籍中出现小写字母
3.运动员年龄为负数
4.运动员号码为负数
5.运动员成绩为负数
6.运动员成绩小数位不足两个
7.运动员成绩整数位超过两个
以上7个异常在匹配运动员变量的时候统一检查,此时也检查了种类2中出现相同元素的情况:
try
{
// 先判断是否有重复元素
Iterator<trackE1> iterator = athlete.iterator();
while (iterator.hasNext())
{
try
{
if (iterator.next().getName().equals(w ord1))
{
throw new Exception(
"same elements:the athlete " + word1 + " appears more than once.");
}
}
catch (Exception e)
{
log.info(
"trackGame:exception happens in class functionTrackGame,method creatingTrackFromFiles:"
+ e.getMessage()
+ " handling:return false,and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
}
// 国籍长度为3
if (!(word2.length() == 3))
{
throw new Exception("statements do not conform to syntax rules: the athlete " + word1 + "'s nationality is not made up by three letters:" + word2);
}
// 国籍由大写字母构成
for (int i = 0; i < 3; i++)
{
if (!((word2.charAt(i) <= 90) && (word2.charAt(i) >= 65)))
{
throw new Exception("statements do not conform to syntax rules: the athlete " + word1 + "'s nationality's first letter is not capital " + word2);
}
}
// 编号为正
if (Integer.parseInt(int1) < 0)
{
throw new Exception("statements do not conform to syntax rules: the number of athlete " + word1 + " is negative " + Integer.parseInt(int1));
}
// 年龄为正
if (Integer.parseInt(int2) < 0)
{
throw new Exception("statements do not conform to syntax rules: the age of athlete " + word1
+ " is not negative " + Integer.parseInt(int2));
}
// 成绩必须为正
if (Float.parseFloat(float1) < 0)
{
throw new Exception("statements do not conform to syntax rules: the result of athlete "
+ word1 + " is negative:" + Float.parseFloat(float1));
}
// 最好成绩必须有两位小数
if (float1.charAt(float1.length() - 1 - 2) != ".".charAt(0))// 必须有两位小数
{
throw new Exception("statements do not conform to syntax rules: the athlete " + word1 + " 's result's fractional part's length isn't 2");
}
// 已经确保有两位小数了,现在需要确保有不超过两位整数
if (float1.length() > 5)// 在此之前已经判断小数点的位数是有两位的,现在需要判断整数位数
{
throw new Exception("statements do not conform to syntax rules: the athlete " + word1 + " 's result's Integral part's length is larger than 2");
}
}
catch (Exception e)
{
log.warning("trackGame:exception happens in class functionTrackGame,method creatingTrackFromFiles:"
+ e.getMessage()
+ " handling:return false,and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
8.轨道数目不在[4,10]区间内:
// 比赛种类不符合要求
try
{
if (!(Integer.parseInt(int11) == 100 || Integer.parseInt(int11) == 200
|| Integer.parseInt(int11) == 400))
{
throw new Exception("statements do not conform to syntax rules: " + "the game type " Integer.parseInt(int11) + " is illegal");
}
}
catch (Exception e)
{
log.warning("trackGame:exception happens in class functionTrackGame,method creatingTrackFromFiles:"
+ e.getMessage()handling:return false,and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
9.比赛种类不在100|200|400的比赛范围内:
if (!((Integer.parseInt(int12) <= 10) && (Integer.parseInt(int12) >= 4)))
{
try
{
throw new Exception("statements do not conform to syntax rules: " + "the number of track " + Integer.parseInt(int12) + " is out of bound");
}
catch (Exception e)
{
log.warning( "trackGame:exception happens in class functionTrackGame,method creatingTrackFromFiles:" + e.getMessage()+ " handling:return false,and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
}
对于原子轨道系统,异常情况以及处理代码如下:
1.原子名称第一位字母小写
2.原子名称第二位字母(若有)大写
3.原子名称超过两位字母
以上三种情况在匹配原子名称时进行判断:
try
{
// 原子名称最多两位字母
if (!((temp.length() > 1) && (temp.length() < 3)))
{
throw new Exception(
"statements do not conform to syntax rules:the name " + temp + " is out of bound ");
}
// 第一位必须大写
if (!((temp.charAt(0) <= 90) && (temp.charAt(0) >= 65)))
{
throw new Exception("statements do not conform to syntax rules:the first letter \""+ temp.charAt(0) + "\" is not capital");
}
// 再保证只有两位的情况下,判断第二个字母是否是小写
if (temp.length() > 1)
{
if (!((temp.charAt(1) <= 122) && (temp.charAt(1) >= 97)))
{
throw new Exception("statements do not conform to syntax rules:the second letter \""+ temp.charAt(1) + "\" is not lowercase");
}
}
}
catch (Exception e)
{
log.warning( "atomStructure:exception happens in class atomStructure,method creatingTrackFromFiles:" + e.getMessage()
+ " handling:return false and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
4.轨道数目不是正整数:
// 轨道数应该是正整数
try
{
if (trackNumber2 <= 0)
{
throw new Exception("statements do not conform to syntax rules: the tracknumber "+ trackNumber2 + " is non-positive");
}
}
catch (Exception e)
{
log.warning(
"atomStructure:exception happens in class atomStructure,method creatingTrackFromFiles:"+ e.getMessage()+ " handling:return false and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
5.电子数量为负数
6.轨道编号为负数
以上两种情况在匹配电子时进行处理
try
{
// 轨道编号必须大于等于零
if (Integer.parseInt(a1) < 0)
{
throw new Exception(
"statements do not conform to syntax rules:the numebr of track is negative: "
+ Integer.parseInt(a1));
}
// 轨道上的电子数目必须大于零
if (Integer.parseInt(a2) <= 0)
{
throw new Exception(
"statements do not conform to syntax rules:the number of electronic is non-positive: "+ Integer.parseInt(a2));
}
}
catch (Exception e)
{
log.warning(
"atomStructure:exception happens in class atomStructure,method creatingTrackFromFiles:"+ e.getMessage()
+ " handling:return false and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
对于社交网络系统,异常情况以及处理代码如下:
1.人的年龄为负数
2.人的性别不在m|f范围中
以上两个异常在匹配到Friend时进行判断:
try
{
if (Integer.parseInt(ffc6) < 0)
{
throw new Exception("statements do not conform to syntax rules:the age "
+ Integer.parseInt(ffc6) + " is negative");
}
if (!(fc7.equals("M") || fc7.equals("m") || fc7.equals("F") || fc7.equals("f")))
{
throw new Exception(
"statements do not conform to syntax rules:the sex " + fc7 + " is illegal");
}
}
catch (Exception e)
{
log.warning("Social:exception happens in class socialNetwork,method creatingTrackFromFiles:"+e.getMessage()+" handling:return false,exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
3.亲密度的小数位超过三个
4.亲密度的大小大于1
5.亲密度为负数
以上三个异常在匹配到亲密度的时候进行判断:
try
{
if (Float.parseFloat(normalSfloat1) > 1)
{
throw new Exception("statements do not conform to syntax rules:socialTie is larger than 1:"
+ normalSfloat1);
}
if (Float.parseFloat(normalSfloat1) < 0)
{
throw new Exception("statements do not conform to syntax rules:socialTie is less than 0:"+ normalSfloat1);
}
// 已经保证亲密度在01之间
if (normalSfloat1.length() > 5)
{
throw new Exception(
"statements do not conform to syntax rules:illegal socialTie: " + normalSfloat1);
}
}
catch (Exception e)
{
// TODO: handle exception
log.warning("Social:exception happens in class socialNetwork,method creatingTrackFromFiles:"+e.getMessage()+" handling:return false,exception message is printed on console");
System.out.println(e.getMessage());
return false;
}
第二种:存在标签完全一样的元素:
// 先判断是否有重复元素
Iterator<trackE1> iterator = athlete.iterator();
while (iterator.hasNext())
{
try
{
if (iterator.next().getName().equals(word1))
{
throw new Exception(
"same elements:the athlete " + word1 + " appears more than once.");
}
}
catch (Exception e)
{
log.info(
"trackGame:exception happens in class functionTrackGame,method creatingTrackFromFiles:"+ e.getMessage() + " handling:return false,and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
}
for (int k = 0; k < socialTie.size(); k++)
{
// 出现两条相同的社交关系,这个不需要在把文件都读完之后再处理
try
{
if (socialTie.get(k).tieEquals(temp))
{
throw new Exception(
"same elements:socialnetwork " + temp.getName1() + "->" + temp.getName2());
}
}
catch (Exception e)
{
log.warning("Social:exception happens in class socialNetwork,method creatingTrackFromFiles:"+e.getMessage()+" handling:return false,exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
}
try
{
if (normalSalphanum1.equals(normalSalphanum2))
{
throw new Exception("same elements:The same name " + normalSalphanum1);
}
}
catch (Exception e)
{
log.warning("Social:exception happens in class socialNetwork,method creatingTrackFromFiles:"+e.getMessage()+" handling:return false,exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
种类3:文件中各元素之间的依赖关系不正确。
// 相当于是在读完之后才判断两个电子轨道数目是否相等
try
{
if (count != trackNumber2)
{
throw new Exception(
"incorrect dependencies:the electronic track number is contradictory:" + count);
}
}
catch (Exception e)
{
log.warning(
"atomStructure:exception happens in class atomStructure,method creatingTrackFromFiles: + e.getMessage()+ " handling:return false and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
for (int j = 0; j < socialTie.size(); j++)
{
try
{
if (!(friend.containsKey(socialTie.get(j).getName1()))&& !(socialTie.get(j).getName1().equals(central.getName())))
{
// 抛出异常之后后面的语句就不会再执行了
throw new Exception("incorrect dependencies:" + socialTie.get(j).getName1() + " appears in socialTie while doesn't appear in Friend");
}
if(!(friend.containsKey(socialTie.get(j).getName2()))&& !(socialTie.get(j).getName2().equals(central.getName())))
{
throw new Exception("incorrect dependencies:" + socialTie.get(j).getName2() + " appears in socialTie while doesn't appear in Friend");
}
}
catch (Exception e)
{
log.warning("Social:exception happens in class socialNetwork,method creatingTrackFromFiles:"+e.getMessage()+" handling:return false,exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
}
在三个具体的轨道系统类以及它们的父类、三个具体的中心物体类以及它们的父、类、三个具体的轨道物体类以及它们的父类、轨道类、关系类中均通过checkRep()函数检查invariants,具体实现如下:
关系类:
// Abstraction function:
// 表示一对关系,包含关系两端的物体,以及关系的亲密度
// Representation invariant:
// 关系时不可变对象,关系包含的三个元素也是不可变对象,亲密度为正,如果是字符串长度不能为0
// Safety from rep exposure:
// 所有的元素类型都是private final,确保其无法被改变
public void checkRep()
{
assertTrue(name1.length()!=0);
assertTrue(name2.length()!=0);
assertTrue(ini>0);
}
轨道类:
// Abstraction function:
// 作为所有轨道的父类,具有共同属性:“半径”
// Representation invariant:
// 一个轨道物体的名称是String类型,不可变
// Safety from rep exposure:
// String本身是不可变的,同时中心物体的名称是final类型,指向不会改变
public void checkRep()
{
assertTrue(rep>=0);
}
轨道物体父类:
//Abstraction function:
//作为所有轨道物体的父类,具有共同属性:“名称”
//Representation invariant:
//一个轨道物体的名称是String类型,不可变
//Safety from rep exposure:
//String本身是不可变的,同时中心物体的名称是final类型,指向不会改变
public void checkRep()
{
assertTrue(name.length()!=0);
}
田径轨道物体类:
//Abstraction function:
//表示一个运动员的所有信息
//Representation invariant:
//每一个信息都是不可变类型,不能为null,且如果信息为数字,则必须大于0
//Safety from rep exposure:
//所有的信息都是private和final类型
public void trackcheckRep()
{
assertTrue(nationaility!=null);
assertTrue(best>0);
assertTrue(age>0);
assertTrue(number>0);
}
原子轨道物体类同父类,未做改动。
社交网络轨道物体类:
//Abstraction function:
//描述社交网络中一个人的全部信息
//Representation invariant:
//所有的信息类型都是不可变的,所有的信息域不允许为空,如果信息是数字,则必须大于0
//Safety from rep exposure
//所有的信息域都通过private final修饰
public void socialEcheckRep()
{
checkRep();
assertTrue(age>0);
}
中心物体父类:
//Abstraction function:
//作为所有中心物体的父类,具有共同属性:“名称”
//Representation invariant:
//一个中心物体的名称是String类型,不可变
//Safety from rep exposure:
//String本身是不可变的,同时中心物体的名称是final类型,指向不会改变
public void checkRep()
{
assertTrue(name.length()!=0);
}
社交网络中心物体类:
// Abstraction function:
// 描述社交网络中一个中心点人的全部信息
// Representation invariant:
// 所有的信息类型都是不可变的,所有的信息域不允许为空,如果信息是数字,则必须大于0
// Safety from rep exposure
// 所有的信息域都通过private final修饰
public void socialcheckRep()
{
checkRep();
assertTrue(age > 0);
}
原子轨道中心物体类与父类相同,田径比赛没有中心物。
轨道系统父类:
// Abstraction function:
// 具体实现一个circularOrbit接口,从文件中读入并建立轨道,
// 采用不同的数据结构存储轨道物体、中心物体、轨道物体与中心物体之间的关系、中心物体与轨道物体之间的关系,
// 并实现增减轨道物体、增减轨道、增减关系、轨道物体跃迁等功能
// Representation invariant:
// 轨道物体,轨道,中心点物体都是不可变的,通过具体的数据结构建立它们之间的联系
// Safety from rep exposure:
// 所有对象的类型是private final的,在返回重要数据结构时,做了防御性克隆
public void checkRep()
{
assertTrue(physical!= null);
}
田径比赛轨道系统类:
// Abstraction function:
// 具体实现田径比赛系统,从文件中读入并建立轨道,
// 采用不同的数据结构存储轨道物体、中心物体、轨道物体与中心物体之间的关系、中心物体与轨道物体之间的关系,
// 并实现增减轨道物体、增减轨道、增减关系、轨道物体跃迁等功能
// Representation invariant:
// 轨道物体,轨道,中心点物体都是不可变的,通过具体的数据结构建立它们之间的联系,
// 每个轨道上的人数不会超过跑道数
// Safety from rep exposure:
// 所有对象的类型是private final的,在返回重要数据结构时,做了防御性克隆
public void checkRep()
{
for (int i = 0; i < groupTrackSystem.size(); i++)
{
assertTrue(groupTrackSystem.get(i).getObjectTrack().size() <= groupTrackSystem.get(i).getPhysical().size());
}
}
社交网络轨道系统类:
// 具体实现社交网络系统,从文件中读入并建立轨道,
// 采用不同的数据结构存储轨道物体、中心物体、轨道物体与中心物体之间的关系、中心物体与轨道物体之间的关系,
// 并实现增减轨道物体、增减轨道、增减关系、轨道物体跃迁等功能
// Representation invariant:
// 轨道物体,轨道,中心点物体都是不可变的,通过具体的数据结构建立它们之间的联系,
// 不管社交关系如何增加或删除,第 i 层轨道上的人与中心点的人之间的最短路径等于 i。
// Safety from rep exposure:
// 所有对象的类型是private final的,在返回重要数据结构时,做了防御性克隆
public void checkRep()
{
assertTrue(socialTie!=null);
}
电子轨道系统:
// 具体实现电子轨道系统,从文件中读入并建立轨道,
// 采用不同的数据结构存储轨道物体、中心物体、轨道物体与中心物体之间的关系、中心物体与轨道物体之间的关系,
// 并实现增减轨道物体、增减轨道、增减关系、轨道物体跃迁等功能
// Representation invariant:
// 轨道物体,轨道,中心点物体都是不可变的,通过具体的数据结构建立它们之间的联系,
// Safety from rep exposure:
// 在返回重要数据结构时,做了防御性克隆
public void checkRep()
{
assertTrue(trackNumber2 >= 0);
assertTrue(trackObjectNumber!= null);
}
对于post-condition,采用assertion加以保障,有时assertion和checkRep()一起保障,对于前置条件,如果参数来自外部,且无法控制,按照7-3 Assertions and Defensive Programming课件的要求,采用异常处理,如果参数来自程序内部,可以信任,则采用assertion处理:
ConcreteCircularObject类:
addTrack中的后置条件处理:
assert physical.size() != 0 : "physical is empty";// 后置条件
deleteTrack中的前置前置条件处理:
assert number >= 0 && number < physical.size() : "The track is out of bound";
deleteTrack中的后置条件处理:
assert physical.size() != 0 : "physical is empty";// physical是控制轨道编号的,只能增加轨道,不能减少轨道
addTrackObject中的前置条件处理:
// 参数来自外部,不适合用assert处理
try
{
if (t >= physical.size())
{
throw new Exception("the track is out of bound!");
}
if (!trackObject.containsKey(t))
{
throw new Exception("the track has been deleted!");
}
}
catch (Exception e)
{
log.info("Concrete:exception happens in class ConcreteCircularObject,method addTrackObject:"
+ e.getMessage() + " handling:return false,and the exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
addTrackObject中的后置条件处理:
assert physical.size() != 0 : "physical is empty";// 后置条件
deleteTrackObject中的前置条件处理
// 参数来自外部,不适合用assert处理
try
{
if (t >= physical.size())
{
throw new Exception("the track is out of bound!");
}
if (!trackObject.containsKey(t))
{
throw new Exception("the track has been deleted!");
}
if (!trackObject.get(t).contains(ob))
{
throw new Exception("The object is not in orbit");
}
}
catch (Exception e)
{
log.info("Concrete:exception happens in class ConcreteCircularObject,method deleteTrackObject:"
+ e.getMessage() + " handling:return false,and the exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
deleteTrackObject中的后置条件处理
assert physical.size() != 0 : "physical is empty";// 后置条件
addCentralObject中的后置条件处理
assert central != null : "central is null";
transit中的前置条件处理
// 参数来自外部,不适合用assert处理,用exception处理
try
{
if (t >= physical.size())
{
throw new Exception("the target track is out of bound!");
}
if (!objectTrack.containsKey(ob))
{
throw new Exception("The object is not in orbit!");
}
}
catch (Exception e)
{
log.info("Concrete:exception happens in class ConcreteCircularObject,method transit:"+e.getMessage()+" handling:return false,and exception message is printed on console");
System.out.println(e.getMessage());
return false;
// TODO: handle exception
}
transit中的后置条件处理
assert physical.size() != 0 : "physical is empty";// 后置条件
addEERelationship中的前置条件处理
// 参数来自外部,不适合用assert处理,考虑到程序的可扩展性,用异常处理
// 在轨道上就说明不是中心物体,因为中心物体不在轨道上
try
{
if (!objectTrack.containsKey(e1))
{
throw new Exception("object1 is not in the orbit system!");
}
if (!objectTrack.containsKey(e2))
{
throw new Exception("object2 is not in the orbit system!");
}
}
catch (Exception e)
{
// TODO: handle exception
System.out.println(e.getMessage());
return false;
}
addEERelationship中的后置条件处理
assert physical.size() != 0 : "physical is empty";// 后置条件
deleteEERelationship中的前置条件处理
try
{
if (!objectTrack.containsKey(e1))
{
throw new Exception("object1 is not in the orbit system!");
}
if (!objectTrack.containsKey(e2))
{
throw new Exception("object2 is not in the orbit system!");
}
}
catch (Exception e)
{
// TODO: handle exception
System.out.println(e.getMessage());
return false;
}
deleteEERelationship中的后置条件处理
assert physical.size() != 0 : "physical is empty";// 后置条件
addLERelationship中的前置条件处理
try
{
if (!objectTrack.containsKey(e2))
{
throw new Exception("The object1 is not in the orbit system!");
}
}
catch (Exception e)
{
// TODO: handle exception
System.out.println(e.getMessage());
return false;
}
addLERelationship中的后置条件处理
assert physical.size() != 0 : "physical is empty";// 后置条件
deleteLERelationship中的前置条件处理
try
{
if (!objectTrack.containsKey(e2))
{
throw new Exception("object1 is not in the orbit system!");
}
}
catch (Exception e)
{
// TODO: handle exception
System.out.println(e.getMessage());
return false;
}
deleteLERelationship中的后置条件处理
assert physical.size() != 0 : "physical is empty";// 后置条件
除了ConcreteCircularOrbit类之外,其余三个具体的轨道系统类中的各个方法均做了前置和后置处理,考虑到代码量较大,不在此赘述,可在在具体代码中检查。
采用JAVA自带的logging工具,新建一个FileHandler将日志写到指定的文件中去,自定义一个formatter来改进日志的显示方式:
public class myFormatter extends Formatter
{
@Override
public String format(LogRecord record) {
return new java.util.Date()+"-["+record.getSourceClassName()+"."+record.getSourceMethodName()+"]"+record.getLevel()+":"+record.getMessage()+"\n";
}
}
在捕获到异常/错误时会进行日志记录,记录包括异常/错误发生的时间、异常/错误类型、异常/错误发生的类名和方法名,异常/错误的具体信息、异常/错误处理的结果;此种记录的级别为warning。
在断言之前会进行日志记录,记录包括断言的时间,断言的内容,断言发生的类、方法;此种记录的级别为info。
在成功进行某项操作时会进行日志记录,记录包括操作的时间,操作的内容,操作发生的类,方法;此种记录的级别为info。
在程序执行过程中遇到其他错误时,会进行日志记录,记录包括错误发生的时间,错误的信息,错误发生的方法名,类名,以及对错误的处理操作;此种记录的级别为severe。
日志记录的代码在轨道系统父类和三个具体轨道系统类中的每个方法中均存在,考虑到代码量过大,在此只举出若干例子,不一一赘述,可在具体代码中进行检查:
ConceteCircularObject类中的若干log语句:
getPhysical中记录返回了physical对象:
log.info("Concrete:return physical list,in class ConcreteCircularObject,method getPhysical");
getSocialTie中记录返回了socialTie对象
log.info("Concrete:return socialTie list,in class ConcreteCircularObject,method getSocialTie");
addTrack中记录对后置条件判断的断言处理
log.info("Concrete:assert physical is not empty,in class ConcreteCircularObject,method addTrack");
addTrack中记录成功增加一条轨道
log.info("Concrete:add a track,in class ConcreteCircularObject,method addTrack");
addTrackObject中记录对后置条件判断的断言处理
log.info("Concrete:assert physical isn't empty,in class ConcreteCircular,method addTrackObject");
addTrackObject中记录成功增加一个轨道物体
log.info("Concrete:another object is added to track " + t);
deleteTrackObject中记录对前置条件判断时捕获到一个异常
log. warning ("Concrete:exception happens in class ConcreteCircularObject,method deleteTrackObject:"
+ e.getMessage() + " handling:return false,and the exception message is printed on console");
transit中记录对前置条件判断时捕获到一个异常
log.warning("Concrete:exception happens in class ConcreteCircularObject,method transit:"+e.getMessage()+" handling:return false,and exception message is printed on console");
System.out.println(e.getMessage());
在进行若干操作后,得到文件中的日志,如下图所示:
在进行写日志的过程中,曾遇到过如果调用的方法不在同一个类里面,有些日志就无法写到目标文件中,经过查阅资料后发现,所有的写日志操作如果公用一个Logger,就可以解决这个问题,因此Logger在父类中定义,子类在写日志时调用父类的Logger。
日志查询分为五种方法,分别是按时间段、按类型、 按类、按方法、按操作类型,为此建立一个类(logSearchMethod),来实现这五种方法,五种方法的本质还是正则表达式的匹配以及根据用户的输入采用switch语句进行不同的输出,用户的操作界面如下:
输入11代表查询日志,输入1代表按时间查询日志,输入起始小时、分钟,终止小时、分钟,就会显示在这个时间段内的所有日志:
继续操作:
输入2代表按轨道类型查找,输入s代表社交网络类型,然后会显示与该轨道类型有关的日志:
继续操作:
输入3代表通过类名查找,此时需要输入具体的类名,然后会输出在该类中记录的日志:
继续操作:
输入4代表按照方法名称查找,此时需要输入具体的方法名,然后会输出在该方法中记录的日志:
继续操作:
输入5代表按日志类型查找,此时需要输入日志的级别,输入w代表warning级别,然后会输出warning级别的日志:
Sun May 19 19:41:15 CST 2019-[circularOrbit.socialNetworkCircle.deleteTrack]WARNING:Social:exception happens in class socialNetwork,method deleteTrack:The track is out of bound! handling:return false,exception message is printed on console
日志只有一条,显示试图删除一条越界的(不存在的)轨道,函数返回false,并将错误信息打印在控制台上。
使用等价类和边界值的测试思想,为各 ADT 添加 testing strategy,大部分测试在lab3中已经完成,lab4中主要增加对异常处理的测试,这一部分的测试策略是:人为的构造包含非法信息的文件,让增加了异常处理的代码读入这些文件,判断代码是否能识别出这是一个非法文件以及能否做出正确处理,包含非法信息的文件与正常文件都在txt文件夹中,截图如下:
与此同时,在lab3的基础上,lab4还对API接口中给出的方法、application(直接给用户使用的类)中的若干函数、functionTrackGame(管理田径轨道系统的类)中的若干函数、以及三个debug类做了测试,测试的基本思想都是给定一个参数,这个参数或是合法的或是非法的,让函数读入这个参数,判断函数的行为是否与预期相符,函数的行为包括返回值,抛出异常等。
对异常的测试用例有:包含非法信息的文件,以及相应的测试代码:
//轨道编号为负
//Testing strategy for exception
//采用带有特定异常的文件作为参数调用原子轨道系统的creatingTrackFromFiles函数
//判断该函数是否能够识别出这些异常
@Test
public void negativeTrackTest()
{
atomStructure temp = new atomStructure();
assertFalse(temp.creatingTrackFromFiles("AtomStructure_NegativeTrack."));
}
//出现指向自身的socialTie
//Testing strategy for exception
//采用带有特定异常的文件作为参数调用社交网络轨道系统的creatingTrackFromFiles函数
//判断该函数是否能够识别出这些异常
@Test
public void socialTieSelfTest()
{
socialNetworkCircle temp = new socialNetworkCircle();
assertFalse(temp.creatingTrackFromFiles("SocialNetworkCircle_SocialTieSelf."));
}
由于测试代码数量过多,在此不再赘述。
对于其他测试,测试用例包括:人为构造的参数(如田径轨道系统),以及相应的测试代码:
//Testing strategy for getDifference()
//用原子轨道系统作为参数调用getDifference函数,观察返回的difference
//各项特征是否与预期相符合
@Test
public void testDifference()
{
atomStructure test1 = new atomStructure();
atomStructure test2 = new atomStructure();
test1.creatingTrackFromFiles("AtomicStructure.");
test1.creatingTrack(test1.getTrackNumber2(), test1.getTrackObjectNumber());
test2.creatingTrackFromFiles("AtomicStructure_Medium.");
test2.creatingTrack(test2.getTrackNumber2(), test2.getTrackObjectNumber());
Difference<atomL1,atomE1> temp = api.getDifference(test2, test1);
System.out.println(temp.getDifferenceNumber());
assertTrue(temp.getDifferenceNumber() == 1);
assertTrue(temp.getNumberTrack().size() == 6);
}
//Testing strategy for getDifference()
//用田径比赛轨道系统作为参数调用getDifference函数,观察返回的difference
//各项特征是否与预期相符合
@Test
public void testDifference2()
{
functionTrackGame test1 = new functionTrackGame();
test1.creatingTrackFromFiles("TrackGame.");
//检查策略模式
trackOrganizer t = new trackOrganizer("r");
t.arrange(test1);
Difference<atomL1,atomE1> temp = api.getDifference(test1.getGroupTrackSystem().get(0), test1.getGroupTrackSystem().get(1));
System.out.println(temp.getDifferenceNumber());
assertTrue(temp.getDifferenceNumber() == 0);
assertTrue(temp.getStringTrack().size() == 5);
}
由于测试代码数量过多,在此不再赘述。
对test文件夹进行整体测试,项目的语句覆盖度,分支覆盖度,路径覆盖度如下图所示:
代码中一共出现了三处这样的错误,对一个变量进行赋值,但从来没有使用,由于对代码的逻辑不清楚导致。
FindMedianSortedArrays:
利用递归法求解两个有序数组A和B的中位数,将A分成两部分:left_A 和 rightA, 将B分成两部分:left_B 和 right_B。将 left_A 和 left_B 放入一个集合,并将 right_A 和 right_B 放入另一个集合。 再把这两个新的集合分别命名为 left_part 和 right_part,找到一种划分方法,使得
条件得以满足,即以下条件得以满足:
因此,按照以下步骤进行二分搜索:
removeComments:
程序的目的是去除代码中的注释,代码中的注释分为两种:块注释/…/和行注释//…,需要对读入的每一行代码的每一个字符进行查看,采用boolean变量进行辅助,将代码的正文加入到string列表中,而对于代码的注释部分不做任何操作,这样最后得到的string列表就是所求的去掉注释后的代码。
TopVotedCandidate:
设置一个目前得票最多的变量count,先循环得票人和时间,用hashmap或数组数组统计每个人的得票数,如果次数大于count,则更新count,用数组记录到目前时间得票最多的人(按时间顺序排列的目前得票多的人)。查找某一时间点得票最多的人时,用二分法查找按时间排序的数组。
FindMedianSortedArrays:
1.初始值i取错了
2.在递归的过程中对于iMin的减小和iMax的增大以i为基准,而不是以它们原来的值为基准。
3.对于奇数和偶数的判断有误。
removeComments:
1.没有在内存中开辟空间,直接使用char[] chars = line.toCharArray();语句,导致出现空指针错误。
2.在if的判断语句中出现了i + 1 <= line.length(),然后取i+1号元素的的典型错误,元素编号是从0开始的,而line.length()返回的却是字符串line的实际长度
3.在读到当前字符是‘/’,后一个字符是‘’,或当前字符是‘’,后一个字符是‘/’这种遇到注释块的情况时,忘记将i再次加1,导致将符号‘/’或 ‘*’读入最终代码中,造成错误。
4.忽略了一种情况://…类型的行注释。
TopVotedCandidate:
1.未对c进行加1操作,导致A中除第一次增加新列表之外再也没有增加新列表。
2.在函数q中出现了死循环,原因是赋值操作只是简单的赋值,而没有增加变量lo的值。
3.函数q中第一个while循环结束时lo多加了一次1,因此将lo赋值给i之前需要减1,但原函数没有此操作。
4.函数q的第二个while循环中的判断条件没有将投票的时间点计入,即少了一个=号。
5.最后取最大值时忘记将lo减1,因为第二个while循环结束后,lo多加了一次1,而数组是从0开始计数的,所以需要减1。
FindMedianSortedArrays:
1.将int i = (iMin + iMax + 1) / 2;改为int i = ((iMin + iMax) / 2);
2.将iMin = i + 1;改为iMin = iMin + 1;,将iMax = i - 1;改为iMax = iMax-1;
3.将(m + n + 1) % 2 == 1改为(m + n) % 2 == 1
removeComments:
1.对每一行字符串,使用charAt(i)语句来查看第i个字符,既保证了正确性,又节省了内存空间。
2.将i + 1 <= line.length()改成i + 1 < line.length()。
3.在遇到注释块的情况下将i再次加1。
4.在if-else条件判断语句中加上:
else if (!inBlock&&line.charAt(i) == '/' && i+1 < line.length() &&line.charAt(i+1) == '/')
{
//如果某一行是单独注释的,那么移除注释后这一行必须保留
//原因:单独注释之前是有空格的,
//这些空格也会被读入到newline之中,导致newline的长度不为0,即使break,这一行空格也
//会被加入到最终的目标代码中.
//改进方法:对newLine的length进行判断之前,先进行trim操作,如果newLine中全部都是空格,trim后其长度会变为0,但于题目要求不符,所以未做改进
//如果某一块是由/**/注释的,那么移除后这些行必须全部删除
break;
}
TopVotedCandidate:
1.先判断count映射里是否包括当前的person,包括则将相应的key值加1,否则将person对应1加入count映射中。
2.将函数q中的赋值操作lo = mi;改成lo = mi+1。
3.将第一个while循环之后的int i = lo;改成int i = lo-1。
4.将函数q的第二个while循环的if判断语句中的判断条件改为A.get(i).get(mi).time <= t。
5.将函数q最后的取最大值语句由int j = Math.max(lo, 0);改成int j = Math.max(lo-1, 0)。
测试代码如下:
package debug;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
//* Example 1: nums1 = [1, 3] nums2 = [2] The output would be 2.0
//*
//* Example 2: nums1 = [1, 2] nums2 = [3, 4] The output would be 2.5
//*
//* Example 3: nums1 = [1, 1, 1] nums2 = [5, 6, 7] The output would be 3.0
//*
//* Example 4: nums1 = [1, 1] nums2 = [1, 2, 3] The output would be 1.0
public class first
{
FindMedianSortedArrays f = new FindMedianSortedArrays();
//1.0
int a1[] = new int[] {1,1};
int b1[] = new int[] {1,2,3};
//2.0
int a2[] = new int[] {1,3};
int b2[] = new int[] {2};
//2.5
int a3[] = new int[] {1,2};
int b3[] = new int[] {3,4};
//3.0
int a4[] = new int[] {1,1,1};
int b4[] = new int[] {5,6,7};
//2.0
int a5[] = new int[] {1,1};
int b5[] = new int[] {2,3,4};
//Testing strategy for findMedianSortedArrays
//采用用不同类型的数组对作为参数调用findMedianSortedArrays,
//判断返回的结果是否与预期相符
@Test
public void test1()
{
assertEquals(1.0,f.findMedianSortedArrays(a1,b1) );
assertEquals(2.0,f.findMedianSortedArrays(a2,b2) );
assertEquals(2.5,f.findMedianSortedArrays(a3,b3) );
assertEquals(3.0,f.findMedianSortedArrays(a4,b4) );
assertEquals(2.0,f.findMedianSortedArrays(a5,b5) );
}
}
测试结果如下:
removeComments:
测试代码如下:
package debug;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import org.junit.Test;
public class second
{
//Testing strategy for RemoveComments
//采用用不同的源代码作为参数调用findMedianSortedArrays,
//判断返回的结果是否与人工去除注释的源代码相同
@Test
public void test()
{
RemoveComments test = new RemoveComments();
ArrayList<String> atxt1 = new ArrayList<String>();
ArrayList<String> atxt = new ArrayList<String>();
String txt;
String txt2;
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("debugTxt/atxt"))))
{
while ((txt = in.readLine()) != null)
{
atxt.add(txt);
}
}
catch (Exception e)
{
// TODO: handle exception
e.printStackTrace();
}
String[] strings = new String[atxt.size()];
atxt.toArray(strings);
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("debugTxt/atxt1"))))
{
while ((txt = in.readLine()) != null)
{
atxt1.add(txt);
}
}
catch (Exception e)
{
// TODO: handle exception
e.printStackTrace();
}
System.out.println(atxt1.size());
System.out.println(test.removeComments(strings).size());
assertTrue(atxt1.size() == test.removeComments(strings).size());
for (int i = 0; i < atxt1.size(); i++)
{
assertEquals(atxt1.get(i).trim(), test.removeComments(strings).get(i).trim());
}
}
@Test
public void test2()
{
RemoveComments test = new RemoveComments();
ArrayList<String> btxt1 = new ArrayList<String>();
ArrayList<String> btxt = new ArrayList<String>();
String txt2;
try (BufferedReader in2 = new BufferedReader(new InputStreamReader(new
FileInputStream("debugTxt/btxt"))))
{
while ((txt2 = in2.readLine()) != null)
{
btxt.add(txt2);
}
}
catch (Exception e)
{
// TODO: handle exception
e.printStackTrace();
}
String[] stringsb = new String[btxt.size()];
btxt.toArray(stringsb);
try (BufferedReader in3 = new BufferedReader(new InputStreamReader(new FileInputStream("debugTxt/btxt1"))))
{
while ((txt2 = in3.readLine()) != null)
{
btxt1.add(txt2);
}
}
catch (Exception e)
{
// TODO: handle exception
e.printStackTrace();
}
System.out.println(btxt1.size());
System.out.println(test.removeComments(stringsb).size());
assertTrue(btxt1.size() == test.removeComments(stringsb).size());
for (int i = 0; i < btxt1.size(); i++)
{
assertEquals(btxt1.get(i).trim(), test.removeComments(stringsb).get(i).trim());
}
}
}
测试结果如下:
package debug;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.Test;
public class third
{
//Testing strategy for TopVotedCandidate
//采用特定的数组参数调用TopVotedCandidate,
//判断特定时刻返回的候选人是否与预期相符
@Test
public void test()
{
// [3],[12],[25],[15],[24],[8]
int persons[] = new int[] {0,1,1,0,0,1,0};
int times[] = new int[] {0,5,10,15,20,25,30};
TopVotedCandidate test = new TopVotedCandidate(persons, times);
assertEquals(0,test.q(3));
assertEquals(1,test.q(12));
assertEquals(1,test.q(25));
assertEquals(0,test.q(15));
assertEquals(0,test.q(24));
assertEquals(1,test.q(8));
}
}
(1) 健壮性和正确性,二者对编程中程序员的思路有什么不同的影响?
健壮性要求程序员始终假定客户端会对程序做各种各样的破坏,需要将程序的关键部分保护好。正确性则要求程序员始终保证程序的逻辑不出错,同时要求程序员保持思维的严谨性,不忽略任何一种可能的情况。
(2) 为了应对1%可能出现的错误或异常,需要增加很多行的代码,这是否划算?(考虑这个反例:民航飞机上为何不安装降落伞?)
这需要看这个错误的致命程度以及应对这个错误需要加多少行代码,如果代码量不多,则应该加上。如果错误致命但发生的可能性极小,且增加的代码量会严重影响程序性能,这时选择不加,如果能让程序在绝大多数情况下都保持高效、良好的运行状态,出现小概率错误也无可厚非。
(3) “让自己的程序能应对更多的异常情况”和“让客户端/程序的用户承担确保正确性的职责”,二者有什么差异?你在哪些编程场景下会考虑遵循前者、在哪些场景下考虑遵循后者?
前者针对程序员,后者是给用户看的。在防御式编程下需要更多的考虑前者;而在追求可维护性编程、追求高聚合、低耦合编程的条件下,需要更多的考虑后者,此时不应该让程序代替客户端做太多的工作,以免形成高耦合。
(4) 过分谨慎的“防御”(excessively defensive)真的有必要吗?如果你在完成Lab5的时候发现Lab5追求的是I/O大文件时的性能(时间/空间),你是否会回过头来修改你在Lab3和本实验里所做的各类defensive措施?如何在二者之间取得平衡?
防御是有必要的,毕竟程序在将来会遇到很多种情况;如果同时还要求程序追求I/O大文件时的性能(时间/空间),那么需要去掉一些对于小概率错误的防御措施,以此为代价,让程序在多数情况下都能保持高效、良好的运行状态。
(5) 通过调试发现并定位错误,你自己的编程经历中有总结出一些有效的方法吗?请分享之。Assertion和log技术是否会帮助你更有效的定位错误?
最原始(但也是很有用的)的方法就是在需要调试错误的地方打印出相关的参数,以此来判断哪些程序执行了,哪些程序没有执行,进而根据这些信息进行debug,更专业一点的就是设置断点,在程序较为复杂的时候,这个方法很管用。Assertion能让我更有效的定位错误,因为它会直接终止程序,点击提示信息时就会直接定位到程序出错的地方,不要自己慢慢找;log对于定位错误也有一定的帮助,但是目前来看帮助不大,毕竟用Assertion效率更高。
(6) 怎么才是“充分的测试”?代码覆盖度100%是否就意味着100%充分的测试
充分的测试是对代码核心部分全部进行了测试,而且用了不同情况下的参数进行测试;代码覆盖度100%不一定就意味着100%充分的测试,因为可能还有未考虑的情况,而可能恰好这种情况就能让程序崩溃。