我又来了,大佬们别复制,必错(狗头)
打击不诚信行为,从每个HITer做起,鄙视代写+抄袭+伸手党
希望同学建议,是不是该删点代码?
本项目计划于5.19日实验课验收
更新中
如果有所参考 请点点关注 点点赞GitHub Follow一下谢谢
2020春计算机学院《软件构造》课程Lab4实验报告
- Software Construction 2020 Spring
- Lab-4 Debugging, Exception Handling, and Defensive Programming
- 版权声明:
1183710109 郭茁宁
文章目录
- 我又来了,大佬们别复制,必错(狗头)
- 打击不诚信行为,从每个HITer做起,*鄙视代写+抄袭+伸手党*
- 希望同学建议,是不是该删点代码?
- 本项目计划于5.19日实验课验收
- `更新中`
- 如果有所参考 请点点关注 点点赞[GitHub Follow一下谢谢](https://github.com/gzn00417)
- 2020春计算机学院《软件构造》课程Lab4实验报告
- 1 实验目标概述
- 2 实验环境配置
- 3 实验过程
- 3.1 Error and Exception Handling
- 3.1.1 处理输入文本中的三类错误
- 3.1.1.1 DataPatternException
- 3.1.1.2 EntryNumberFormatException
- 3.1.1.3 SameAirportException
- 3.1.1.4 TimeOrderException
- 3.1.1.5 PlaneNumberFormatException
- 3.1.1.6 PlaneTypeException
- 3.1.1.7 PlaneSeatRangeException
- 3.1.1.8 PlaneAgeFormatException
- 3.1.1.9 SameEntryException
- 3.1.1.10 HugeTimeGapException
- 3.1.1.11 EntryInconsistentInfoException
- 3.1.1.12 PlaneInconsistentInfoException
- 3.1.1.13 SameEntrySameDayException
- 3.1.2 处理客户端操作时产生的异常
- 3.2 Assertion and Defensive Programming
- 3.3 Logging
1 实验目标概述
略
2 实验环境配置
略
3 实验过程
3.1 Error and Exception Handling
在data/Exceptions/中构造了错误数据,并在ExceptionTest.java
中测试了这些错误。
3.1.1 处理输入文本中的三类错误
第1-8个为不符合语法规则错误,第9个为元素相同错误,第10-13个为依赖关系不正确错误。
处理方法为:
try {
……
throw new ……Exception();
} catch (……Exception e1) {
logger.log(Level.WARNING, e1.getMessage(), e1);
……
}
3.1.1.1 DataPatternException
原因:由于数据的常量错误而没有匹配到单个元素。
抛出异常方法:在正则表达式匹配时,若没有匹配到则抛出该错误。
if (!matcher.find()) {
throw new DataPatternException("Data: " + stringInfo + " mismatch Pattern.");
}
3.1.1.2 EntryNumberFormatException
原因:计划项编号不符合规则。
抛出异常方法:检查是否符合“前两个字符为大写字母,后2-4个字符为数字”。
/**
* check entry number
* @param planningEntryNumber
* @throws EntryNumberFormatException
*/
public static void checkEntryNumber(String planningEntryNumber) throws EntryNumberFormatException {
if (Character.isUpperCase(planningEntryNumber.charAt(0))
&& Character.isUpperCase(planningEntryNumber.charAt(1))) {
for (int i = 2; i < planningEntryNumber.length(); i++) {
if (!Character.isDigit(planningEntryNumber.charAt(i)))
throw new EntryNumberFormatException(planningEntryNumber + " has incorrect format.");
}
} else
throw new EntryNumberFormatException(planningEntryNumber + " has incorrect format.");
}
3.1.1.3 SameAirportException
原因:起飞和到达机场相同引起的错误。
抛出异常方法:对比两个机场字符串是否相等。
/**
* check airports are different
* @param departureAirport
* @param arrivalAirport
* @throws SameAirportException
*/
public static void checkDiffAirport(String departureAirport, String arrivalAirport) throws SameAirportException {
if (departureAirport.equals(arrivalAirport))
throw new SameAirportException(departureAirport + " is the same with " + arrivalAirport + " .");
}
3.1.1.4 TimeOrderException
原因:起飞时间应该在到达时间之前(不能相等)。
抛出异常方法:首先try时间能否被parse,若不行则抛出DateTimeParseException;否则在finally中使用LocalDateTime.isBefore()方法比较时间先后。
/**
* check time format and departure is before arrival
* @param departureTime
* @param arrivalTime
* @throws TimeOrderException
* @throws DateTimeParseException
*/
public static void checkTime(String departureTime, String arrivalTime)
throws TimeOrderException, DateTimeParseException {
LocalDateTime dt = null, at = null;
try {
dt = LocalDateTime.parse(departureTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
at = LocalDateTime.parse(arrivalTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
} catch (Exception e) {
throw new DateTimeParseException("The date time is not matched.", departureTime + arrivalTime, 0);
} finally {
if (dt != null && at != null) {
if (!dt.isBefore(at))
throw new TimeOrderException(
"Departure time " + departureTime + " is not before arrival time " + arrivalTime + " .");
}
}
}
3.1.1.5 PlaneNumberFormatException
原因:飞机编号不符合格式。
抛出异常方法:检查字符串长度以及首字母、后4位数字。
/**
* check plane number
* @param planeNumber
* @throws PlaneNumberFormatException
*/
public static void checkPlaneNumber(String planeNumber) throws PlaneNumberFormatException {
if (planeNumber.length() == 5 && (planeNumber.charAt(0) == 'N' || planeNumber.charAt(0) == 'B')) {
for (int i = 1; i < planeNumber.length(); i++) {
if (!Character.isDigit(planeNumber.charAt(i)))
throw new PlaneNumberFormatException(planeNumber + " has incorrect format.");
}
} else
throw new PlaneNumberFormatException(planeNumber + " has incorrect format.");
}
3.1.1.6 PlaneTypeException
原因:飞机类型不符合格式。
抛出异常方法:检查是否由字母和数字构成。
/**
* check plane type
* @param strType
* @throws PlaneTypeException
*/
public static void checkPlaneType(String strType) throws PlaneTypeException {
for (int i = 0; i < strType.length(); i++) {
char ch = strType.charAt(i);
if (!(Character.isAlphabetic(ch) || Character.isDigit(ch)))
throw new PlaneTypeException(strType + " has incorrect format.");
}
}
3.1.1.7 PlaneSeatRangeException
原因:飞机座位数范围错误。
抛出异常方法:转换为整数比较范围。
/**
* check plane seat range
* @param strSeats
* @throws PlaneSeatRangeException
*/
public static void checkPlaneSeat(String strSeats) throws PlaneSeatRangeException {
int intSeats = Integer.valueOf(strSeats);
if (intSeats < 50 || intSeats > 600)
throw new PlaneSeatRangeException(intSeats + " is not in [50, 600].");
}
3.1.1.8 PlaneAgeFormatException
原因:飞机年龄非一位小数或整数,且介于0-30之间
抛出异常方法:查找小数点的位置,与字符串长度比较,得出几位小数,并查找区间。
/**
* check plane age format
* @param strAge
* @throws PlaneAgeFormatException
*/
public static void checkPlaneAge(String strAge) throws PlaneAgeFormatException {
double age = Double.valueOf(strAge);
if (strAge.indexOf(".") < strAge.length() - 2 || age < 0 || age > 30)
throw new PlaneAgeFormatException();
}
3.1.1.9 SameEntryException
原因:存在两个航班,飞机和航班号都相等。
抛出异常方法:遍历所有计划项,两两比较是否存在上述条件。
/**
* check dates and numbers conflict
* @throws SameEntryException
*/
public void checkDateNumberConflict() throws SameEntryException {
List<PlanningEntry<Resource>> entries = this.getAllPlanningEntries();
int n = entries.size();
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (i != j) {
PlanningEntry<Resource> e1 = entries.get(i), e2 = entries.get(j);
if (e1.getPlanningEntryNumber().equals(e2.getPlanningEntryNumber())) {
if (((FlightSchedule<Resource>) e1).getResource()
.equals(((FlightSchedule<Resource>) e2).getResource()))
throw new SameEntryException(e1.getPlanningEntryNumber() + " and "
+ e2.getPlanningEntryNumber() + " are the same entries.");
}
}
}
}
}
3.1.1.10 HugeTimeGapException
原因:起飞时间和到达时间超过一天。
抛出异常方法:判断每个计划项的起飞时间晚1d是否比到达时间晚。
/**
* check gap between leaving and arrival
* @throws HugeTimeGapException
*/
public void checkTimeGap() throws HugeTimeGapException {
List<PlanningEntry<Resource>> entries = this.getAllPlanningEntries();
int n = entries.size();
for (int i = 0; i < n - 1; i++) {
FlightSchedule<Resource> e = (FlightSchedule<Resource>) entries.get(i);
LocalDateTime t1 = e.getTimeLeaving(), t2 = e.getTimeArrival();
if (t1.plusDays(1).isBefore(t2))
throw new HugeTimeGapException(t1.toString() + " is to early than " + t2.toString());
}
}
3.1.1.11 EntryInconsistentInfoException
原因:相同航班号的航班信息(起降地点/时间)不一致。
抛出异常方法:检查每一对计划项,得到其时间和地点对象。
/**
* check entry information consistent
* @throws EntryInconsistentInfoException
*/
public void checkEntryConsistentInfo() throws EntryInconsistentInfoException {
List<PlanningEntry<Resource>> entries = this.getAllPlanningEntries();
int n = entries.size();
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (i != j) {
FlightSchedule<Resource> e1 = (FlightSchedule<Resource>) entries.get(i),
e2 = (FlightSchedule<Resource>) entries.get(j);
if (e1.getPlanningEntryNumber().equals(e2.getPlanningEntryNumber())) {
LocalTime t11 = e1.getTimeLeaving().toLocalTime(), t12 = e1.getTimeArrival().toLocalTime(),
t21 = e2.getTimeLeaving().toLocalTime(), t22 = e2.getTimeArrival().toLocalTime();
if (!(t11.equals(t21) && t12.equals(t22)) || !e1.getLocation().equals(e2.getLocation()))
throw new EntryInconsistentInfoException(e1.getPlanningEntryNumber() + " and " e2.getPlanningEntryNumber() + " is inconsistent.");
}
}
}
}
}
3.1.1.12 PlaneInconsistentInfoException
原因:不同的航班中出现相同的飞机。
抛出异常方法:遍历每一对飞机,若飞机号相同,但内容不相同,则出现不一致信息。
/**
* check plane information consistent
* @throws PlaneInconsistentInfoException
*/
public void checkPlaneConsistentInfo() throws PlaneInconsistentInfoException {
Set<Resource> planes = this.getAllResource();
for (Resource r1 : planes) {
for (Resource r2 : planes) {
if (r1 != r2) {
Plane p1 = (Plane) r1, p2 = (Plane) r2;
if (p1.getNumber().equals(p2.getNumber()) && !p1.equals(p2))
throw new PlaneInconsistentInfoException(p1.getNumber() + " has inconsistent information.");
}
}
}
}
3.1.1.13 SameEntrySameDayException
原因:相同航班号的航班在同一天。
抛出异常方法:遍历比较
/**
* check same entry in different days
* @throws SameEntrySameDayException
*/
public void checkSameEntryDiffDay() throws SameEntrySameDayException {
List<PlanningEntry<Resource>> entries = this.getAllPlanningEntries();
int n = entries.size();
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if (i != j) {
PlanningEntry<Resource> e1 = entries.get(i), e2 = entries.get(j);
if (e1.getPlanningEntryNumber().equals(e2.getPlanningEntryNumber())) {
if (((CommonPlanningEntry<Resource>) e1).getPlanningDate()
.isEqual(((CommonPlanningEntry<Resource>) e2).getPlanningDate()))
throw new SameEntrySameDayException();
}
}
}
}
}
3.1.2 处理客户端操作时产生的异常
在App中遇到客户端操作异常时,抛出异常后使用Logger记录,并取消该操作。
3.1.2.1 DeleteAllocatedResourceException
原因:在删除某资源的时候,如果有尚未结束的计划项正在占用该资源。
抛出异常方法:遍历计划项,对于多个使用该资源的计划项,均检查计划项状态。捕获到异常后将“允许删除标签”设为false,最后显示弹窗声明删除失败。
Resource deletingResource = allResourceList.get(num);
boolean flag = true;
try {
checkResourceAllocated(flightScheduleCollection, deletingResource);
} catch (DeleteAllocatedResourceException e1) {
logger.log(Level.WARNING, e1.getMessage(), e1);
flag = false;
}
flag &= flightScheduleCollection.deleteResource(deletingResource);
JOptionPane.showMessageDialog(resourceFrame, flag ? "Successful" : "Failed", "Deleting Resource", JOptionPane.PLAIN_MESSAGE);
3.1.2.2 DeleteOccupiedLocationException
原因:在删除某位置的时候,如果有尚未结束的计划项正在该位置执行。
抛出异常方法:遍历计划项,对于多个使用该位置的计划项,均检查计划项状态。
(与上DeleteAllocatedResourceException
同理)
Check方法Spec如下:
/**
* check location occupied
* @param flightScheduleCollection0
* @param location
* @throws DeleteOccupiedLocationException
*/
public static void checkLocationOccupied(FlightScheduleCollection flightScheduleCollection0, String location)
throws DeleteOccupiedLocationException {
List<PlanningEntry<Resource>> planningEntries = flightScheduleCollection0.getAllPlanningEntries();
for (PlanningEntry<Resource> planningEntry : planningEntries) {
FlightSchedule<Resource> flightSchedule = (FlightSchedule<Resource>) planningEntry;
if (flightSchedule.getLocationOrigin().equals(location)
|| flightSchedule.getLocationTerminal().equals(location))
if (planningEntry.getState().getState().equals(EntryStateEnum.ALLOCATED)
|| planningEntry.getState().getState().equals(EntryStateEnum.BLOCKED)
|| planningEntry.getState().getState().equals(EntryStateEnum.RUNNING))
throw new DeleteOccupiedLocationException(location + " is occupied");
}
}
3.1.2.3 UnableCancelException
原因:在取消某计划项的时候,如果该计划项的当前状态不允许取消。
抛出异常方法:通过cancelPlanningEntry()
–>setNewState()
返回的Boolean来判断是否可取消。
operationFlag = flightScheduleCollection.cancelPlanningEntry(planningEntryNumber);
if (!operationFlag)
try {
throw new UnableCancelException();
} catch (UnableCancelException e1) {
logger.log(Level.WARNING, e1.getMessage(), e1);
}
3.1.2.4 ResourceSharedException
原因:在为某计划项分配某资源的时候,如果分配后会导致与已有的其他计划项产生“资源独占冲突”。
抛出异常方法:与DeleteAllocatedResourceException
类似,与其不同的是在分配资源是遍历查找。
boolean flag = true;
try {
checkResourceShared(flightScheduleCollection, flightScheduleCollection.getPlaneOfNumber(strResourceNumber));
} catch (ResourceSharedException e1) {
logger.log(Level.WARNING, e1.getMessage(), e1);
flag = false;
}
if (flag) flightScheduleCollection.allocateResource(strPlanningEntryNumber, strResourceNumber);
JOptionPane.showMessageDialog(allocateResourceFrame, flag ? "Successfully" : "Failed", "Allocate Resource", JOptionPane.PLAIN_MESSAGE);
3.1.2.5 LocationSharedException
原因:在为某计划项变更位置的时候,如果变更后会导致与已有的其他计划项产生“位置独占冲突”。
抛出异常方法:与ResourceSharedException
同理。该功能在Activity Calendar App
中。
/**
* check location modifiable
* @param flightScheduleCollection0
* @param location
* @throws LocationSharedException
*/
public static void checkLocationModifiable(FlightScheduleCollection flightScheduleCollection0, String location)
throws LocationSharedException {
List<PlanningEntry<Resource>> planningEntries = flightScheduleCollection0.getAllPlanningEntries();
for (PlanningEntry<Resource> planningEntry : planningEntries) {
ActivityCalendar<Resource> activityCalendar = (ActivityCalendar<Resource>) planningEntry;
if (activityCalendar.getLocation() != null && activityCalendar.getLocation().equals(location))
throw new LocationSharedException(location + " is shared.");
}
}
3.2 Assertion and Defensive Programming
3.2.1 checkRep()检查rep invariants
3.2.1.1 TimeSlot
TimeSlot
的AF、RI如下:
/*
* AF:
* arrival[i] represent the time it arrives locations[i]
* leaving[i] represent the time it leaves locations[i]
*
* when Flight Schedule:
* length == 2, arrival[0] == leaving[0], arrival[1] == leaving[1]
*
* when Activity Schedule:
* length == 1, arrival[0] is ending time, leaving[0] is beginning time
*
* RI:
* the length of arrival and leaving should be equal
* leaving[i] should be later than arrival[i]
* when i<length arrival[i] and leaving[i] should be non-null
*
* Safety:
* do not provide mutator
*/
由此可以设计checkRep()
方法:
/**
* check Rep
*/
private void checkRep() {
assert (arrival.size() == leaving.size());
for (int i = 0; i < arrival.size(); i++) {
assert (arrival.get(i) != null);
assert (leaving.get(i) != null);
}
}
3.2.1.2 Location
Location
的AF、RI如下:
/*
* AF:
* locations represent the locations in the plan
*
* RI:
* locations should be as long as arrival and leaving in class TimeSlot
*
* Safety:
* do not provide mutator
*/
Location的Representation可以保证包括航班和高铁在内的“任意两个站不相同”。该checkRep()
如下:
/**
* check Rep
*/
private void checkRep() {
for (String strLocation1 : locations) {
assert (strLocation1.length() > 0);
for (String strLocation2 : locations) {
if (strLocation1 != strLocation2)
assert (!strLocation1.equals(strLocation2));
}
}
}
3.2.1.3 EntryState
EntryState
的AF、RI如下:
/*
* AF:
* the state enum's name represents the state
* RI:
* state must be in enums
* Safety:
* it's a mutable object, but do not let the outside modify state directly
*/
该checkRep()
非常容易,略。
3.2.1.4 Resource
Resource
的3个实现类均是immutable类型ADT,存储一定信息,因此其checkRep就是保证信息存储的变量符合格式,检查方法与抛出异常方法类似,因此对抛出异常的方法进行复用。以Plane为例:
/**
* check Rep
*/
private void checkRep() {
try {
FlightScheduleCollection.checkPlaneNumber(number);
} catch (PlaneNumberFormatException e) {
assert false;
}
try {
FlightScheduleCollection.checkPlaneType(strType);
} catch (PlaneTypeException e) {
assert false;
}
try {
FlightScheduleCollection.checkPlaneSeat(String.valueOf(intSeats));
} catch (PlaneSeatRangeException e) {
assert false;
}
try {
FlightScheduleCollection.checkPlaneAge(Double.toString(age));
} catch (PlaneAgeFormatException e) {
assert false;
}
}
3.2.1.5 PlanningEntry
在新建计划项时,资源、位置、时间、状态均被检查过,因此只要检查4者不为空,且标签正确即可。
private void checkRep() {
assert (strPlanningEntryType.equals("FlightSchedule"));
assert (location != null);
assert (timeSlot != null);
assert (state != null);
assert (resource != null);
}
3.2.2 Assertion/异常机制来保障pre-/post-condition
Assertion主要针对mutable对象的mutator。
3.2.2.1 EntryState
在修改状态时,前置条件和后置条件均为:当前状态合法。除了类型为高铁,否则不能为blocked。因此判断两次状态的合法性。
/**
* set the new state
* @param strPlanningEntryType in {"FlightSchedule", "TrainSchedule", "ActivityCalendar"}
* @param strNewState
* @return true if the setting is successful, false if not
*/
public Boolean setNewState(String strPlanningEntryType, String strNewState) {
assert (strPlanningEntryType.toLowerCase().contains("train")
|| !this.getStrState().toLowerCase().equals("blocked"));
if (this.setAvailability(strPlanningEntryType, strNewState.toUpperCase())){
this.state = EntryStateEnum.valueOf(strNewState.toUpperCase());
assert (strPlanningEntryType.toLowerCase().contains("train")
|| !this.getStrState().toLowerCase().equals("blocked"));
return true;
}
return false;
}
3.2.2.2 PlanningEntry
计划项的mutator在于分配资源和更改状态。
分配资源时,前置条件为:被分配的资源不能为空。以ActivityCalendar为例:
/**
* allocate the resource to the flight schedule
* set the state as ALLOCATED
* @param resource
* @param intResourceNumber
* @return true if the resource is set and state is ALLOCATED
*/
public Boolean allocateResource(R resource, int intResourceNumber) {
assert (resource != null && intResourceNumber > 0);
super.resource = resource;
this.intResourceNumber = intResourceNumber;
return this.state.setNewState(strPlanningEntryType, "Allocated");
}
更改状态时,后置条件为:更改后的状态不能为空且为某一合法状态。以CommonPlanningEntry.start()
为例:
@Override
public Boolean start() {
boolean flag = this.state.setNewState(strPlanningEntryType, "Running");
assert (this.state != null && this.state.getState() != null);
return flag;
}
其中,PlanningEntry
中的TrainSchedule
有操作“取第i个车厢”,对于该i的前置条件为:不能查询第1个站的到达时间且不能查询最后一个站的出发时间。以查询出发时间为例:
/**
* get the LocalDateTime of leaving time of No.indexLocation Location
* @param indexLocation
* @return the LocalDateTime of leaving time of No.indexLocation Location
*/
public LocalDateTime getLeavingTimeOfIndex(int indexLocation) {
assert (indexLocation != TERMINAL);
return super.getTimeSlot().getLeaving().get(indexLocation);
}
3.2.2.3 PlanningEntryCollection
在计划项集合类中,有许多关联到计划项编号的操作,前置条件要求计划项编号参数不能为blank。同理,所有有关查询操作的参数均不能为空白。
/**
* search for a planning entry whose number matches the given
* @param planningEntryNumber
* @return the planning entry
*/
public PlanningEntry<Resource> getPlanningEntryByStrNumber(String planningEntryNumber) {
assert (!planningEntryNumber.isBlank());
for (PlanningEntry<Resource> planningEntry : planningEntries)
if (planningEntry.getPlanningEntryNumber().equals(planningEntryNumber))
return planningEntry;
return null;
}
3.2.3 你的代码的防御式策略概述
代码的“错误传递”发生在客户端到API、API到ADT之间,因此在这两种传递过程的起始和完成阶段,都应该进行防御。
3.2.3.1 Client–>API前置条件防御
客户端和API之间,需要基于用户输入参数进行功能控制,因此用户输入的内容正确性决定了API功能实现的正确性。客户端的输入方法或API的方法起始阶段需要对用户输入进行检查。
例如FlightScheduleCollection.addPlanningEntry()中需要读入一段数据,在方法中进行了对各项参数的检查,错误则抛出包括EntryNumberFormatException在内的相应异常;而在查询指定计划项信息时,则是在FlightScheduleApp中先对该编号正确性进行检查(该操作委派给了FlightScheduleCollection)然后才获取指定信息。
3.2.3.2 Client–>API后置条件防御
在API操作完成之后,在客户端或API中需要对结果进行正确性的大致检查,避免一下明显错误情况;若API操作不当,可能在程序中引入隐式错误。
例如在启动计划项时,FlightScheduleApp在完成操作后弹窗显示操作结果;在暂停计划项之后,会检查该计划项类型是否为可暂停的计划项对象类型。
3.2.3.3 API–>ADT前置条件防御
在API的操作会对ADT进行影响,若ADT为可变的,则要求Setter()参数正确。检查参数正确可以在API的方法中,也可以在ADT的方法中。
例如API在获得某计划项的资源时,会判断该ADT的资源是否为空;在API需要获得高铁的第i站的到达时间,在ADT的方法中会对i的取值进行断言(不能为0)。
3.2.3.4 API–>ADT后置条件防御
在修改ADT的内容之后,需要确认修改后的ADT符合RI。此时,可以调用ADT私有方法checkRep()进行校验。在各个ADT中均有checkRep(),出现在构造器(immutable对象),也会出现在mutator(mutable对象)。
3.3 Logging
我报告基本写完了,但是在这歇会,因为我料:写到这的人也不会参考我,会参考我的人也不会写到这