結對項目-最長單詞鏈博客

一、Github項目地址

二、PSP表格(程序各個模塊在開發上預計耗費的時間和實際耗費的時間)

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

三、關於Information Hiding, Interface Design, Loose Coupling等方法的接口設計

Information Hiding

  • 其實題目給出的三種方法或者說是原則都是和封裝有關的,在個人理解裏封裝是技術,而好比信息隱藏實則是目的。雖然咱們在實際的代碼中能夠說是沒有用到面向對象的格式來編寫,但其實用到的仍然仍是面向對象的思想。好比說計算核心Core的內部功能是不展現出來的,經過做業規定的兩個接口來使用戶和代碼交換各自須要的東西。相似的在用戶輸入模塊、讀取文件模塊等咱們都設計了與計算模塊Core相似的接口,每一個不一樣獨立的功能都封裝成了函數,保證信息的隱藏功能。

Interface Design

  • 我查閱了一些資料,找到的大多數的接口設計貌似都是很陌生並且與如今所學格格不入。但仍是從其中學習到了一些不管設計什麼接口都應該最起碼遵循的原則,好比命名必須規範優雅,保證接口要作的事情是比較單一的事情(單一性),良好的可擴展性和可移植性,而在實際編程中咱們也是這樣作的。

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.[1] Loose coupling is the opposite of tight coupling.git

——引用自維基百科程序員

  • 可是仍然覺的對這個概念很不清晰,繼續往下看並按照中文翻譯查了一些資料,大概知道了其仍是指一個組件與另外一個組件具備直接聯繫的程度。仍然仍是封裝與非封裝的意思。在這裏舉幾個咱們程序中的封裝接口的例子:
void InputHandler(int argc, char* argv[], bool &enable_loop, int &word_or_char, char &head, char &tail, string &Filename);
void ReadFile(string FileName, vector<string> &words);
void DFS_Length(Graph G, int v, vector<string> words, char tail);

四、計算模塊接口的設計與實現過程

4.1 問題分析

之前作過相似的題,輸入的全部單詞可否所有首尾相連造成鏈。因爲單詞首尾相連有多種鏈接方式,故基本的數據結構爲圖。
建圖有兩種方式,一種是以單詞爲節點,若是單詞間正好能夠首尾鏈接,則添加一條邊,該邊即爲鏈接的字母。另外一種建圖方式是以字母爲節點,以單詞爲邊,出現一個單詞,即把首字母節點向尾字母節點添加一條邊,邊的值即爲該單詞。
對於這道題目而言,因爲單詞須要輸出,加之對第二種建圖方式掌握並不熟練,所以選擇的是第一種建圖方式。
模型確立後,問題就能夠簡化成「求圖中的最長鏈」,即最長路徑問題,顯然問題是多源最長路徑問題。github

4.2 數據結構與算法

數據結構爲圖,存儲方式爲鄰接矩陣,理由是能更契合floyd算法。
對於無環狀況,因爲爲多源最長路徑問題,聯想到最短路徑問題,能夠肯定爲floyd算法。
而對於有環狀況,因爲出現了正值環,floyd算法再也不適用。在找不到更有解決方法的狀況下,只能適用DFS深度優先搜索求解。算法

4.3 模塊組織

ReadFile: 讀取文件的模塊,將文件中的單詞提取進入容器vector中。
Graph: 圖的定義。
InputHandler:處理輸入的模塊,讀取命令行並處理參數。
FindLongestWordList: 計算模塊,內含計算接口。計算出單詞中的最長鏈。編程

4.4 算法關鍵

首先須要判斷有無環,對於沒有-r參數的輸入來講,若是有環須要報錯。這裏也是用到DFS的染色算法。每一個點有三種狀態:未遍歷過,遍歷過,當前序列正在遍歷。若是一次DFS中一個點與正在遍歷中的點相連了,說明DFS回到了以前的點,即圖中有環。
另外一問題是因爲無環狀況最多可有10000個單詞,而floyd算法時間複雜度爲O(n^3),暴力的計算顯然是不行的。考慮到對於無環的狀況,有以下特性:對於單詞element和elephant,因爲無環,這兩個單詞最多隻有一個會出如今鏈中。(不然會出現element, t..., ..., ....e, elephant / element,這樣必定是有環的),而若是要知足字母最多,顯然這時候須要選擇elephant加入鏈中。所以咱們能夠對於全部首尾字母相同的單詞,保留首尾字母組合中,最長的一個單詞。這樣的操做以後,最多的單詞數目爲351,即便是時間複雜度O(n^3)的算法也能很快得出結果。另外能夠計算得,最長鏈的長度最大爲51。數組

五、UML圖

六、計算模塊接口部分的性能改進

  • 首先是無環狀況,其性能最大阻礙是10000個單詞大樣本狀況下,floyd算法時間複雜度太高致使的。可是在4.4有介紹過,咱們能夠經過無環單詞鏈的特性來削減樣本數量,削減後單詞數量少,即便時間複雜度高也能很快跑出結果。所以性能方面上沒有太大問題。

  • 其次是有環狀況,因爲DFS算法仍屬於暴力遞歸搜索,並不算很好的算法,其性能也着實較差。可是咱們也想不到更好的解決算法,因此並無改進。

七、關於Design by Contract, Code Contract的優缺點以及結對做業中的體現

  • 契約式編程對於軟件工程是一個極大的理論改革,對於C/S模式形成了極大的影響和衝擊。對於C/S模式,咱們看待兩個模塊的地位是不平等的,咱們每每要求server很是強大,能夠處理一切可能的異常,而對client漠不關心,形成了client代碼的低劣。而在DbC中,使用者和被調用者地位平等,雙方必須彼此履行義務,才能夠行駛權利。調用者必須提供正確的參數,被調用者必須保證正確的結果和調用者要求的不變性。雙方都有必須履行的義務,也有使用的權利,這樣就保證了雙方代碼的質量,提升了軟件工程的效率和質量。缺點是對於程序語言有必定的要求,契約式編程須要一種機制來驗證契約的成立與否。而斷言顯然是最好的選擇,可是並非全部的程序語言都有斷言機制。那麼強行使用語言進行模仿就勢必形成代碼的冗餘和不可讀性的提升。好比.NET4.0之前就沒有assert的概念,在4.0後全面引入了契約式編程的概念,使得契約式編程的可用性大大提升了。此外,契約式編程並未被標準化,所以項目之間的定義和修改各不同,給代碼形成很大混亂,這正是不多在實際中看到契約式編程應用的緣由。在咱們的代碼中,對於模塊間使用了契約的思想,保證雙方地位的平等。調用者的傳入參數必須是正確的,不然責任不在被調用者,而在傳入者。

——優缺點引用自維基百科數據結構

八、計算模塊部分單元測試展現

  • 在單元測試部分咱們對程序中除輸出部分外(因爲輸出部分只是一個簡單的輸出到文件)其餘因此部分或函數進行的全面的單元測試,如圖共25個,單元測試的所有代碼也已上傳至Github。下面我將拿出部分單元測試代碼具體介紹,並在這部分的最後附上單元測試的測試服機率截圖。
TEST_METHOD(TestMethod3)
        {
            // TODO: normal_test3
            char* words[101] = { "element", "heaven", "table", "teach", "talk"};
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }

            int l = gen_chain_word(words, 5, answer, 0, 0, true);
            Assert::AreEqual(l, 4);
            Assert::AreEqual("table", answer[0]);
            Assert::AreEqual("element", answer[1]);
            Assert::AreEqual("teach", answer[2]);
            Assert::AreEqual("heaven", answer[3]);
            for (int i = 0; i < 101; i++)
            {
                free(answer[i]);
            }
        }
  • 上面的單元測試代碼是測試計算核心中的gen_chain_word接口函數,因爲單元測試須要我手動加入words,因此這裏的單元測試數據比較小,就是構造一個有環有鏈的單詞文本,而且是在輸入‘-r’的狀況下,從而獲得一個正確的單詞鏈。
TEST_METHOD(TestMethod6)
        {
            // TODO: normal_test6
            char* words[101] = { "apple", "banane", "cane", "a", "papa", "erase" };
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }

            int l = gen_chain_char(words, 6, answer, 'a', 'e', false);
            Assert::AreEqual(l, 3);
            Assert::AreEqual("a", answer[0]);
            Assert::AreEqual("apple", answer[1]);
            Assert::AreEqual("erase", answer[2]);
            for (int i = 0; i < 101; i++)
            {
                free(answer[i]);
            }
        }
  • 上面的單元測試代碼是測試計算核心中的gen_chain_char接口函數,這裏構造了一個沒有環的文本數據,並且其最多單詞鏈和最長單詞鏈不一樣,並固定了首尾字母。
TEST_METHOD(TestMethod2)
        {
            // 正確_2
            int argc = 6;
            char* argv[101] = { "Wordlist.exe", "-r", "-h", "a", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);

            Assert::AreEqual(enable_loop, true);
            Assert::AreEqual(word_or_char, 2);
            Assert::AreEqual(head, 'a');
            Assert::AreEqual(tail, char(0));
            Assert::AreEqual(Filename, (string)"test_1.txt");
        }
  • 上面的單元測試代碼是測試接收命令行輸入函數InputHandler,這裏沒有什麼太多好說的, 就是把命令行輸入參數的全部正確組合所有測試一遍便可(參數輸入順序能夠改變)。

單元測試覆蓋率截圖(因爲C++沒有找到直接測試單元測試覆蓋率的插件,這裏用的方法是將單元測試代碼移至main函數中用OpenCppCoverage插件獲得的覆蓋率,部分異常測試沒有放進來,因此覆蓋率沒有達到100%)app

九、計算模塊部分異常處理說明

  • 在異常處理模塊咱們一共自定義了8種類型的異常,接下來我將會結合每種異常的單元測試說明每種異常的設計目標以及錯誤對應的場景(單元測試的構造方法就是保證此函數能夠捕捉到異常且捕捉的是與當前錯誤相對應的異常,不然單元測試不經過)。

1. 錯誤的參數組合(其中包括出現多個相同命令好比‘-r’、‘-r’,‘-h’和‘-c’同時出現,‘-h’和‘-c’都沒有,即不指定求何種單詞鏈)

TEST_METHOD(TestMethod3)
        {
            // 錯誤_1
            int argc = 5;
            char* argv[101] = { "Wordlist.exe", "-r", "-r", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            try {
                InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);
                Assert::IsTrue(false);
            }
            catch (myexception1& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是‘-r’出現了兩次,錯誤的參數組合。函數

2. 指定單詞鏈首尾不合法(好比‘-h’、‘1’或者‘-t’、‘ag’)

TEST_METHOD(TestMethod7)
        {
            // 錯誤_5
            int argc = 6;
            char* argv[101] = { "Wordlist.exe", "-r", "-h", "1", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            try {
                InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);
                Assert::IsTrue(false);
            }
            catch (myexception2& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是‘-h’指定首字母爲‘1’,明顯是錯誤的。oop

3. 輸入的參數不是指定的那幾個參數,不符合規定(如輸入‘-b’)

TEST_METHOD(TestMethod9)
        {
            // 錯誤_7
            int argc = 5;
            char* argv[101] = { "Wordlist.exe", "-b", "-r", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            try {
                InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);
                Assert::IsTrue(false);
            }
            catch (myexception3& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是輸入參數‘-b’顯然是不符合規定的。

4. 文件不存在的狀況

TEST_METHOD(TestMethod2)
        {
            // 錯誤
            vector <string> words;
            try {
                ReadFile("normal_test3.txt", words); // 不存在的文件
                Assert::IsTrue(false);
            }
            catch (myexception4& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是測試了一個在此路徑下不存在的文件。

5. 讀取的文件中有單詞長度超過600

TEST_METHOD(TestMethod2)
        {
            // 錯誤
            vector <string> words;
            try {
                ReadFile("long_word_test.txt", words);
                Assert::IsTrue(false);
            }
            catch (myexception4& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是文件中存在長度超過600的單詞。

6. 讀取的文件中無環的超過10000個單詞,有環的超過100個單詞

TEST_METHOD(TestMethod2)
        {
            // 錯誤
            vector <string> words;
            try {
                ReadFile("more_words_test.txt", words); 
                Assert::IsTrue(false);
            }
            catch (myexception4& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是所測試文件中單詞數超過了10000。

7. 讀取文件中有單詞環且參數沒有輸入‘-r’

TEST_METHOD(TestMethod10)
        {
            // wrong_test2
            char* words[101] = { "alement", "oeaven", "tabla", "teaco", "talk" };
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }


            try {
                int l = gen_chain_char(words, 5, answer, 0, 'n', false);
                Assert::IsTrue(false);
            }
            catch (myexception7& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是傳入單詞能夠造成環,且用戶沒有傳入參數‘-r’。

8. 讀取文件中沒法造成最少兩個單詞的單詞鏈

TEST_METHOD(TestMethod11)
        {
            // wrong_test3
            char* words[101] = { "alement", "oeaven", "tabla", "teaco", "talk" };
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }


            try {
                int l = gen_chain_word(words, 5, answer, 'b', 'n', true);
                Assert::IsTrue(false);
            }
            catch (myexception8& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

這個單元測試是規定了首尾字母后,單詞鏈中沒有用戶所要求的單詞鏈。

十、界面模塊的詳細設計過程(GUI)

  • 在界面模塊這方面咱們沒有實現GUI,而是完成了最基本的命令行模塊。其實若是是命令行模塊的話就很是簡單了,根據在命令行輸入的內容及長度存入char* argv[]以及int argc中,而後再傳給InputHandler函數中對傳入的參數進行分析處理,主要是識別錯誤的參數輸入(第9部分已經詳細介紹)以及將正確的參數組合中的信息存下來,好比說head和tail是否有限定,單詞文本是否容許有環以及要求的單詞鏈是要單詞最多仍是單詞總長度最長。因爲實現很簡單,這裏沒必要再貼上代碼贅述。

十一、界面模塊與計算模塊的對接(GUI)

  • 命令行模塊與兩個計算核心模塊的對接其實也很簡單。咱們從命令行讀入的各種參數若是是正確無誤的,那麼咱們能夠相對應地肯定傳入兩個計算模塊的head、tail、enable_loop以及執行哪一個計算模塊的判斷變量。即肯定規範的單詞鏈首字母尾字母,若是沒有規定則傳入0,是否容許有環的變量。若是不容許,則須要判斷傳入單詞文本是否能夠造成環,若是造成環則報告異常。下面是簡單是一張命令行輸入截圖:

十二、結對之過程

  • 因爲與隊友爲舍友,結對時相對簡單不少,只須要到對鋪和隊友一塊兒結對編程就好了。咱們的水平差很少, 編程能力和數據結構算法的掌握都不算太好。初期時咱們主要是一塊兒討論算法,如何實現基本的功能,數據結構應該用什麼。敲定一個算法以後就開始分頭找資料,最後再彙總資料,交給他來敲代碼或者我來在一些地方進行修改。編寫時常常會遇到一些意料不到的bug,最後必須一塊兒搜索如何解決。可是兩我的在一塊兒編寫代碼時,有一我的來隨時審視代碼,有不懂的地方或者不對勁的地方另外一人均可以隨時提出來。所以雖然結對編程效率沒有提升, 可是效果會比兩個單人編寫來的更好。
    總的來講此次題目難度仍是沒有那麼爆炸,因此咱們之間的合做也比較愉快。至於提意見的藝術是根本用不上的,畢竟是舍友也不會產生矛盾。下面是咱們在初期時討論算法的圖片:

1三、結對編程的優缺點及評價

結對編程優缺點

  • 下面是一些結對編程的優勢:程序員互相幫助,互相教對方,能夠獲得能力上的互補。可讓編程環境有效地貫徹Design。加強代碼和產品質量,並有效的減小BUG。下降學習成本。一邊編程,一邊共享知識和經驗,有效地在實踐中進行學習。在編程中,相互討論,可能更快更有效地解決問題。固然,結隊編程也會有一些很差的地方:對於有不一樣習慣的編程人員,能夠在起工做會產生麻煩,甚至矛盾。有時候,程序員們會對一個問題互不相讓(代碼風格可能會是引起技術人員口水戰的地方),爭吵不休,反而產生重大內耗。兩我的在一塊兒工做可能會出現工做精力不能集中的狀況。程序員可能會交談一些與工做無關的事情,反而分散注意力,致使效率比單人更爲低下。

評價(隊友陳致遠)

  • 優勢:
    • 認真負責,輪流編程時的任務完成準時並且質量很高
    • 有探索精神,有遇到不管軟件問題仍是算法問題必定要探個究竟
    • 考慮全面,程序不管正確狀況方面仍是報錯方面都考慮的很細緻
  • 缺點:
    • 咱們項目經驗都比較少,有些地方都不是很駕輕就熟
相關文章
相關標籤/搜索