軟工結對做業—最長單詞鏈

[2019BUAA軟件工程]結對做業——最長單詞鏈

一、github連接:

https://github.com/KarCute/Wordlistgit

二、PSP表格

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

三、看教科書和其它資料中關於Information Hiding, Interface Design, Loose Coupling的章節,說明大家在結對編程中是如何利用這些方法對接口進行設計的。

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).github

維基百科——Information Hiding
在這裏信息隱藏並非指信息加密或者隱匿,而是一種設計思想,將程序中設計決策中最容易改變的部分分離開來,從而保護其餘部分不受影響。在這裏咱們的設計自始至終都將計算接口剝離開單獨處理,由於這部分自己較難實現,而且在優化代碼算法時也是隻須要對這部分進行改進。算法

Interface Design (接口設計)

Interface除了接口外還有界面的意思,通常說來User Interface Design指的是UI設計。這裏應該指的接口設計。
咱們在設計時是按照指定的接口完成了計算模塊的設計,同時注意到,雖然咱們在整個項目實現時,知足了傳入接口的參數都保證正確,可是對用戶來講,這個接口內部是未知的,他們不必定會按照規範傳入參數,所以須要考慮到設計的完備性,即異常處理。編程

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.數組

維基百科——Loose Coupling數據結構

耦合是軟件結構中各模塊之間相互鏈接的一種度量,耦合強弱取決於模塊間接口的複雜程度、進入或訪問一個模塊的點以及經過接口的數據。app

百度百科——高內聚低耦合
早在接觸面向對象時就已經知道了好的程序應該作到「高內聚,低耦合」。在咱們的編程中,模塊的接口設計大多傳入參數並不複雜,耦合程度較低。如讀取文件部分,咱們只接受一個文件名,而後就能將單詞存入一個vector容器中。其他模塊只須要接受這個容器,而不須要與該模塊有較多的交互。數據結構和算法

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

4.1 問題分析

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

4.2數據結構與算法

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

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

契約式設計:定義正式、精確和可驗證的接口規範。

  • 優勢:
    • 按照契約設計,能夠簡單清楚的瞭解一個接口,而且可以使錯誤率下降。
    • 按照前置條件始終爲真來編寫代碼,能夠省去不少測試檢查。
  • 缺點:契約設計的規格很是複雜,必須花很長時間編寫規格。
    • 契約設計並不能取代常規的單元測試,僅僅只是對外部測試的補充。
    • 出現違背契約的人時,會對工程形成嚴重打擊。
      在咱們的結對編程過程當中,並無過多使用契約式設計。由於做爲結對編程,大多數時候咱們在編寫代碼時,都是兩我的同時溝通和編寫。每寫完一個接口,咱們都對接口有了大體的認識,所以不太須要啓悅設計。

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

在單元測試部分咱們對程序中除輸出部分外(因爲輸出部分只是一個簡單的輸出到文件)其餘因此部分或函數進行的全面的單元測試,如圖共25個。

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%)

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

  • 在異常處理模塊咱們一共自定義了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’,明顯是錯誤的。

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,而是完成了最基本的命令行模塊。命令行參數(個數、參數內容)首先傳入main函數中,而後再傳給InputHandler函數中對傳入的參數進行分析處理,主要是識別錯誤的參數輸入(第9部分已經詳細介紹)以及將正確的參數組合中的信息存下來,好比說head和tail是否有限定,單詞文本是否容許有環以及要求的單詞鏈是要單詞最多仍是單詞總長度最長。

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

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

十二、描述結對的過程

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

1三、結對編程的優勢和缺點在哪裏

  • 優勢:
    • 結對編程時,有實時的複審過程,這樣可使得編碼時一些意想不到的bug出現的更少。而單人編程(如OO)時,每每出現較多bug,疲於修改。
    • 結對編程可以使得編程時思想更加集中,不會出現開小差的狀況。
    • 結對編程時,設計時通過了兩我的的思考,不太會出現設計出問題的狀況。
  • 缺點:
    • 時間必須協調好,有時在對方有事的狀況下,必須得等待對方。
    • 在兩人水平相差不大時,可能會出現兩人都放過bug的狀況。

結對的每個人的優勢和缺點在哪裏 (要列出至少三個優勢和一個缺點)

本身的話,優勢是設計時肯思考,能接受對方的意見,願意花時間尋找資料。缺點多是有點愛開小差。 對對方的話,優勢是能主動找資料,能包容個人不足,在我有事時能主動在差很少的測試上花時間編寫。缺點多是和我同樣數據結構和算法掌握不夠好。

相關文章
相關標籤/搜索