軟工結對編程做業-(非附加題)

結對編程項目文檔前端

/* 本文檔主要包含了項目分析、設計等各方面內容,是爲後期寫博客時直接摘抄準備的。因爲博客容許共享項目相關的部份內容,所以,儘可能將可共享的內容寫到此文檔中。 */git

1、需求分析

根據這次的需求,咱們將以前我的項目時的需求分析表進行了進一步完善,將這次新增的需求填入其中,結果以下。算法

 

功能編程

質量數據結構

約束架構

業務級需求併發

程序核心封裝爲Core庫:模塊化

  支持計算表達式結果函數

  支持生成表達式工具

  支持參數設定

發生錯誤時支持Exception

 

用戶級需求

自動生成四則運算,支持整數和真分數(以及負數)

支持括號

題目存入Exercises.txt中

同時生成題目答案,存入Answers.txt中

能夠判斷答案對錯

支持一萬道題目的生成

支持參數設定題目中:

  題目數量

  數據範圍

  最大運算符數量

  (或運算過程當中)有無分數

  是否有乘除法

  是否有括號

  (或運算過程當中)有無負數

用戶輸入有誤時程序不崩潰

錯誤輸入時需輸出幫助信息

生成的算式需知足:

  減法計算無負數

  除法計算結果爲真分數

  運算符不超過3個

  +和*交換不重複

真分數五分之三表示爲3/5

真分數二又八分之三表示爲2’3/8

Exercises.txt文件格式以下:

1. 四則運算題目1

2. 四則運算題目2

……

Answers.txt文件格式以下:

1. 答案1

2. 答案2

判卷結果輸出到文件Grade.txt,格式以下:

Correct: 5 (1, 3, 5, 7, 9)

Wrong: 5 (2, 4, 6, 8, 10)

開發級需求

寫出至少10個測試用例確保你的程序可以正確處理各類狀況。

須要有單元測試

算法複雜度在O(n)到O(nlogn)左右

通過Code Quality Analysis工具的分析並消除全部的警告

採用TFS進行管理

每一個階段完成後均需進行迴歸測試

單元測試覆蓋率足夠高

採用C++或者C#語言實現,可使用.Net Framework

運行環境爲32-bit Windows 7或8

分爲五個階段,每一個階段均須要一次提交:

  計算及生成接口

  「設置」功能接口

  支持各種Exception

  界面、測試、庫的鬆耦合

  改進程序併發布

 

2、接口設計

在接口的設計中,咱們儘可能保持信息隱藏、鬆耦合等原則。爲了提高模塊的內聚性,下降和外部的耦合性,咱們只暴露出最必要的數據結構和接口函數。對於外界無需瞭解的任何信息,咱們的接口中都不會將其暴露出去。如下是咱們遵循這些想法設計的結果

(一)算式

算式採用如下的接口表達,經過標準的ToString進行輸出。經過value()能夠得到以分數形式表示的值,經過numValue則能夠得到相應的double類型的值。

    interface ExprAction

    {

        Fraction value();

        double numValue();

}

 

 

因爲外部不須要改變表達式自己,所以,咱們只須要將該接口暴露給用戶便可。至於該接口具體怎樣實現,則是內部完成的。用戶所須要的只是表達式的值以及輸出該表達式而已,這已經足以知足用戶全部的需求,所以,咱們沒有再暴露更多的東西給外部。

(二)計算及生成

public static ExprAction getExpr(String str)

 

經過字符串構造表達式。因爲表達式支持返回答案的功能,故而這一接口可用於實現計算。

public static List<ExprAction> generate(int n, int r)

 

生成必定數量的表達式,參數n爲題目數量,r爲範圍限制

 

上述兩個接口也保持了咱們以前的設計思路,徹底經過ExprAction這個接口來將表達式的相關信息給用戶,而不暴露任何多餘的內容。這有利於咱們維護內部的算法細節。例如,在實現的過程當中,咱們的生成算法幾經調整,但除了對generate函數內部進行了一些改變以外,沒有影響其他的任何部分。

(三)設置

設置經過settings函數進行,經過如下結構體將具體的配置信息傳入到庫中

    public class Configure : ICloneable

{

        // 最大運算符數目

        public int MaxOperatorNum

        {

            get { return maxOperatorNum; }

            set { maxOperatorNum = value; }

        }

        // 運算過程當中可否有分數

        public bool Fraction

        {

            get { return fraction; }

            set { fraction = value; }

        }

        // 是否容許乘除法

        public bool MulAndDiv

        {

            get { return mulAndDiv; }

            set { mulAndDiv = value; }

        }

        // 是否容許括號

        public bool Parentheses

        {

            get { return parentheses; }

            set { parentheses = value; }

        }

        // 是否容許運算中出現負數

        public bool Negative

        {

            get { return negative; }

            set { negative = value; }

        }

}

 

咱們認爲,採用單獨的結構體傳遞數據相較於XML來講有這樣幾點優點。XML在傳遞設置的時候須要進行必定量的解析工做,而採用結構體是不須要的。在耦合性上,XML中的內容、設置等字段與咱們的庫也是緊密耦合的,因此,在咱們看來XML就是一個以XML形式寫成的結構體。咱們最終仍是會將XML解析成一個結構體的,與其這樣,不如直接用結構體傳遞信息。

咱們認爲,即便最終的配置經過XML儲存在磁盤上,那麼解析XML的步驟也應當由前端來完成,而不該該由庫自己來完成。庫只負責接收設置並根據這一設置進行生成。而不該該牽扯到與用戶相關的細節上。

這是咱們最終設計庫的接口時採用結構體的理由。

3、概要設計

(一)表達式文法

在對輸入的字符串進行解析時,咱們採用了文法制導的解析方式。這種方式實現較爲簡單,只需按照文法中的非終結符號進行自頂向下的遞歸分析便可構造出表達式所對應的語法樹。所以,咱們參考需求中給出的文法,將整個文法補齊。

<e> := <sf> | <e> + <e> | <e> − <e> | <e> × <e> | <e> ÷ <e> | (<e>)

<sf> := <f> | -<f>

<f> := <digit> | <digit>’<digit>/<digit> | <digit>/<digit>

<digit> := <n> | <n><digit2>

<n> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

然而,咱們發現因爲有+/-和*/÷兩個優先級,直接採用上述文法沒法有效地按照運算的優先級構造出正確的表達式樹。乘除法和加減法是不一樣優先級的,構建表達式樹時,先計算的應當放在下面,後計算的放在上面,經過上述文法進行語法分析,是難以達到這一要求的。

所以,咱們改寫了上述文法,以<e>來表示+/-這一優先級,<term>來表示*/÷這一優先級。這樣就可以經過語法分析來構建出知足優先級關係的表達式樹了。改寫後的文法以下:

<e> := <e> + <term> | <e> − <term> | <term>

<term> := <term> × <factor> | <term> ÷ <factor> | <factor>

<factor> := <sf> | (<e>)

<sf> := <f> | -<f>

<f> := <digit> | <digit>’<digit>/<digit> | <digit>/<digit>

<digit> := <n> | <n><digit2>

<n> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9          

但該文法具備左遞歸性,沒法直接進行自頂向下分析。因此爲了使用自頂向下分析,咱們又對該文法進行消除左遞歸的處理。消除左遞歸後的文法以下:

<e> := <term><e2>

<e2> := + <term> <e2> | − <term> <e2> | ε

<term> := <factor><term2>

<term2> := × <factor> <term2> | ÷ <factor> <term2> | ε

<factor> := <sf> | (<e>)

<sf> := <f> | -<f>

<f> := <digit> | <digit>’<digit>/<digit> | <digit>/<digit>

<digit> := <n> | <n><digit2>

<n> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

採用上述文法進行分析,便可構造出輸入的表達式的表達式樹。

(二)表達式樹

咱們將表達式的數據結構設計爲一顆二叉樹。每一個葉節點爲一個單獨的數,每一個內部節點爲一個子表達式。整個表達式樹按照中序遍歷進行計算。在咱們的設計中,咱們將表達式樹的內部節點和葉節點抽象爲了ExprAction這個接口。

        Fraction value();

        double numValue();

value()函數會將表達式的值以Fraction,即咱們自定義的分數類的形式返回。而numValue則是其所對應的double值。numValue()主要用於單元測試。咱們將按照分數的計算法則算出來的值,與咱們直接使用浮點數計算出來的表達式的值進行比較,以肯定咱們的分數計算是否正確。

咱們的Fraction類(葉節點)和Expression類(內部節點)這兩個類實現了這一接口。

咱們發現,四則運算的計算過程當中的每個步驟,實際上都是在根據左右兩個表達式的值,計算當前表達式的值。所以整個計算過程只涉及Fraction直接的運算。所以,咱們重載了Fraction類的四則運算操做符。

對於表達式樹的設計,還有一點須要補充的是:Fraction和Expression是不可變對象。一旦被構造,則其對外表現出的狀態是永遠不變的。這樣設計簡化了代碼實現的難度,也提高了程序的穩定性。

(三)表達式生成

首先咱們提出一種不須要判重且能夠生成所有不等價式的線性算法。

首先定義狹義上的一元的概念,一元就是指知足值域要求的天然數、真分數,而後狹義上的二元式就是指兩個一元和一個操做符。

那麼咱們發現,在此基礎上定義的三元式,四元式,多元式,他們的計算過程的最後一步必然是兩個能夠用一元表示的值按照最後剩下的操做符進行運算,也就是說,任何多元式均可概括爲二元式,而此時,一元就不只僅是指狹義上的一元,而是包含了狹義上的二元式,多元式。

判斷二元式的等價是很是簡單的,只要使之+和*運算不知足交換律便可。

那麼,咱們只要由不等價的一元生成二元式,由一元和不等價的二元式生成三元式和四元式,那麼這些式子必定都是不等價的。或者說,就是由廣義上不等價的元生成全部廣義上不等價的二元式。

給出其數學證實:

首先定義元a,元集A。二元式b,二元式集B。運算符f,運算符集F。

 

A := 知足值域要求的 天然數、真分數 | B;

 

B := AFA;

 

F := ‘+’|’-’|’*’|’/’;

 

若是b1=a1 f1 a2,b2=a3 f2 a4屬於B,則:

 

b1與b2不等價的充分條件是:

 

f1 != f2 或

 

f1 == f2 且 f1 == ‘+’ 或 ‘*’ ,f1(a1,a3) != f2(a3,a4) && f1(a1,a3) != f2(a4,a3)

 

由以上定義和推理咱們能夠概括出不生成重複表達式的線性算法,即用全部一元構造狹義二元式(即兩個一元和一個運算符組成),以後用狹義二元式和一元構造足 夠數目的廣義二元式(即二元式自己也可做爲元),只要在遞歸和迭代的生成過程當中,任意二元式知足不重複的充分條件,則必定沒有重複的二元式。

如今咱們已經有了一個高效的線性生成算法,然而咱們按照需求把它調整成隨機化。

由上述算法不難看出,他是由少元向多元生成的,因此直接打亂順序的方法是不行的。

因此咱們採起這樣的作法,首先生成全部的狹義一元,以後把這些一元打亂順序,以後每次取極小一部分一元進行生成,並將生成結果存儲,以後再取一小部分一元,先於其自身生成,以後再將其結果與上次生成的全部表達式組合進行生成,所有生產完以後將這些第二次生成的表達式合併到第一次的結果中,重複此操做直到知足個數或者所有生成完成爲之。

最後因爲咱們採起的數據結構,直接這樣輸出仍是有必定的線性規律,以後再把全部結果打亂一次順序便可。

這種生成算法效率極高,30000個表達式的生成耗時1s左右。

4、單元測試

咱們對代碼進行了細緻而詳細的單元測試。單元測試對於咱們代碼的正確性有很大的幫助。因爲TFS始終鏈接出錯,所以,咱們暫時採用了git進行版本控制。每次進行git pull拉取下來同伴的代碼後,咱們都會跑一遍全部的單元測試,以確保合併正確,而且本地新增的代碼沒有致使原有功能的倒退。

 

咱們的代碼總體的覆蓋率比較高。對於一些比較重要的類,咱們的覆蓋率更是高達90%以上。多數沒有被覆蓋到的代碼都是與隨機生成相關的,因爲其自己的隨機性,難以達到單元測試的結果能夠重現的要求。因此,這部分代碼咱們並無進行單元測試。其他的相對較容易測試的代碼,咱們都進行了相應的單元測試。特別是解析表達式、帶分數等關鍵的類,咱們都儘量地覆蓋到了全部分支。

5、我的總結和感覺:

(一)結對編程:

 

結對編程可讓雙方的代碼效率和代碼質量都有所提升,由於是兩我的合做,因此不想讓本身的工做進度耽誤了對方,因此都加緊開發,而且爲了能讓兩方的模塊能鏈接起來,而且讓對方能讀懂本身的代碼,因此代碼結構,代碼註釋都會有提升。而且結對編程中遇到一些問題時兩我的的思路會更加廣闊和靈活。並且,每一個人在編程中擅長的方面不一樣,結對編程可讓雙方合理分工,取長補短。

結對編程也有缺點,兩我的在理解對方代碼時總多多少少要花些時間,若是一我的作的代碼架構,另外一我的沒有理解或者應用,極可能致使整個工程的代碼混亂,因此隊員之間必定要作好交流。

我在結對編程中的優勢,我對一些核心算法有一些本身獨到的思考,可以構建出高效的算法,而且我開發效率比較高,能夠快速解決問題,寫出代碼。

我在結對編程中的缺點:我對架構,模塊化,封裝這些概念上的理解不夠深入。

隊友在結對編程中的優勢:超級牛逼的架構師,前期對軟件的架構,模塊化的封裝,各部分的關係作了簡潔的處理,使我在構建算法的時候很是輕鬆,而且讓後來的調用和異常處理也變得異常簡單。

(二)接口設計方法:

在接口的設計中,咱們儘可能保持信息隱藏、鬆耦合等原則。爲了提高模塊的內聚性,下降和外部的耦合性,咱們只暴露出最必要的數據結構和接口函數。對於外界無需瞭解的任何信息,咱們的接口中都不會將其暴露出去。外界對內部的改變只能經過一些特定的調用完成,而且經過深拷貝,使得其外部不能有在設計以外的操做。而且將全部的模塊的接口都封裝到一個類中,方便調用和操做。

(三)算法關鍵

請見上面的概要設計章節。

相關文章
相關標籤/搜索