[北航軟工]第一次結對做業-最長單詞鏈

BUAA軟件工程 第一次結對做業

項目 內容
這個做業屬於哪一個課程? 北航軟工
這個做業的要求在哪裏? 第一次結對做業
我在這個課程的目標是? 學習高效嚴謹的軟件工程開發過程,創建團隊意識
這個做業在哪一個具體方面幫助我實現目標 熟悉並瞭解軟件工程的基本知識,創建興趣

1. 項目的Github地址

binggge/longestWordChain html

項目有兩個分支,master分支是命令行形式,UI分支是帶GUI的程序。c++

2. 14. PEP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 30 30
· Estimate · 估計這個任務須要多少時間 30 30
Development 開發 815 1200
· Analysis · 需求分析 (包括學習新技術) 30 120
· Design Spec · 生成設計文檔 30 90
· Design Review · 設計複審 (和同事審覈設計文檔) 15 30
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 30 15
· Design · 具體設計 50 30
· Coding · 具體編碼 300 600
· Code Review · 代碼複審 60 90
· Test · 測試(自我測試,修改代碼,提交修改) 300 500
Reporting 報告 240 195
· Test Report · 測試報告 120 120
· Size Measurement · 計算工做量 15 15
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 105 60
合計 1085 1700

3.看教科書和其它資料中關於Information Hiding, Interface Design, Loose Coupling的章節,說明大家在結對編程中是如何利用這些方法對接口進行設計的

信息隱藏

信息隱藏是指在設計和肯定模塊時,使得一個模塊內包含的特定信息(過程或數據),對於不須要這些信息的其餘模塊來講,是不可訪問的。 在咱們的設計過程當中,模塊除了被調用的兩個接口或者說成員函數之外,其餘的成員都是private屬性的,它如何實現算法,如何存儲數據,過程當中某個變量的值如何,都與外部無關,從外部不可訪問和修改,保證編程的安全性。git

接口設計

對調用者和使用者而言,不須要知道函數內部的實現,只須要掌握接口的信息。接口設計主要是做用和反作用的明確性,若是接口應該一開始就設計的比較成熟,這樣咱們修改實現接口的程序時,調用接口的程序就不用修改。咱們設計的兩個接口,約定了函數的具體做用即以不一樣的方式尋找最長單詞鏈,約定了傳入參數的具體意義,而且規定了只能修改char * result變量做爲結果的輸出,其餘的參數應該不改變。程序員

鬆耦合

鬆耦合和接口設計是相輔相成的。只有在規定好接口規範之後,才能實現進一步的解耦合。咱們只規定接口的做用,而不規定接口的內容。這樣不一樣組件之間能夠輕易地作到互相獨立。github

附加題:換GUI體現鬆耦合

咱們在完成代碼編寫後,與其餘組同窗互換了GUI和DLL,另外一個組是週二的白世豪(16061167),宋卓洋(16061170)組。因爲生成dll的教程相似,並無出現什麼問題,對於無異常狀況能夠正常運行,以下圖所示:算法

工做目錄

其中Core.dll是咱們生成的dll,DLL1.dll是另一個組的dll,通過測試,加載兩個dll時GUI的行爲一致。 otherGUI.PNG mycore.PNG 微信圖片_20190314142743.png編程

4. 計算模塊接口的設計與實現過程

整個程序的邏輯大體以下圖所示:設計模式

graph LR A[命令行啓動] -->B(分析參數) B --> C{參數正確} C --> I{單詞個數} C --> J{字母個數} B --> F{參數錯誤} F --> G{報錯} I --> H{參數是否容許有圈?} H -->|YES| D[Result one] H -->|NO| E[Result two] J -->K{參數是否容許有圈} K -->|YES| M[Result three] K -->|NO| N[Result four]

由於GUI程序是不會涉及到普通的參數正確性的(只有關於指定頭尾的字母是否正確),因此 出於解耦合的須要,咱們並無將處理參數的程序集成在Core類之中,而是獨立出來,在main函數中判斷參數的正確性。Core類中確保接受到的是正確的單詞組char *words[]和其餘合適的參數。咱們暴露在外的只有兩個接口。安全

int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);

其餘的部分以private成員的形式,供內部使用。微信

而後每一個接口中對單詞的處理也須要分紅兩種。

\\沒有-r選項,檢測有環時直接報錯,無環時以寬度優先搜索的形式(不會陷入死循環)找到最長單詞鏈;
\\有-r選項,不須要檢測是否有環,直接以深度優先搜索的形式搜索最長單詞鏈;
\\僞代碼以下
if(!enable_loop)
{
    roundtest();//有環時拋出錯誤
    createMap();//創建bfs的地圖
    BFS();//寬度優先搜索
    getBFSResult();//處於節省時間和空間的考慮,上一步寬度優先搜索並無保存路徑,而是得出了最長的長度,而後根據長度倒推出路徑。
}
else
{
    createMap();//創建dfs的地圖
    DFS();//深度優先搜素
    getDFSResult();//推導最終路徑,緣由同上
}

基礎的函數大體就是這些,兩種類型chain_wordchain_char的各有一套。而後咱們還引入了get_tails()就是在掃描邊的時候,排除不可能的首尾選項。大體原理就是當不成環的時候,出度爲0的點纔多是尾字母,不然不多是最長鏈。這樣咱們能夠剪枝,減小搜索時的複雜度。

5.畫出UML圖顯示計算模塊部分各個實體之間的關係 [u]

使用了自動化工具生成類圖與調用關係,參考工具Github,生成的調用關係以下: 類圖.png 手畫類圖以下,咱們的類關係至關簡單。

6.計算模塊接口部分的性能改進

總體上說,咱們採起了BFS解決沒有-r的狀況,使用DFS解決有-r的狀況。

首先是沒有-r的部分。因爲算法在設計之初就已經考慮到了一些優化,咱們重複執行同一條指令50次,取得更好的採樣效果。如圖所示,整個算法的耗時最長是在建圖與刪重邊。程序在輸入8000個單詞時運算時間在1s之內。所以沒有作進一步改進,這裏介紹一下完成的優化。 耗時分析BFS.PNG BFS-1.PNG 咱們主要完成了如下幾個優化:

  • 建圖時有重邊的只保留一條。這樣能夠避免BFS重複搜索節點。
  • BFS節點記憶化。在訪問BFS節點後記錄當前距離,在下一次另外一條邊訪問時判斷,若是小於當前記憶的距離則剪枝。
  • 減小起始搜索節點。考慮到沒有環,所以不考慮自環時起始節點的入度爲0,末尾節點的出度爲0。在沒有指定開始/結束字符時,經過這種方法減小可能的起始節點,下降搜索次數。

在含有-r的部分,考慮到這是一個NP問題,並且要求的是準確解,不是近似解,咱們採用回溯法進行深度搜索。程序輸入90個單詞,摘自Wikipedia的一個隨機網頁,運行時間3分04秒。 DFS.PNG DFS-2.PNG 能夠發現,程序在DFS上耗費了大量的時間。咱們對於這種模式依然作了一些優化,咱們同BFS同樣減小了可能的起點,由於起點的出度>=入度,終點的入度>=出度,也有必定效果。

此外,咱們還在總體上對程序進行了優化。考慮處處理器支持AVX指令集,咱們在編譯選項中選擇了支持生成AVX2指令的編譯選項,取得了不錯的效果,對於一組較複雜的測試用例,運行時間從2分58秒減小到2分03秒,提高接近1/3。

7.看Design by Contract, Code Contract的內容:描述這些作法的優缺點, 說明你是如何把它們融入結對做業中的

契約式設計要求軟件設計者爲軟件組件定義正式的,精確的而且可驗證的接口,接受一個合規的輸入,並保證合規的輸出。根據百科的定義來看,這種設計模式重在精確二字。

  • 優勢
    • 減小了防護式編程的一部分工做量
    • 提升了程序的可維護性
    • 使程序員的之間的合做變得有序
  • 缺點
    • 制定契約,維護契約,使程序知足契約的代價(時間和人力)比較大
    • 必須強制要求整個項目內的代碼都符合契約,不然部分的無效影響整個工程的質量

本次結對做業中,首要契約是做業要求中規定的接口,這個下降了溝通的一部分任務。實際編程中咱們制定的契約主要是雙方完成的函數方法部分對於類成員的行爲,以及對於異常狀況的處理。在如此契約之下,咱們才完成了從Core類到GUI的低耦合。

8.計算模塊部分單元測試展現

單元測試總體覆蓋率91%。未覆蓋到的部分主要是拋出異常的if語句。 Coverage.PNG

計算模塊暴露的接口共有兩個,分別爲

  • int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop)
  • int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop)

咱們對其進行了單元測試。測試的流程是測試程序加載一組測試數據,放入char * words[]中,與一些參數一塊兒傳入函數,獲得返回的result後加載標答並逐個比較,以下面的代碼片斷所示:

TEST_METHOD(TestMethod1)
    {
        Core * core = new Core();
        // do sth to update words
        core->gen_chain_word(words1, len, result1, 0, 0, false);
        // get true value into realAnswer
        Assert::AreEqual(51, length_of_result1);
        for (int i = 0; i < length_of_result1; i++) {
            Assert::AreEqual(strcmp(result1[i], realAnswer[i]), 0);
        }
    }

對於正常數據,咱們使用隨機生成+對拍的方式檢驗。 對於異常數據,咱們構造瞭如下幾個特殊狀況:

  • words 爲空,模擬文件異常或不存在
  • words 有環,可是沒有-r
  • tail 或 head 指定的單詞鏈找不到
  • 沒有單詞鏈

其中對於有環的狀況,咱們還分了如下幾種狀況:

  • 有自環
  • 有多個自環
  • 有多條邊組成的環

如下爲單元測試展現,因爲太長默認收起。

注意,請依次運行每一個單元測試,不要一塊兒運行,防止出錯。

正確數據測試 -w
TEST_METHOD(TestMethod1)
    {
        Core * core = new Core();
        int len = readFile1("../WordChainUnitTesr/words1.txt");
        core->gen_chain_word(words1, len, result1, 0, 0, false);
        int len2 = readFile1("../WordChainUnitTesr/solution1.txt");
        Assert::AreEqual(51, len2);
        for (int i = 0; i < len2; i++) {
            Assert::AreEqual(strcmp(result1[i], words1[i]), 0);
        }
    }
其中 words1.txt, solution1.txt能夠在Github上找到。
相關文章
相關標籤/搜索