欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Lab-4 Report

程序员文章站 2024-02-10 08:40:52
...

1 实验目标概述

本次实验重点训练学生面向健壮性和正确性的编程技能,利用错误和异常处
理、断言与防御式编程技术、日志/断点等调试技术、黑盒测试编程技术,使程序
可在不同的健壮性/正确性需求下能恰当的处理各种例外与错误情况,在出错后
可优雅的退出或继续执行,发现错误之后可有效的定位错误并做出修改。
实验针对Lab 3 中写好的ADT代码和基于该ADT的三个应用的代码,使用
以下技术进行改造,提高其健壮性和正确性:
错误处理
异常处理
Assertion 和防御式编程
日志
调试技术
黑盒测试及代码覆盖度

2 实验环境配置

在eclipse的marketplace安装了spotbugs,远程clone

3 实验过程

请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。

3.1 Error and Exception Handling

3.1.1 处理输入文本中的三类错误

在Exception下定义了13种错误,包括DifferentDateException, DifferentFlightException, DifferentPlaneException, ElementFormatException, FlightFormatException, MoreThanOneDayException, OneDecimalException, PlaneAgeException, PlaneNumberException, PlaneSeatException, SameAirportException, SameFlightException来处理不同的错误,用这些exception足够处理很多异常了。测试文本保存在txt下。

public class DifferentDateException extends Exception {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public DifferentDateException() {super();}
	
	public DifferentDateException(String message) {super(message);}

}

在可能出现异常的地方直接throw,然后通过exception chain将其传递到App统一处理,处理时采用try-catch,catch到后记录上日志,并且提示用户重新读入文件。

3.1.2 处理客户端操作时产生的异常

用了自定义的LocalConflictException, LocalDeleteException, ResourceConflictException, ResourceDeleteException和系统自带的IllegalStateException对客户端操作的异常进行处理。catch到这些异常后,在控制台打印提示信息,并且恢复操作前的状态,即是忽略了用户的操作。这样可以保证即使用户做出一些违规操作系统仍然能够正常运行。

public class ResourceConflictException extends Exception{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public ResourceConflictException() {super();}
	
	public ResourceConflictException(String message) {super(message);}
}

3.2 Assertion and Defensive Programming

3.2.1 checkRep()检查rep invariants

rep详细请见源代码,此处讲几个例子:

  1. 航班的起飞时间必须要早于降落时间,起飞和降落的机场不相同;
  2. 高铁的起始站和各途经站不能重复,所分配的各个车厢不能有相同的编号
  3. 课程上课时间早于下课时间
	private void checkRep() {
		assert tle != null;
		assert sre != null;
		assert super.getTime().getStartTime().isBefore(super.getTime().getFinishTime());
		assert getArrival() == null || getDeparture() == null || !getArrival().equals(getDeparture());
	}

3.2.2 Assertion/异常机制来保障pre-/post-condition

对于很多pre-condition,采用assertion保障

  1. 在set时需要原来的属性为null
  2. 在change是需要原来的属性不为null
    在状态改变时,对于非法的状态改变采用异常机制
@Override
	public EntryState finish() {
		throw new IllegalStateException("未启动");
	}

	@Override
	public EntryState set() {
		return instance;
	}

	@Override
	public EntryState block() {
		throw new IllegalStateException("未启动");
	}

	@Override
	public EntryState unblock() {
		throw new IllegalStateException("未启动");
	}

3.2.3 你的代码的防御式策略概述

在Client和ADT中嵌入了不少的防御策略。对于client的输入,ADT会用assertion来保障前置条件的正确,而ADT本身又利用了异常处理机制来保障了程序的健壮性。不仅如此,对于Client端的一些输入我也采用了异常处理机制对齐进行处理,争取不需要某个模块的运行就已经发现并提示了错误。

3.3 Logging

3.3.1 异常处理的日志功能

利用log4j和commons-logging实现了日志功能。抛出异常时直接加上日志的log即可。采用WARN级别或者是ERROR级别进行记录。
FlightScheduleApp.log.log(Level.WARN, e.getMessage(), e);

3.3.2 应用层操作的日志功能

采用INFO级别进行记录。
FlightScheduleApp.log.info(“读取文件” + filename);

3.3.3 日志查询功能

根据配置文件,不同app的log都记录到了Log下的*.log文件当中。
配置文件:

log4j.rootLogger=INFO

log4j.logger.Flight=INFO,Flight
log4j.logger.Course=INFO,Course
log4j.logger.Train=INFO,Train

log4j.appender.Flight=org.apache.log4j.FileAppender
log4j.appender.Flight.File=src/Log/FlightLog.log
log4j.appender.Flight.Append=false
log4j.appender.Flight.layout=org.apache.log4j.PatternLayout
log4j.appender.Flight.layout.ConversionPattern=%d %p [%c] = %m%n

log4j.appender.Course=org.apache.log4j.FileAppender
log4j.appender.Course.File=src/Log/CourseLog.log
log4j.appender.Course.Append=false
log4j.appender.Course.layout=org.apache.log4j.PatternLayout
log4j.appender.Course.layout.ConversionPattern=%d %p [%c] = %m%n

log4j.appender.Train=org.apache.log4j.FileAppender
log4j.appender.Train.File=src/Log/TrainLog.log
log4j.appender.Train.Append=false
log4j.appender.Train.layout=org.apache.log4j.PatternLayout
log4j.appender.Train.layout.ConversionPattern=%d %p [%c] = %m%n

在日志查询时,采用正则表达式进行读入,用jtable将日志加以显示。查询时需要输入时间段。

3.4 Testing for Robustness and Correctness

3.4.1 Testing strategy

testing strategy主要是检测了一下null和普通的情况,将输入数据划分为了这两个等价类进行测试。

3.4.2 测试用例设计

对于App和Exception,我构造了一些数据和文件人为的进行了测试,主要是由于这部分我都是采用GUI实现的,Junit不太好处理这方面和Client端的操作。

而Junit中则使用了如下的测试

@Test
	public void setLocationTest() {
		fe.setLocation(loc);
		assertEquals(loc, fe.getLocation());
	}
	
	@Test
	public void changeLocationTest() {
		fe.setLocation(loc);
		fe.changeLocation(loc_1);
		assertEquals(loc_1, fe.getLocation());
	}
	
	@Test
	public void setTest() {
		fe.set(plane);
		assertEquals(plane, fe.get());
	}
	
	@Test
	public void getLocationssTest() {
		List<Location> s = new ArrayList<>();
		s.add(loc);
		fe.setLocation(loc);
		assertEquals(s, fe.getLocationss());
	}
	
	@Test
	public void getTimesTest() {
		List<Timeslot> t = new ArrayList<>();
		t.add(x);
		assertEquals(t, fe.getTimes());
	}
	
	@Test
	public void getResourceTest() {
		fe.set(plane);
		List<Teacher> pl = new ArrayList<>();
		pl.add(plane);
		assertEquals(pl, fe.getResource());
	}

	@Test
	public void toStringTest() {
		assertEquals("Course:1", fe.toString());
	}
	
	@Test
	public void cancelTest() {
		fe.set(plane);
		fe.cancel();
	}

3.5 SpotBugs tool

这部分没有发现什么错误,可能是防御策略比较得当的原因。

3.6 Debugging

3.6.1 EventManager程序

temp里保存的是该点的事件数的一个增量start+1,end-1,事件数为某点的start+end。所以在查询事件数时,直接将之前所有点的temp累加即可,从中找到最大值。
测试时发现不同天的时候会出问题,debug发现原来temp的键表示会出现重复,于是将其改为start += (day * 25); end += (day * 25);这样就避免了重复。

3.6.2 LowestPrice程序

LowestPrice实际上是采用了一个递归的方式实现。感觉有点贪心法的味道。大多数是读代码的时候就发现了有问题将其改了过来,因为代码的思想本身没有多大的错误,主要集中在语法和一些细节上。

3.6.3 FlightClient/Flight/Plane程序

程序本身根据一个Random对List中的计划项的飞机是否会有冲突进行了检测,但是可能会陷入一个没有可分配的飞机的死循环,为其加入一个flag条件,我用的是一个set保存对于该计划项已经查过的飞机,如果全都查过了就退出循环。还有错误是关于Calendar比较的错误,这个比较简单,eclipse都有提示了。