天天看點

結對項目部落格

項目 内容
這個作業屬于哪個課程 2020計算機學院軟體工程(羅傑 任健)
這個作業的要求在哪裡 結對項目作業
教學班級 006
項目位址 https://github.com/CrapbagMo/PairProgramIntersect

1. PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 30
· Estimate · 估計這個任務需要多少時間
Development 開發 930 1020
· Analysis · 需求分析 (包括學習新技術) 90 120
· Design Spec · 生成設計文檔
· Design Review · 設計複審 (和同僚稽核設計文檔) 60
· Coding Standard · 代碼規範 (為目前的開發制定合适的規範)
· Design · 具體設計 150 180
· Coding · 具體編碼 360
· Code Review · 代碼複審
· Test · 測試(自我測試,修改代碼,送出修改) 240
Reporting 報告 70
· Test Report · 測試報告 40
· Size Measurement · 計算工作量 10
· Postmortem & Process Improvement Plan · 事後總結, 并提出過程改進計劃 20
合計 1110 1210

2. Information Hiding,Interface Design,Loose Coupling

  • Information Hiding

    是指資訊隐藏原則,在1972年被 David Parnas 提出,他指出:代碼子產品應該采用定義良好的接口來封裝,這些子產品的内部結構應該是程式員的私有财産,外部是不可見的。在結對程式設計中,我們對于資訊隐藏的應用是:
    • 在多層設計中的層與層之間加入接口層
    • 所有類與類之間都通過接口類通路
    • 類的所有資料成員都是private,所有通路都是通過通路函數實作的
  • Interface Design

    是指接口設計,接口被定義為職責(或角色、能力),接口決定了一種類能夠做什麼,賦予了他在某種情況下的權力和義務。在結對程式設計中,我們專注于定義行為,把某個具體功能提取出來,先把這個功能弄成政策,然後各自具體用類去繼承這個接口。
  • Loose Coupling

    是指松耦合,是指減少一個代碼單元與其他代碼單元的配合關系。最理想、最松散的耦合,是一個單元無需其他代碼單元的配合可以單獨完成它的功能。松耦合的目标是最小化依賴。松耦合這個概念主要用來處理可伸縮性、靈活性和容錯這些需求。在結對程式設計中,我們通過加入中間層,來減少各個代碼單元之間的耦合度。

3. 計算子產品接口的設計與實作

  1. 函數及類:
    • 五個主要類

      點類:

      class Point

      圖形類:

      class Figure

      線條類:

      class Line: Figure

      圓形類:

      class Circle: Figure

      平面容器類:

      class PlaneContainer

    • 五個主要函數:

      添加圖形:

      int add_Figure(std::string buf)

      初始化容器:

      void initial_PlaneContainer()

      釋放容器:

      void dispose_PlaneContainer()

      擷取交點序列:

      double* get_IntersectionPoints()

      擷取交點數目:

      int get_NumOfIntersectionPoints()

  2. 主要作用:
    • Circle

      Line

      類繼承自

      Figure

      類,實作

      std::set<Point>intersect(Figure*)

      方法。該方法傳回兩個圖形的交點集合。
    • 五個函數都是對外提供的接口函數。

      init_PlaneContainer、dispose_PlaneContainer

      采用單例模式用于維護全局靜态變量

      PlaneContainer* pc

      add_Figure

      get_NumOfIntersectionPoints

      get_IntersectionPoints

      用于提供添加圖形、擷取交點序列、交點數目功能。
    • 異常由add_Figure函數捕獲并處理,處理完後已錯誤代碼形式傳回,具體處理方式本節不做介紹。
  3. 算法關鍵及獨到之處:
    • 首先考慮直線和圓的情況(先不考慮射線和線段):

      按照直線和直線, 直線和圓, 圓和圓在平面上的關系分為下面三種情況考慮:

      直線和直線:

      • 判斷直線是否相交: \(A_1*B_2-A_2*B_1!=0\)則相交.
      • 若相交則求交點: \((\frac{B_1*C_2-B_2*C_1}{A_1*B_2-A_2*B_1},\frac{A_2*C_1-A_1*C_2}{A_1*B_2-A_2*B_1})\)

        直線和圓:

      • 聯立直線和圓方程(為了起見簡便, 若\(B!=0\), 化為斜截式再聯立), 求得系數\(tA\), \(tB\), \(tC\).
      • 根據\(Delta=tB^2-4*tA*tC\)判斷交點個數
      • 若\(Delta\ge0\) , 根據求根公式求得交點橫坐标, 進而求出交點.

        圓和圓:

      • 計算圓心距\(dis=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\).
      • 比較圓心距\(dis\) 和半徑和\(r_1+r_2\), 半徑差\(|r_1-r_2|\) .
      • 若有交點則兩圓相減求出相交弦方程, 進而轉化為直線和圓得交點.
    • 注意到直線、射線、線段的不同點僅在于其坐标範圍不同。是以為Line類引入範圍屬性(\(R \leq x \leq S\))即可同時表示三種線,其中射線、直線的無窮端以宏INF表示。求交點時,将

      Line

      Figure

      求交點,求得的交點再判斷是否在

      Line

      所在範圍内即可,若不在範圍内則剔除。這就将直線、射線、線段統一了。

4. UML實體關系圖

結對項目部落格

5. 計算子產品接口部分的性能改進

  • 改進之前,一分鐘之内隻能運算兩百張圖不到,運作一分鐘(處理了大約150個圖形)後性能探測截圖:
結對項目部落格

可以看到,主要時間都花在了,

PlaneContainer.insert

方法的

set_union

函數上。

結對項目部落格

經查閱資料發現,在set_union函數中,存在了多次set複制,效率極低。于是修改了此處邏輯如下:

結對項目部落格
  • 修改之後,可以在20秒左右完成2000個圖形的計算,性能大大提高,與改前可謂“天壤之别”再次執行了性能探查:
結對項目部落格
結對項目部落格

此時

PlaneContainer.insert

方法已經不耗費太多時間了。

6. Design by Contract & Code Contract

Design by Contract

Code Contract

是指契約式設計和代碼遵守的契約,是按照某種規定對一些資料等做出約定,如果超出約定,程式将不再運作,例如要求輸入的參數必須滿足某種條件,否則不會運作。我們在聲明一個函數/方法的時候,對函數的輸入和輸出所具備的性質是有所期望和規定的。有時候這種性質會被我們明确的寫出來,有時候會被我們忽略掉。這些期望和規定就是Contract。

契約程式設計在面向對象設計課程中有所涉及,第一次接觸 JML,我的反應是:這也太傻了,不就是幫程式員把程式用僞代碼實作了嗎!其實這并不對,契約式程式設計的重要原則就是推遲對于過程的思考。JML是在代碼中增加了一些符号,這些符号隻表述一個方法要幹什麼,并不關心它的實作過程。

這樣的契約程式設計在當時看來是非常耗時的,我們需要首先在設計好接口的條件下,先對所有的函數仔細思考其“輸入” 及 “輸出”,再進行具體的編碼。

我們在結對程式設計中由于時間關系,并沒有使用契約式程式設計的方法。經過個人項目的磨練,

core

中的核心代碼涉及的函數在我們看來,其輸入輸出參數的條件都已經非常明了,特意地為了完成契約式程式設計的任務而改變相對更熟悉的工作方式,在我們看來是不明智的。

7. 計算子產品部分單元測試展示

代碼覆寫率情況:(使用VS2017 Enterprise代碼覆寫率工具生成)

結對項目部落格

對項目中的類的單元測試覆寫率為 \(93.07\%\) ,對類 Circle 的覆寫率為 \(100\%\), 對類 Line 的覆寫率為 \(98.5\%\) 。

我們設定了針對功能的測試和針對異常的測試。

針對功能性測試,我們測試了圓、直線、線段和射線的一些函數在正常情況下的功能性,同時也構造了一些特殊情況下的測試用例,比如

// 射線 圓 内部相交
PlaneContainer pc;
pc.insert(new Circle(0, 0, 2));
pc.insert(new Line(1, 0, 2, 2, RL));
int count = pc.countIntersectionPoints();
Assert::AreEqual(count, 1);
           
// 射線 射線 一個交點
PlaneContainer pc;
pc.insert(new Line(0, 0, 1, 1, RL));
pc.insert(new Line(0, 0, -1, -1, RL));
int count = pc.countIntersectionPoints();
Assert::AreEqual(count, 1);
           
// 精度測試
PlaneContainer pc;
pc.insert(new Line(0, -100000, 1, 100000, SL));
pc.insert(new Line(0, 0, 0, 1, SL));
pc.insert(new Line(0, -99999, 1, -99999, SL));
int count = pc.countIntersectionPoints();
Assert::AreEqual(count, 3);
           

8. 計算子產品部分異常處理說明

本次結對項目中,我們共設計了五類異常,

  • 輸入不滿足标準格式

    我們使用正規表達式來識别輸入是否滿足标準格式,正規表達式如下:

    std::regex segREGEX("S\\s+-?\\d+\\s+-?\\d+\\s+-?\\d+\\s+-?\\d+\\s*\\n?");
    std::regex lineREGEX("L\\s+-?\\d+\\s+-?\\d+\\s+-?\\d+\\s+-?\\d+\\s*\\n?");
    std::regex rayREGEX("R\\s+-?\\d+\\s+-?\\d+\\s+-?\\d+\\s+-?\\d+\\s*\\n?");
    std::regex circleREGEX("C\\s+-?\\d+\\s+-?\\d+\\s+-?\\d+\\s*\\n?");
               
  • 參數不在标準範圍 (-100000, 100000) 内
  • 定義直線的兩點重合
  • 圓的半徑不大于零
  • 計算中出現無窮交點
    • 直線、線段或射線與直線、線段或射線出現部分或完全重合
    • 兩圓完全重合

對于單元測試,我們對每一類異常場景及正常場景設計了3-4個測試樣例,測試代碼如下,

TEST_METHOD(TestMethod1)
{	// 格式錯誤
    int res = add_Figure("acsd");
    Assert::AreEqual(res, -1);
    delete(pc);
}

TEST_METHOD(TestMethod2)
{	// 格式錯誤
    int res = add_Figure("C 5 3 -2 1");
    Assert::AreEqual(res, -1);
    delete(pc);
}

TEST_METHOD(TestMethod3)
{	// 格式錯誤
    int res = add_Figure("L -5 3 -2 0 4");
    Assert::AreEqual(res, -1);
    delete(pc);
}

TEST_METHOD(TestMethod4)
{	// 格式錯誤
    int res = add_Figure("R 5 -3 2");
    Assert::AreEqual(res, -1);
    delete(pc);
}

TEST_METHOD(TestMethod5)
{	// 格式錯誤
    int res = add_Figure("c 5 -3 2");
    Assert::AreEqual(res, -1);
    delete(pc);
}

TEST_METHOD(TestMethod6)
{	// 格式錯誤
    int res = add_Figure("S 5 -3 2 0-1\n");
    Assert::AreEqual(res, -1);
    delete(pc);
}

TEST_METHOD(TestMethod7)
{	// 正常
    int res = add_Figure("R 5 -3 2 3");
    Assert::AreEqual(res, 0);
    delete(pc);
}

TEST_METHOD(TestMethod8)
{	// 正常
    int res = add_Figure("C 5 -3 3\n");
    Assert::AreEqual(res, 0);
    delete(pc);
}

TEST_METHOD(TestMethod10)
{	// 半徑小于0
    int res = add_Figure("C 5 -3 -2 \n");
    Assert::AreEqual(res, 3);
}

TEST_METHOD(TestMethod11)
{	// 半徑等于0
    int res = add_Figure("C -5 -3 0\n");
    Assert::AreEqual(res, 3);
}

TEST_METHOD(TestMethod12)
{	// 點超出坐标軸範圍
    int res = add_Figure("L 963214 -3 2 3\n");
    Assert::AreEqual(res, 1);
}

TEST_METHOD(TestMethod13)
{	// 點超出坐标軸範圍
    int res = add_Figure("R 5 -3 526151 3\n");
    Assert::AreEqual(res, 1);
}

TEST_METHOD(TestMethod14)
{	// 點超出坐标軸範圍
    int res = add_Figure("C -3 526151 3\n");
    Assert::AreEqual(res, 1);
}

TEST_METHOD(TestMethod15)
{	// 兩點重合
    int res = add_Figure("R 2 -3 2 -3 ");
    Assert::AreEqual(res, 2);
}

TEST_METHOD(TestMethod16)
{	// 兩點重合
    int res = add_Figure("L 99999 88888 99999 88888 \n");
    Assert::AreEqual(res, 2);
}

TEST_METHOD(TestMethod17)
{	// 兩點重合
    int res = add_Figure("S 0 2 0 2 \n");
    Assert::AreEqual(res, 2);
}

TEST_METHOD(TestMethod18)
{	// 無窮交點
    add_Figure("L 2 2 3 3 ");
    int res = add_Figure("L 0 0 -1 -1 \n");
    Assert::AreEqual(res, 4);
}

TEST_METHOD(TestMethod19)
{	// 無窮交點
    add_Figure("S 2 2 4 4 ");
    int res = add_Figure("S 3 3 0 0 \n");
    Assert::AreEqual(res, 4);
}

TEST_METHOD(TestMethod20)
{	// 無窮交點
    add_Figure("R 1 1 4 4 ");
    int res = add_Figure("S 2 2 0 0 \n");
    Assert::AreEqual(res, 4);
}

TEST_METHOD(TestMethod21)
{	// 無窮交點
    add_Figure("R 1 1 1 0 ");
    int res = add_Figure("S 1 0 1 5 \n");
    Assert::AreEqual(res, 4);
}

TEST_METHOD(TestMethod22)
{	// 無窮交點
    add_Figure("C 1 1 1  ");
    int res = add_Figure("C 1 1 1 \n");
    Assert::AreEqual(res, 4);
}
           

9. 界面子產品的詳細設計過程 & 10. 界面模與計算子產品的對接

  • 界面子產品是基于.NET 4.7.2 的窗體應用,采用C#語言編寫。
  • Program

    用于定義

    Main

    函數外,整個子產品一共隻有類,下面結合圖形、代碼介紹該類的組成
結對項目部落格
namespace GUI
{
    unsafe public partial class MainForm : Form
    {
        //下面為控件類執行個體,由VS自動生成
        //Panel是主要的控件,用于繪圖
        private System.Windows.Forms.Panel panel1;
        //打開輸入檔案對話框
        private System.Windows.Forms.OpenFileDialog openFileDialog1;
        //檔案輸入按鈕和手工輸入按鈕
        private System.Windows.Forms.Button inputButton;
        private System.Windows.Forms.Label pathInput;//儲存并顯示輸入檔案路徑
        private System.Windows.Forms.Button button2;
        //1-6用于擷取手工輸入圖形的資料,7用于顯示多行提示資訊
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.TextBox textBox2;
        private System.Windows.Forms.TextBox textBox3;
        private System.Windows.Forms.TextBox textBox4;
        private System.Windows.Forms.TextBox textBox5;
        private System.Windows.Forms.TextBox textBox6;
        private System.Windows.Forms.TextBox textBox7;
        //以下label用于顯示單行資訊
        private System.Windows.Forms.Label num;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label4;
        private System.Windows.Forms.Label label5;
        private System.Windows.Forms.Label label6;
        private System.Windows.Forms.Label label7;
        private System.Windows.Forms.Label label8;
        private System.Windows.Forms.Label label9;
        
        //下面為使用者變量
        //繪圖畫筆
        private Pen pen;
        //繪圖類
        private Graphics g;
        //直線容器,每四個數字代表一條直線
        private LinkedList<int> StraightLines;
        //射線容器,每四個數字代表一條射線
        private LinkedList<int> RayLines;
        //線段容器,每四個數字代表一條線段
        private LinkedList<int> LineSegments;
        //圓形容器,每三個數字代表一個圓形
        private LinkedList<int> Circle;
        
		//下面的五個函數皆為從core.dll中導入的接口函數
        //添加圖形并傳回添加結果
        [DllImport("core.dll")]
        private static extern int add_Figure(StringBuilder buf);
        //初始化容器
        [DllImport("core.dll")]
        private static extern void initial_PlaneContainer();
        //摧毀容器
        [DllImport("core.dll")]
        private static extern void dispose_PlaneContainer();
        //擷取交點序列
        [DllImport("core.dll")]
        private static extern double* get_IntersectionPoints();
        //擷取交點數目
        [DllImport("core.dll")]
        private static extern int get_NumOfIntersectionPoints();
        
       	//下面為成員方法(包括回調函數)
        //構造方法,主要為變量配置設定。
        public MainForm();
        //窗體打開和關閉的回調,用于顯示歡迎和再見提示
        private void MainForm_Load(object sender, EventArgs e);
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        //輸入按鈕的回調函數,用于處理檔案輸入
        private void inputButton_Click(object sender, EventArgs e);//檔案輸入
        private void button2_Click(object sender, EventArgs e)//手工輸入
        //畫闆的重繪回調函數,用于繪制圖形
        private void panel1_Paint(object sender, PaintEventArgs e);
        //處理add_Figure函數傳回來的錯誤代碼
        private bool processRetVal(int retCode, string line);
        //存儲圖形,以供繪圖函數使用
        private void store(string buf);
        //四個小的繪圖函數
        private void drawLine();
        private void drawCircle();
        private void drawRay();
        private void drawLineSegment();
        //坐标系變換函數
        private void change(ref Point p);
        private void change(ref PointF p)
    }
}
           
  1. 重點介紹以下幾個函數的運作邏輯
    • private void inputButton_Click(object sender, EventArgs e);//檔案輸入

      • 若打開不成功則不進行處理并給出提示。
      結對項目部落格
      • 否則,按行循環讀取檔案送

        string line

        ,調用

        add_Figure

        處理,并擷取處理結果送

        int retCode

      • line

        retCode

        交給

        processRetVal

        處理。
      • 通過

        get_NumOfIntersectionPoints

        更新交點數目并顯示
    • private void button2_Click(object sender, EventArgs e);//手工輸入

      • 通過6個

        TextBox

        輸入框擷取輸入資料。
      • 根據LRSC輸入框輸入的類型代碼,将LRSC和後面的相應輸入框中内容組成

        string line

        ,調用add_Figure處理,并擷取處理結果送

        int retCode

      • line

        retCode

        processRetVal

      • get_NumOfIntersectionPoints

    • private bool processRetVal(int retCode, string line);

    • retCode==0

      ,未發生異常,調用

      store

      line

      存儲。
    • 否則,不進行存儲并提示相應的錯誤,提示使用者,例如。
    結對項目部落格
    • private void panel1_Paint(object sender, PaintEventArgs e);

      • 繪制坐标系、刻度等。
      • 調用

        drawLine

        drawCircle

        drawRay

        drawSegment

        繪制相應圖形。
  2. 子產品對接
    1. core子產品不提供記憶體管理,由接口函數

      init_PlaneContainer

      dispose_PlaneContainer

      管理。
    2. 核心接口add_Figure供界面子產品調用。
      1. 正則比對,如果格式不符合要求,不進入下一步并傳回相應的錯誤代碼。否則予以解析進入下一步。
      2. 根據解析結果,構造相應的

        Figure

        并捕獲可能的異常,如果構造過程中出現兩點重合、半徑非正等異常,不進入下一步,并反回相應的錯誤代碼。
      3. 将構造的

        Figure

        假如容器

        pc

        ,并捕獲可能的異常,如果出現無窮交點等異常,不進入下一步,并傳回相應錯誤代碼。
      4. 正常傳回。代碼代碼0;
  3. 使用圖例
    結對項目部落格

11. 結對過程描述

我們在結對過程中使用了 Visual Studio 中的 Live Share 和微信進行交流,截圖如下。

結對項目部落格
結對項目部落格

12. 結對程式設計的優缺點

優點:

  • 結對程式設計中,兩個人都必須對代碼熟悉,是以兩個人都處于對代碼不斷地處于 “複審‘ 的過程,不斷地稽核、提高的過程。這樣可以提高代碼品質。
  • 結對程式設計可以讓程式員更專注于工作,更注重代碼品質、代碼風格等。
結對程式設計的過程也是一個互相督促的過程,每個人的一舉一動都在别人的視線之内,所有的想法都要受到對方的評價。由于這種督促的壓力,使得程式員更認真地工作。
  • 結對程式設計使工作更容易、簡單,因為一個人工作時遇到困難很容易陷入頹廢無力的境況。而結對程式設計中,往往比較少産生兩個人都無力解決的問題,即使遇到了,雙方也會互相鼓勵,而不是陷入惡性循環。

缺點:

  • 結對程式設計中如果雙方的水準差不多,就能具有更高的效率。但如果雙方的水準差距較大,則會出現強的一方拉着弱的一方走的情形。對于強的一方,可能承包了大部分工作,既累又沒辦法保障代碼的品質;;而對于弱的一方,沒有參與感,對項目一知半解,更産生了挫敗感。在這種情況下,雙方的交流也不在同一個層次上,結對程式設計不如獨自程式設計。

結對夥伴的優點:

  • 有責任心
  • 自學能力強
  • 思路清晰

結對夥伴的缺點:

  • 有點貪玩,使項目完成時間略微滞後

我的優點:

  • 認真
  • 經常主動交流

我的缺點:

  • 沒有腦子,做不了設計,隻能跟着隊友走