文章目录
实验目标概述
本次实验重点训练学生面向健壮性和正确性的编程技能,利用错误和异常处 理、断言与防御式编程技术、日志/断点等调试技术、黑盒测试编程技术,使程序 可在不同的健壮性/正确性需求下能恰当的处理各种例外与错误情况,在出错后 可优雅的退出或继续执行,发现错误之后可有效的定位错误并做出修改。
实验针对 Lab 3 中写好的 ADT 代码和基于该 ADT 的三个应用的代码,使用 以下技术进行改造,提高其健壮性和正确性:
错误处理
异常处理
Assertion 和防御式编程
日志
调试技术
黑盒测试及代码覆盖度
实验环境配置
在Eclipse中找到Marketplace下载SpotBugs安装
实验过程
3.1 Error and Exception Handling
3.1.1处理输入文本中的三类错误
3.1.1.1输入文件中存在不符合语法规则的语句
- IllegalPatternException
不符合规范模式的输入文件,如行数不对,Plane打错字符等,使其无法匹配到正则表达式的异常
Pattern pattern = Pattern.compile(
"Flight:(.*?),(.*?)\n\\{\nDepartureAirport:(.*?)\nArrivalAirport:(.*?)\nDepatureTime:(.*?)\nArrivalTime:(.*?)\nPlane:(.*?)\n\\{\nType:(.*?)\nSeats:(.*?)\nAge:(.*?)\n\\}\n\\}\n");
Matcher matcher = pattern.matcher(info);
if (!matcher.find())
throw new IllegalPatternException("输入文件不符合规范模式");
- IllegalDateException
首行的日期不符合yyyy-MM-dd的模式,抛出该异常
Pattern pattern2 = Pattern.compile("((19|20)[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])");
Matcher matcher2 = pattern2.matcher(FlightDate);
if (!matcher2.find())
throw new IllegalDateException("Date " + FlightDate + "不符合规范模式");
- IllegalFlightNumberException
不合法的航班号,即不符合lab3中规定的两个英文字母和四个数字的要求
if (Character.isUpperCase(Number.charAt(0)) && Character.isUpperCase(Number.charAt(1))) {
for (int i = 2; i < Number.length(); i++) {
if (!Character.isDigit(Number.charAt(i)))
throw new IllegalFlightNumberException("FlightNumber:" + Number + "不符合规范模式");
}
if (Number.length() > 6)
throw new IllegalFlightNumberException("FlightNumber " + Number + "不符合规范模式");
} else
throw new IllegalFlightNumberException("FlightNumber " + Number + "不符合规范模式");
- IllegalPlaneNumberException
不合法的飞机编号,不符合第一位为N或B,后面为四位数字的规定
if (Number.charAt(0) == 'N' || Number.charAt(0) == 'B') {
for (int i = 1; i < Number.length(); i++) {
if (!Character.isDigit(Number.charAt(i)))
throw new IllegalPlaneNumberException("PlaneNumber " + Number + "不符合规范模式");
}
if (Number.length() > 5)
throw new IllegalPlaneNumberException("PlaneNumber " + Number + "不符合规范模式");
} else
throw new IllegalPlaneNumberException("PlaneNumber " + Number + "不符合规范模式");
- IllegalPlaneSeatException
不合法的飞机座位数,即超过了600或小于50时抛出异常
if (seats > 600 || seats < 50)
throw new IllegalPlaneSeatException("PlaneSeats " + seats + "不符合规范模式");
- IllegalPlaneTypeException
Type只有数字和字母组成,否则抛出该异常
String regex = "^[a-z0-9A-Z]+$";
if (!type.matches(regex))
throw new IllegalPlaneTypeException("PlaneType " + type + "不符合规范模式");
- IllegalPlaneAgeExcepion
Age只能是整数或一位小数,否则抛出该异常
String regex2 = "^[+]?([0-9]+(.[0-9]{1})?)$";
String regex3 = "^[0-9]*[1-9][0-9]*$";
if (age >= 0 && age <= 30) {
if (!strAge.matches(regex2) && !strAge.matches(regex3))
throw new IllegalPlaneAgeException("PlaneAge " + age + "不符合规范模式");
} else {
throw new IllegalPlaneAgeException("PlaneAge " + age + "不符合规范模式");
}
- IllegalAirportException
起飞和降落机场相同时,是不符合规定的,抛出该异常
if (departureLocation.equals(arrivalLocation))
throw new IllegalAirportException("Airport " + departureLocation + "同时为起始终止机场");
3.1.1.2输入文件中存在标签完全一样的元素
SameLabelException:两个航班航班号日期全都一样,抛出该异常
for (FlightEntry<Plane> f : flights) {
if (f.getPlanningEntryName().equals(planningEntryName)) {
if (f.getDepartureTime().getMonthValue() == dt.getMonthValue()
&& f.getDepartureTime().getDayOfMonth() == dt.getDayOfMonth())
throw new SameLabelException(f.getPlanningEntryName() + "存在相同日期的航班");
3.1.1.3输入文件中各元素之间的依赖关系不正确
- DateInconsistentException
首行日期与信息内部出发日期不相同,抛出该异常
LocalDateTime dt = localDate.atStartOfDay();
DateTimeFormatter df2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dt2 = LocalDateTime.parse(departureTime, df2);
if (!(dt.getMonth() == dt2.getMonth()) || !(dt.getDayOfMonth() == dt2.getDayOfMonth())
|| !(dt.getYear() == dt2.getYear()))
throw new DateInconsistentException("首行时间" + dt + "与内部时间" + dt2 + "不一致");
- SameNumberLabelInconsistentException
相同航班号内部信息不一致
if (f.getDepartureTime().getHour() != dt.getHour() || f.getDepartureTime().getMinute() != dt.getMinute()
|| f.getArrivalTime().getHour() != at.getHour()
|| f.getArrivalTime().getMinute() != at.getMinute())
throw new SameNumberLabelInconsistentException(f.getPlanningEntryName() + "相同航班出发到达时间不一致");
- PlaneElementInconsistentException
相同飞机内部信息不一致
for (FlightEntry<Plane> fl : flights) {
if (fl.getResourceName().equals(planeName)) {
if (!fl.getResource().getPlaneType().equals(type) || fl.getResource().getPlaneAge() != age
|| fl.getResource().getPlaneSeats() != seats)
throw new PlaneElementInconsistentException("PlaneNumber " + "相同飞机号" + planeName + "的内部信息不一致");
}
}
- TimeGapOveraDayException
起飞和降落时间间隔大于一天
if (dt.plusDays(1).isBefore(at)) {
throw new TimeGapOveraDayException("Date " + dt + "与" + at + "相差大于一天");
}
3.1.2处理客户端操作时产生的异常
- CancelWrongSateException
当前状态不能取消时,用户执行取消操作抛出该异常
if (f.getStateofPlanningEntry().equals("WAITING")
|| f.getStateofPlanningEntry().equals("ALLOCATED")) {
f.cancel();
isContain = true;
} else {
throw new CancelWrongStateException(
"State " + f.getStateofPlanningEntry() + "can't be cancelled");
}
- DeleteUsingLocationException
用户删除的位置已经被分配或者正在使用时,抛出该异常
if (fe.getOriginLocation().equals(location.getLocation())
|| fe.getTerminalLocation().equals(location.getLocation())) {
throw new DeleteUsingLocationException("Location " + location.getLocation() + "is using.");
}
- DeleteUsingResourceException
用户删除的资源已经被分配或者正在使用时,抛出该异常
if (fe.getResourceName().equals(plane.getPlaneNumber())) {
throw new DeleteUsingResourceException("Plane " + plane.getPlaneNumber() + "is using.");
-
AllocateConflictLocationException
用户分配的位置造成了位置使用冲突 -
AllocateConflictResourceException
用户分配的资源造成了资源使用冲突
3.2Assertion and Defensive Programming
3.2.1 checkRep()检查rep invariants
3.2.1.1Resource
以Train为例:AF、RI如下
设计对应的checkRep()
3.2.1.2LocationEntry
以MultipleLocationEntry为例AF、RI
设计对应的checkRep()
3.2.1.3ResourceEntry
以MultipleSortedResourceEntry为例AF、RI
checkRep()
3.2.1.4PlanningEntry
以TrainEntry为例AF、RI设计如下
checkRep()
Assertion/异常机制来保障pre-/post-condition
如加入资源检查资源是否为空,是否命名
删除资源也一样
添加资源项检查是否为空
检查后置条件:运行计划项后检查状态是否改变
3.2.3你的代码的防御式策略概述
用户输入的计划项,要判断是否符合规范模式,如flight中的航班号,飞机号等等,应该进行判断,如果不是就抛出异常,进行处理。
方法内部要判断,如此操作是否符合规范,如分配一个资源的时候,要进行资源冲突检查,活动app中更改一个位置要进行位置冲突检查。在计划项运行,暂停,停止,分配的操作中,在state类中添加WrongStateChangeException如果当前计划项的状态不符合转换要求,抛出该异常,Collection类中捕获异常,进行处理。
对某个计划项操作时,如果改计划项不在计划集合中,也需要能够判断出来,并返回false表示,进行不了用户指定的操作,让用户重新输入。
3.3Logging
3.3.1 异常处理的日志功能
在捕获到的异常中,添加logger.SEVERE()或logger.WARNING()
3.3.2 应用层操作的日志功能
在app中初始化一个logger
在app的主类中设置logger并且输出第一条信息
在每一个用户的选择下,增加一条日志信息,显示用户的操作
3.3.3日志查询功能
在Board中添加查询日志的方法按行读入文件,正则表达式判断,匹配出日期,日志级别,异常信息
还要进行异常判断
加入信息到表格中
3.4Testing for Robustness and Correctness
3.4.1Testing strategy
3.4.2测试用例设计
针对3.1设计的每个异常进行test
设计测试文件,读取文件,然后捕获异常,这里我重写了inputByFile使其能直接抛出异常,而不是捕获异常,这样在test里可以直接判断
还设计一个正确的文件,调用collection的方法进行测试
针对各个类,resourceEntry,PlanningEntry,LocationEntry分别构造一些测试用例,调用其中的方法进行测试
3.4.3测试运行结果与EclEmma覆盖度报告
有许多代码是直接向屏幕输出信息,无法测试,还有客户端代码无法测试
3.5SpotBugs tool
没有检测到bug
3.6Debugging
3.6.1EventManager程序
理解待调试程序的代码思想:
该程序是求当天中,所有事件重合的最大数,map中保存0到24的整数点,表示所有事件,value表示当前时间最大事件数,遍历所有的key找到最大的value即可
发现并定位错误的过程:
这里我将TreeMap又套了一层map,表示365天的记录,每当输入了一个天数,开始时间和结束时间,先判断当天的map有没有加入到temp中,没有的话就加入,同样遍历当天的map判断从start到end是否加入了该键值,如果没加入初始化为value=0
然后将从start到end的value加一,表示这些时间的事件数增加了一个,end处的value-1表示这里结束了一个事件,然后就可以遍历当天day的value找到最大的一个输出
修复之后的测试结果
3.6.2LowestPrice程序
理解待调试程序的代码思想:
该程序是给出几种优惠方案,在购买物品不超过需求的情况下,选择花费最少的方案,并输出最小花费
发现并定位错误的过程:
发现他判断优惠方案中的数量是否大于需求不完善,所以重新修改了一下,并且把递归求花费变成了,直接将他的优惠方案钱数加上,剩余的需求乘单价,即调用了一下dot方法
修复之后的测试结果
3.6.3FlightClient/Flight/Plane程序
理解待调试程序的代码思想:
该程序是遍历航班,随机找飞机,看是否能够避免时间冲突的情况下安排飞机
发现并定位错误的过程:
发现这里应该用equals()进行比较
然后这里比较日期应该用before和after来比较
改正如下:
还有sort方法应该重写comparator应该比较两个航班的起始和终止时间
修复之后的测试结果