[BUAA軟工]第二次博客做業---結對編程

[BUAA軟工]結對做業

項目 內容
這個做業屬於哪一個課程 北航軟工
這個做業的要求在哪裏 2019年軟件工程基礎-結對項目做業
我在這個課程的目標是 學習如何以團隊的形式開發軟件,提高我的軟件開發能力
這個做業在哪一個具體方面幫助我實現目標 瞭解結對開發的流程,並親自體驗學習
項目地址 https://github.com/sephyli/wordlist_BUAA
項目做者信息 16231030 焦雲鵬16231031 李天羽

運用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).git

Written another way, information hiding is the ability to prevent certain aspects of a class or software component from being accessible to its clients, using either programming language features (like private variables) or an explicit exporting policy. --- Wikipedia程序員

具體在程序中的體現是,咱們使用面向對象的編程思想,經過調用類的Public成員函數的方式,執行程序的核心邏輯。在這個過程當中,咱們經過將一些不該該被外部更改的成員屬性(如一個單詞的具體信息),設計爲私有成員,並設計Public成員函數,做爲訪問的接口(無寫權限),從而達到了Information Hiding思想中的對於信息保護的需求。github

Interface Design

User interface design (UI) or user interface engineering is the design of user interfaces for machines and software, such as computers, home appliances, mobile devices, and other electronic devices, with the focus on maximizing usability and the user experience. The goal of user interface design is to make the user's interaction as simple and efficient as possible, in terms of accomplishing user goals (user-centered design). --- Wikipedia算法

具體在程序中的體現是,咱們經過封裝核心邏輯爲Core類,再經過Core類生成相應的dll,從而將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)等核心功能函數設計爲接口。這樣的話,新的項目只須要設置相關路徑,讓Core.dllCore.h的位置能被探測到,並設置Core.lib爲工程的連接庫。這樣的話,就能夠提供相關接口給別的程序使用。編程

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. --- Wikipedia數據結構

具體在程序中的體現是,咱們在使用面向對象的思想設計程序的時候,將從屬於一個類的成員方法與成員屬性均放在這個類本身這裏,而不要放在別的地方。這樣的好處就是能夠防止由於一個對象成員的改動,而致使不少不想管的類也要作調整,從而傷害程序的可維護性。app

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


類關鍵信息

  • Word類
    • 屬性
      • char head : 頭字母
      • char tail :尾字母
      • int length :單詞長度
      • std::string s :單詞
      • bool use :標誌是否被使用
    • 方法
      • Word(const char* s, int length) :構造函數
  • WordSet類
    • 屬性
      • std::vector set[26][26] :單詞組
    • 方法
      • void append(Word w) :添加單詞
  • Mode類
    • 屬性
      • bool recurMode :單詞成環模式
      • bool headMode:頭字母指定模式
      • bool tailMode:尾字母指定模式
      • bool wordNumMaxMode:單詞數量最大模式
      • bool charNumMaxMode:單詞字母最多模式
      • char head:指定的頭字母
      • char tail:指定的尾字母
    • 方法
      • void append(Word w) :添加單詞
  • Searcher類
    • 屬性
      • Data data :數據信息
      • Mode mode :模式信息
      • std::vector maxWordList :歷史max單詞list
      • std::vector tmpWordList :當前max單詞list
    • 方法
      • bool exe() :執行函數

程序分析

咱們在拿到題目後,第一反應是直接使用暴力深搜解決問題。可是看到程序正確性要求中的300s運行時間限制(儘管咱們如今也沒能弄明白是針對-w模式,-c模式,仍是二者兼有),咱們意識到純粹使用深搜是不合理的。在對算法進行必定的分析以後,咱們意識到,不遍歷全部知足條件的單詞鏈,是不可能找出其中知足要求的最長/最多單詞鏈的。也就是說,深度優先搜索,將會是咱們必需要使用的算法。electron

完成這部分分析以後,咱們將優化的視角投向了數據結構部分。咱們注意到,程序要求單詞鏈知足以下條件。ide

單詞鏈的定義爲:由至少2個單詞組成,前一單詞的尾字母爲後一單詞的首字母,且不存在重複單詞函數

這就意味着,咱們在從文件中讀取單詞的時候就該避免重複單詞的讀入。不只如此,咱們還設計了獨到的數據結構來放置單詞,從而實現了訪查效率的最大化。

舉例來講,咱們在WordSet類中,設置了26*26的二位vector來放置頭字母相同、尾字母相同的單詞,並在vector中按照單詞的長度來排列,這樣的話,無論對於-w模式仍是-c模式,均可以採起同一套訪查算法。不只如此,如此組織數據結構,可讓咱們的核心搜索函數(深搜函數)快速經過找到以目標字母開頭的單詞,相比傳統的深度優先搜索,咱們在這一步的複雜度從O(N) 降到了O(1)。考慮到深搜核心函數的調用次數是一個隨着單詞鏈長度增加而成階乘級增加的,咱們的優化方法,應該來講也是有必定做用的。

類關係UML圖

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


在構造數據結構時,我將問題的思惟模式轉變爲一種帶權重的有向圖。如hello,即結點h到結點o的有向邊,若-w模式,權重爲1,-c則爲5。那麼我存下了每一個節點到另外一個節點的邊的信息並以節點號進行索引,構造出了vector<Word> WordSet[26][26]這樣的結構,每一個vector內用權重進行排序。

由此問題便轉變爲了避免容許重複節點和路徑的最長路徑問題。考慮過DP,但沒法構造高效的子問題,及子問題合併時須要判斷重複的路徑和節點,猜想並不會提高效率。由此使用了深度優先搜索,在非-r模式,的複雜度約爲26!這個數量級,所以構造出了最複雜的樣例後,是絕無可能在300s以內完成計算的。由此放棄了在整體算法層次上的優化,僅僅追求最短的搜索路徑和較小的訪存開銷。

結果: 算法在-r模式下的100個詞中,能夠在20ms內完成單詞鏈搜索。

性能統計圖以下所示:

Design by Contract, Code Contract


Design by contract (DbC), also known as contract programming, programming by contract and design-by-contract programming, is an approach for designing software. It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants. These specifications are referred to as "contracts", in accordance with a conceptual metaphor with the conditions and obligations of business contracts.

The DbC approach assumes all client components that invoke an operation on a server component will meet the preconditions specified as required for that operation. Where this assumption is considered too risky (as in multi-channel client-server or distributed computing) the opposite "defensive design" approach is taken, meaning that a server component tests (before or while processing a client's request) that all relevant preconditions hold true, and replies with a suitable error message if not. --- Wikipedia

這個概念與我在面向對象程序設計中所學到過的設計契約思想比較類似,都是要求調用它的客戶模塊都保證必定的進入條件,保證退出時給出特定的屬性,即知足必定的先驗條件與後驗條件。

具體來看,在程序設計的時候,咱們就遵循了這樣的規範。例如,在進行Mode類設計的時候,對於set方法(即命令行信息賦值函數),程序就要求傳入的參數是有意義的。這就是對於先驗條件的實現。

程序在進行getWordFromVec函數實現的時候,須要返回一個知足條件的Word對象。可是,若是沒有找到這樣的Word對象,程序也不能什麼都不作,而是應該經過調用Word的默認構造函數,返回一個空的Word。這樣的話,就保證了getWordFromVec函數所約定的後驗條件。

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


使用工具爲OpenCppCoverage。通過merge事後的結果展現:

單元測試部分代碼:

TEST_METHOD(TestRecur)
        {
            FILE *fin;
            fopen_s(&fin, "../test/recurtest1.txt", "r");

            char *words[105], *result[105];
            Inputer *inputer = new Inputer();
            int wordNum = inputer->getWord(fin, words);
            for (int i = 0; i < 105; i++) {
                result[i] = new char(105);
            }
            int len = 0;
            len = Core::gen_chain_char(words, wordNum, result, 0, 0, true);
            Assert::AreEqual(len, 60);
        }

在這個例子中,程序測試了Core::gen_chain_char接口,經過對文件·../test/recurtest1.txt內容測試,檢查了在指定字母數最多,無頭尾字母指定,容許單詞環存在的狀況下,獲得的單詞鏈長度的測試。

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


單詞超長

  • 用途:檢測輸入文本中含有超過長度限制的單詞的狀況。
  • 預期結果:經過try-catch的方法,捕獲命令行報錯信息。其中,./test/testfile.txt文件中包含一個連續長度超過100的字母串。
  • 單元測試以下:
TEST_METHOD(TestTooLongWord)
        {
            FILE *fin;
            fopen_s(&fin, "../test/testfile.txt", "r");

            char *words[10000], *result[105];
            Inputer *inputer = new Inputer();
            int wordNum = inputer->getWord(fin, words);
            for (int i = 0; i < 105; i++) {
                result[i] = new char(100);
            }
            int len = 0;
            len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
        }

隱含單詞環

  • 用途:檢測輸入文本中隱含單詞環的狀況。
  • 預期結果:經過try-catch的方法,捕獲命令行報錯信息。其中,./test/testfile.txt文件中包含若干個能夠構成單詞環的單詞。
  • 單元測試以下:
TEST_METHOD(TestNonRecureFalse)
        {
            FILE *fin;
            fopen_s(&fin, "../test/testfile.txt", "r");

            char *words[10000], *result[105];
            Inputer *inputer = new Inputer();
            int wordNum = inputer->getWord(fin, words);
            for (int i = 0; i < 105; i++) {
                result[i] = new char(100);
            }
            int len = 0;
            len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
        }

隱含單詞環

  • 用途:檢測輸入文本中隱含單詞環的狀況。
  • 預期結果:經過try-catch的方法,捕獲命令行報錯信息。其中,./test/testfile.txt文件中包含若干個能夠構成單詞環的單詞。
  • 單元測試以下:
TEST_METHOD(TestNonRecureFalse)
        {
            FILE *fin;
            fopen_s(&fin, "../test/testfile.txt", "r");

            char *words[10000], *result[105];
            Inputer *inputer = new Inputer();
            int wordNum = inputer->getWord(fin, words);
            for (int i = 0; i < 105; i++) {
                result[i] = new char(100);
            }
            int len = 0;
            len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
        }

單詞鏈長度太短

  • 用途:檢測沒法找到長度超過1的單詞鏈的狀況。
  • 預期結果:經過try-catch的方法,捕獲命令行報錯信息。其中,./test/testfile.txt文件中僅包含一個單詞,或所包含的單詞沒法造成長度超過1的單詞鏈。
  • 單元測試以下:
TEST_METHOD(TestNonRecureFalse)
        {
            FILE *fin;
            fopen_s(&fin, "../test/testfile.txt", "r");

            char *words[10000], *result[105];
            Inputer *inputer = new Inputer();
            int wordNum = inputer->getWord(fin, words);
            for (int i = 0; i < 105; i++) {
                result[i] = new char(100);
            }
            int len = 0;
            len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
        }

命令行參數沒法正確解析

  • 用途:檢測命令行參數沒法正確解析的狀況。
  • 預期結果:經過try-catch的方法,捕獲命令行報錯信息。

文件讀取錯誤

  • 用途:檢測沒法找到文件的狀況。
  • 預期結果:經過try-catch的方法,捕獲命令行報錯信息。其中,./test/testfile.txt文件不存在。
  • 單元測試以下:
TEST_METHOD(TestNonRecureFalse)
        {
            FILE *fin;
            fopen_s(&fin, "../test/testfile.txt", "r");

            char *words[10000], *result[105];
            Inputer *inputer = new Inputer();
            int wordNum = inputer->getWord(fin, words);
            for (int i = 0; i < 105; i++) {
                result[i] = new char(100);
            }
            int len = 0;
            len = Core::gen_chain_word(words, wordNum, result, 0, 0, false);
        }

命令行模塊描述


參數約定

程序支持經過命令行的方式輸入參數以及文件位置信息。參數及其約定以下。

參數名字 參數意義 範圍限制 用法示例
-w 須要求出單詞數量最多的單詞鏈 絕對或相對路徑 示例:Wordlist.exe -w input.txt [表示從input.txt中讀取單詞文本,計算單詞數量最多的單詞鏈]
-c 須要求出字母數量最多的單詞鏈 絕對或相對路徑 示例:Wordlist.exe -c input.txt [表示從input.txt中讀取單詞文本,計算字母數量最多的單詞鏈]
-h 指定單詞鏈首字母 a-z,A-Z 示例:Wordlist.exe -h a -w input.txt [表示從input.txt中讀取單詞文本,計算知足首字母爲a的、單詞數量最多的單詞鏈]
-t 指定單詞鏈尾字母 a-z,A-Z 示例:Wordlist.exe -t a -c input.txt [表示從input.txt中讀取單詞文本,計算知足尾字母爲a的、字母數量最多的單詞鏈]
-r 容許單詞文本中隱含單詞環 NONE 示例:Wordlist.exe -r -w input.txt [表示從input.txt中讀取單詞文本,計算單詞數量最多的單詞鏈,即便單詞文本中隱含單詞環也須要求解]

實現過程

程序將命令行參數的提取部分放在了main函數中,經過引用int main(int agrc, char* agrv[])函數的方式,將命令行參數的數量讀取在agrc中,分詞讀取在agrv[]中。

經過判斷分詞是否爲-開頭,來判斷該詞是否表明着命令行參數,在經過該詞的第二個字母,判斷具體屬於哪一個命令行參數(支持大小寫)或者報錯。同時經過判斷第三個字母是否爲\0來判斷參數是否過長。若是爲參數有後續的範圍限制,如-h-t後須要指定開頭、結尾的字母,則繼續判斷字母是否符合要求(個數爲一個且在有意義)。

所有判斷完成後,將判斷的結果傳入構造的Mode對象中,爲後續程序所使用。

具體來看,程序聲明瞭以下變量,在對不一樣參數作解析的時候,就針對不一樣狀況對變量進行賦值,這樣就收集所有的命令行參數信息。

bool rMode = false;
    bool hMode = false;
    bool tMode = false;
    bool wMaxMode = false;
    bool cMaxMode = false;
    char h = 0;
    char t = 0;
    char filePath[1000] = "\0";

再經過對Mode對象的賦值,從而實現了命令行模式的判斷。

Mode *mode = new Mode();
    mode->Set(rMode, hMode, tMode, wMaxMode, cMaxMode, h, t);

結對編程的優缺點

在結對編程模式下,一對程序員肩並肩地、平等地、互補地進行開發工做。兩個程序員並排坐在一臺電腦前,面對同一個顯示器,使用同一個鍵盤,同一個鼠標一塊兒工做。他們一塊兒分析,一塊兒設計,一塊兒寫測試用例,一塊兒編碼,一塊兒單元測試,一塊兒集成測試,一塊兒寫文檔等。

關於結對編程是不是一種高效的編程手段,這我很差評說,我就來分享一下我和個人小夥伴在結對編程時的一些體會。

在第一週中,由於個人小夥伴白天要去公司實習,而我則要在白天作實驗室的項目,因此程序開發的大部分環節其實沒有遵循結對編程的要求。大多數狀況下,我在白天實現咱們在前一天晚上所共同設計的部分,在下午或晚上則由他來審查個人代碼,作一些單元測試。到了咱們都有時間的晚上,才能共同坐在同一個電腦前,像結對編程要求的那樣,針對咱們所實現的不一樣部分,作協調,並構思次日的工做。

而到了第二週,狀況有些變化,咱們發現有一些東西須要咱們共同探索,例如如何生成dll模塊,以及如何對程序進行全覆蓋的單元測試。這時候,咱們拿出了統一的時間,坐在了電腦前,完成告終對編程。

與各自分開編程所不一樣的是,咱們代碼的錯誤率有了明顯的降低,單元測試的經過率變高了。不只如此,在實現的過程之中,因爲有了「領航員」的存在,咱們在進行類的實現的時候更加高效了,咱們思考的更加全面,類方法與屬性的設置能更好地知足低耦合、高內聚的要求,總體返工的次數也沒有了。在「駕駛員」感到疲憊的時候,咱們能夠從容地進行角色互換,從而更持久的完成程序的實現。惟一美中不足的是,咱們用於結對編程的時間不是不少,若是在第一週就能這樣作的話,咱們第二週的工做無疑會輕鬆很對:)

PSP表格

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

結對編程照片


相關文章
相關標籤/搜索