2019軟件工程結對做業

軟件工程結對編程做業

項目 內容
本次做業所屬課程 2019BUAA軟件工程
本次做業要求 結對編程做業
我在本課程的目標 熟悉結對編程流程
本次做業的幫助 實踐告終對編程的流程,對結對編程的優缺點有了更深的體會
本次做業項目github地址 項目地址

1.本次做業項目github地址

項目地址html

2.開發前PSP表

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

3.接口設計方法

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

我認爲這三個方法是類似的,首先不管是面向過程編程中的函數的概念仍是面向對象中的封裝的思想都是信息隱藏的原則的體現,這個原則強調代碼模塊應該採用定義良好的接口來封裝(模塊的內部結構僅由負責開發的程序員關注),也體現了接口抽象的原則。鬆耦合原則的也被人稱做「高內聚,低耦合」原則,第一次接觸這個概念是高老師在計組課上提出CPU這種工程設計模塊的時候必定要遵循高內聚低耦合的原則,強調一個代碼單元都須要是獨立的。在咱們的結對編程中,咱們採用了類將實現過程封裝,類的大部分方法和成員變量對外部均不可見的,符合了信息隱藏的原則。同時將整個項目按照功能劃分紅一個個模塊,不一樣功能的部分被分紅不一樣的部分。接口的返回值以及輸入參數均是基本類型,生成的dll文件也能夠適用於其餘程序,因此符合低耦合的標準。程序員

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

計算模塊接口的設計與實現過程,設計包括代碼如何組織,好比會有幾個類,幾個函數,他們之間關係如何,關鍵函數是否須要畫出流程圖?說明你的算法的關鍵(沒必要列出源代碼),以及獨到之處。github

計算模塊是整個項目的核心,發現這個模塊計算的內容有很強的聚合性,所以採用面向對象的方式進行封裝,最後抽象出一個實體計算類Solver以及接口類Core,共兩個類。Core類的接口每次會實例化一個Solver對象,而後調用max_chain_word函數進行計算。畫出一個調用的流程圖:算法

算法大體思路:首先拿到問題須要對原問題進行建模,一條單詞鏈相鄰兩個單詞的特徵是,前一個單詞的末尾等於後一個單詞的頭,這個能夠經過有向圖的有向邊實現。對於不出現-r參數的狀況至關於求圖中一些指定點做爲起點,一些點做爲終點的最大路徑問題(採用了一個經典的SPFA算法)。對於出現-r參數的狀況歸約爲求有向有環圖中的最大路徑問題,屬於一個很是經典的NP問題,對於這種狀況並無多項式時間的準確算法,所以採用dfs搜索算法。編程

算法的獨到之處:採用一圖多權值的方法,由於原問題有求最長單詞數和字符數兩種需求,因此在兩個節點邊的權值記錄了兩個,求最大單詞數目時使用權值爲1,求最長單詞數使用權值爲目的節點的字符長度。這樣對於兩種問題的求解方法是基本同樣的,只是計算不一樣的邊權,很好的作到了代碼複用。對於輸入的單詞列表轉化爲圖以後首先採用拓撲排序的方法判斷圖是否有環,對於-r參數的狀況下不必定調用dfs搜索(當判斷出圖中無環時仍然採用時間複雜度低的SPFA算法)。設計模式

5.UML圖

閱讀有關UML的內容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。畫出UML圖顯示計算模塊部分各個實體之間的關係(畫一個圖便可)。性能優化

VS2017能夠下載安裝包,支持自動生成類圖,參考博客 。最後生成的類圖以下:app

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

計算模塊接口部分的性能改進。記錄在改進計算模塊性能上所花費的時間,描述你改進的思路,並展現一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),並展現你程序中消耗最大的函數。函數

在完成基本程序的運行功能以後,我對程序的性能進行了必定的改進,在這個過程當中是很痛苦的,大概花了兩天的時間,其中也走了不少彎路,最後在無環圖上優化取得了必定效果,可是有環圖改進的幅度很小。

改進的思路:對於無環的狀況,儘量採用時間複雜度比較低的算法,權衡再三最後將最終算法改成SPFA算法。對於有環的狀況,由於跟許多同窗討論證明這是一個NP問題,因此能作的改進也是頗有限,主要的思路就是在dfs搜索的時候能剪枝,以及在某些特殊的狀況下須要進行特殊處理(好比若是判斷出無環,即便帶-r參數要調用SPFA函數而不是暴力搜索,以及若是有環的狀況下找到一個長度爲n的鏈(n爲節點總數)也能夠中止搜索獲得答案)。對於有環的狀況,實際上還能夠考慮一些啓發式算法好比模擬退火算法等,可是本題目須要輸出準確解,啓發式算法有必定風險,所以沒有采用該方法。

這部分主要有兩種狀況,一個是沒有-r參數的時候調用SPFA方法時的性能,

下面展現一個較小數據集下不帶-r的性能分析圖:

發現其中耗時最可能是的生成圖的函數,說明計算算法性能基本能夠。

一個是帶有-r參數且圖中有環會頻繁遞歸調用DFS函數的狀況,這一部分毫無疑問是dfs函數被屢次遞歸調用。


7.Design by Contract, Code Contract

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

Design by Contract,又稱契約式設計,是面向對象程序設計的一大原則。這個設計原則須要咱們使用三類斷言:前提條件,後繼狀態和不變量。前提條件是指執行某種操做以前指望具有的環境,後繼狀態是知足前提條件在方法結束退出時系統所擁有的狀態。

優勢:契約式設計強調了調用者和被調用者地位的平等性,雙方都須要履行必定的義務,在傳統的C/S設計模式下,被調用者每每指望處理一切可能的異常,對調用者不作約束,每每形成調用方代碼質量不好。契約式設計要求調用者調用前準備好正確的參數,被調用者須要保證正確的結果和不變形,保證了雙方的代碼質量。

缺點:對於程序設計語言有必定要求,DBC須要斷言來驗證契約是否成立,可是並非全部的程序設計語言都有斷言機制。

結對編程做業中,對異常的捕捉都由一個函數進行處理,把類的大部分方法都封裝成私有方法,調用的時候會知足隱含前置條件,對於接口的前置進行了約束。模塊之間以及函數之間採用了契約的思想,保證了雙方在一次函數調用的時候都須要承擔必定的責任。

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

計算模塊部分單元測試展現。展現出項目部分單元測試代碼,並說明測試的函數,構造測試數據的思路。並將單元測試獲得的測試覆蓋率截圖,發表在博客中。要求整體覆蓋率到90%以上,不然單元測試部分視做無效。

計算模塊單元測試覆蓋率達到了98%,截圖以下

單元測試分爲兩大部分,一個是對正常狀況下的處理,一個是對異常處理,異常部分的單元測試請參見博客第九部分。正常狀況下的測試分爲三種,一個是無環無-r參數,一個無環有-r參數,一個是有環有-r參數。對前兩種,測試了最大單詞數和最大字符數結果相同、結果不一樣的狀況,同時也測試了不一樣的頭尾對結果的限制。對於第三種有環的狀況,也測試全部無環的狀況,同時測試了圖中有多個環的狀況。除上面的常規測試以外還增長了邊界測試,好比最後的結果只有一個單詞(不能構成單詞鏈),以及輸入長度爲0的情形。展現部分單元測試代碼以下:

char *test_list1[] = { "abc","cbd","dbbw","csw","zde","opl","wxx" };
char *test_list2[] = {"room", "mazhenya", "apple", "elephant","mahaoxiang","gxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzorange","peanut"};
char *test_list3[] = {"abc","cbd","bba","ddb","yz","uv","wx","vw","xy"};
char *test_list4[] = { "xppppy", "fb","ef","bc","de","cd","zpppb","yppppz" };
char *test_list5[] = { "xppppy", "fb","ef","bc","ft","tb","zpppb","yppppz" };
char *test_list6[] = { "xppppy", "ce","ef","bc","de","cd","zpppb","yppppz" };
char *test_list7[] = { "tppppppppz","zppppppppx","ab","ef","bc","de","cd" };
char *test_list8[] = { "ac","bz","cb","cc" };
char *test_list9[] = { "ac","bc","cb","cc" };//有環
char *test_list10[] = { "cx","xy","bc","zd","yz","ab","de","cpppppppppppppppppppd" };
char *test_list11[] = {"abc", "abc", "xyz"};
TEST_METHOD(TestMethod1)
        {
            
            char *answer1[] = { "abc","cbd","dbbw","wxx" };
            int word_num = 7;
            int answer_num = 4;
            char **results1 = new char*[word_num + 1];
            int res = Core::gen_chain_word(test_list1, word_num, results1, 0, 0, false);
            Assert::IsTrue(judge(array2string(results1,res), array2string(answer1,answer_num)));
            for (int i = 0; i < res; i++)
                delete[] results1[i];
            delete[] results1;
        }

        TEST_METHOD(TestMethod2)
        {
            int word_num = 7;
            int answer_num = 2;
            char **results2 = new char*[word_num + 1];
            char *answer2[] = { "dbbw", "wxx" };
            int res = Core::gen_chain_word(test_list1, 7, results2, 'd', 0, false);
            Assert::IsTrue(judge(array2string(results2, res), array2string(answer2, 2)));
        }

        TEST_METHOD(TestMethod3)
        {
            int word_num = 7;
            int answer_num = 3;
            char **results3 = new char*[word_num + 1];
            char *answer3[] = { "abc","cbd","dbbw"};
            int res = Core::gen_chain_word(test_list1, word_num, results3, 0, 'w', false);
            Assert::IsTrue(judge(array2string(results3, res), array2string(answer3, answer_num)));
        }
        TEST_METHOD(TestMethod4) {
            int word_num = 8;
            int answer_num = 4;
            char **results4 = new char*[word_num + 1];
            char *answer4[] = { "room", "mazhenya", "apple", "elephant" };
            int res = Core::gen_chain_word(test_list2, word_num, results4, 0, 0, false);
            Assert::IsTrue(judge(array2string(results4, res), array2string(answer4, answer_num)));
        }
        TEST_METHOD(TestMethod5) { 
            int word_num = 8;
            int answer_num = 3;
            char **results5 = new char*[word_num + 1];
            char *answer5[] = { "mazhenya", "apple", "elephant" };
            int res = Core::gen_chain_char(test_list2, word_num, results5, 'm', 't', false);
            Assert::IsTrue(judge(array2string(results5, res), array2string(answer5, answer_num)));
        }
        TEST_METHOD(TestMethod6) {// -c 以t結尾
            int word_num = 8;
            int answer_num = 2;
            char **results = new char*[word_num + 1];
            char *answer[] = { "zzzzzzzzzzzzzzzzzzzzzzzzzzzzorange", "elephant" };
            int res = Core::gen_chain_char(test_list2, word_num, results, 0, 't', false);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }
        TEST_METHOD(TestMethod7) {
            int word_num = 8;
            int answer_num = 2;
            char **results = new char*[word_num + 1];
            char *answer[] = { "mahaoxiang","gxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"};
            int res = Core::gen_chain_char(test_list2, word_num, results, 'm', 0, false);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }
        TEST_METHOD(TestMethod8) {
            int word_num = 9;
            int answer_num = 5;
            char **results = new char*[word_num + 1];
            char *answer[] = { "uv","vw","wx","xy","yz" };
            int res = Core::gen_chain_word(test_list3, word_num, results, 0, 0, true);
            //output(results, res);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }

        TEST_METHOD(TestMethod9) {
            int word_num = 9;
            int answer_num = 4;
            char **results = new char*[word_num + 1];
            char *answer[] = { "abc","cbd","ddb","bba" };
            int res = Core::gen_chain_word(test_list3, word_num, results, 0, 'a', true);
            //output(results, res);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }

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

計算模塊部分異常處理說明。 在博客中詳細介紹每種異常的設計目標。每種異常都要選擇一個單元測試樣例發佈在博客中,並指明錯誤對應的場景。

異常1 有環無-r參數 異常發生在輸入文件中存在單詞環,可是命令設置不支持-r參數,一般發生在忘記設置-r參數的情景下。設計目標是提醒用戶檢查輸入文本或者參數設置是否有誤。如下是一個測試循環異常的單元測試用例。

char *test_list3[] = {"abc","cbd","bba","ddb","yz","uv","wx","vw","xy"};
TEST_METHOD(TestMethod10) {
            try {
                int word_num = 9;
                int answer_num = 4;
                char **results = new char*[word_num + 1];
                int res = Core::gen_chain_word(test_list3, word_num, results, 0, 0, false);
                Assert::IsTrue(res == -1);
            }
            catch (const char*  s) {
                Assert::IsTrue(strcmp(s, LOOP_ERROR) == 0);
                cout << s << endl;
            }
    }

異常2 輸入的頭尾字符不符合題目要求異常發生在用戶指定的頭尾字符不是0也不是小寫字母,目的是提醒用戶檢查設置的頭尾字符限制參數。如下是一個頭尾字符異常的單元測試。

char *test_list3[] = {"abc","cbd","bba","ddb","yz","uv","wx","vw","xy"};
try {
                int word_num = 9;
                int answer_num = 4;
                char **results = new char*[word_num + 1];
                int res = Core::gen_chain_char(test_list3, word_num, results, '+', '-', true);
                Assert::IsTrue(res == -1);
            }
            catch (const char*  s) {
                Assert::IsTrue(strcmp(s, TAIL_CHAR_ERROR) == 0);
                //Assert::IsTrue(s == LOOP_ERROR);
                cout << s << endl;
            }

異常3 輸入的單詞列表中含非法單詞 異常發生在用戶直接調用接口的時候,沒有保證每個單詞都是由小寫英文單詞組成,所以在計算以前也須要進行單詞合法性檢查。

TEST_METHOD(TestMethod12) {
            char *test_list[] = {"happ1we2", "yuer", "opui8op"};
            try {
                int word_num = 3;
                int answer_num = 0;
                char **results = new char*[word_num + 1];
                int res = Core::gen_chain_word(test_list, word_num, results, 0, 0, false);
            }
            catch (const char* s) {
                Assert::IsTrue(strcmp(s, WORD_ILLEGAL)==0);
                cout << s << endl;
            }
    }

其他還有一些參數錯誤等異常均在命令行輸入處理模塊進行處理。

10.界面模塊的詳細設計過程

界面模塊的詳細設計過程。 在博客中詳細介紹界面模塊是如何設計的,並寫一些必要的代碼說明解釋實現過程。

對於界面模塊的設計,採用了MFC程序來實現。整個UI界面包括輸入,輸出,參數選擇三個部分,具體以下圖所示。

下面對部分MFC元素的實現做解釋說明。

(1)選擇文件輸入仍是界面輸入的CombBox框

comb_choose_input.AddString(_T("選擇文件輸入(默認)"));
comb_choose_input.AddString(_T("直接輸入"));
comb_choose_input.SetCurSel(0);

在初始化時插入選擇枝,並設定默認選擇爲文件輸入,在點擊運行後的時間經過GetCurSel()方法來獲取用戶的選擇。

(2)參數選擇區mode單選框

使用

GetCheckedRadioButton(IDC_max_word, IDC_max_length)

來判斷選擇枝

(3)文本框(包括輸入,輸出,head,tail)

採用

GetDlgItem(ID)->GetWindowText(Cstring)

來獲取文本框中的文本,須要注意的是,文本內容獲取到後類型爲Cstring,可用

CT2A(Cstring.GetBuffer())

來轉換Cstring爲string。

(4)選擇(保存)文件的按鈕

使用

CString strFile = _T("");
CFileDialog    dlgFile(TRUE, NULL, NULL, OFN_HIDEREADONLY, _T("Describe Files All Files (*.*)|*.*||"), NULL);
if (dlgFile.DoModal())
{
    strFile = dlgFile.GetPathName();
    file_path = CT2A(strFile.GetBuffer());
}

來調用MFC內封裝的選擇文件UI,若是要保存文件直接打開文件並將內容寫入便可。

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

界面模塊與計算模塊的對接。詳細地描述UI模塊的設計與兩個模塊的對接,並在博客中截圖實現的功能。

UI模塊的運行

在UI模塊的運行按鈕中綁定事件,點擊後,UI模塊獲取界面上用戶輸入的信息,轉換爲相應的參數來調用。

須要注意的是,在運行UI模塊時,並不支持經過命令行輸入參數進行測試,請直接打開UI模塊(MFC_max_word_chain.exe)。若是但願經過命令行打開加參數的方式進行測試,請使用max_word_chain.exe。

對接方式

UI模塊在資源文件中引入Core.lib,在MFC_max_word_chainDlg.cpp這個事件響應文件中,須要調用計算模塊時,頭文件引入Core.cpp。

處理好用戶界面輸入的信息後,調用

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

這兩個接口進行運算。運算後將result中獲取的結果轉換爲Cstring返回到用戶界面。
最終實現的功能以下:

12.結對的過程

描述結對的過程,提供非擺拍的兩人在討論的結對照片。

咱們兩人的結對編程,經歷了順利的開端,略微坎坷的通過和最後圓滿的結束。

起初最開始接觸結對編程,咱們按照領航員與駕駛員的模式。首先兩人一塊兒商討了關於最長鏈算法的問題,並在有環時如何處理上進行了一段時間的探討。隨後按照駕駛員寫代碼,領航員檢查設計並監督的方式進行,開始效果良好,避免了很多人爲bug的產生。

在幾回身份對換以後,咱們發現,有時在他人的代碼基礎之上繼續完成本身的代碼十分困難。思路不斷交替,寫代碼的效率有了必定的降低。在同伴在本身的代碼基礎上進行完成時,若完成的結果和本身的思路有些衝突,還會形成一些麻煩。好在項目比較小,咱們兩方也都比較認真且有團隊精神,在不斷的磨合之中,適應了對方的步伐,磨合的也愈來愈好。在最後例如單元測試,錯誤處理等模塊的實現中,咱們的結對編程比較順利。

整體來講此次結對編程是一次不錯的體驗,與他人一塊兒交流,互相交換編程思想的經歷難能難得。雖然在效率上可能有些問題,但最終項目的完成質量不錯就是好的。

13.結對編程反思

看教科書和其它參考書,網站中關於結對編程的章節,例如:
http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html
說明結對編程的優勢和缺點。
結對的每個人的優勢和缺點在哪裏 (要列出至少三個優勢和一個缺點)。

結對編程的優勢:

1> 提供更好的設計質量,一個不合理的設計通常不會獲得兩我的的支持,好比在此次結對編程的過程當中,尤爲是在性能優化部分,我提出的一些優化方法老是能很快獲得pair的反饋(支持或者舉出反例),在設計的時候避免不合理的想法可以極大下降試錯所帶來的代價。

2> 獲得更好的代碼質量以及減小bug出現的概率,在此次結對編程中在寫代碼的時候若是沒有遵循規範的時候PAIR會及時詢問代碼含義,督促每一個人遵循良好的代碼規範,同時PAIR會指出不少寫代碼過程當中實現細節的bug。

3> 有利於兩個結對編程的人互相交流學習和傳遞經驗。

結對編程的缺點:

1> 兩我的一塊兒編程的時候會有不少思惟上的碰撞,每一個人的思考方式不同,也比較容易出現對彼此的意見產生疑問沒法融合的狀況。

2> 有可能出現結對編程逐漸演變成單人工做的情形。

隊友的優缺點

優勢:學習能力很強,對於以前沒有接觸過的技術可以快速查找資料並實現;工做能力很強,可以基本按時完成相關的計劃;頗有本身的想法,對於項目的每一步都有思考。

缺點:有時候有點懶散,須要不定時提醒進度和計劃。

本身的優缺點

優勢:由於有班幹部的經歷,因此溝通交流能力較強;有時間觀念,對計劃的執行力較強;可以及時與隊友溝通交流本身的想法與問題。

缺點:

算法能力有限,到最後也沒有找到300s內跑出100個點的有向有環圖的最長鏈問題。

14.PSP表格回填

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

注:有些部分的計時我也不是很肯定究竟算進哪部分比較合理,好比爲了想實現300s跑100個有向有環圖的點,查找了不少算法,也親自動手寫了一些進行驗證,所以這部分一部分放在設計裏面,一部分算進測試裏面了。

15 模塊交換與模塊鬆耦合

由於有兩個小組的邀請,因此最後和兩個小組進行了模塊交換,由於跟第一個小組中出現了問題並進行了修改,因此如下博客展現與第一個小組的交換出現的問題。

合做小組1成員: 16061192 汪慕瀾 16061103 趙智源

合做小組2成員: 16021160 莊廓然 15061078 楊帥

問題: 問題主要起源於我對接口的理解有誤,在初版程序的時候將類的兩個函數public化做爲了一個接口,並且這個類是有狀態的。可是發現對方的直接聲明瞭一個沒有狀態的類,聲明瞭類的static方法。以前本身的寫法會產生不少warning(warning提示調用接口須要聲明一個類的客戶端對象),在對面小組的解釋後對本身的接口設計進行了重構,對接口又進行了一次封裝。

交換效果:

咱們的程序在對方的gui上能夠完成運行,效果以下:

對方的程序也能夠在咱們的gui上完成運行:

相關文章
相關標籤/搜索