結對項目--計算最長單詞鏈

項目 內容
這個做業屬於哪一個課程 2019春季計算機學院軟件工程(羅傑)
這個做業的要求在哪裏 做業要求
我在這個課程的目標是 完成結對編程
這個做業在哪一個具體方面幫助我實現目標 爲團隊合做打基礎

Github地址

https://github.com/zackertypical/WordChaingit

PSP預估時間

PSP2.1 Personal Software Process Stages 預估耗時(分鐘)
Planning 計劃 60
·Estimate 估計這個任務須要多少時間 60
Development 開發 57*60
·Analysis ·需求分析 (包括學習新技術) 8*60
·Design Spec · 生成設計文檔 4*60
·Design Review · 設計複審 (和同事審覈設計文檔) 2*60
·Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 1*60
·Design · 具體設計 5*60
·Coding · 具體編碼 24*60
·Code Review · 代碼複審 8*60
·Test · 測試(自我測試,修改代碼,提交修改) 5*60
Reporting 報告 5*60
·Test Report · 測試報告 2*60
·Size Measurement · 計算工做量 60
·ostmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 2*60
合計 63*60

接口設計原則

Information Hiding

  • 爲了實現良好的封裝,須要從兩個方面考慮:程序員

    一、將對象的屬性和實現細節隱藏起來,不容許外部直接訪問。 讓使用者只能經過事先預約好的方法來訪問數據,從而能夠在該方法里加入控制邏輯,限制對屬性的不合理訪問。github

    二、把方法暴漏出去,讓方法來操做或訪問這些屬性。算法

咱們在設計DFS圖的類時,全部數據成員都沒法被外部直接訪問,例如圖的權重數組,鄰接表數組,經過外部的接口對圖中節點進行權重的修改,進行邊的插入的操做,實現了信息的隱藏。編程

在計算最長路徑的時候,也僅提供了輸出的接口,沒法對圖的私有成員進行操做,全部計算過程在類裏私有函數完成,外部僅訪問獲得結果的接口。數組

Interface Design

將需求抽象成一個個獨立的接口/抽象類,而後被繼承或委託/組成的形式來實現或拓展新的具體或更增強大完善的抽象,經過層層封裝、繼承,最後就會實現運行時多態的特性,從而提升代碼的靈活性。app

良好的接口須要有單一職責性和可拓展性。在本次項目中,咱們利用繼承與多態的思想,創建了一個圖的基類,其餘類都實現這個基類提供的方法,例如修改結點權值,插入邊等操做,對於結果的讀取,實現findAns()函數,但不一樣的類該接口的實現方法不同。框架

好比有首尾字母約束的類,實現一樣的findAns()接口,不管是怎樣的參數組合,最終都要經過這個接口來訪問結果。這樣下降的模塊之間的耦合度,提升了代碼複用,提升了模塊的單一性。函數

同時Core計算模塊也實現了對外的接口,須要傳入word還有一些參數,返回result結果,下降了耦合性。

Loose Coupling

軟件工程中對象之間的耦合度就是對象之間的依賴性。對象之間的耦合越高,維護成本越高。所以對象的設計應使類和構件之間的耦合最小。

對於不一樣的參數類型,咱們構建了繼承於基類的子類,例如實現約束首字母的類,須要單獨有數組來存單詞是否符合首字母約束,而約束尾字母也能夠用該類進行計算,只須要在結果輸出之後把數組反轉便可。

對於首尾字母都有約束的狀況也單獨有一個子類來完成功能,繼承自只有首字母約束的類。

在圖內實現dfs,判斷是否有環,最終輸出結果都是單獨實現的函數,每一個方法完成一個功能,下降了函數之間的耦合性。

Core的接口設計和實現過程

一、代碼組織:

  • 類的設計

    一、接口

    須要對傳入的參數進行解析,實例化Core核心計算類和DFS圖,經計算後返回結果

    二、core類

    經過core接口對圖進行操做。關鍵函數以下:

    • void insertChain(char * words[], int len);
      進行單詞鏈的去重操做和排序。
    • void setHeadTail(DFSHeadTailGraph &graph, char tail);
      對圖的數組進行操做,指定頭尾節點的約束。
    • void insert_weighedEdge(DFSGraph &graph);
      對圖的邊進行插入操做,賦予邊權重。
    • void getresult(char *result[], vector & ans);
      經過圖內部函數的計算獲得結果。

    三、圖類

    對建好的圖進行dfs計算出最終的結果鏈,由core打印結果,自己不存儲單詞信息,只存儲節點的編號。

    關鍵函數以下:

    ** 私有函數 **
    void findAnsChain();
    調用dfs進行最長鏈的尋找,把結果保存在私有變量vector ans中。
    int dfs(int index);
    對有環圖的dfs。
    int dpDfs(int index);
    對無環圖的dfs。

    ** 公有函數 **
    void insertEdge(int i, int j);
    對圖進行邊的插入。
    void changeVecWeigh(int i, int weight);
    改變圖的節點權重。
    const vector & getAnsChain();
    外部訪問獲得最長鏈的節點編號數組。
    bool hasCircle();
    外部進行訪問,能夠獲得圖是否有環的信息。

    四、Exception類

    對各類異常進行處理,包括參數異常,對圖的操做異常等。

    五、命令行輸入類

    對輸入的單詞文本進行處理,實現讀入文件,寫文件等操做。

二、算法關鍵

有環的狀況要比沒有環的複雜度高不少,因此算法第一步要判斷是否有環,若是有環,進行普通的深度優先遍歷的方法。沒有環的話開一個dp數組進行記憶化搜索,性能會提升不少。

對於有首尾字母約束的狀況下,沒有單獨在一個類裏面實現,而是經過類的繼承來下降模塊的耦合性。

UML圖

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

性能分析結果:

在小文本數據處理時,並無遇到很大的性能瓶頸,因而咱們利用了大文本進行測試。發如今處理圖節點之間連邊的函數性能消耗很大,根據VS的性能分析工具,能夠看到是在string的處理上進行索引的部分消耗較大,咱們使用的是iterator去訪問string的頭和尾字母。因而最後改爲了用下標訪問,速度有所提高。

在單詞鏈有隱環的狀況下,dfs耗費的時間的確很大,並無找到改進的方法。

改進以後結果:

Design by Contract, Code Contract

通常認爲在模塊中檢查錯誤情況而且上報,是模塊自己的義務。而在契約體制下,對於契約的檢查並不是義務,其實是在履行權利。一個義務,一個權利,差異極大。例如:

if (dest == NULL) { ... }

這就是義務,其要點在於,一旦條件不知足,我方(義務方)必須負責以合適手法處理這尷尬局面,或者返回錯誤值,或者拋出異常。而:

assert(dest != NULL);

這是檢查契約,履行權利。若是條件不知足,那麼錯誤在對方而不在我,我能夠馬上「撕毀合同」,罷工了事,無需作任何多餘動做。這無疑能夠大大簡化程序庫和組件庫的開發。

契約所覈查的,是「爲保證正確性所必須知足的條件」,所以,當契約被破壞時,只代表一件事:軟件系統中有bug。其意義是說,某些條件在到達我這裏時,必須已經確保爲「真」。若是在我這裏發現契約沒有被遵照,那麼代表系統中其餘模塊沒有正確履行本身的義務。

通常來講,在面向對象技術中,咱們認爲「接口」是惟一重要的東西,接口定義了組件,接口肯定了系統,接口是面向對象中咱們惟一須要關心的東西,接口不只是必要的,並且是充分的。然而,契約觀念提醒咱們,僅僅有接口還不充分,僅僅經過接口還不足以傳達足夠的信息,爲了正確使用接口,必須考慮契約。

契約式編程的優勢:實現面向對象的目標:可靠性、可擴展性和可複用性。

缺點: 若是異常在程序運行過程當中纔可以檢測出來的話可能致使一些錯誤。

在本項目中,咱們在計算模塊中實現了Core接口,而且定義了傳入參數的規範,因此能夠採用契約式編程,若是傳入的參數不合法,或者傳入的不是符合規範的字符,說明調用者沒有遵循契約調用參數,能夠直接assert。在執行無錯誤程序期間,不該違反契約條件。

在單元測試當中,咱們所用的也都是斷言。

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

1.對圖模塊的公開類以及公開類裏面的公開方法添加單元測試。對於構造函數和公共屬性進行單元測試。咱們建立了一個測試圖模塊的單元測試類進行測試。

思路:對圖進行構建,改變節點的權重和邊的信息,而後尋找圖的最長路,看是否和正確結果相同。

部分代碼展現:

TEST_METHOD(TestHeadTailGraph)
{
    DFSHeadTailGraph g(4);
    for (int i = 1; i <= 4; i++)
    {
        g.changeVecWeigh(i, 1);
    }
    g.setHeadSingle(3);
    g.setTailSingle(1);
    g.insertEdge(3, 2);
    g.insertEdge(2, 1);
    Assert::AreEqual(2, g.getEdgeNum());
    vector<int> ans = g.getAnsChain();
    Assert::AreEqual(3, (int)ans.size());
}
TEST_METHOD(LoopGraph)
{
    DFSGraph g(4);
    for (int i = 1; i <= 4; i++)
    {
        g.changeVecWeigh(i, 1);
    }
    g.insertEdge(3, 4);
    g.insertEdge(4, 3);

    Assert::AreEqual(true,g.hasCircle());

}

二、對不一樣參數組合的測試

思路:對於全部參數組合,能夠進行分析,尋找最長單詞鏈,最長字母鏈,是否有環,是否有首尾字母的約束,一共有 2*2*4 = 16 狀況,分別構造測試數據進行測試。

測試數據的構建:

  • 對於邊界條件,好比只輸入一個單詞,或者沒有找到單詞鏈的狀況,都須要單獨構造測試數據。
  • 全部單詞都互相能構成鏈的狀況,好比 「aaaaa aaa aa a」的狀況
  • 最長單詞鏈和最長字母鏈同時存在但結果不一樣的狀況。
  • 常規測試數據,隨機生成。
  • 大文本測試數據。

部分代碼展現:

TEST_METHOD(HeadTest_Loop)
{
    char *words[4] = { "cddd","dddc","aac","bad" };
    char *result[4];
    int ans = gen_chain_char(words, 4, result, 'a', 0, true);
    Assert::AreEqual(3, ans);
    string str;
    for (int i = 0; i < ans; i++)
    {
        str.append(result[i]);
    }
    Assert::AreEqual((string) "aaccddddddc", str);
}
TEST_METHOD(TailTest_Loop)
{
    char *words[4] = { "kzz","kdd","ak","ka" };
    char *result[4];
    int ans = gen_chain_char(words, 4, result, 0, 'z', true);
    Assert::AreEqual(3, ans);
    string str;
    for (int i = 0; i < ans; i++)
    {
        str.append(result[i]);
    }
    Assert::AreEqual((string) "kaakkzz", str);
}
TEST_METHOD(HeadTailTest_Loop)
{
    char *words[13] = { "abcd","defg","gkbb","bmmm","mjjj","jooo" ,"bg","gb"};
    char *result[6];
    int ans = gen_chain_word(words, 8, result, 'd', 'j', true);
    Assert::AreEqual(6, ans);
    string str;
    for (int i = 0; i < ans; i++)
    {
        str.append(result[i]);
    }
    Assert::AreEqual((string) "defggkbbbggbbmmmmjjj", str);
}

三、單元測試覆蓋率展現

單元測試覆蓋率結果以下,覆蓋率達到98%。

計算模塊異常處理說明

一、圖模塊的異常種類

在公有方法中,插入邊和修改結點權值的函數須要判斷是否溢出邊界,若是是要拋出異常。

TEST_METHOD(Vertex_insert_edge_outofrange)
{
    try
    {
        DFSGraph g(3);
        g.insertEdge(5, 6);
    }
    catch (exception &e)
    {
        Assert::AreEqual(edge_out_of_range_error, e.what());
    }
}
TEST_METHOD(Vertex_change_weight_outofrange)
{
    try
    {
        DFSGraph g(3);
        g.changeVecWeigh(4, 8);
    }
    catch (exception &e)
    {
        Assert::AreEqual(vertex_out_of_range_error, e.what());
    }
}

二、Core模塊輸入沒法識別的單詞

TEST_METHOD(Core_words_unrecognized)
{
    try
    {
        Core core;
        char *words[3] = { "aa123","32432","333" };
        core.insertChain(words, 3);
    }
    catch (exception &e)
    {
        Assert::AreEqual(m_word_error, e.what());
    }
}

三、在core的接口部分,若是出現len超出最大範圍,或者head和tail不在指定的字母範圍內,則要拋出異常

TEST_METHOD(Interface_check_head_parameter)
{
    try
    {
        checkParameter(10, 'A', 0);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_headchar_error, e.what());
    }
}
TEST_METHOD(Interface_check_tail_parameter)
{
    try
    {
        checkParameter(10, 0, 1);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_tailchar_error, e.what());
    }
}

TEST_METHOD(Interface_check_len_parameter)
{
    try
    {
        checkParameter(1000000, 0, 1);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_len_error, e.what());
    }
}

四、core部分,若是沒有選擇enable_loop可是單詞鏈中出現隱環,拋出異常

TEST_METHOD(Interface_check_loop)
{
    try
    {
        char *words[2] = { "abb","baa" };
        char *result[2];
        int ans = gen_chain_word(words, 2, result, 0, 0, false);
        Assert::AreEqual(ans, 0);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_loop_error, e.what());
    }
}

界面模塊的詳細設計過程

界面模塊咱們使用了VS的MFC框架來進行搭建,主要是對用戶的輸入進行響應,調用咱們Core模塊的dll接口來進行結果的輸出。

  • 首先須要進行需求分析,用戶須要哪些交互的模塊,須要輸入文本框,選項的按鈕,文件名的文本框,最終的確認操做按鈕,導出文件按鈕,結果展現的文本框等。

  • 接下來給每一個ui進行代碼編輯,響應用戶的操做。

  • 對接dll接口進行測試。

部分代碼展現:

void CWordChainGUIDlg::OnBnClickedOk()
{
        UpdateData(true);
        char *words[MAX];
        int chainlen;
        if (m_inputFile != "")
        {
            bool isread = read_file(m_inputFile, m_inputWords);
            if(!isread)
            {
                throw exception("file not found!");
            }
            chainlen = dealInput(words, m_inputWords);
        }
        else
            chainlen = dealInput(words, m_inputWords);
        char *result[MAX];
        char head = m_headChar.GetAt(0);
        char tail = m_tailChar.GetAt(0);
        if ((head != 0)&&((head <= 96) || (head >= 123)))
            throw exception("head charactor must be lower alphabet");
        if ((tail != 0)&&((tail <= 96) || (tail >= 123)))
            throw exception("tail charactor must be lower alphabet");

        //printf("%s", m_inputWords);
        if (m_isLongestWord)
        {
            m_answer = gen_chain_word(words, chainlen, result, head, tail, m_enableLoop);
        }
        else
        {
            m_answer = gen_chain_char(words, chainlen, result, head, tail, m_enableLoop);
        }
        CString str;
        for (int i = 0; i < m_answer; i++)
        {
            str += result[i];
            str += "\r\n";
            delete[]result[i];
        }
        m_wordAnsChain = str;
        INT_PTR nRes;               
        AnswerDisplayDlg ansDlg;         
        ansDlg.m_ansLength = m_answer;
        ansDlg.m_wordStr = str;
        nRes = ansDlg.DoModal();   

        UpdateData(false);
        for (int i = 0; i < chainlen; i++)
        {
            delete[]words[i];
        }
        if (IDCANCEL == nRes) 
            return;
}

界面模塊與計算模塊的對接

ui 功能
單詞輸入框 能夠支持輸入單詞文本,而且對單詞文本進行自動分割處理,和文件輸入格式相同
首字母輸入框 若是沒有內容則默認爲0,能夠支持輸入小寫字母,若是輸入不合理會有錯誤框彈出提示
尾字母輸入框 若是沒有內容則默認爲0,能夠支持輸入小寫字母,若是輸入不合理會有錯誤框彈出提示
單詞鏈選項 選擇最長單詞數目或者最長字母數目
是否容許單詞鏈隱環 默認不容許,若是選擇則容許
指定輸入文件 若是不輸入則默認從單詞輸入框讀取,輸入文件名則從文件讀取,若是找不到文件則會有錯誤框彈出提示
生成按鈕 設置完後生成單詞鏈,會有新窗口彈出
導出文件按鈕 能夠填寫文件名後導出文件

結對編程

優勢:

最大的優勢是在於兩我的之間能夠隨時的複審和交流,程序各方面的質量取決於一對程序員中各方面水平較高的那一位。這樣,程序中的錯誤就會少得多,程序的初始質量會高不少,這樣會省下不少之後修改、測試的時間。

如下摘自博客

(1)在開發層次,結對編程能提供更好的設計質量和代碼質量,兩人合做能有更強的解決問題的能力。

(2)對開發人員自身來講,結對工做能帶來更多的信心,高質量的產出能帶來更高的知足感。

(3)在心理上, 當有另外一我的在你身邊和你緊密配合, 作一樣一件事情的時候, 你很差意思開小差, 也很差意思糊弄。

(4)在企業管理層次上,結對能更有效地交流,相互學習和傳遞經驗,能更好地處理人員流動。由於一我的的知識已經被其餘人共享。

缺點:

結對的兩我的須要時間磨合,沒有嘗試過這種模式的人也須要時間去適應。

對於須要研究的項目不適合結對編程。

一些比較簡單的測試驗證工做,若是須要花較長的時間,結對會形成時間的浪費。

自我評價

優勢:執行力較強,態度良好,有合做精神,注意力比較集中,可以較好地統籌規劃時間。

缺點:編程能力較弱,對於語言和算法掌握不熟練,花費大量的時間進行學習。

評價隊友

優勢:可以細心發現bug,態度良好,有合做精神,在合做的過程當中能相互學習、相互磨合。

缺點:執行力較弱。

與其餘小組的鬆耦合測試

  • 本組學號:16021160 15061078

  • 合做小組學號:16061109 16061097

  • 出現的問題:

    一、在測試另外一個小組的dll時,我在文件中寫入了中文字符,致使程序沒有正常退出,該小組沒有對文本的內容進行詳細地異常分析,致使程序異常退出。

    二、在該小組測試咱們dll的時候,發現程序中的bug,即對於全部單詞都能構成首尾鏈的狀況輸出異常,咱們組對本身的bug進行了改進。

    三、對方小組使用類封裝的dll,分析編譯的時候出現warning提示,接口調用須要類的實例化客戶端,因此我當時測試的時候是實例化了該Core類,不能直接調用方法進行測試。

PSP表格實際消耗

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 60 2*60
·Estimate 估計這個任務須要多少時間 60 2*60
Development 開發 57*60 71*60
·Analysis ·需求分析 (包括學習新技術) 8*60 9*60
·Design Spec · 生成設計文檔 4*60 2*60
·Design Review · 設計複審 (和同事審覈設計文檔) 2*60 1*60
·Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 1*60 1*60
·Design · 具體設計 5*60 6*60
·Coding · 具體編碼 24*60 36*60
·Code Review · 代碼複審 8*60 12*60
·Test · 測試(自我測試,修改代碼,提交修改) 5*60 5*60
Reporting 報告 5*60 5*60
·Test Report · 測試報告 2*60 3*60
·Size Measurement · 計算工做量 1*60 1*60
·ostmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 2*60 1*60
合計 63*60 78*60
相關文章
相關標籤/搜索