哈工大2020軟件構造Lab4實驗報告

我又來了,大佬們別複製,必錯(狗頭)

打擊不誠信行爲,從每個HITer做起,鄙視代寫+抄襲+伸手黨

希望同學建議,是不是該刪點代碼?

本項目計劃於5.19日實驗課驗收

更新中

如果有所參考 請點點關注 點點贊GitHub Follow一下謝謝

HIT

2020春計算機學院《軟件構造》課程Lab4實驗報告

  • Software Construction 2020 Spring
  • Lab-4 Debugging, Exception Handling, and Defensive Programming
  • 版權聲明:1183710109 郭茁寧

文章目錄

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

我報告基本寫完了,但是在這歇會,因爲我料:寫到這的人也不會參考我,會參考我的人也不會寫到這

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章