項目 | 內容 |
---|---|
這個做業屬於哪一個課程 | 軟件工程 羅傑 |
這個做業的要求在哪裏 | 結對項目 最長單詞鏈 |
我在這個課程的目標是 | 熟悉軟件開發總體流程,提高自身能力 |
這個做業在哪一個具體方面幫助我實現目標 | 實踐教材中內容,體會「結對編程」模式 |
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
Planning | 計劃 | 30 | |
· Estimate | · 估計這個任務須要多少時間 | 30 | |
Development | 開發 | 720 | |
· Analysis | · 需求分析 (包括學習新技術) | 90 | |
· Design Spec | · 生成設計文檔 | 60 | |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 30 | |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 30 | |
· Design | · 具體設計 | 120 | |
· Coding | · 具體編碼 | 210 | |
· Code Review | · 代碼複審 | 60 | |
· Test | · 測試(自我測試,修改代碼,提交修改) | 120 | |
Reporting | 報告 | 90 | |
· Test Report | · 測試報告 | 30 | |
· Size Measurement | · 計算工做量 | 30 | |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 30 | |
合計 | 840 |
- Information Hiding
In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change).html
- Interface Design
(No relevant description.)git
- Loose Coupling
In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Subareas include the coupling of classes, interfaces, data, and services.github
其實這三者的共同目的或訴求就是:經過對接口進行設計,來保證類與類之間的隱私性、獨立性,使屬性不會過分共享(暴露),使對象的信息不可直接訪問,而是要調用方法,從而保證對象的安全性。咱們在進行接口設計時,將每一個類的信息設爲private屬性,並提供獲取屬性和合理的修改屬性的方法,而不能夠直接訪問或修改屬性,並提供總的接口,可能內部要調用其餘類的方法,但不須要用戶或測試人員調用,他們只須要調用總的接口便可,以此來保證安全性。算法
在這裏咱們首先設計了一個Solver類用於實現對單詞個數最多字符串和單詞長度最長字符串的計算,Node類實現存儲計算中所須要的各個單詞的節點數據。編程
算法的大體思路以下:安全
考慮這是一個特殊的有向無環圖的最長路徑問題(無環狀況下),咱們能夠將全部首先將全部的單詞進行拓撲排序,按排序後的順序可使用動態規劃的算法實現尋找出最長的路徑。而對於有環的狀況,咱們使用了有剪枝策略的DFS搜索算法查找出最長單詞鏈。函數
獨到之處:無環狀況下,按單詞首字母和尾字母將單詞分爲26類,減小了單詞數量較多時的排序時間。性能
從圖中能夠看出佔用時間最長的是計算出文本中的最長單詞鏈的函數,在這裏咱們考慮使用動態規劃算法來減小計算模塊所花的時間。單元測試
而對於有環的狀況,咱們這裏就實現了一個基礎的DFS算法,並採起了必定的剪枝策略,好比記錄已經遍歷過的點它的最長路徑,而後當遍歷到這個點的時候,判斷一下當前長度+當前點的最長路徑長度,若是小於當前發現的最長路徑長度,那就不須要繼續遍歷下去了,由於當前節點所能到達的最長路徑仍然小於當前最長路徑。用這些剪枝策略,減小了一些遞歸遍歷,必定程度上優化了算法。學習
Design by Contract和Code Contract的定義可在連接中查看。
Design by Contract,指的是契約編程。在面向對象編程中,「接口」是咱們着重要關注的部分,面向對象編程就好像打磨好一個個小部件,而這些部件須要經過「接口」鏈接配合,成爲一個總體。然而,當前的部件是否須要對以前的部件的正確性負責呢?或者說,若是以前的部件出現錯誤,當前部件是否要對這種錯誤進行補救措施呢?契約編程解決了這種問題。
所謂「契約」,就好像各個部件之間作好的約定通常:若是你傳遞給個人輸入是不符合個人接口規範的,那我有權直接拒絕(結束程序),即方法的輸入有必定的約束和指望。這種「契約」所保證的是,確保當前部件起碼具備正確的輸入,若是輸入是不符合接口要求的,只能說明以前的部件存在bug。
契約編程的好處是保證了程序的健壯性,合理地「分攤責任」,使得編程人員在分工時,僅須要對本身的模塊按照契約負責,並對輸入提出必定的要求限制便可。考慮了契約,才能夠保證面向對象的可靠性和可擴展性。
在本次結對編程中,咱們的計算模塊(即Core)中的接口,對調用時傳入參數進行了限制,採用了「契約編程」的模式,若是傳入參數不符合接口的要求,說明調用者存在問題,沒有遵循契約,所以能夠經過assert跳出,撕毀契約。在單元測試中,咱們一樣也使用assert來進行測試。但最終完成測試後,從程序性能的角度出發,在已經保證各部件接口正確的狀況下,咱們刪除了assert來提升效率。
在測試計算模塊的以前,咱們首先測試了一下命令行參數處理模塊是否正確:
TEST_METHOD(TestMethod1) { // TODO: 在此輸入測試代碼 argc = 3; argv[1] = "-c"; argv[2] = "input.txt"; tag = -1; headCh = '\0'; endCh = '\0'; isRing = false; filename = std::string(); getopt(argc, argv, tag, headCh, endCh, isRing, filename); Assert::AreEqual(1, tag); Assert::AreEqual(headCh, '\0'); Assert::AreEqual(endCh, '\0'); Assert::IsFalse(isRing); Assert::AreEqual(0, filename.compare("input.txt")); } TEST_METHOD(TestMethod2) { // TODO: 在此輸入測試代碼 argc = 3; argv[1] = "-w"; argv[2] = "input.txt"; tag = -1; headCh = '\0'; endCh = '\0'; isRing = false; filename = std::string(); getopt(argc, argv, tag, headCh, endCh, isRing, filename); Assert::AreEqual(0, tag); Assert::AreEqual(headCh, '\0'); Assert::AreEqual(endCh, '\0'); Assert::IsFalse(isRing); Assert::AreEqual(0, filename.compare("input.txt")); }
相似的TEST_METHOD咱們寫了6個,用以測試各項參數讀入處理後,tag、headCh、endCh、isRing和filename是否正確。
而後,咱們又對計算模塊的單詞個數最多字符串和單詞長度最長字符串這兩個方法進行了單元測試:
TEST_METHOD(TestMethod1) { // TODO: 在此輸入測試代碼 int wordIndex = 0; char **result; char headCh = '\0'; char endCh = '\0'; bool isRing = false; char *wordlist[100]; wordlist[0] = new char[20]{ "annzcclv" }; wordlist[1] = new char[20]{ "klebwukqbui" }; wordlist[2] = new char[20]{ "qhqkibinpyew" }; wordlist[3] = new char[20]{ "fkapwouje" }; wordlist[4] = new char[20]{ "mitecsqa" }; wordlist[5] = new char[20]{ "mogowquzdsmto" }; wordlist[6] = new char[20]{ "oxkyhmgemdfpq" }; wordlist[7] = new char[20]{ "hzvreibfb" }; wordlist[8] = new char[20]{ "phgxdlmyrw" }; wordlist[9] = new char[20]{ "kuckfwlghglua" }; wordlist[10] = new char[20]{ "ucqavnwkqseyy" }; wordlist[11] = new char[20]{ "quhxkzqxf" }; wordlist[12] = new char[20]{ "iwoegjfbxhu" }; wordIndex = 13; result = new char*[wordIndex]; int ans = gen_chain_word(wordlist, wordIndex, result, headCh, endCh, isRing); Assert::AreEqual(4, ans); Assert::IsFalse(std::strcmp(result[0], "mogowquzdsmto")); Assert::IsFalse(std::strcmp(result[1], "oxkyhmgemdfpq")); Assert::IsFalse(std::strcmp(result[2], "quhxkzqxf")); Assert::IsFalse(std::strcmp(result[3], "fkapwouje")); } TEST_METHOD(TestMethod3) { // TODO: 在此輸入測試代碼 int wordIndex = 0; char **result; char headCh = '\0'; char endCh = '\0'; bool isRing = false; char *wordlist[100]; wordlist[0] = new char[20]{ "annzcclv" }; wordlist[1] = new char[20]{ "klebwukqbui" }; wordlist[2] = new char[20]{ "qhqkibinpyew" }; wordlist[3] = new char[20]{ "fkapwouje" }; wordlist[4] = new char[20]{ "mitecsqa" }; wordlist[5] = new char[20]{ "mogowquzdsmto" }; wordlist[6] = new char[20]{ "oxkyhmgemdfpq" }; wordlist[7] = new char[20]{ "hzvreibfb" }; wordlist[8] = new char[20]{ "phgxdlmyrw" }; wordlist[9] = new char[20]{ "kuckfwlghglua" }; wordlist[10] = new char[20]{ "ucqavnwkqseyy" }; wordlist[11] = new char[20]{ "quhxkzqxf" }; wordlist[12] = new char[20]{ "iwoegjfbxhu" }; wordIndex = 13; result = new char*[wordIndex]; headCh = 'k'; int ans = gen_chain_word(wordlist, wordIndex, result, headCh, endCh, isRing); Assert::AreEqual(3, ans); Assert::IsFalse(std::strcmp(result[0], "klebwukqbui")); Assert::IsFalse(std::strcmp(result[1], "iwoegjfbxhu")); Assert::IsFalse(std::strcmp(result[2], "ucqavnwkqseyy")); }
TEST_METHOD(TestMethod2) { // TODO: 在此輸入測試代碼 int wordIndex = 0; char **result; char headCh = '\0'; char endCh = '\0'; bool isRing = false; char *wordlist[100]; wordlist[0] = new char[20]{ "annzcclv" }; wordlist[1] = new char[20]{ "klebwukqbui" }; wordlist[2] = new char[20]{ "qhqkibinpyew" }; wordlist[3] = new char[20]{ "fkapwouje" }; wordlist[4] = new char[20]{ "mitecsqa" }; wordlist[5] = new char[20]{ "mogowquzdsmto" }; wordlist[6] = new char[20]{ "oxkyhmgemdfpq" }; wordlist[7] = new char[20]{ "hzvreibfb" }; wordlist[8] = new char[20]{ "phgxdlmyrw" }; wordlist[9] = new char[20]{ "kuckfwlghglua" }; wordlist[10] = new char[20]{ "ucqavnwkqseyy" }; wordlist[11] = new char[20]{ "quhxkzqxf" }; wordlist[12] = new char[20]{ "iwoegjfbxhu" }; wordIndex = 13; result = new char*[wordIndex]; int ans = gen_chain_char(wordlist, wordIndex, result, headCh, endCh, isRing); Assert::AreEqual(4, ans); Assert::IsFalse(std::strcmp(result[0], "mogowquzdsmto")); Assert::IsFalse(std::strcmp(result[1], "oxkyhmgemdfpq")); Assert::IsFalse(std::strcmp(result[2], "quhxkzqxf")); Assert::IsFalse(std::strcmp(result[3], "fkapwouje")); } TEST_METHOD(TestMethod9) { // TODO: 在此輸入測試代碼 int wordIndex = 0; char **result; char headCh = '\0'; char endCh = '\0'; bool isRing = false; char *wordlist[100]; wordlist[0] = new char[20]{ "rlqokvxuq" }; wordlist[1] = new char[20]{ "vvitmqskdyeap" }; wordlist[2] = new char[20]{ "llkgasgiuzlgx" }; wordlist[3] = new char[20]{ "cxadwktc" }; wordlist[4] = new char[20]{ "yinrlisikdjq" }; wordlist[5] = new char[20]{ "cbrcxzoyigcv" }; wordlist[6] = new char[20]{ "roeuzja" }; wordlist[7] = new char[20]{ "pwwbogbwp" }; wordlist[8] = new char[20]{ "rjztssi" }; wordlist[9] = new char[20]{ "vypbjouumrc" }; wordlist[10] = new char[20]{ "vgorbjxqpap" }; wordlist[11] = new char[20]{ "vrczrlwavkfq" }; wordIndex = 12; result = new char*[wordIndex]; isRing = true; int ans = gen_chain_char(wordlist, wordIndex, result, headCh, endCh, isRing); Assert::AreEqual(5, ans); Assert::IsFalse(std::strcmp(result[0], "vypbjouumrc")); Assert::IsFalse(std::strcmp(result[1], "cxadwktc")); Assert::IsFalse(std::strcmp(result[2], "cbrcxzoyigcv")); Assert::IsFalse(std::strcmp(result[3], "vvitmqskdyeap")); Assert::IsFalse(std::strcmp(result[4], "pwwbogbwp")); }
與測試命令行參數處理模塊相似,咱們對gen_chain_word和gen_chain_char進行了headCh、endCh和isRing這幾個參數的各類狀況的測試,充分考慮了各個分支,達到了比較好的測試效果。最終咱們的整體覆蓋率以下:
WordRingsException:當默認狀況下輸入出現單詞環時拋出
IllegalInterfaceParaException:當調用接口時傳入不合理的參數時(如len <= 0,head或tail既不爲'\0'也不是字母時)
IllegalParametersException:命令行參數出現錯誤時
FileNotExitException:輸入的文本不存在時
命令行處理咱們採用最簡樸的字符串比較方法,依次對參數進行分析:
對於整體咱們處理了不含有「-w」和「-c」的異常(由於此時咱們不知道該如何選擇最長字符串),同時在每個分支中,咱們也都進行了異常處理,如既有「-w」又有「-c」的,或是「-h」和「-t」後面沒有字母或是否是字母字符等等狀況。
命令行處理模塊將從控制檯讀入的信息處理好後,將信息儲存在tag,headCh,endCh,isRing和filename中,供getFileInput()方法調用。
命令行模塊引入了定義接口的頭文件,直接能夠經過需求,調用定義的兩個接口中的一個,實現計算最長單詞鏈的過程。
總的來講本次結對編程的體驗很不錯,開始很順利,過程當中雖然遇到了或大或小的麻煩,但咱們共同克服,最後寫出的項目質量也還不錯。
剛開始的時候,咱們還不是很適應「駕駛員」、「領航員」的模式,基本就是商量着寫代碼,但效率也還不錯,設計的時候兩我的相互補充,也避免了未考慮到某些方面的狀況發生。很快咱們就將「有向無環圖」的狀況完成,並進行了測試,性能還不錯。不過以後的「有向有環圖」的設計就陷入了僵局,咱們除了暴力深度搜索想不出什麼比較好的方式(事實是咱們最後也仍是暴力搜索),一度兩人都在摸魚。不事後來,咱們決定先將其完成,而後再進行剪枝來優化(畢竟測試數據集也不是很大),很快也將「有向有環圖」的狀況完成。最後的單元測試和錯誤處理模塊,咱們也很順利地完成。
整體來講此次結對編程讓咱們體驗到了這種新的編程模式,可以和隊友在交流碰撞,產生新的想法和點子,互相糾正、互相補充,這種經歷十分難得,於我我的而言也是一次很大的提高。不過,這都是創建在我和個人隊友是平日關係密切、很熟悉的前提下,若是在公司中採起這種方式,我就不是很確定效率會如何了。
最後,奉上結對編程留念圖:
鄒欣大大的有關「結對編程與兩人合做」的文章鎮樓!!
由於以爲這種編程方式是一個頗有趣的事情,因此在寫本次結對編程做業的時候也是嚴格按照兩我的一塊兒編碼的方式進行的。體驗下來仍是有一些感想的:
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 30 | 20 |
· Estimate | · 估計這個任務須要多少時間 | 30 | 20 |
Development | 開發 | 720 | 890 |
· Analysis | · 需求分析 (包括學習新技術) | 90 | 120 |
· Design Spec | · 生成設計文檔 | 60 | 30 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 30 | 20 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 30 | 40 |
· Design | · 具體設計 | 120 | 120 |
· Coding | · 具體編碼 | 210 | 360 |
· Code Review | · 代碼複審 | 60 | 50 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 120 | 150 |
Reporting | 報告 | 90 | 100 |
· Test Report | · 測試報告 | 30 | 40 |
· Size Measurement | · 計算工做量 | 30 | 20 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 30 | 40 |
合計 | 840 | 1010 |