軟件工程結對做業

結對做業博客

Github 項目地址

https://github.com/AlbertShenC/LongestWordChainc++

PSP表格估計時間

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

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

咱們的程序總體上來講分爲了四個互相獨立,由互相依靠的部分,分別是計算模塊Core,圖形界面GUI,命令行界面CLI,鏈接計算模塊和GUI,CLI,文件讀寫的API模塊。這幾個模塊之間的沒有全局的數據,運行時也不會直接影響到其餘模塊,僅經過參數,返回值等方式互相傳遞數據。實現了高內聚低耦合的思想。git

這使得咱們能夠將整個程序分爲以上四個部分分別進行編程和測試,同時在debug時也可以很方便地直接定位到具體是哪個模塊出現了問題。github

同時在模塊內部,咱們也盡力使得其各部分之間實現低耦合。例如API模塊,咱們將其分爲了文件讀寫,鏈接功能兩個部分。算法

在編寫程序的過程當中,逐個編寫,逐個測試,使得咱們在很早的時候就基本上解決了程序的bug。同時因爲提早約定了接口定義,也使得各模塊之間的組合基本沒有遇到問題。編程

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

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

總體共使用了一個類以及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()函數,因爲沒有環路,所以每一個字母實際上只須要計算一次,若是發現這個字母已經計算過了,就直接返回它的最長鏈的長度。若是沒計算過,就判斷它是否是到達告終尾。若是是結尾,根據可能規定的結尾字母來判斷這個字母的最長鏈長度。ide

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

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


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

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

UML圖

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

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

看Design by Contract, Code Contract的內容:http://en.wikipedia.org/wiki/Design_by_contracthttp://msdn.microsoft.com/en-us/devlabs/dd491992.aspx描述這些作法的優缺點, 說明你是如何把它們融入結對做業中的

優勢

當許多人一塊兒合做開發一個項目時,對於每個接口都須要有明確的規定,Design by contract可以極大地下降多人開發時因爲接口不統一形成的衝突,減小後續debug的時間。

缺點

當團隊規模較小,項目較簡單時,Design by contract的意義沒有那麼明顯,反而可能由於花費了太多時間在規格統一上,而下降了整體的效率。若是後期有需求變更,須要對規格進行修改,工做量也較大。

如何融入做業

這次做業咱們試圖對咱們的編程進行規範,例如咱們約定了代碼風格,同時也在編寫API模塊時對接口進行了細緻的定義,例如輸入的要求,返回值的性質,運行過程當中可能修改的數據,甚至有哪些須要調用者進行釋放的空間。同時,爲了下降學習和使用成本,這些內容咱們均採用了天然語言進行描述,可能會致使必定程度的不精確。

但在後續的屢次修改過程當中,尤爲是在計算模塊中,修改了多處bug,同時也有中途增長新的數據結構,拋棄之前的數據結構等,使得結構變化較爲劇烈,因此後期刪除了模塊內部的接口規範,僅保留了與其餘模塊相關的接口的明肯定義。

應該說咱們最後也嚐到了忽略規範的苦頭,在進行了屢次修改後,不管是代碼風格仍是接口定義都與最開始的約定有了很大區別,甚至也出如今不一樣的函數中,同一個變量的含義不一樣的狀況,致使程序的可讀性不好,因此還花了一部分時間對程序的接口進行優化,從而方便後期的調試和修改。

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

單元測試覆蓋率以下:

部分測試代碼展現

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。因爲雙方是一人編程,一人引導,雙方視角的不一樣,思路的不一樣,可以及早的發現bug並修正,從而減小後期debug的時間花費。

結對編程的缺點

  • 若是結對雙方不熟悉,可能會致使雙方磨合須要花費大量時間,同時因爲不熟悉對方的習慣,工做環境等,可能反而會使得工做效率下降
  • 時間上難以協調。結對編程要求兩人都須要在場,對於如今學生階段而言,你們的課程生活習慣都不同,難以找到雙方同時有空的時候。

搭檔的優勢

  • 穩紮穩打。在前期考慮如何編寫核心的計算代碼時,搭檔沒有急於下手,而是仔細地考慮有哪些優化方法,在編寫程序的過程當中,咱們基本上沒有大規模修改過程序的框架。
  • 執着。在合做的過程當中,搭檔從未說過放棄的話,不管程序出現了什麼問題,都會耐心的去解決他。
  • 編程能力優秀。不管時總體上的算法,仍是具體實現時一些小細節,搭檔都能想出不少我沒想到的點,對於程序總體效率的提升有很大的功勞。

搭檔的缺點

  • 代碼風格不是很好。在合做的過程當中,雖然咱們開始時約定了代碼風格,但在後期寫程序的過程當中,尤爲是debug時,代碼風格其實比較混亂,甚至也出現了一些相似於aaa命名的變量。

模塊鬆耦合

交換同窗及學號

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

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

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

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

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

相關文章
相關標籤/搜索