1. 程式人生 > 其它 >哈工大軟體構造Lab2(2022)

哈工大軟體構造Lab2(2022)

(防扒小助手)

本人CSDN部落格:

https://blog.csdn.net/m0_61753302https://blog.csdn.net/m0_61753302本人部落格園部落格(同步CSDN):

https://www.cnblogs.com/kalesky/https://www.cnblogs.com/kalesky/如果對你有用的話歡迎點贊關注喲!

目錄

 

1、實驗目標概述

2、實驗環境配置

3、實驗過程

3.1 Poetic Walks

3.1.1 Get the code and prepare Git repository

3.1.2 Problem 1: Test Graph

3.1.3 Problem 2: Implement Graph

3.1.4 Problem 3: Implement generic Graph

3.1.5 Problem 4: Poetic walks

​​​​​​​3.1.6  Before you’re done

3.2 ​​​​​​​ Re-implement the Social Network in Lab1

​​​​​​​3.2.1 FriendshipGraph類

​​​​​​​3.2.2  Person類

​​​​​​​3.2.3  客戶端main()

3.2.4 測試用例 

3.2.5 提交至Git倉庫

4 實驗進度記錄

5 實驗過程中遇到的困難與解決途徑

6 實驗過程中收穫的經驗、教訓、感想

6.1 實驗過程中收穫的經驗和教訓

6.2 針對以下方面的感受


1、實驗目標概述

本次實驗訓練抽象資料型別(ADT)的設計、規約、測試,並使用面向物件

程式設計(OOP)技術實現 ADT。具體來說:

  1.  針對給定的應用問題,從問題描述中識別所需的 ADT
  2.  設計 ADT 規約(pre-conditionpost-condition)並評估規約的質量;
  3.  根據 ADT 的規約設計測試用例;
  4.  ADT 的泛型化;
  5.  根據規約設計 ADT 的多種不同的實現;針對每種實現,設計其表示 (representation)、表示不變性(rep invariant)、抽象過程(abstraction
    function)
  6.  使用 OOP 實現 ADT,並判定表示不變性是否違反、各實現是否存在表示洩露(rep exposure)
  7.  測試 ADT 的實現並評估測試的覆蓋度;
  8.  使用 ADT 及其實現,為應用問題開發程式;
  9.  在測試程式碼中,能夠寫出 testing strategy 並據此設計測試用例。

 

2、實驗環境配置

(1)安裝測試用例程式碼覆蓋度外掛

經過網上查閱資料瞭解到,在IDEA中已經集成了程式碼覆蓋度外掛JaCoCo,切換方式如下:

編輯

 

點選Edit Configuration,彈出如下視窗:

 在Code Coverage一欄點選Modify勾選紅框中的選項,則會彈出切換程式碼覆蓋度工具的選項:


 通過諮詢軟體構造課程老師與助教老師,瞭解到使用IDEA自帶的程式碼覆蓋度工具即可,不需要切換到JaCoCo,因此下文程式碼覆蓋度測試均使用IDEA自帶的coverage runner進行測試。

 

2、GitHub Lab2倉庫的URL地址(Lab2-學號)

略。 

 

 3、實驗過程

3.1 Poetic Walks

該任務主要是通過實現一個圖的模組來練習ADT的規約設計和ADT的不同實現。

(1)完善Graph介面類,並運用泛型的思想,將String拓展為泛型L類;

(2)實現Graph介面類:以邊和點兩種方式實現介面;

(3)利用實現的Graph類,應用圖的思想,實現GraphPoet類。如果輸入的文字的兩個單詞之間存在橋接詞,則插入該橋接詞;若存在多個單一橋接詞,則選取邊權重較大者。

 

3.1.1 Get the code and prepare Git repository

從要求檔案中對應網址下載得到實驗程式碼,建立好project,進入目錄,開啟Git bush

依次輸入:

git init

git remote add origin [email protected]:ComputerScienceHIT/HIT-Lab2-120L022408.git

git pull origin master

git add .

git commit -m “init”

git push origin master

 

3.1.2 Problem 1: Test Graph <String>

測試Graph的靜態方法。

為了方便測試Graph的多種實現,在 GraphInstanceTest 中測試了例項方法。

編寫測試用例主要利用等價類劃分的思想進行測試,測試策略如下:


 分別編寫覆蓋以上條件的測試用例。

 

執行GraphStaticTest得到測試結果如下:

 

3.1.3 Problem 2: Implement Graph <String>

3.1.3.1 Implement ConcreteEdgesGraph

(1)Edge類實現

定義兩個private String型別的變數source和target存放每個邊的起止點

定義一個private int型別的變數weight儲存這條邊的權重(長度)

private final String source, target;

private final int weight;

關於AF,RI和rep exposure:


① 構造器 constructor

構造方法,使用上述三個資料域宣告一個新的邊

public Edge(L source_new, L target_new, int weight_new)
{
    this.source = source_new;
    this.target = target_new;
    this.weight = weight_new;
    checkRep();
}

② 檢查表示不變數 checkRep

檢查表示不變數,其中source和target必須非空,weight必須大於0

public void checkRep()
{
    assert source != null;
    assert target != null;
    assert weight > 0;
}

③ get方法

get_Source:返回source域

get_Target:返回target域

get_Weight:返回weight域

④ toString方法

返回一個字串表明這條邊是從哪個source到哪個target,其weight是多少。

public String toString()
{
    return source.toString() + "->" + target.toString() + "\t權重為" + weight + '\n';
}

 

(2)ConcreteEdgesGraph實現

vertices和edges分別記錄當前graph所含有的點和邊

private final Set<String> vertices = new HashSet<>();
private final List<Edge<String>> edges = new ArrayList<>();

關於AF,RI和rep exposure:

 


 ① add方法

public boolean add(String vertex)

如果頂點不為空,新增一個頂點。如果在vertices的Set集合中成功添加了vertex,則返回true。

② Set方法

public int set(String source, String target, int weight)

輸入source,target,weight,確定一條有向邊。

具體做法:如weight!=0,移去可能已經存在的相同起始點的邊,然後加入新的邊,如weight=0,尋找可能已經存在的相同起始點的邊,刪去。

③ remove方法

public boolean remove(String vertex)

從vertices中刪去給定的vertex點,遍歷edges,尋找該vertex是否為某條邊的起點或者終點,刪去相應的邊。在使用迭代器遍歷時要使用iterator.remove方法保證安全性。

④ vertices方法

public Set<String> vertices()

返回vertices集合。注意做到safety from rep exposure ,使用Collections.unmodifiableSet()方法。


⑤ sources方法

public Map<String, Integer> sources(String target)

引數:target。根據傳入的target引數尋找以targe為終點的邊。返回一個鍵值對為(點,權重)的map。

實現:建立一個map,利用迭代器遍歷edges,如果某個edge的edge.get_Target()和傳入引數target相等,則將該邊的source和weight存入map中。

⑥ targets方法

public Map<String, Integer> targets(String source)

引數:source。根據傳入的source引數尋找以source為起點的邊。返回一個鍵值對為(點,權重)的map。

實現:建立一個map,利用迭代器遍歷edges,如果某個edge的edge.get_Source()和傳入引數source相等,則將該邊的target和weight存入map中。

⑦ 檢查表示不變數 checkRep

思路:n個點,最多構成n*(n-1)條有向邊,因此存在這種不可變的數學關係

⑧ toString方法

對每條邊呼叫toString方法,整合起來。

public String toString()
{
    String s = "";
    for (Edge<L> e : edges)
    {
        s = s + e.toString();
    }
    return s;
}

 

(3)ConcreteEdgesGraphTest測試

JUnit測試結果如下:

 

 試覆蓋率:

 

 3.1.3.2 Implement ConcreteVerticesGraph

(1)Vertex類實現

定義兩個private String型別的變數source和target存放每個邊的起止點

定義一個private int型別的變數weight儲存這條邊的權重(長度)

private final String name;
private final Map<String, Integer> sources;
private final Map<String, Integer> targets;

關於AF,RI和rep exposure:


① 構造器 constructor

構造方法,傳入引數name建立新的點。

public Vertex(String name)
{
    this.name = name;
    sources = new HashMap<>();
    targets = new HashMap<>();
}

② 檢查表示不變數 checkRep

檢查表示不變性,各邊weight的值應該永遠大於0。

 

③ get方法

get_Name:返回source域

get_Sources:返回weight域

get_Target:返回Targets域

④ set方法

set_Target:為當前點新增一個target,

如果weight為0,刪去當前點的target,成功返回刪去target的weight,不存在返回0;如果weight不為0,為當前點新增一個target,長度為weight,如果該點已存在,返回舊的weight,否則返回0

 

set_Source:為當前點新增一個source,

如果weight為0,刪去當前點的source,成功返回刪去source的weight,不存在返回0;如果weight不為0,為當前點新增一個source,長度為weight,如果該點已存在,返回舊的weight,否則返回0

 

⑤ remove方法

remove_Source:刪去當前點的指定source

public int remove_Source(String source)
{
    Integer weight = sources.remove(source);
    return weight == null ? 0 : weight;
}

 

remove_Target:刪去當前點的指定target

public int remove_Target(String target)
{
    Integer weight = targets.remove(target);
    return weight == null ? 0 : weight;
}

 

⑥ toString方法

返回一個字串表明這個頂點的資訊

public String toString()
{
    return String.format("Vertex %s has %d sources and %d targets", this.get_Name().toString(), this.get_Sources().size(), this.get_Targets().size());
}

 

(2)ConcreteVerticesGraph實現

使用如下資料型別儲存頂點的資料:

private final List<Vertex<String>> vertices = new ArrayList<>();

關於AF,RI和rep exposure:

 

① 檢查表示不變數 checkRep

所有點的標識不能為空

private void checkRep()
{
    assert vertices != null;
}

② add方法

public boolean add(String vertex)

引數:vertex,判斷vertices中無重複點就加入

 

③ Set方法

public int set(String source, String target, int weight)

引數:source, target, weight。先將可能不在vertices中的source點和target加入vertices。隨後遍歷vertices,找到source對它增加一個target,找到target為它增加一個source,並設定距離。

 

④ remove方法

public boolean remove(String vertex)

引數:vertex。遍歷vertices,如果當前點是vertex,刪去(使用iterator.remove方法),如果不是,檢查它的source和target是否包含vertex,如果有刪去。

 

⑤ vertices方法

遍歷vertices,找到每個點對應的string,新增進set即可。使用防禦性拷貝:

public Set<String> vertices()
{
    Set<String> set = new HashSet<>();
    for (Vertex<String> v : vertices)
    {
        set.add(v.get_Name());
    }
    return set;
}

 

⑥ sources方法

public Map<String, Integer> sources(String target)

引數:target。根據傳入的target引數尋找以targe為終點的邊。返回一個鍵值對為(點,權重)的map。

實現:建立一個map,利用迭代器遍歷edges,如果某個edge的edge.get_Target()和傳入引數target相等,則返回target對應的源點圖。

 

 

⑦ targets方法

public Map<String, Integer> targets(String source)

引數:source。根據傳入的source引數尋找以source為起點的邊。返回一個鍵值對為(點,權重)的map。

實現:建立一個map,利用迭代器遍歷edges,如果某個edge的edge.get_Source()和傳入引數source相等,則返回source對應的目標點圖。

 

⑧ toString方法

列印當前頂點圖的頂點數量:

public String toString()
{
    return String.format("This graph has %d vertices", this.vertices.size());
}

 

(3)ConcreteEdgesGraphTest測試

JUnit測試結果如下:

 

測試覆蓋率:

3.1.4 Problem 3: Implement generic Graph<L>

3.1.4.1 Make the implementations generic

將具體類的宣告更改為:

public class ConcreteEdgesGraph<L> implements Graph<L> { ... }

 

class Edge<L> { ... }

public class ConcreteVerticesGraph<L> implements Graph<L> { ... }

 

class Vertex<L> { ... }

更新兩個實現以支援任何型別的頂點標籤,使用佔位符L代替String。

充分利用IDEA的智慧改錯功能快速修改成泛型實現。

 

​​​​​​​3.1.4.2 Implement Graph.empty()

選擇ConcreteEdgesGraph來實現Graph.empty()

測試全部通過:

   

3.1.5 Problem 4: Poetic walks

3.1.5.1 Test GraphPoet

關於測試策略:

 

具體測試:

​​​​​​​3.1.5.2 Implement GraphPoet

首先宣告:

private final Graph<String> graph = new ConcreteEdgesGraph<String>();

關於AF,RI和rep exposure:

 

① 檢查表示不變數 checkRep

所有點的標識不能為空

private void checkRep()
{
    assert graph != null;
}

 

② GraphPoet方法

引數:corpus檔案路徑。開啟檔案,讀取檔案輸入,識別序列,構建圖結構。

具體:利用BufferedReader.readLine方法讀取全部輸入後用string.split以空格劃分,儲存在陣列中,隨後每次取相鄰元素,在圖中新增邊。

 

③ poem方法

引數:input。

利用相同方法分割輸入字串,宣告一個StringBuilder儲存返回結果。每次讀取一個詞,然後以當前詞為source,下一個詞為target,在graph中尋找符合此條件的邊,記錄權值,結束後選擇權值最大的,利用StringBuilder. Append方法,將節點名字加入字串。

 

④ toString方法

呼叫ConcreteEdgesGraph的toString方法,輸出圖結構

public String toString()
{
    return graph.toString();
}

​​​​​​​3.1.5.3 Graph poetry slam

語料庫為泰戈爾經典名句集錦

輸入輸出如下:

 

 

​​​​​​​3.1.6 Before you’re done

通過Git提交當前版本到GitHub上你的Lab2倉庫。

git add .

git commit -m "P1 Finished"

git push -u origin master

 

專案的目錄結構樹狀示意圖。

3.2 ​​​​​​​Re-implement the Social Network in Lab1

這個實驗是基於在Poetic Walks中定義的Graph及其兩種實現,重新實現Lab1中的 FriendshipGraph類。我們需要儘可能複用ConcreteEdgesGraph或 ConcreteVerticesGraph中已經實現的add()和set()方法,而不是從零開始。另外基於所選定的 ConcreteEdgesGraph 或 ConcreteVerticesGraph的rep來實現,而不能修改父類的rep。

​​​​​​​3.2.1 FriendshipGraph

(1)設計思路

繼承ConcreteEdgesGraph<Person>,並在類中增加一些對非法情況的判斷,例如頂點已經存在,邊已經存在或找不到對應頂點等。

public class FriendshipGraph extends ConcreteEdgesGraph<Person>

 

(2)方法實現

構造一個ArrayList型別的變數person_list儲存頂點列表

private final ArrayList<String> person_list = new ArrayList<String>();

① public boolean addVertex(Person people)

這個函式是為把引數新增到圖中,作為圖的一個頂點,直接呼叫父類的this.add()即可。呼叫過程中檢查頂點列表中是否已出現引數對應的頂點,若重複則列印錯誤資訊並返回false,成功新增頂點則返回true

 

② public boolean addEdge(Person people1, Person people2)

構建圖的要素,在圖中新增邊。先呼叫 this.vertices().contains()方法來判斷所新增邊的頂點是否存在,再判斷兩頂點之間是否已有邊連線,若條件滿足,則呼叫this.set()方法設定邊,權重初始化為1並返回true,其餘情況返回false。

 

③ public int getDistance(Person People1, Person People2)

獲取兩個頂點之間距離的函式,題目要求返回最短距離,因此採用廣度遍歷的方式,此處需要用到Queue的資料結構,並且設定了一個List來存放已經訪問過的person。

 

​​​​​​​3.2.2 Person

Person類根據FriendshipGraph類的需求編寫的。它用於描述每個成員的性質,主要是例項化姓名的構造方法,getName()方法,判斷姓名是否重複的isSameName方法。

public class Person
{
    private final String Name;

    public Person (String Name)
    {
        this.Name = Name;
    }
    public String getName()
    {
        return this.Name;
    }
    public boolean isSameName(String Name)
    {
        return this.Name.equals(Name);
    }
}

​​​​​​​3.2.3 客戶端main()

main函式主體內容即為實驗指導書給定的內容:先new一個FriendshipGraph類的物件,然後新增頂點,新增邊。

輸出錯誤型別在實現FriendshipGraph類時已輸出,故此處不需要再次判斷是否出現錯誤。

① 正常輸出的測試結果

 

② 註釋掉rachel -> ross後的測試結果

 

3.2.4 測試用例

測試策略:

 根據劃分的等價類設計測試用例

測試結果與覆蓋度報告:

   

3.2.5 提交至Git倉庫

通過Git提交當前版本到GitHub上的Lab2倉庫。

git add .

git commit -m "P1 P2 first finished"

git push -u origin master

 本專案的目錄結構樹狀示意圖:

 

4 實驗進度記錄

請使用表格方式記錄你的進度情況,以超過半小時的連續程式設計時間為一行。

日期

時間段

計劃任務

實際完成情況

2022-05-23

8:00-11:30

瀏覽報告,檢視MIT相關內容要求

按計劃完成

2022-05-23

15:00-19:00

完成P1的GraphStaticTest類測試

按計劃完成

2022-05-24

9:00-11:00

完成P1的ConcreteEdgesGraph類編寫

按計劃完成

2022-05-24

15:00-20:00

完成P1的ConcreteVerticesGraph類

按計劃完成

2022-05-25

8:15-11:00

實現泛型Graph<L>的轉換

按計劃完成

2022-05-25

13:00-14:00

瀏覽Poetic Walks的編寫要求

按計劃完成

2022-05-25

21:00-23:00

嘗試實現Poetic Walks

遇到困難,延期完成

2022-05-26

9:00-17:00

完成Poetic Walks的test和Implement

按計劃完成

2022-05-27

8:00-18:00

完成P2的Social Network的改寫

按計劃完成

 

5 實驗過程中遇到的困難與解決途徑

遇到的難點

解決途徑

不瞭解IDEA如何實現程式碼覆蓋率測試

 

 

查閱網上資料後發現相應解決方案因IDE版本迭代已發生改變。自己摸索解決了實現程式碼覆蓋率測試外掛切換的問題。

對面向test的程式設計思想理解不夠深入

 

 

複習《軟體構造》課程的PPT,上網查閱了相關資料並諮詢了同學,完成了對測試程式碼的編寫。

對規約的要求不夠理解

 

 

通過學習模仿,嘗試自己編寫相應規約並實現之。

 

6 實驗過程中收穫的經驗、教訓、感想

6.1 實驗過程中收穫的經驗和教訓

經驗:

加深了自己對於泛型的理解和認識,提高了程式碼編寫、ADT設計的能力。編寫test測試檔案時,有些方法的測試也能覆蓋到其他的方法,避免重複測試增加工作量。

教訓:

在設計多個類並使之互相配合的方面做得不好,編寫程式碼的邏輯性有待提高。

6.2 針對以下方面的感受

(1)面向ADT的程式設計和直接面嚮應用場景程式設計,你體會到二者有何差異?

我感覺,對於面向ADT的程式設計,類是其主要特點,程式執行過程中,先由主函式進入,定義一些類,根據需要,執行類的成員函式,過程的概念被淡化了。而直接面嚮應用場景程式設計的抽象程度不高,雖然邏輯清晰但是程式碼思路混亂,不利於實現。

 

(2)使用泛型和不使用泛型的程式設計,對你來說有何差異?

泛型程式設計可以使程式碼被很多不同型別的物件所重用,並使程式碼具有更好的可讀性。

 

(3)在給出ADT的規約後就開始編寫測試用例,優勢是什麼?你是否能夠適應這種測試方式?

優勢是不考慮程式碼的內部實現,只需考慮是否完成了規約中指定的功能。作為java語言的初學者來說我很不適應這種測試方式。

 

(4)P1設計的ADT在多個應用場景下使用,這種複用帶來什麼好處?

提高了程式碼的利用率,減輕程式設計工作量。

 

(5)為ADT撰寫specification, invariants, RI, AF,時刻注意ADT是否有rep exposure,這些工作的意義是什麼?你是否願意在以後程式設計中堅持這麼做?

使編寫的程式碼更加安全和可讀性更強。願意這麼做。

 

(6)關於本實驗的工作量、難度、deadline。

我認為,考慮到實驗時間,與其他專業課的複習時間與考試時間有大量衝突,因此顯得本實驗工作量十分巨大,難度也很高,deadline十分緊張。

 

(7)《軟體構造》課程進展到目前,你對該課程有何體會和建議?

希望減少工作量,增加課時安排,增加動手實驗分數,減少筆試考試分數,合理安排課程開展時間,可以在剛開學時開課或者在小學期開課,更有利於學生能力的提升。