項目 | 內容 |
---|---|
這個做業屬於的課程是 | 2019BUAA軟件工程 |
做業要求是 | 結對編程做業 |
我在此次的目標是 | 體會結對編程、鍛鍊合做能力 |
這個做業在哪些方面幫助我實現目標 | 對結對編程有了更深的理解,爲多人合做打下基礎 |
github地址html
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) |
---|---|---|
Planning | 計劃 | 20 |
· Estimate | · 估計這個任務須要多少時間 | 20 |
Development | 開發 | 840 |
· Analysis | · 需求分析 (包括學習新技術) | 60 |
· Design Spec | · 生成設計文檔 | 20 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 10 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 10 |
· Design | · 具體設計 | 50 |
· Coding | · 具體編碼 | 500 |
· Code Review | · 代碼複審 | 40 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 150 |
Reporting | 報告 | 60 |
· Test Report | · 測試報告 | 30 |
· Size Measurement | · 計算工做量 | 10 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 20 |
合計 | 920 |
三個概念學習總結以下:前端
在本項目中,是這樣借鑑這些方法的:git
在計算模塊中,一共有4個類,包括:Input類,處理命令行輸入的類;Readin類,從文件中讀入單詞的類;Genlist類:搜索並生成單詞列表的類;Core類:封裝好的接口類。程序員
Input類有兩個函數github
函數名 | 函數做用 | 調用關係 |
---|---|---|
CompareStr | 用來比較讀入的字符串最後是否是」.txt「 | 無 |
InputHandle | 用來處理命令行的輸入,修改Input類變量 | CompareStr |
Readin類中有四個函數算法
函數名 | 函數做用 | 調用關係 |
---|---|---|
compare | 重定向sort的比較函數,變成降序 | 無 |
compare_str | 重定向sort的比較函數,改爲字符串比較 | 無 |
GetWords | 從文件中讀取單詞,到Words裏,修改類成員變量 | 無 |
ClassifyWords | 將已經讀入的單詞分類,創建26*26個vector,存放以某個字母開頭以某個字母結尾的全部單詞,並將他們排序 | compare、compare_str |
GenList類中有十個函數編程
函數名 | 函數做用 | 調用關係 |
---|---|---|
PrintRoad | 輸出搜到的路徑,路徑是有環的時候搜到的 | 無 |
ClassifyWords | 單詞分類,將已經讀入的單詞分類,創建26*26個vector,存放以某個字母開頭以某個字母結尾的全部單詞,並將他們排序 | 無 |
SerchList | 在已經排過序的單詞中查找單詞鏈,適用於無環模式,運用深度優先搜索的方式,而且採用剪枝 | 無 |
SerchCircle | 查找單詞列表中有沒有環,若是不是-r模式,有環須要報錯 | 無 |
SerchListCircle | 有環的模式下查找單詞鏈,運用深度優先搜索的方式 | 無 |
輸出查找到的單詞鏈,單詞鏈是無環模式下找到的 | 無 | |
SerchListLastMode | 規定結束字母而且是無環模式下找到單詞鏈 | 無 |
gen_chain_word | 模式是-w的時候,按照傳入的各個參數查找單詞鏈,存到result裏,返回單詞鏈長度 | PrintRoad、ClassifyWords、SerchList、SerchCircle、SerchListCircle、Print、SerchListLastMode、GetOutDeg |
gen_chain_char | 模式是-c的時候,按照傳入的各個參數查找單詞鏈,存到result裏,返回單詞鏈長度 | PrintRoad、ClassifyWords、SerchList、SerchCircle、SerchListCircle、Print、SerchListLastMode、GetOutDeg |
GetOutDeg | 獲得全部字母的出度 | 無 |
Core類中有兩個函數:數組
函數名 | 函數做用 | 調用關係 |
---|---|---|
gen_chain_word | 實例化GenList類,調用GenList類中gen_chain_word,實現封裝 | GenList->gen_chain_word |
gen_chain_char | 實例化GenList類,調用GenList類中gen_chain_char,實現封裝 | GenList->gen_chain_char |
使用的時候,首先調用Input類中的InputHandle來處理命令行參數,處理完後,這個類中的變量已經被修改,以後拿到修改後的變量來調用Readin類中的GetWords函數,獲得單詞數組,以後實例化Core類,根據Input類中的類變量來肯定調用Core中的哪個函數,若是是-w模式就調用gen_chain_word函數,若是是-c模式就調用gen_chain_char函數。安全
算法有兩部分,一部分是不能夠存在環的,另外一部分是能夠存在環的。函數
不能夠存在環的部分,主要算法是深度優先搜索,算法把每一個字母做爲圖的一個節點,由於不存在環,因此從一個字母到一個字母只能走一遍,因此能夠忽略相同開始和結束字母的全部單詞。以後進行深度優先搜索,在搜索的過程當中,進行剪枝的操做,當一個點已經走完以後,這個點到結尾的最大深度已經能夠知道,這個時候咱們記錄下來這個最大深度,若是這個點出度爲0,那麼這個點的最大深度爲0,在進入遞歸前,判斷這個點有沒有被走過,若是被走過了,就不進入這個點的遞歸,這樣就減小了不少進入遞歸的次數,從而使速度變快。
這個算法的獨到之處在於這個算法減小了遞歸的次數,作到了一個剪枝的操做,而且以後記錄下來了最大的長度,至關於每一個邊最多隻走一遍,也就是算法的最大複雜度爲26*26,相似於動態規劃的速度,而且代碼從深度優先搜索上的基礎而來,代碼也比較好寫易懂。
對於存在環的部分,使用深度優先搜索,由於能夠存在環,因此以單詞做爲節點,進行了部分剪枝,當一個當前最大路徑中存在一個字母,從這個字母開始的全部邊都走過,那麼這個時候,就不須要以這個點爲起點開始搜路,由於以這個點開始的全部點都被走過,路徑的最長長度也小於這個如今的最長長度,通過這個剪枝操做,可使得速度快不少。
在查找與當前節點相連的邊的時候,記錄下了那些字母可能與這個點相連,這樣遍歷的時候就不須要遍歷26個字母,加快了查找的速度。進行了剪枝操做,使得速度變快,總體代碼思路也是從深度優先搜索開始設計,因此代碼比較好寫易懂。
查找環的部分採用拓撲排序的辦法。首先查找收尾字母同樣的單詞序列,若是長度大於兩個,那麼就成環,以後,按照單詞的收尾字母來初始化每一個字母的入度,從入度爲0的點開始刪掉,而且與它相連的點的入度減一,重複這個操做直到沒有點能夠被刪掉,最後遍歷每一個點的入度,若是有大於0的點,那麼說明成環。
採用拓撲排序的方式,避免了深度優先搜索的速度慢,而且代碼簡潔效率高。
UML使用VS繪製,類圖畫法分享以下:在VS2017的安裝包內,安裝其餘工具中的類設計器,以後進入vs2017,找到須要查看的項目,右鍵項目點擊查看會找到類圖,以後能夠看到每一個類的具體信息,以後本身修改添加調用信息。
UML圖以下:
在改進計算模塊上,一共花費了大約12小時,主要改進了算法在無環的時候的搜索速度,程序最開始的時候,計算無環的算法是無任何剪枝的深度優先搜索,這樣,最慢的狀況是26!,顯然,這樣搜索的速度太慢,因此要進行優化,這部分,咱們主要進行了剪枝的優化。
首先我記錄了每個節點到最終節點的距離,若是這個點自己就是最終節點,那麼就記錄它的距離爲0,而且,若是這個點已經有到結尾的最大距離,那麼就不進入這個點的遞歸,也就是不進入下一層,這樣就少了不少重複遞歸的操做,走過的邊不會再次被走到,因此就使得代碼運行速度顯著提升。而且,有些計算vector的size的函數在for中被重複使用,在把這些提出來之後,代碼運行速度變高。開始,成環部分進行深度優先搜索的時候,每次進入遞歸都要把建好的單詞圖複製一份傳入下一層,這個複製操做很慢,因此改進的過程當中,把這個圖放到類變量中,就不須要每次都傳進去,這樣速度快了十倍以上,以後,思考了剪枝的方式,在當前最長路徑中若是有一個字母,它的全部後繼都被用到,這個時候,最長的單詞鏈不可能以這個字母爲開頭,因此能夠減小遍歷的次數,速度快了五倍左右。
消耗最大的函數:
由於算法在尋找路這一方面作的比較優秀,因此慢在單詞的分類,分類中有單詞的查重功能,在找到當前的單詞就查找一下有沒有和他重複的,這個時候,strcmp就會慢不少。
在成環的時候,性能分析和不成環就不同。
消耗最大的函數:
這個時候,算法主要在遞歸中,因此全部的消耗基本在進入遞歸的函數中,函數出來也消耗了大量寫入的操做。
如下內容參考自一篇優秀的博客
Design by Contract指的是契約式編程,Code Contract指的是代碼契約,兩者的主要觀點是一致的:
契約式編程,源自生活中供應者與客戶之間的「契約/合同」,契約用於兩方,每一方都期待從契約中得到利益,同時也要接受一些義務。一般,一方視爲義務的對另外一方來講是權利。契約文檔要清楚地寫明雙方的權利與義務。
一樣的道理也適用於軟件,簡而言之,就是函數調用者應該保證傳入函數的參數是符合函數的要求,若是不符合函數要求,函數將拒絕繼續執行。
在軟件體系中,程序庫和組件庫被類比爲server,而使用程序庫、組件庫的程序被視爲client。根據這種C/S關係,咱們每每對庫程序和組件的質量提出很嚴苛的要求,強迫它們承擔本不該該由它們來承擔的責任,而過度縱容client一方,甚至要求庫程序去處理明顯因爲client錯誤形成的困境。客觀上致使程序庫和組件庫的設計和編寫異常困難,並且質量隱患反而更多;同時client一方代碼大多鬆散隨意,質量低劣。這種情形,就好像在一個權責不清的企業裏,必然會養一批屍位素餐的混混,苦一批不辭辛苦,不計得失的老黃牛。引入契約觀念以後,這種C/S關係被打破,你們都是平等的,你須要我正確提供服務,那麼你必須知足我提出的條件,不然我沒有義務「排除萬難」地保證完成任務。
優勢:
缺點:須要斷言機制來驗證契約是否成立,但並不是全部的程序語言都有斷言機制。
在本項目中,咱們在代碼中設置了一些斷言機制,代表某個函數對於傳入的參數的一些最基本的要求,好比 處理文件內容的函數GetWords會要求傳入的文件名不能爲null。
計算模塊部分的單元測試代碼 部分展現以下:
TEST_METHOD(TestMethod1) { // TODO: 在此輸入測試代碼 Input *input = new Input(); int n = 3; char * instr[] = { " ", "-w","..\\Wordlist\\a.txt" }; char ** result = new char *[11000]; int len = 0; input->InputHandle(n, instr); Readin *readin = new Readin(); readin->GetWords(input->FileName); int i = 0; for (i = 0; i < 8; i++) { Core *core = new Core(); switch (i) { case(0): len = core->gen_chain_word(readin->Words, readin->WordNum, result, '0', '0', false); Assert::AreEqual(len, 29); break; case(1): len = core->gen_chain_word(readin->Words, readin->WordNum, result, 'd', '0', false); Assert::AreEqual(len, 27); break; case(2): len = core->gen_chain_word(readin->Words, readin->WordNum, result, '0', 'e', false); Assert::AreEqual(len, 27); break; case(3): len = core->gen_chain_word(readin->Words, readin->WordNum, result, 'd', 'e', false); Assert::AreEqual(len, 25); break; case(4): len = core->gen_chain_char(readin->Words, readin->WordNum, result, '0', '0', false); Assert::AreEqual(len, 29); break; case(5): len = core->gen_chain_char(readin->Words, readin->WordNum, result, 'd', '0', false); Assert::AreEqual(len, 27); break; case(6): len = core->gen_chain_char(readin->Words, readin->WordNum, result, '0', 'e', false); Assert::AreEqual(len, 27); break; case(7): len = core->gen_chain_char(readin->Words, readin->WordNum, result, 'd', 'e', false); Assert::AreEqual(len, 25); break; } delete core; } }
計算模塊的單元測試的函數是TestCore,被測試的函數是gen_chain_char和get_chain_word。構造測試數據主要是兩個思路:
單元測試獲得的測試覆蓋率截圖以下,可見覆蓋率達到了90%以上:
計算模塊部分的異常處理有兩種:
(1)按照文本內容和命令要求,不存在可行解。這包括:
(2)命令中沒有-r,可是文本內容能夠成環。一個單元測試樣例的輸入是:abc cfs ehshoda sefe sewqq
這裏順便列出整個項目的全部異常種類的設計:
拋出異常的模塊 | 拋出異常的場景 | 錯誤提示語 |
---|---|---|
Input(命令行參數處理模塊) | -h 後緊跟着的參數不是單字符 | Too Long Begin! |
-t 後緊跟着的參數不是單字符 | Too Long End! | |
-h或-t後緊跟着的參數是單字符,但不是字母 | Need a alapa | |
文件名後還有參數 | Too many parameters | |
沒有-w或-c | need one -c or -w | |
不是-w -c -r -h -t,也不是以.txt結尾的文件名 | illegal parameter | |
命令行參數中沒有文件名 | No a legal file | |
Readin(文件內容處理模塊) | 沒法打開命令行中指定的文件(好比文件不存在) | Fail to open the file |
文本中單詞個數過多 | Too many words | |
單詞的長度過長 | Too long word! | |
Core(計算核心模塊) | 按照文本內容和用戶命令的要求,沒有找到解 | No solution |
命令中沒有-r,可是文件中出現了單詞環 | Become Circle | |
Main(主函數) | 其它異常狀況 | Error |
界面模塊使用VS+Qt,這裏分享兩個安裝教程:教程1,教程2。
整個界面是一個QDialog(因爲是一個小軟件因此沒有選擇QMainWindow),界面模塊主要包括「視圖view」和「控制controller」兩部分:
(1)經過所見既所得的Qt Designer設計UI視圖,對佈局作了一些考慮,放一張佈局設計圖:
總體採用縱向佈局,其中最上面的部分採用水平佈局,各個部分的功能以下:
(2)經過信號與槽機制設置「哪一個組件的什麼事件會觸發哪一個類的什麼方法」。按照上述每一個組件的功能設置槽函數,並在go按鈕點擊事件對應的槽函數中調用文本處理模塊readin和計算模塊core,完成視圖、控制器、模型的對接。
比較難寫的槽函數在於文件的導入和導出,咱們參考了這篇博客使用了QFileDialog,下面給出咱們這部分的代碼:
void Dialog::on_btn_import_clicked() { QString filepath = QFileDialog::getOpenFileName(this,tr("choose file")); if(filepath!=NULL){ QByteArray ba = filepath.toLatin1(); char *filepath_c; filepath_c = ba.data(); ui->le_path->setText(filepath); FILE *fp; fopen_s(&fp,filepath_c,"r"); fseek(fp,0,SEEK_END); int filesize = ftell(fp); fseek(fp,0,SEEK_SET); char *buf = new char[filesize+1]; int n = fread(buf,1,filesize,fp); if(n>0){ buf[n] = 0; ui->te_in->setText(buf); } delete[] buf; fclose(fp); } } void Dialog::on_btn_export_clicked() { QString filename = QFileDialog::getSaveFileName(this,tr("save as")); QByteArray ba = filename.toLatin1(); char *filepath_c; filepath_c = ba.data(); QString text = ui->tb_out->toPlainText(); QByteArray ba2 = text.toLatin1(); char *text_c; text_c = ba2.data(); if(filename.length()>0){ FILE *fp; fopen_s(&fp, filepath_c, "w"); fwrite(text_c,1,text.length(),fp); } }
前面提到,咱們在GO按鈕點擊事件對應的槽函數中 調用文本處理模塊Readin和計算模塊Core,從而完成視圖、控制器、模型的對接。
具體來講,咱們把槽函數都放到了Dialog類中,當用戶點擊GO按鈕後,會觸發Dialog類中的on_btn_go_clicked()槽函數,這個函數將視圖中文本輸入框的內容傳遞給核心控制器Calculator類,設置其成員變量textIn,而後調用Calculator類的核心函數core()進行計算,計算結果再經過setText()函數設置到視圖上的文本輸出框中。
上面這個過程是視圖與控制器的對接,而控制器和計算模型的對接主要體如今Calculator類的核心函數core()中:將textIn內容傳遞給Readin實例化對象並調用其getWords方法,從而將文本內容處理爲單詞數組。隨後,根據用戶的參數設置,分-w和-c兩類,分別調用Core.dll的gen_chain_word和gen_chain_char函數(經過dll),從而 或得出計算結果(長度和單詞鏈)、或接收拋出的異常(無解或無-r卻成環)。最後,將計算結果或異常提示信息設置到控制器Calculator類的textOut變量中,再由控制器傳遞到視圖層在輸出文本框中。
實現的功能截圖以下:
(1)初始界面:
(2)一個正確的樣例:
(3)一個測試異常的樣例:
整個項目的實現過程當中,因爲時間上很差安排、不大熟悉、性格比較內向等緣由,咱們大多數時候仍是並行工做,保持線上交流討論、互報進度,每隔1-3天線下開個小會,進行需求分析、工做分配、問題討論、模塊對接等工做。具體以下:
時間/事件 | 16061155王冰 | 16061093謝靜芬 |
第一次開會前 | 粗讀項目核心要求,開始編寫初版代碼 | 研讀和分析項目要求,記錄問題,考慮如何分工 |
第一次開會 | 討論需求中不明確的地方;而後一致認爲,因爲時間緊任務重、二人時空上不大方便進行結對編程(沒法像其餘組那樣串宿舍結對編程...)等緣由,決定二人仍是並行工做;建倉庫,進行分工 | |
第一次開會後 | 編寫完成初版核心代碼,包括命令行參數處理、文本單詞提取、最長鏈計算 | 設計異常種類、構造測試用例、安裝和學習qt、代碼複審 |
第二次開會前 | 進行測試,修復一些bug,測試性能,嘗試優化性能 | 設計和編寫GUI,代碼複審,開始書寫博客的共同部分 |
第二次開會 | 將GUI和計算核心進行對接(暫未轉成dll),修復了一些bug。各自查閱並嘗試進行dll的生成和調用(失敗了) | |
第二次開會後 | 繼續嘗試進行dll的生成和調用、書寫博客的共同部分中關於計算核心和優化等內容 | 書寫完成博客的共同部分中的其它部分、找到一些不錯的連接便於隊友學習博客中提到的一些概念 |
第三次開會 | 將GUI和計算核心dll進行對接,成功!與另外一組同窗的模塊進行交換對接,成功! | |
第三次開會後 | 各自完成博客中的非共同部分 | |
總結 | 真是太優秀了! | 打雜也很認真! |
討論時的照片:
結對編程的優勢:
缺點:
我認爲的,結對二人的優缺點:
優勢 | 缺點 | |
---|---|---|
16061155 王冰 | 1.代碼能力強,項目核心代碼的編寫者(十分感謝王冰隊友的carry!) 2.交流溝通、獲取信息的能力較強,所以解決告終對過程當中的不少小問題。 3.十分負責任,合做真誠。 |
1.偶爾會粗心、考慮不嚴密 2.晚睡晚起 |
16061093 謝靜芬 | 1.善於作計劃、分配和管理。 2.邏輯清晰嚴密,較擅長測試和寫文檔(誤)。 3.蒐集資料、學習新東西的能力較強。 |
1.編程和算法能力較弱。 2.人際交流能力較弱。 3.脾氣容易暴躁。 |
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 20 | 20 |
· Estimate | · 估計這個任務須要多少時間 | 20 | 20 |
Development | 開發 | 840 | 1780 |
· Analysis | · 需求分析 (包括學習新技術) | 60 | 120 |
· Design Spec | · 生成設計文檔 | 20 | 15 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 10 | 10 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 10 | 5 |
· Design | · 具體設計 | 50 | 40 |
· Coding | · 具體編碼 | 500 | 1500 |
· Code Review | · 代碼複審 | 40 | 30 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 150 | 60 |
Reporting | 報告 | 60 | 105 |
· Test Report | · 測試報告 | 30 | 40 |
· Size Measurement | · 計算工做量 | 10 | 5 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 20 | 60 |
合計 | 920 | 1905 |
咱們隊把咱們隊的Core.dll與其餘隊的Core.dll進行了交換,並使用咱們的GUI與他們的dll進行耦合,結果比較符合要求,成功運行。
最終,咱們與16061173鮑屹偉和16061135張沛澤一組、16061167白世豪和16061170宋卓洋一組以及16061144餘宸狄和16061137張朝陽一組交換了程序,進行了鬆耦合。
在咱們的GUI上調用其餘組的dll:
16061173鮑屹偉和16061135張沛澤調用咱們的dll:
16061167白世豪和16061170宋卓洋調用咱們的dll:
16061144餘宸狄和16061137張朝陽調用咱們的dll: