天天看點

結對程式設計

  • 教學班級:006

  • 項目位址:

  • https://github.com/Pandapan-Buaa/IntersectInPair.git

PSP 表格

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

Information Hiding,Interface Design,Loose Coupling

Information Hiding:對資料進行封裝,讓外部不能随便通路。由于本次作業的計算比較多,為了提高效率,并沒有遵守這個原則,類的成員變量都被設為public。

Loose Coupling:松耦合。每個子產品自身是一個整體,各個子產品之間的依賴盡可能少,這樣修改一個子產品時,就不用修改其他的子產品。我們的核心計算子產品向UI子產品提供了添加和删除圖形的兩個接口,UI子產品不用考慮計算子產品的具體實作,隻需要調用接口函數,UI子產品和計算子產品之間的耦合比較低。

計算子產品接口的設計與實作過程

​ 與上次作業類似,本次作業裡隻有三個類(點/向量、線、圓)及其公共常用函數以及三個求解函數,在類中隻需對應屬性以及提供向量計算的相關函數,并在三個求解函數中對類的交點進行求解。

求解函數的關鍵代碼如下:

其中line.isOnLine()是判斷點是否線上段/射線上的簡單方法,其實作則是根據直線/線段/射線的向量方程 l = u + tv ,u是直線上一點,v是方向向量,t是系數,計算出t則易判斷是否在所給線上。

void lineIntersectLine(const Line& l1, const Line& l2)
{
	if (dcmp(l1.v ^ l2.v) == 0)	return;
	Vector u = l1.p - l2.p;
	double t = (l2.v ^ u) / (l1.v ^ l2.v);

	Point temp = l1.p + l1.v * t;
	if (l1.type != "L" && !l1.isOnLine(temp))	return;
	if (l2.type != "L" && !l2.isOnLine(temp))	return;

	try {
		points.insert(temp);
	}
	catch(exception e){}
}

void lineIntersectCircle(const Line& L, const Circle& C)
{
	double t1, t2;
	double a = L.v.x, b = L.p.x - C.c.x, c = L.v.y, d = L.p.y - C.c.y;
	double e = a * a + c * c, f = 2 * (a * b + c * d), g = b * b + d * d - C.r * C.r;
	double delta = f * f - 4 * e * g;
	if (dcmp(delta) < 0)    return; //線圓相離
	if (dcmp(delta) == 0) {		    //線圓相切
		t1 = t2 = -f / (2 * e);
		if (L.type != "L" && !L.isOnLine(t1))	return;
		try {
			points.insert(L.point(t1));
		}
		catch (exception e) {
		}
		return;
	}
	//線圓相交
	t1 = (-f - sqrt(delta)) / (2 * e);
	t2 = (-f + sqrt(delta)) / (2 * e);
	Point p1 = L.point(t1);
	Point p2 = L.point(t2);
	if (L.type != "L" && !L.isOnLine(p1));
	else {
		try {
			points.insert(p1);
		}
		catch (exception e) {
		}
	}

	if (L.type != "L" && !L.isOnLine(p2));
	else {
		try {
			points.insert(p2);
		}
		catch (exception e) {
		}
	}
	return;
}

void circleIntersectCircle(const Circle& c1, const Circle& c2)
{
	double d = Length(c1.c - c2.c);
	if (dcmp(d) == 0) return; //兩圓重合
	if (dcmp(c1.r + c2.r - d) < 0)    return;
	if (dcmp(fabs(c1.r - c2.r) - d) > 0)    return;
	double a = angle(c2.c - c1.c);
	double da = acos((c1.r * c1.r + d * d - c2.r * c2.r) / (2 * c1.r * d));
	Point p1 = c1.point(a - da), p2 = c1.point(a + da);
	try {
		points.insert(p1);
	}
	catch (exception e) {
	}
	if (p1 == p2)	return;
	try {
		points.insert(p2);
	}
	catch (exception e) {
	}
}
           

獨到之處在于:用計算幾何的方法,簡潔高效的解決了核心代碼的拓展部分。

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

性能改進方面依然沒有想出更優複雜度的算法,隻能夠進行一些細節上的優化。由于C++的set是基于紅黑樹實作的,插入操作的複雜度為O(logn),是以考慮了換成基于哈希表實作的unordered_set, 在哈希函數理想的情況下,插入操作的複雜度為O(1)。換了unordered_set之後性能确實有了提升,在1000條直線,400000個交點的情況下,所用時間從9秒左右提升到了6秒左右。

結對程式設計

Design by Contract

Design by Contract就是契約式程式設計,在大二的面向對象課程有過了解。這種方法規定了函數的前提條件,後繼條件,不變量條件等,優點是嚴格區分了責任,讓每個人隻用關心自己的代碼正确性。缺點是可能會在函數各種條件的考慮上花費很多時間。

項目中并沒有嚴格地使用契約式程式設計,隻是規定了一些接口的作用,參數,傳回值。

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

在構造測試資料時,兩兩組合,分為L_L,R_R, S_S, L_R, L_S, L_C, R_S, R_C, S_C, C_C十種情況。

結對程式設計

首先把能想到的情況進行了測試,比如直線相交,直線平行,射線端點,直線與圓相交,相切,相離等情況,然後根據覆寫率工具顯示的未覆寫的分支,再添加對應的測試資料 。

測試覆寫率:

結對程式設計

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

隻設計了一個異常類,定義了五種異常類型。

WRONGTYPE: 輸入了L, R, S, C之外的不支援的類型。

WRONGFORMAT: 輸入了不符合規範的格式。

BADPOINT: 輸入的點的坐标不在(-100000,100000)範圍内。

BADLINE: 輸入的直線的兩個端點重合。

BADCIRCLE: 輸入的圓的半徑r小于等于0。

結對程式設計
結對程式設計
結對程式設計

界面子產品的詳細設計過程

首先決定采用基于C++的QT編寫UI,在資料查詢的過程中,我發現了QT的第三方庫QcustomPlot,看完其基本示範後,發現能夠較為簡單的實作類似GeoGebra的界面放縮和繪制功能,是以決定以QT+QcustomPlot完成UI任務。在閱讀完《Qt5.9 c++開發指南》的前四章以及第七章IO處理之後,我認為已有的知識已經足夠了,是以開始查詢QcustomPlot官方文檔以及答疑,同時進行UI代碼編寫。

結對程式設計

UI界面如上,共有四個Button(分别負責繪制圖像、交點、添加、删除,其中添加删除格式與輸入格式一緻,例如“L 0 0 1 1”),一條menubar(負責打開文本檔案),一個TextEdit(負責顯示文本檔案以及輸入),一個結果Lable(顯示交點數目),以及QcustomPlot區域(繪圖),由于QT是可以可視化拖動子產品并自動對其産生代碼的,是以該部分的設計實作并不是人工的。

接着為了實作上述的功能,需要利用QT中的信号與槽機制,按鈕的信号在其庫中已有定義,需要寫的是按鈕按下後的反應,即按鈕對應的槽,如下:

private slots:
    void on_actOpen_triggered();
    void on_AddBtn_clicked();
    void on_DelBtn_clicked();
    void on_PaintBtn_clicked();
    void on_PointBtn_clicked();
           

具體代碼過長就不做展示了。這些槽函數定義了按鈕的反應,Add和Del對應的是核心代碼接口,這裡主要講兩個繪制按鈕的槽。

根據QcustomPlot官方文檔可知其對圓,直線,射線,點均有多種不同實作方法,但大體而言分兩類,一類在Graph對象中添加點集,形成圖像;另一類AbstractItem則是直接對圖像進行繪制(這裡的原理并未細究,但其繪制速度是遠超于點繪制的)。是以我編寫了不同的繪制函數,如下:

void paintPoint(QCustomPlot *customPlot,double x,double y);
    void paintLine(QCustomPlot *customPlot,double x1, double y1, double x2, double y2);
    void paintRay(QCustomPlot *customPlot,double x1, double y1, double x2, double y2);
    void paintSegment(QCustomPlot *customPlot,double x1, double y1, double x2, double y2);
    void paintCircle(QCustomPlot *customPlot,double x1, double y1, double x2, double y2);
           

經測試,在500000數量級的繪制下均可以保持使用流暢(當然,繪制需要時間,該庫還提供了Opengl加速,但我并未實驗)

界面子產品與計算子產品的對接

由于提前設計了接口

void add_diagram(char T, int x1, int y1, int x2, int y2);
void sub_diagram(char T, int a, int b, int c, int d);
void calPoints();
set<pair<double, double>> uiPoints;
           

是以在對接時隻需要在四個按鈕對應槽中合理調用接口即可。

實作的功能:

  1. 支援從檔案導入幾何對象的描述。

    左上角File處可以添加檔案,并顯示在下方文本框中。

  2. 支援幾何對象的添加、删除。

    按下Add,Del按鈕會添加/删除文本框中的集合對象

  3. 支援繪制現有幾何對象。

    PaintGraph按鈕實作

  4. 支援求解現有幾何對象交點并繪制。

    PaintPoint按鈕實作

  5. 坐标系放縮,平移 (數量級10^-5 ~ 10^250)

結對過程

騰訊會議螢幕共享截圖
結對程式設計
日常微信交流截圖
結對程式設計

結對程式設計優缺點

我認為結對程式設計的好處在于:結對程式設計的兩個人可以互相監督,不容易偷懶,可以互相學習程式設計技巧,以及可以同時檢查代碼,有效地減少bug。

壞處:可能會在某個問題上産生不同的想法,互相溝通會花費一些時間。

成員的優點和缺點:

我: 優點:思路清晰,比較認真

缺點:容易偷懶

對方:優點:很認真,耐心,學習能力很強

缺點:不是很細心

附加題

交換團隊(17373259 、 17373250)

由于和對方團隊提前商量好了接口,是以子產品的替換較為容易,基本無需更改。

将DLL替換後編碼如下(以添加Add按鈕為例)

QString text = ui->textEdit->toPlainText();
    QFile outFile("./temp.txt");
    outFile.open(QIODevice::WriteOnly);
    QTextStream out(&outFile);
    for(int i = 0 ; i < text.size();i++){
        out << text.at(i);
    }
    outFile.close();

    QFile inFile("./temp.txt");
    inFile.open(QIODevice::ReadOnly);
    QTextStream in(&inFile);
    QChar sub;
    int x1,y1,x2,y2,r;
    QCustomPlot *customPlot = ui->qcustomPlot;
    while(in.atEnd() == false){
        in >> sub ;
        if(sub == "L")  {
            in >> x1 >> y1 >> x2 >> y2;
            add_diagram('L',x1,y1,x2,y2);
        }
        else if(sub == "R"){
            in >> x1 >> y1 >> x2 >> y2;
            add_diagram('R',x1,y1,x2,y2);
        }

        else if(sub == "S"){
            in >> x1 >> y1 >> x2 >> y2;
            add_diagram('S',x1,y1,x2,y2);
        }

        else if(sub == "C"){
            in >> x1 >> y1 >> r;
            add_diagram('C',x1,y1,r,0);
        }
    }
    ui->textEdit->clear();
    cout << point_map.size() << endl;
           

基本不需要修改源代碼,即可完成dll更改。

一開始由于随機生成的測試樣例中存在平行直線,出現了一些問題(己方dll是在QT中重新改寫生成的,并未将核心代碼中的錯誤處理部分導入,造成了這個測試樣例中的問題未被及時發現)。後續修改後,經過測試,對方dll能夠完成任務需求。

結對程式設計