https://github.com/kilotron/Wordlist-Pair-git
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
Planning | 計劃 | 60 | 40 |
· Estimate | · 估計這個任務須要多少時間 | · 60 | 40 |
Development | 開發 | 1040 | 1430 |
· Analysis | · 需求分析 (包括學習新技術) | · 120 | 240 |
· Design Spec | · 生成設計文檔 | · 60 | 30 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | · 30 | 10 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | · 20 | 10 |
· Design | · 具體設計 | · 90 | 60 |
· Coding | · 具體編碼 | · 480 | 720 |
· Code Review | · 代碼複審 | · 120 | 120 |
· Test | · 測試(自我測試,修改代碼,提交修改) | · 120 | 240 |
Reporting | 報告 | 120 | 80 |
· Test Report | · 測試報告 | · 30 | 30 |
· Size Measurement | · 計算工做量 | · 30 | 20 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | · 60 | 30 |
合計 | 1220 | 1550 |
Information hiding is part of the foundation of both structured design and object-oriented design. In structured design, the notion of 「black boxes」 comes from information hiding. In object-oriented design, it gives rise to the concepts of encapsulation and modularity and it is associated with the concept of abstraction. (Code Complete 2nd)程序員
信息隱藏在結構化設計和麪向對象設計中都很重要,與封裝、模塊化、抽象的概念緊密相關。github
Loose coupling means designing so that you hold connections among different parts of a program to a minimum. Use the principles of good abstractions in class interfaces, encapsulation, and information hiding to design classes with as few interconnections as possible. Minimal connectedness minimizes work during integration, testing, and maintenance. (Code Complete 2nd)算法
Loose coupling是減小程序各部分的聯繫,在設計接口時,須要將各模塊相互之間的聯繫降到最低。編程
在設計接口時,咱們儘可能放寬函數的前置條件,同時讓函數名簡潔直觀,儘可能準確界定函數功能。例若有這樣兩個函數:數組
class WordGraph { public: bool IsCyclic(); Path * LongestPathBetween(char head, char tail, bool isWeighted); }
對於第二個函數,調用時指定開頭字母和結尾字母,以及計算單詞鏈長度的方法(是否以單詞長度做爲權重),函數返回單詞鏈。這就作到了Loose coupling,調用者關心的參數和結果比較明確,不須要依賴其餘的函數或模塊。對於第一個函數,它的功能是判斷是否存在單詞環,調用者不需關心內部實現(怎麼計算是否存在環的),這就作到了Information hiding。數據結構
計算模塊主要在WordGraph這個類中,同時還有與WordGraph相關的Edge、Node和Path類,前兩者組成WordGraph內部數據結構,Path用於表示計算結果。app
WordGraph對外提供5個接口:ssh
WordGraph(char *words[], int len)
用給定的單詞列表構造WordGraph對象bool IsCyclic()
判斷是否存在單詞環Path *LongestPathFrom(char start, bool isWeighted)
計算給定開頭字母的最長單詞鏈Path *LongestPathBetween(char start, char end, bool isWeighted)
計算給定開頭和結尾字母的最長單詞鏈Path *LongestPathTo(char end, bool isWeighted)
計算給定結尾字母的最長單詞鏈Path *LongestPath(bool isWeighted)
計算未給定開頭結尾字母的最長單詞鏈參數isWeighted是計算單詞鏈長度的方式,true
爲使用單詞字母數做爲權重,false
則反之。模塊化
在此基礎之上,定義核心模塊的對外接口:
int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop); int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
上面兩個函數調用WordGraph的對外接口實現其功能。
爲了清晰地顯示類之間的關係,圖中省略了類的成員。
在未指定開頭結尾字母時,程序進行了屢次重複搜索,改進的思路是利用已有的LongestPathFrom
函數避免重複搜索,改進後程序減小了50%的執行時間。
下圖是未指定開頭和結尾字母,按字母數計算單詞鏈長度,存在單詞環,總共有60個單詞的狀況下前28s的性能分析圖。
改進性能用了大約半個小時。
契約式設計(Design by Contract)是設計軟件的一種方法,它用形式化可驗證的方法來定義前置條件、後置條件和不變式。從一個被調用模塊的角度來說,contract包括expect, guarantee, maintain三個部分的內容,也就是在進入模塊前,指望一些條件是成立的,模塊執行結束後,保證一些條件是成立的,而且保持類的某些屬性是一致的。其優勢在於模塊功能邊界清晰,全責分明,出現問題時容易定位是誰出現問題,不過要提早制定contract須要時間,制定一個合適的contract也須要必定的經驗和技巧。
Code Contracts for .NET是一個工具,能夠執行前置條件、後置條件和不變式的靜態或運行時檢查,而且能夠生產文檔,對自動化驗證DbC頗有幫助。
在此次咱們實現的計算模塊中的這個接口就體現了DbC:int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
,例如words
的長度和len
保持一致,result
數組須要在調用函數前分配好足夠的空間,head和tail只能是0或者字母,這些是前置條件;若是存在單詞環但enable_loop
爲false
則拋出異常,若是一切正常則將結果放到result
數組中並返回result的長度,這是後置條件。測試時也是根據這些條件來測試的。
有關異常的測試在下一部分給出。
這部分測試計算的兩個接口,構造測試數據的思路是根據是否指定開頭結尾字母,計算單詞鏈長度的方式,是否存在單詞環,以及邊界狀況分別討論。下面給出部分代碼。
代碼中,TEST_INIT
、GEN_CHAIN_WORD
、GEN_CHAIN_CHAR
、ASSERT_RESULT_LEN
、ASSERT_RESULT
是爲了簡化測試代碼定義的宏。
TEST_INIT
:聲明測試須要使用的變量,準備單詞列表GEN_CHAIN_CHAR
/GEN_CHAIN_WORD
:計算模塊對外接口的簡化版ASSERT_RESULT_LEN
:單詞鏈中單詞的個數ASSERT_RESULT(word, i)
: 單詞鏈第i個單詞應該是word// 字母最多,不指定開頭結尾 TEST_METHOD(TestMethod7) { TEST_INIT(22, "Absorb", "bailout", "basic", "cat", "team", "calm", "cad", "hedonism", "dam", "moon", "damn", "dame", "happen", "earn", "nap", "each", "equip", "pack", "hasp", "hack", "hot", "AGoddamnSuperLongWordEndsWithT"); GEN_CHAIN_CHAR(0, 0, false); ASSERT_RESULT_LEN(5); ASSERT_RESULT("agoddamnsuperlongwordendswitht", 0); ASSERT_RESULT("team", 1); ASSERT_RESULT("moon", 2); ASSERT_RESULT("nap", 3); ASSERT_RESULT("pack", 4); } // 單詞數最多, 不指定開頭和結尾 TEST_METHOD(TestMethod3) { TEST_INIT(22, "Absorb", "bailout", "basic", "cat", "team", "calm", "cad", "hedonism", "dam", "moon", "damn", "dame", "happen", "earn", "nap", "each", "equip", "pack", "hasp", "hack", "hot", "AGoddamnSuperLongWordEndsWithT"); GEN_CHAIN_WORD(0, 0, false); ASSERT_RESULT_LEN(10); ASSERT_RESULT("absorb", 0); ASSERT_RESULT("basic", 1); ASSERT_RESULT("cad", 2); ASSERT_RESULT("dame", 3); ASSERT_RESULT("each", 4); ASSERT_RESULT("hot", 5); ASSERT_RESULT("team", 6); ASSERT_RESULT("moon", 7); ASSERT_RESULT("nap", 8); ASSERT_RESULT("pack", 9); }
這部分根據輸入參數的各類不一樣組合構造測試用例,檢測程序是否正確解析。下面給出其中一個用例。
// pre.command 指定head TEST_METHOD(TestMethod5) { Preprocess pre; int argc = 5; char ** argv = new char *[argc]; for (int i = 0; i < argc; i++) { argv[i] = new char[10]; } strcpy(argv[0], "Wordlist.exe"); strcpy(argv[1], "-w"); strcpy(argv[2], "-h"); strcpy(argv[3], "A"); strcpy(argv[4], "wordlist.txt"); pre.command(argc, argv); Assert::IsTrue(pre.kind == W); Assert::AreEqual("wordlist.txt", pre.filename); Assert::AreEqual('a', pre.head); }
分兩個模塊分別測試,下圖是參數解析和文件讀取部分的測試覆蓋率,總覆蓋率爲93%。
下圖是計算模塊的覆蓋率,總覆蓋率爲91%。
異常類型 | 異常說明 |
---|---|
輸入文件異常 | 文件名非法或者文件不存在 |
輸入參數異常 | 未輸入參數,-h或-t選項後跟的不是單個字母,包含未定義的選項 |
單詞環異常 | 存在單詞環但未給出-r選項 |
// 文件錯誤 異常測試 TEST_METHOD(TestMethod8) { Preprocess pre; Assert::ExpectException<std::exception>([&] { pre.readfile("you asshole"); }); }
場景:文件不存在。
// pre.command 異常測試 TEST_METHOD(TestMethod6) { try { Preprocess pre; int argc = 1; char ** argv = new char *[argc]; argv[0] = new char[10]; strcpy(argv[0], "Wordlist.exe"); pre.command(argc, argv); Assert::Fail(); } catch (std::exception e) { } }
場景:未輸入參數。
// 有環但enable_loop是false,應該拋出異常 TEST_METHOD(TestMethod1) { TEST_INIT(7, "gag", "fag", "glitz", "zaf", "jof", "fij", "lkkj"); Assert::ExpectException<std::exception>([&] { GEN_CHAIN_WORD(0, 0, false); }); }
場景:給出的單詞列表中存在環,但未給出-r選項。
界面使用Qt實現的,風格上採用了默認的樣式,沒有進行調整。
有一個菜單欄,支持打開文件和查看幫助的操做。左邊是輸入輸出界面,能夠直接在Input
對應的框裏編輯,右邊是計算的選項,用QRadioButton
和QCheckBox
實現-w -c -h -t -r
五個選項。
右下方有三個按鈕,Extract Words
把輸入框中的單詞提取出來,而後分行顯示在Input
框中。Find
根據上方參數的設定狀況查找單詞鏈,結果顯示在Output
框中,Export
把Output
框中的文本導出到文件。
界面以下圖。
佈局以下圖。採用手工編碼的方式實現,使用了一些QHBoxLayout和QVBoxLayout。
佈局的代碼以下:
leftLayout = new QVBoxLayout; rightLayout = new QVBoxLayout; topRightLayout = new QHBoxLayout; bottomRightLayout = new QHBoxLayout; mainLayout = new QHBoxLayout; // 左邊的輸入輸出框 leftLayout->addWidget(inputLabel); leftLayout->addWidget(inputTextEdit); leftLayout->addWidget(outputLabel); leftLayout->addWidget(outputTextEdit); // 右上角的單詞數最多和字母數最多單選框 topRightLayout->addWidget(maxLabel); topRightLayout->addWidget(wordRadioButton); topRightLayout->addWidget(charRadioButton); // 右下角查找和導出按鈕 bottomRightLayout->addWidget(findButton); bottomRightLayout->addWidget(exportButton); // 右邊佈局 rightLayout->addLayout(topRightLayout); rightLayout->addLayout(headLayout); rightLayout->addLayout(tailLayout); rightLayout->addWidget(loopCheckBox); rightLayout->addWidget(extractButton); rightLayout->addLayout(bottomRightLayout); mainLayout->addLayout(leftLayout); mainLayout->addLayout(rightLayout);
合做小組的學號:
16061011
16061152
把咱們小組記爲A,合做小組記爲B。下面分別說明Core B + GUI A和Core A + GUI B兩種狀況。一開始兩種狀況都不能正常運行,通過討論咱們發現使用Core.dll的方式不一致,A組使用動態加載的方式,B組採用的是靜態加載的方式,解決方法是統一加載方式,對於兩種加載方式咱們都進行了嘗試。
這個組合採用動態加載方式。咱們爲B組的Core模塊添加了def文件,將源代碼從新編譯後獲得新的Core.dll。而後從新運行程序,可以正常使用。
這個組合採用靜態加載方式。如圖,未修改前看不到B組的GUI。咱們將A組的Core模塊與B組的GUI放在同一個解決方案中,在Core的兩個接口的函數的聲明前添加__declspec(dllexport)
修飾。未修改B組GUI源碼前發現其中有幾個Core A沒有定義的異常,因而咱們將異常種類統一後從新編譯。
在測試過程當中發現GUI B未提供單詞去重的功能,Core A拋出了異常,GUI B顯示了異常信息,整個程序沒有崩潰。因而咱們在Core A中實現了單詞去重並從新編譯。最後的運行截圖以下。
因爲前期找不到合適的時間,爲了保證進度,咱們一開始採用的是先分工,再一塊兒複審代碼的形式。週末有共同的空閒時間時,咱們再一塊兒討論和編寫測試用例。整體來講比較順利。上圖是咱們再新主樓結對編程的圖片。
優勢:
缺點:
對隊友的評價
優勢:
缺點:
隊友對個人評價
優勢:
缺點: