項目 | 內容 |
---|---|
本次做業所屬課程 | 2019BUAA軟件工程 |
本次做業要求 | 結對編程做業 |
我在本課程的目標 | 熟悉結對編程流程 |
本次做業的幫助 | 實踐告終對編程的流程,對結對編程的優缺點有了更深的體會 |
本次做業項目github地址 | 項目地址 |
項目地址html
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 |
看教科書和其它資料中關於Information Hiding, Interface Design, Loose Coupling的章節,說明大家在結對編程中是如何利用這些方法對接口進行設計的git
Information Hiding-信息隱藏是一種編程原則,一般與封裝一塊兒使用。Information Hiding建議對於程序內部的實現進行隱藏,從而防止對於對程序進行粗放的修改。對外提供比較穩定的接口,在使用時可忽略內部的實現。github
Interface Design-在閱讀資料過程當中,發現良好的接口設計要遵循許多原則例如單一職責,里氏替換,依賴倒置等,因爲本次實現的接口較爲簡單,基本保證了這些原則。算法
Loose Coupling-即鬆散耦合。鬆散耦合系統中,組件對於其餘組件定義的系統利用較少,鬆散耦合的優勢是,能夠將一些模塊自由的替換爲其餘功能相同的模塊。編程
在咱們的設計中,爲了保證依賴倒置原則以及採用鬆散耦合的結構,對於內部的計算類Sovler類用Core類進行額外的封裝,Core類僅僅提供兩個靜態的接口可供調用。這樣一來,用戶便不用對於Sovler類的內部方法又必定的瞭解,也不用調用接口前先產生一個Solver對象。架構
計算模塊接口的設計與實現過程,設計包括代碼如何組織,好比會有幾個類,幾個函數,他們之間關係如何,關鍵函數是否須要畫出流程圖?說明你的算法的關鍵(沒必要列出源代碼),以及獨到之處。app
計算模塊是整個項目的核心,發現這個模塊計算的內容有很強的聚合性,所以採用面向對象的方式進行封裝,最後抽象出一個實體計算類Solver以及接口類Core,共兩個類。Core類的接口每次會實例化一個Solver對象,而後調用max_chain_word函數進行計算。畫出一個調用的流程圖:函數
算法大體思路:首先拿到問題須要對原問題進行建模,一條單詞鏈相鄰兩個單詞的特徵是,前一個單詞的末尾等於後一個單詞的頭,這個能夠經過有向圖的有向邊實現。對於不出現-r參數的狀況至關於求圖中一些指定點做爲起點,一些點做爲終點的最大路徑問題(採用了一個經典的SPFA算法)。對於出現-r參數的狀況歸約爲求有向有環圖中的最大路徑問題,屬於一個很是經典的NP問題,對於這種狀況並無多項式時間的準確算法,所以採用dfs搜索算法。工具
算法的獨到之處:採用一圖多權值的方法,由於原問題有求最長單詞數和字符數兩種需求,因此在兩個節點邊的權值記錄了兩個,求最大單詞數目時使用權值爲1,求最長單詞數使用權值爲目的節點的字符長度。這樣對於兩種問題的求解方法是基本同樣的,只是計算不一樣的邊權,很好的作到了代碼複用。對於輸入的單詞列表轉化爲圖以後首先採用拓撲排序的方法判斷圖是否有環,對於-r參數的狀況下不必定調用dfs搜索(當判斷出圖中無環時仍然採用時間複雜度低的SPFA算法)。oop
閱讀有關UML的內容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。畫出UML圖顯示計算模塊部分各個實體之間的關係(畫一個圖便可)。
VS2017能夠下載安裝包,支持自動生成類圖,參考博客 。最後生成的類圖以下:
計算模塊接口部分的性能改進。記錄在改進計算模塊性能上所花費的時間,描述你改進的思路,並展現一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),並展現你程序中消耗最大的函數。
在完成基本程序的運行功能以後,我對程序的性能進行了必定的改進,在這個過程當中是很痛苦的,大概花了兩天的時間,其中也走了不少彎路,最後在無環圖上優化取得了必定效果,可是有環圖改進的幅度很小。
改進的思路:對於無環的狀況,儘量採用時間複雜度比較低的算法,權衡再三最後將最終算法改成SPFA算法。對於有環的狀況,由於跟許多同窗討論證明這是一個NP問題,因此能作的改進也是頗有限,主要的思路就是在dfs搜索的時候能剪枝,以及在某些特殊的狀況下須要進行特殊處理(好比若是判斷出無環,即便帶-r參數要調用SPFA函數而不是暴力搜索,以及若是有環的狀況下找到一個長度爲n的鏈(n爲節點總數)也能夠中止搜索獲得答案)。對於有環的狀況,實際上還能夠考慮一些啓發式算法好比模擬退火算法等,可是本題目須要輸出準確解,啓發式算法有必定風險,所以沒有采用該方法。
這部分主要有兩種狀況,一個是沒有-r參數的時候調用SPFA方法時的性能,
下面展現一個較小數據集下不帶-r的性能分析圖:
發現其中耗時最可能是的生成圖的函數,說明計算算法性能基本能夠。
一個是帶有-r參數且圖中有環會頻繁遞歸調用DFS函數的狀況,這一部分毫無疑問是dfs函數被屢次遞歸調用。
看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,即契約編程,意爲咱們在聲明一個函數/方法的時候,對函數的輸入和輸出所具有的性質是有所指望和規定的。對於全部組件,規定前置條件,後置條件以及不變量。契約編程要求對於前置條件,後置條件,不變量。
契約編程的好處是能夠保證程序的健壯性,並嚴格的分析責任。編程人員僅須要對本身的部分按照契約負責。缺點是,契約式設計比較繁瑣,若是每一處都採用契約式設計會費時費力。
具體在咱們的做業中,並未嚴格按照契約式編程規定前置條件,後置條件以及不變量,更多的是在組件內檢測是否符合條件。
計算模塊部分單元測試展現。展現出項目部分單元測試代碼,並說明測試的函數,構造測試數據的思路。並將單元測試獲得的測試覆蓋率截圖,發表在博客中。要求整體覆蓋率到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))); }
計算模塊部分異常處理說明。 在博客中詳細介紹每種異常的設計目標。每種異常都要選擇一個單元測試樣例發佈在博客中,並指明錯誤對應的場景。
異常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 輸入的單詞列表中含非法單詞 異常發生在用戶直接調用接口的時候,沒有保證每個單詞都是由小寫英文單詞組成,所以在計算以前也須要進行單詞合法性檢查。
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; } }
其他還有一些參數錯誤等異常均在命令行輸入處理模塊進行處理。
在博客中詳細介紹界面模塊是如何設計的,並寫一些必要的代碼說明解釋實現過程。
對於界面模塊的設計,採用了MFC程序來實現。整個UI界面包括輸入,輸出,參數選擇三個部分,具體以下圖所示。
下面對部分MFC元素的實現做解釋說明。
comb_choose_input.AddString(_T("選擇文件輸入(默認)")); comb_choose_input.AddString(_T("直接輸入")); comb_choose_input.SetCurSel(0);
在初始化時插入選擇枝,並設定默認選擇爲文件輸入,在點擊運行後的時間經過GetCurSel()方法來獲取用戶的選擇。
使用
GetCheckedRadioButton(IDC_max_word, IDC_max_length)
來判斷選擇枝
採用
GetDlgItem(ID)->GetWindowText(Cstring)
來獲取文本框中的文本,須要注意的是,文本內容獲取到後類型爲Cstring,可用
CT2A(Cstring.GetBuffer())
來轉換Cstring爲string。
使用
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,若是要保存文件直接打開文件並將內容寫入便可。
詳細地描述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返回到用戶界面。
咱們兩人的結對編程,經歷了順利的開端,略微坎坷的通過和最後圓滿的結束。
起初最開始接觸結對編程,咱們按照領航員與駕駛員的模式。首先兩人一塊兒商討了關於最長鏈算法的問題,並在有環時如何處理上進行了一段時間的探討。隨後按照駕駛員寫代碼,領航員檢查設計並監督的方式進行,開始效果良好,避免了很多人爲bug的產生。
在幾回身份對換以後,咱們發現,有時在他人的代碼基礎之上繼續完成本身的代碼十分困難。思路不斷交替,寫代碼的效率有了必定的降低。在同伴在本身的代碼基礎上進行完成時,若完成的結果和本身的思路有些衝突,還會形成一些麻煩。好在項目比較小,咱們兩方也都比較認真且有團隊精神,在不斷的磨合之中,適應了對方的步伐,磨合的也愈來愈好。在最後例如單元測試,錯誤處理等模塊的實現中,咱們的結對編程比較順利。
整體來講此次結對編程是一次不錯的體驗,與他人一塊兒交流,互相交換編程思想的經歷難能難得。雖然在效率上可能有些問題,但最終項目的完成質量不錯就是好的。
看教科書和其它參考書,網站中關於結對編程的章節,例如:
http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html
說明結對編程的優勢和缺點。
結對的每個人的優勢和缺點在哪裏 (要列出至少三個優勢和一個缺點)。
結對編程最大的優勢在於,兩人不斷的複審,從而提升代碼的質量。在結對編程中,兩人不斷交換角色,不斷審覈對方的代碼。「當局者迷,旁觀者清」,做爲旁觀者更容易發現代碼的問題。同時,寫代碼時兩人不時交流,利於在某些算法或者架構上優化。
固然,結對編程中也會有兩人配合不佳致使效率較低的狀況。問題過難或過簡單都不太適合結對編程,比較浪費時間。
成員 | 優勢 | 缺點 |
---|---|---|
馬振亞 | (1)檢查設計時認真仔細,是個好的領航員 (2)創建測試樣例比較完備 (3)爲人比較耐心細緻,對搭檔友好 | 有時不太冷靜,容易對代碼有誤操做 |
馬浩翔 | (1)可以本身探索一些解決方案(2)對搭檔友善(3)對git的掌握還能夠 | 不愛看做業要求 |
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 | 50 |
合計 | 1710 | 2345 |
由於有兩個小組的邀請,因此最後和兩個小組進行了模塊交換,由於跟第一個小組中出現了問題並進行了修改,因此如下博客展現與第一個小組的交換出現的問題。
合做小組1成員: 16061192 汪慕瀾 16061103 趙智源
合做小組2成員: 16021160 莊廓然 15061078 楊帥
問題: 問題主要起源於我對接口的理解有誤,在初版程序的時候將類的兩個函數public化做爲了一個接口,並且這個類是有狀態的。可是發現對方的直接聲明瞭一個沒有狀態的類,聲明瞭類的static方法。以前本身的寫法會產生不少warning(warning提示調用接口須要聲明一個類的客戶端對象),在對面小組的解釋後對本身的接口設計進行了重構,對接口又進行了一次封裝。
交換效果:
咱們的程序在對方的gui上能夠完成運行,效果以下:
對方的程序也能夠在咱們的gui上完成運行: