天天看點

軟體工程結對項目作業

項目 内容
本作業屬于北航軟體工程課程 2020春季計算機學院軟體工程(羅傑 任建)
本作業的要求請點選連結檢視 2020BUAA軟體工程結對項目作業
教學班級 005
Github項目位址 https://github.com/syncline0605/IntersectPairProject
我在這個課程的目标 提高自身的代碼能力、學習團隊協作開發的過程
本作業幫助我實作目标的具體方面 實踐結對程式設計過程、學習使用Qt進行GUI開發、學習異常處理、學習封裝dll、熟悉用VS進行C++開發的流程、熟悉VS帶有的各種工具

1.在文章開頭給出教學班級和可克隆的Github項目位址

  • 教學班級:005
  • 項目位址:https://github.com/syncline0605/IntersectPairProject

2.在開始程式之前,在下述PSP表格記錄下你估計将在程式的各個子產品的開發上耗費的時間。在實作完程式之後,記錄下在各個子產品上實際花費的時間。

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

本次作業基本直接沿用上次的算法、調用上次的函數。在想清楚程式設計方法後非常簡單,編碼工作比預想的簡單得多。測試工作也相對來說非常簡單,特别是在完成GUI子產品後,可以直接在GUI中直覺地看到每個交點是否都被計算出,不用再借助其他工具。

本次作業最耗費時間的工作是學會各種新工具的使用。

  • 發現一種錯誤的結構體初始化方式導緻單元測試無法正常運作
  • 下載下傳檢視單元測試覆寫率的插件并将其用于單元測試
  • 下載下傳配置Qt

以上幾點都耗費了比編碼和測試多得多的時間。

3.看教科書和其他資料中關于 Information Hiding,Interface Design,Loose Coupling 的章節,說明你們在結對程式設計中是如何利用這些方法對接口進行設計的

過去我在程式設計時常常是大片代碼和函數放在同一個檔案中,“一main到底”。在這次作業時,因為有充分的時間來解決這個并不困難的問題,我得以能夠認真地設計代碼架構。

我設計了定義了幾何對象的基礎子產品,基于這個基礎子產品完成了指令行處理的輸入輸出子產品和計算交點的計算子產品。主函數要先引用這個基礎子產品,然後調用輸入輸出子產品,得到基于基礎子產品儲存的輸入資料,然後将輸入資料傳入計算子產品,将計算的結果再傳入輸入輸入子產品。這樣一來,“得到輸入檔案”的過程、“将資料輸出到檔案”的過程和“計算交點”的過程就完全分離。兩個子產品的交流就僅僅是以基礎子產品定義為基礎的資料結構。

因為不便進行完全面對面的結對程式設計,我和我的隊友采用了近似分工的方式。基于我設計的計算子產品的接口,我的隊友非常輕松地了解了我的思路,并完成了GUI的編碼。

至于松耦合的部分,因為時間原因我們沒有去實作,但是我認為我們的代碼在松耦合上面的實作并不太好。如上所述,所有的行為都是基于定義了幾何對象的基礎子產品來說的,如果與别人交換子產品,我還沒有想好如何處理這個基礎子產品。另一方面,在後面的異常處理部分,有些異常發生在輸入輸入子產品,有的發生在計算子產品,有的異常在主函數中捕獲,有的異常在計算子產品中捕獲,如果與别人交換子產品,我不知道應該如何保證異常處理能夠正常、完備地發揮作用。

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

我的獨到之處在于針對線段與射線的新功能的實作基本都是對原有的直線相關函數的調用,計算交點時先将線段或射線轉化為它們所在的直線,直線計算出交點後再判斷該點是否線上段或射線上,極大地簡化了代碼邏輯。

因為考慮到封裝的幾何對象僅儲存資料,不涉及方法,是以用struct封裝幾何對象

struct Point {//點
	double x;
	double y;
	double length;

	bool operator ==(const Point& b) const noexcept
	{
		if (compareDouble(x - b.x) == 0 && compareDouble(y - b.y) == 0) return true;
		return false;
	}
	bool operator <(const Point& b) const noexcept
	{
		if (compareDouble(x - b.x) == 0 && compareDouble(y - b.y) < 0) return true;
		if (compareDouble(x - b.x) < 0) return true;
		return false;
	}

};

typedef Point Vector; //向量

struct Line { //直線
	Point p1, p2;
};

struct Segment { //線段
	Point p1, p2;
};

struct Ray { //射線
	Point start, direction;
};

struct Circle { //圓
	Point center;
	double r;
};

           

通過指令行處理函數,獲得輸入輸出檔案的名稱。從輸入檔案中得到所有的幾何對象,将同類的幾何對象存入同一個vector中。将全體交點的

Point

集合存入一個

set

vector<Line> lineSet;
vector<Segment> segmentSet;
vector<Ray> raySet;
vector<Circle> circleSet;
set<Point> pointSet;
           

計算交點的

calPoint

函數在同一個vector内部進行或是在兩個不同類vector間進行。計算交點的

getPoint

函數在兩個幾何對象間進行,傳回計算出的交點個數:

NOCROSS

ONECROSS

TWOCROSS

MANYCROSS

(無數個交點)。

計算交點的函數定義如下

int getPoint(Line l1, Line l2, Point& crossPoint) noexcept;
int getPoint(Line l, Segment s, Point& crossPoint) noexcept;
int getPoint(Line l, Ray r, Point& crossPoint) noexcept;
int getPoint(Line l, Circle c, pair<Point, Point>& crossPair) noexcept;

int getPoint(Segment s1, Segment s2, Point& crossPoint) noexcept;
int getPoint(Segment s, Ray r, Point& crossPoint) noexcept;
int getPoint(Segment s, Circle c, pair<Point, Point>& crossPair) noexcept;

int getPoint(Ray r1, Ray r2, Point& crossPoint) noexcept;
int getPoint(Ray r, Circle c, pair<Point, Point>& crossPair) noexcept;

int getPoint(Circle c1, Circle c2, pair<Point, Point>& crossPair) noexcept;

int calPoint(vector<Line>& lineSet, set<Point>& pointSet);
int calPoint(vector<Line>& lineSet, vector<Segment>& segmentSet, set<Point>& pointSet);
int calPoint(vector<Line>& lineSet, vector<Ray>& raySet, set<Point>& pointSet);
int calPoint(vector<Line>& lineSet, vector<Circle>& circleSet, set<Point>& pointSet);

int calPoint(vector<Segment>& segmentSet, set<Point>& pointSet);
int calPoint(vector<Segment>& segmentSet, vector<Ray>& raySet, set<Point>& pointSet);
int calPoint(vector<Segment>& segmentSet, vector<Circle>& circleSet, set<Point>& pointSet);

int calPoint(vector<Ray>& raySet, set<Point>& pointSet);
int calPoint(vector<Ray>& raySet, vector<Circle>& circleSet, set<Point>& pointSet);

int calPoint(vector<Circle>& circleSet, set<Point>& pointSet);

int calPoint(vector<Line>& lineSet, vector<Segment>& segmentSet, vector<Ray>& raySet, vector<Circle>& circleSet, set<Point>& pointSet);
           

将線段或射線轉換為直線、判斷點是否在一線段或射線範圍内的關鍵函數:

//将線段轉化成對應的直線
//将橫坐标較小的點作為p1,若兩點橫坐标相同則将縱坐标較小的點作為p1
Line segmentToLine(Segment s) {
	Line l;
	if ((s.p1.x < s.p2.x) || ((s.p1.x == s.p2.x) && (s.p1.y < s.p2.y))) {
		l.p1 = s.p1;
		l.p2 = s.p2;
	} else {
		l.p1 = s.p2;
		l.p2 = s.p1;
	}
	return l;
}

//将射線轉化成對應的直線
Line rayToLine(Ray r) {
	Line l{ r.start, r.direction };
	return l;
}

//判斷一個點是否在一線段的坐标範圍内
int pointIfOnSeg(Point p, Line l)
{
	if (l.p1.x == l.p2.x) {
		if ((p.y >= l.p1.y) && (p.y <= l.p2.y)) {
			return ON;
		} else {
			return NOTON;
		}
	} else {
		if ((p.x >= l.p1.x) && (p.x <= l.p2.x)) {
			return ON;
		} else {
			return NOTON;
		}
	}
}

//判斷一個點是否在一射線的坐标範圍内
int pointIfOnRay(Point p, Line l)
{
	if (l.p2.x < l.p1.x) {
		//若射線指向負方向
		if (p.x <= l.p1.x) {
			return ON;
		} else {
			return NOTON;
		}
	} else if (l.p2.x == l.p1.x && l.p2.y < l.p1.y) {
		//若射線指向正下方
		if (p.y <= l.p1.y) {
			return ON;
		} else {
			return NOTON;
		}
	} else if (l.p2.x == l.p1.x && l.p2.y > l.p1.y) {
		//若射線指向正上方
		if (p.y >= l.p1.y) {
			return ON;
		} else {
			return NOTON;
		}
	} else {
		//若射線指向正方向
		if (p.x >= l.p1.x) {
			return ON;
		} else {
			return NOTON;
		}
	}
}

           

5.畫出UML圖顯示計算各子產品部分各個實體之間的關系

軟體工程結對項目作業

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

使用一個随機生成的包含一千多條資料的測試集。

軟體工程結對項目作業

很明顯,程式将大多數時間用在了set的元素插入上。

除此之外,基于确定的精确值對equals意義的重寫也占用了大量時間

軟體工程結對項目作業

7.看 Design by Contract,Code Contract的内容,描述這些做法的優缺點,說明你是如何把它們融入結對作業中的

  • http://en.wikipedia.org/wiki/Design_by_contract
  • http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
The contracts take the form of pre-conditions, post-conditions, and object invariants. Contracts act as checked documentation of your external and internal APIs. The contracts are used to improve testing via runtime checking, enable static contract verification, and documentation generation. Code Contracts bring the advantages of design-by-contract programming to all .NET programming languages.

Code Contracts工具使用pre-conditions, post-conditions 和 object invariants來檢查内部和外部的API。這一工具展現了design-by-contract程式設計的優點。

Design by contracts是一種設計軟體的方式。一個簡單的DBC(契約式設計),就是限制了某個方法調用的要求以及傳回的承諾。與DBC差別的是“防範式設計”,它的設計前提是假設調用這個方法是惡意的,是以要對所有可能出錯的輸入進行檢測,在這種設計模式下,程式會包含很多與業務邏輯無關的代碼。

Design by contracts的優點:

  • 調用者必須提供正确的參數,被調用者必須保證正确的結果和調用者要求的不變性,雙方都有必須履行的義務,也有使用的權利,這樣就保證了雙方代碼的品質,提高了軟體工程的效率和品質

Design by contracts的缺點:

  • 對于語言有一定的要求,DBC一般用斷言來實作但并不是所有的程式語言都有斷言機制
  • DBC并未被标準化,代碼造成很大混亂

我目前對這種程式設計方式的體會還不是很深刻。我在代碼中盡量保證了單個函數實作明确的單一功能,我認為大多數時候是運用了DBC思想的。但是我并不是很明确這種DBC的方法如何與異常處理結合,我平時的程式設計思維與DBC距離還有多遠。

至于在結對式程式設計上的應用,我的體會并不是很深,二人在結對程式設計時,函數的作用往往已是公認商量好的。我的了解中,DBC是一種組織代碼的思想,與結對這種程式設計方法關系不是很大。

8.計算子產品部分單元測試顯示

如上文的子產品設計部分展示的函數定義,計算子產品分為L-L,L-R,L-S,L-C,S-S,S-R,S-C,R-R,R-C,C-C幾個函數,隻要分别覆寫了這些這些計算交點的

getPoint

函數,就能覆寫其調用的其他計算函數。為了覆寫所有

calPoint

函數,再編寫一個較大的測試集測試包含所有類别的完整

calPoint

函數即可實作較好的覆寫率。

//測試圓與圓交點的多種情況,除保證計算交點個數正确外,還要保證計算的交點坐标正确
TEST_METHOD(Circle_Circle_TwoCross)
{
	Circle c1;
	Circle c2;
	c1.center.x = 0; c1.center.y = 0; c1.r = 2;
	c2.center.x = 2; c2.center.y = 0; c2.r = 2;
	Point realPoint1, realPoint2;
	pair<Point, Point> testPair;
	realPoint1.x = 1; realPoint1.y = 1.73205081;
	realPoint2.x = 1; realPoint2.y = -1.73205081;
	Assert::IsTrue(getPoint(c1, c2, testPair) == 2);
	Assert::IsTrue(((realPoint1 == testPair.first) && (realPoint2 == testPair.second)) || ((realPoint2 == testPair.first) && (realPoint1 == testPair.second)));
}

TEST_METHOD(Circle_Circle_OneCross)
{
	Circle c1;
	Circle c2;
	c1.center.x = 0; c1.center.y = 0; c1.r = 2;
	c2.center.x = 4; c2.center.y = 0; c2.r = 2;
	Point realPoint1, realPoint2;
	pair<Point, Point> testPair;
	realPoint1.x = 2; realPoint1.y = 0;
	Assert::IsTrue(getPoint(c1, c2, testPair) == 1);
	Assert::IsTrue(realPoint1 == testPair.first);
}

TEST_METHOD(Circle_Circle_NoCross)
{
	Circle c1;
	Circle c2;
	c1.center.x = 0; c1.center.y = 0; c1.r = 2;
	c2.center.x = 5; c2.center.y = 0; c2.r = 2;
	Point realPoint1, realPoint2;
	pair<Point, Point> testPair;
	Assert::IsTrue(getPoint(c1, c2, testPair) == 0);
}

TEST_METHOD(TestAll)
{
	vector<Line> lineSet;
	vector<Segment> segmentSet;
	vector<Ray> raySet;
	vector<Circle> circleSet;
	set<Point> pointSet;
			
	Line l1, l2; Segment s1, s2, s3; Ray r1, r2, r3; Circle c1, c2;
	l1.p1.x = 1; l1.p1.y = 2; l1.p2.x = 3; l1.p2.y = 3;
	l2.p1.x = 4; l2.p1.y = 2; l2.p2.x = 2; l2.p2.y = -4;
	lineSet.push_back(l1); lineSet.push_back(l2);
	s1.p1.x = -5; s1.p1.y = 1; s1.p2.x = 5; s1.p2.y = -3;
	s2.p1.x = 5; s2.p1.y = 3; s2.p2.x = 2; s2.p2.y = 2;
	s3.p1.x = 6; s3.p1.y = -5; s3.p2.x = 6; s3.p2.y = 6;
	segmentSet.push_back(s1); segmentSet.push_back(s2);
    segmentSet.push_back(s3);
	r1.start.x = 5; r1.start.y = 2; r1.direction.x = 3; r1.direction.y = 4;
	r2.start.x = 2; r2.start.y = -4; r2.direction.x = -1; r2.direction.y = -1;
	r3.start.x = -10; r3.start.y = 1; r3.direction.x = 10; r3.direction.y = 1;
	raySet.push_back(r1); raySet.push_back(r2); raySet.push_back(r3);
	c1.center.x = 2; c1.center.y = 3; c1.r = 4;
	c2.center.x = 5; c2.center.y = 2; c2.r = 2;
	circleSet.push_back(c1); circleSet.push_back(c2);
	Assert::AreEqual(calPoint(lineSet, segmentSet, raySet, circleSet, pointSet), 31);
}
           

覆寫率截圖,計算子產品

GeoCalculate.pp

的覆寫率達到了90%:

軟體工程結對項目作業

9.計算子產品部分異常處理說明。在部落格中詳細介紹每種異常的設計目标。每種異常都要選擇一個單元測試樣例釋出在部落格中,并指明錯誤對應的場景。

設計了以下幾類異常:

  • 非法輸入格式
  • 輸入幾何對象不足
  • 檔案尾部輸入過多内容
  • 坐标範圍超出限制
  • 指令行參數是其他字元
  • 交點個數為無數個

當遇到不符合要求的輸入資料時,如檔案開始未出現n、出現非法字元、幾何對象的資料不正确時,抛出異常

illegalInputPattern

單元測試樣例:

軟體工程結對項目作業
軟體工程結對項目作業
軟體工程結對項目作業
軟體工程結對項目作業
軟體工程結對項目作業

從檔案的首行獲得n,但是當在檔案之後輸入資料不足n個時,抛出異常

notEnoughInputElement

軟體工程結對項目作業

當讀入n和n個幾何對象的資料後,檔案尾還有多餘内容,抛出異常

TooManyInputElements

軟體工程結對項目作業

對于每個輸入資料,判斷是否在(-100000,100000)範圍内,如果不在,則抛出異常

outRangeException

。當圓的輸入半徑小與或等于0時,也會抛出這一異常。

軟體工程結對項目作業

若指令行參數不正确,則抛出異常

commandException

單元測試樣例:(指令行參數為"-d")

軟體工程結對項目作業
軟體工程結對項目作業
軟體工程結對項目作業
軟體工程結對項目作業
交點個數為無窮個

若兩個圖形有無數個交點,則抛出異常

infException

單元測試樣例:(兩條直線重合)

軟體工程結對項目作業
軟體工程結對項目作業
軟體工程結對項目作業

10.界面子產品的詳細設計過程。在部落格中詳細介紹界面子產品是如何設計的,并寫一些必要的代碼說明解釋實作過程。

界面子產品使用Qt Creator進行開發,設計了兩個視窗,一個是主界面dialog,另一個是畫圖界面new_window。

主界面如圖所示:

軟體工程結對項目作業

接下來逐一介紹所實作的四個功能:

  • 打開檔案

    void Dialog::readFile()

    函數

    使用了

    getOpenFileName

    方法擷取檔案路徑,并打開檔案,用

    readLine

    方法逐行讀取檔案内容,并用

    split

    方法對字元串按空格進行分割,存儲到相應的結構中。最後使用

    ui->label->setText

    方法将讀取的内容顯示在界面上。

    點選“打開檔案”按鈕并選擇檔案後效果如圖所示:

    軟體工程結對項目作業
  • 添加圖形

    void Dialog::addone()

    使用

    ui->text->toPlainText

    方法擷取文本框中的字元串,用

    split

    方法分割并存儲到相應的結構中。最後使用

    ui->label_2->setText

    方法将所添加的資料顯示在界面上。

    點選“添加”按鈕後效果如圖所示:

    軟體工程結對項目作業
  • 删除圖形

    void Dialog::deleteone()

    ui->text->toPlainText

    split

    方法分割,找到該資料對應的元素并删除。最後使用

    ui->label_2->setText

    方法将所删除的資料顯示在界面上。

    點選“删除”按鈕後效果如圖所示:

    軟體工程結對項目作業
  • 繪制圖形和交點

    void Dialog::open()

    打開新視窗new_window,重寫

    paintEvent

    方法,進行圖像繪制。

    繪圖方法:

    • 線:drawLine
    • 圓:drawEllipse
    • 點:drawPoint
    圖形繪制效果如圖所示:
    軟體工程結對項目作業

11.界面子產品與計算子產品的對接。詳細地描述 UI 子產品的設計與兩個子產品的對接,并在部落格中截圖實作的功能。

計算交點需要用到

calPoint

方法,它的接口是五個結構體的集合,分别代表直線、線段、射線、圓和點。我的想法是把界面子產品中的結構體也包裝成相同的形式,直接調用該方法進行計算即可。将Release版的動态連結庫和相關頭檔案添加到項目中,即可直接調用該方法。

軟體工程結對項目作業
功能實作展示

從檔案中添加3個圖形,并手動添加1個圖形(射線),如圖所示:

軟體工程結對項目作業

點選“繪制圖形和交點”按鈕後效果如圖所示:

軟體工程結對項目作業

從檔案中添加4個圖形,并手動删除1個圖形(線段),如圖所示:

軟體工程結對項目作業
軟體工程結對項目作業

12.描述結對的過程

軟體工程結對項目作業
軟體工程結對項目作業

13.說明結對程式設計的優點和缺點。同時描述結對的每一個人的優點和缺點在哪裡。

結對程式設計
  • 優點
    • 邊編碼邊複審,思維更嚴密,減少低級bug
    • 兩人可能掌握不同的技能、有各自的程式設計特點,能夠融合優點,通過互相學習,在對方的指導下更快地掌握新技能。
    • 集思廣益,通過讨論得到更好的結構設計
  • 缺點
    • 如果兩人技術能力差距過大,會拖慢進度,結對程式設計的效率甚至不如較強的一方單獨程式設計
    • 如果有一方或兩方的責任心都不強,會互相甩鍋,或者将兩人的代碼分得很清、不關心對方的代碼效果如何

  • 優點:代碼架構思考較深入;程式設計習慣好、代碼風格優;對解決問題比較執着
  • 缺點:學習新東西速度慢,有時會要求隊友直接告訴我而不是自主解決;有很多無謂的較真、工作效率較低

隊友

  • 優點:學習能力強、學習新工具的速度快;交流主動積極、熱心;執行力強、效率高
  • 缺點:有時候不太注意代碼風格