結對做業博客

結對做業博客

項目 內容
這個做業屬於哪一個課程 2019春BUAA SCSE軟件工程
這個做業的要求在哪裏 結對項目-最長單詞鏈
我在這個課程的目標是 學習結對編程
這個做業在哪一個具體方面幫助我實現目標 兩人合做完成項目

Github 項目地址

最長單詞鏈c++

PSP表格估計時間

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

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

咱們的程序設計對於信息隱藏的最具體體現可能就是對於Core 類的封裝,在實現時咱們使用了大量的函數,最終封裝出了兩個,這樣就可以很好的隱藏起其餘的函數,只提供了兩個接口。對於接口,我以爲主要仍是要實現的合理,好比簡單明瞭的名稱,以及後續的可擴展性,這些是比較重要的。在此次做業裏,咱們也是按照規定好的接口完成了任務。至於低耦合,我以爲咱們仍是能夠的,將各大部件都進行了分離,好比計算、GUI、輸入輸出等,所以咱們的撰寫過程裏遇到BUG時,處理也比較輕鬆。git

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

計算模塊最終的函數是get_chain_word()和get_chain_char(),在個人編寫代碼的過程當中,發現這兩種狀況十分的類似,幾乎就是能夠當作一種狀況來考慮。咱們將26個字母視爲節點,這樣可以必定程度上加快效率。每一個節點都要保存最長單詞鏈的長度,一個包含着全部以它爲頭字母的字母鏈,以及這個字母的最長鏈。github

總體共使用了一個類以及14個函數。封裝時,咱們將這些函數分裝到了Core類中,並導出Dll。

算法

其主要調用關係以下圖。首先判斷是否容許環路存在,若是容許不環路存在,就調用get_Chain()函數來處理。若是容許環路存在,就調用get_Chain_With_R()函數來處理。get_Chain()和get_Chain_With_R()都是靠傳入的參數來判斷是要求單詞數仍是字符數。進入這兩個函數後,對整個圖進行初始化,對每一個單詞,若是判斷的是單詞數目最多,就將其長度設置爲1,不然就將其長度設置爲字符數,這樣就能夠將兩種狀況統一判斷了。初始化後判斷單詞中是否存在環路,若是不容許環路卻出現了環路報異常。而後就開始生成起始節點,而後對每一個起始節點開始判斷其最長路,最終找出最長鏈,將其保存在result中。其計算過程就是對每一個節點找出其對應的最長鏈,並將最長鏈掛在下面,最終就能找到起始節點的最長鏈。
編程


最重要的函數就是find_Longest_Chain()函數和find_Longest_Chain_With_R()函數。對於find_Longest_Chain()函數,因爲沒有環路,所以每一個字母實際上只須要計算一次,若是發現這個字母已經計算過了,就直接返回它的最長鏈的長度。若是沒計算過,就判斷它是否是到達告終尾。若是是結尾,根據可能規定的結尾字母來判斷這個字母的最長鏈長度。數組

沒有被計算過也不是結尾時,就對這個字母下的全部單詞的結尾字母都判斷一遍,找到裏面最長的,最終計算出字母的最長鏈長度和最長鏈。函數

對於find_Longest_Chain_With_R()就稍稍有點區別,由於一個字母能夠被屢次用到,因此就不能經過判斷這個字母是否被計算過來獲得它的最長鏈。咱們的作法是,針對每一次遞歸調用,找到以這個字母爲開頭,沒有在這一層遞歸調用中判斷過,也不在臨時的判斷路徑,也沒有被肯定下來最終路徑的最長單詞,在進行遞歸判斷這個單詞的尾字母。經過這種方式,可以訪問到整個圖。同時也不會出現重複訪問。
工具

在每次調用的結束前,都要判斷,此次找到的最長路徑是否比如今的最長路徑長,若是更加長的話,就將新的路徑替換掉舊的路徑。性能

算法的關鍵之處在於將兩種不一樣的要求合二爲一,同時只使用26個字母做爲節點,將全部以某個字母爲開頭的單詞所有保存在節點的鏈表之中,而不是針對大量的單詞,以單詞做爲節點來考慮。單元測試

UML圖

在封裝的的Dll裏Core類中只暴露了兩個函數get_chain_word()和get_chain_char()。

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

在性能這一塊,咱們最開始的想法就是單獨保存每一個單詞,將單詞視爲圖中的節點,可是後來發現這樣作會致使圖太大,臨接矩陣的生成就要花費大量的時間,更別說去判斷裏面是否存在環路了。所以咱們考慮26個字母,將字母做爲圖來看,將以這個字母開頭的單詞所有保存在鏈表之中,再來進行判斷。因爲改進前尚未寫的太深,不少重要函數都尚未進行編寫,改進的時間花費的很少,兩個小時就完成了新的設計。

使用性能分析工具生成測試100個點的帶環圖的結果如圖,能夠發現,在執行的過程當中,find_Longest_Chain_With_R()這個函數遞歸調用了屢次,佔用了整個executiveCommand()函數的較長時間,另外的時間都花在了輸出結果到文件之中。

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

Design by Contract的優勢
首先優勢就是可以有一個規範,這樣子對於大型團隊而言,人員衆多,可以減小代碼中的風格差別,還有接口,這樣可以讓這種大型項目比較融洽的進行下去。不至於最後又要統一各類各樣的接口、函數而浪費時間。
Design by Contract的缺點
其最主要的缺點在於,若是要制定詳細的規範,會花費大量的時間,尤爲是在時間較爲緊迫的狀況下,不得不去稍微放款點標準,畢竟不可能在一開始就想到某些接口的具體實現,這個須要必定時間的判斷。最終拖累了項目。而在人員較少的時候,當面溝通比較方便的時候,能夠在撰寫的同時約定好,不用事先進行約定。

如何融入做業
其實咱們在一開始也是約定好了一系列的標準,而且在一開始咱們是比較嚴格的在遵照,可是在後期,在我完成的那部分裏出現了較大的問題,最終致使幾乎是從新更換了一個方法,於是最終可能某些地方沒有遵照約定,好比某些函數裏的變量的命名,在不一樣的函數裏存在着相同的變量名可是意義卻不同,也存在着兩個數據的功能幾乎一致,可是卻不能將其調整爲一個數據的現象。這是個人疏忽。

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

單元測試覆蓋率以下:

部分測試代碼展現

TEST_METHOD(RightTest)
        {
            char * argv_temp[100];
            int argc_temp = 7;

            ofstream outputFile("temp.txt");
            outputFile << "aaa abb bbb bcc ccc cdd";
            outputFile.close();

            argv_temp[0] = (char*)malloc(sizeof(char) * 14);
            strcpy(argv_temp[0], "WordList.exe");
            argv_temp[1] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[1], "-w");
            argv_temp[2] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[2], "-h");
            argv_temp[3] = (char*)malloc(sizeof(char) * 2);
            strcpy(argv_temp[3], "a");
            argv_temp[4] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[4], "-t");
            argv_temp[5] = (char*)malloc(sizeof(char) * 2);
            strcpy(argv_temp[5], "c");
            argv_temp[6] = (char*)malloc(sizeof(char) * 10);
            strcpy(argv_temp[6], "temp.txt");

            string error_message;
            Assert::AreEqual((double)kErrorNone,
                (double)executiveCommand(argc_temp, argv_temp, true, &error_message));
            Assert::AreEqual(error_message.c_str(),
                "");
        }

此樣例測試測試最爲簡單的狀況,只有一個方向,且沒有分支的。

TEST_METHOD(RightTestWithR)
        {
            char * argv_temp[100];
            int argc_temp = 8;

            ofstream outputFile("temp.txt");
            outputFile << "aaa abb acc baa bbb bcc caa cbb ccc";
            outputFile.close();

            argv_temp[0] = (char*)malloc(sizeof(char) * 14);
            strcpy(argv_temp[0], "WordList.exe");
            argv_temp[1] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[1], "-w");
            argv_temp[2] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[2], "-h");
            argv_temp[3] = (char*)malloc(sizeof(char) * 2);
            strcpy(argv_temp[3], "a");
            argv_temp[4] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[4], "-t");
            argv_temp[5] = (char*)malloc(sizeof(char) * 2);
            strcpy(argv_temp[5], "c");
            argv_temp[6] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[6], "-r");
            argv_temp[7] = (char*)malloc(sizeof(char) * 10);
            strcpy(argv_temp[7], "temp.txt");

            string error_message;
            Assert::AreEqual((double)kErrorNone,
                (double)executiveCommand(argc_temp, argv_temp, true, &error_message));
            Assert::AreEqual(error_message.c_str(),
                "");
        }

此樣例較上同樣例略複雜一些,爲全聯通三角形,且爲雙向

TEST_METHOD(ErrorCirculation)
        {
            char * argv_temp[100];
            int argc_temp = 7;

            ofstream outputFile("temp.txt");
            outputFile << "aaa abb acc baa bbb bcc caa cbb ccc";
            outputFile.close();

            argv_temp[0] = (char*)malloc(sizeof(char) * 14);
            strcpy(argv_temp[0], "WordList.exe");
            argv_temp[1] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[1], "-w");
            argv_temp[2] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[2], "-h");
            argv_temp[3] = (char*)malloc(sizeof(char) * 2);
            strcpy(argv_temp[3], "a");
            argv_temp[4] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[4], "-t");
            argv_temp[5] = (char*)malloc(sizeof(char) * 2);
            strcpy(argv_temp[5], "c");
            argv_temp[6] = (char*)malloc(sizeof(char) * 10);
            strcpy(argv_temp[6], "temp.txt");

            string error_message;
            try {
                executiveCommand(argc_temp, argv_temp, true, &error_message);
            }
            catch (exception &e) {
                Assert::AreEqual(e.what(), "Error: Found circulation in words");
            }
        }

此樣例爲較上述樣例少了-r參數,故會拋出異常。

TEST_METHOD(RightTestWithR)
        {
            char * argv_temp[100];
            int argc_temp = 8;

        ofstream outputFile("temp.txt");
        outputFile << "aaa abb acc bdd bee cff cgg dhh hii";
        outputFile.close();

        argv_temp[0] = (char*)malloc(sizeof(char) * 14);
        strcpy(argv_temp[0], "WordList.exe");
        argv_temp[1] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[1], "-w");
        argv_temp[2] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[2], "-h");
        argv_temp[3] = (char*)malloc(sizeof(char) * 2);
        strcpy(argv_temp[3], "a");
        argv_temp[4] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[4], "-t");
        argv_temp[5] = (char*)malloc(sizeof(char) * 2);
        strcpy(argv_temp[5], "c");
        argv_temp[6] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[6], "-r");
        argv_temp[7] = (char*)malloc(sizeof(char) * 10);
        strcpy(argv_temp[7], "temp.txt");

        string error_message;
        Assert::AreEqual((double)kErrorNone,
            (double)executiveCommand(argc_temp, argv_temp, true, &error_message));
        Assert::AreEqual(error_message.c_str(),
            "");
    }

此樣例爲一個簡單的樹,只有一個根節點。

TEST_METHOD(RightTestWithR)
        {
            char * argv_temp[100];
            int argc_temp = 8;

        ofstream outputFile("temp.txt");
        outputFile << "aaa abb acc bdd bee cff cgg dhh hii jkk khh";
        outputFile.close();

        argv_temp[0] = (char*)malloc(sizeof(char) * 14);
        strcpy(argv_temp[0], "WordList.exe");
        argv_temp[1] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[1], "-w");
        argv_temp[2] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[2], "-h");
        argv_temp[3] = (char*)malloc(sizeof(char) * 2);
        strcpy(argv_temp[3], "a");
        argv_temp[4] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[4], "-t");
        argv_temp[5] = (char*)malloc(sizeof(char) * 2);
        strcpy(argv_temp[5], "c");
        argv_temp[6] = (char*)malloc(sizeof(char) * 3);
        strcpy(argv_temp[6], "-r");
        argv_temp[7] = (char*)malloc(sizeof(char) * 10);
        strcpy(argv_temp[7], "temp.txt");

        string error_message;
        Assert::AreEqual((double)kErrorNone,
            (double)executiveCommand(argc_temp, argv_temp, true, &error_message));
        Assert::AreEqual(error_message.c_str(),
            "");
    }

此樣例爲稍複雜的樹,有多個根節點,一塊兒組成了森林。

計算模塊部分異常處理說明

計算模塊異常主要分爲 部分,一是命令格式錯誤,例如-w和-c均不存在或均存在,命令參數重複,首尾字符長度不爲1等,這些是在接收到命令時就能得出結論的。二是文件相關錯誤,包括讀寫錯誤,也包括讀取string時的錯誤;三是文本錯誤,例如沒有-r指令時卻有環路。

對此咱們解決辦法是當遇到錯誤時拋出異常,基本格式以下:

std::logic_error ex("Error: Repeated parameter -c");
    throw exception(ex);

而在CLI或者GUI的模塊中,對此異常進行處理,例如:

try{
        executiveCommand(argc, argv, true, &result_string);
    } catch (exception &e){
        cout << e.what() << endl;
        system("pause");
        return 0;
    }

此時程序將會中止後續的運行,並顯示錯誤提示信息。

例如-w -c參數同時出現:

// -w -c 參數均出現
        TEST_METHOD(CommandParsing_CoexistenceOfWC) {
            char * argv_temp[100];
            int argc_temp = 4;
            ofstream output_file("temp.txt");
            output_file.close();

            argv_temp[0] = (char*)malloc(sizeof(char) * 14);
            strcpy(argv_temp[0], "WordList.exe");
            argv_temp[1] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[1], "-w");
            argv_temp[2] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[2], "-c");
            argv_temp[3] = (char*)malloc(sizeof(char) * 10);
            strcpy(argv_temp[3], "temp.txt");

            string error_message;
            try {
                executiveCommand(argc_temp, argv_temp, true, &error_message);
            }
            catch (exception &e) {
                Assert::AreEqual(e.what(), "Error: Coexistence of -w and -c");
            }
        }

例如須要讀取的文件路徑不存在

// 須要讀取的文件路徑不存在
        TEST_METHOD(GetWords_getWordsFromFile_noFile)
        {
            remove("temp.txt");

            GetWords get_words;
            Assert::IsFalse(get_words.getWordsFromFile("temp.txt"));
        }

例如沒有-r指令時卻有環

// 沒有-r指令卻有環路
        TEST_METHOD(Core_circle_without_r) {
            char * argv_temp[100];
            int argc_temp = 3;

            ofstream outputFile("temp.txt");
            outputFile << "abc cba";
            outputFile.close();

            argv_temp[0] = (char*)malloc(sizeof(char) * 14);
            strcpy(argv_temp[0], "WordList.exe");
            argv_temp[1] = (char*)malloc(sizeof(char) * 3);
            strcpy(argv_temp[1], "-w");
            argv_temp[2] = (char*)malloc(sizeof(char) * 10);
            strcpy(argv_temp[2], "temp.txt");

            string error_message;
            try {
                executiveCommand(argc_temp, argv_temp, true, &error_message);
            }
            catch (exception &e) {
                Assert::AreEqual(e.what(), "Error: Found circulation in words");
            }
        }

界面模塊的詳細設計過程

程序的GUI使用Qt進行設計。程序UI以下:

其中-w和-c是單選按鈕,-h,-t,-r是複選按鈕,若使用-h,-t則須要同時在其對應的輸入框中輸入相應首尾字母。Reading file path和Reading string text是單選按鈕,分別表示從指定文件路徑讀取文本,或直接輸入文本,路徑或文本均在輸入框1中輸入。點擊Executive 後將會解析命令,若是執行正確,結果將會顯示在輸出框中,不然將會彈出錯誤提示信息。輸入框2指定導出文件路徑,默認爲當前路徑下的solution.txt文件,點擊Export result to按鈕便可將輸出框中的內容導出到指定文件。

GUI部分僅涉及界面及相關邏輯,命令解析後的執行均交給API部分執行。GUI的主體部分是QWDialog類,其包含以下變量:

包含以下函數:

界面模塊與計算模塊的對接

整個程序主要分爲3個部分,分別爲負責計算的Core模塊,負責獲取命令的界面模塊(分別包括CLI和GUI),負責在二者之間傳遞數據,解析命令,文件讀寫的API模塊,此部分主要介紹API模塊。

API模塊主要由兩部分組成,第一部分是文件讀寫部分,第二部分是命令解析部分。

首先是文件讀寫部分,主要包含於GetWords.h和GetWords.cpp文件中,文件讀寫部分包含一個類GetWords,包含以下變量:

包含以下函數:

負責從文件中或string中讀取並分割單詞,返回獲取到的單詞列表,將指定單詞列表輸出到指定文件。

而後是命令解析和數據傳遞部分,主要包含於CommandParsing.h和CommandParsing.cpp文件中,首先其中定義了兩個enum,分別用於表示命令和錯誤代碼:

包含一個函數:

參數含義:命令參數列表,命令參數數量,文原本源(即從文件中讀取仍是直接輸入),result_string用於返回字符串形式的錯誤信息,是否儲存至文件(若不儲存至文件,result_string將會在正確運行的狀況下,儲存運行結果),儲存結果的文件路徑。

executiveCommand函數運行邏輯以下:

首先進行初始化

統計命令參數,此時會對命令的合法性進行檢測,例如是否存在重複參數,-w和-c是否均存在或均不存在

若是命令沒有語法錯誤,將會調用GetWords類,計算模塊逐步進行 文件讀取,分解得到單詞列表,計算結果,同時也會對文件是否存在等進行檢測

上述步驟均正確後,將結果寫入指定文件或返回至調用者

而GUI和CLI則會對調用executiveCommand函數,實現與計算模塊的對接,例如:

CLI:

GUI:

結對過程

咱們兩人結對時間爲第二週週五(即3月1日),當天即見面並進行討論。在後續過程當中保持着較高頻率的結對編程(兩次見面之間的間隔通常不超過2天)。

兩人第一次見面時的討論照片:

說明結對編程的優勢和缺點。結對的每個人的優勢和缺點在哪裏 (要列出至少三個優勢和一個缺點)。

結對編程的優勢:互相監督,可以當場發現編寫時產生的小BUG,同時可以及時交流,而且避免了一我的寫代碼時出現的"摸魚"的狀況。

結對編程的缺點:相比於兩我的同時寫,代碼量有所降低,尤爲是一些比較簡單的代碼,結對編程時比較浪費時間。

同伴的優勢

  • 可以及時完成,積極交流。
  • 想法獨特,在我向他抱怨單詞量太大可能有問題時,他提議針對26個字母單獨判斷。
  • 認真負責。

同伴的缺點

  • 可能對個人督促力度仍是不夠,我可能在必定程度上拖慢了進度。

模塊鬆耦合

交換同窗及學號

姓名 學號
牛雅哲 16131059
王文珺 16061007

咱們兩組均將模塊封裝爲了dll,對方採用的是cmake進行編譯,可使用vs直接打開,故測試時並無遇到不少問題。

在測試的過程當中,咱們發現對方沒有嚴格按照做業的要求對接口進行定義,例如輸入和輸出字符數組,他們採用的是string類,故在調用前,咱們須要將界面模塊和API模塊獲得的字符數組拼接成爲一個string。而對於控制檯輸出能夠直接採用cout進行輸出,而對於文件的輸出,因爲咱們是封裝了一個函數進行文件輸出,因此須要將string轉化爲字符數組做爲參數進行傳遞,但本質上沒有太大區別。

除此以外,對方的函數比規定多了一個參數,用於具體的返回錯誤信息,而咱們對此採用的是使用throw拋出異常的方式。

在閱讀對方的源代碼的過程當中,咱們發現對方定義了大量的自定義結構,枚舉,使得代碼的可讀性較高,哪怕在沒有註釋的狀況下閱讀也不會很困難,這一點值得咱們學習。

相關文章
相關標籤/搜索