軟件工程做業——結對編程實踐

  1. 在文章開頭給出教學班級和可克隆的 Github 項目地址(例子以下)。(1')
項目 內容
這個做業屬於 2020春季計算機學院軟件工程(羅傑 任健)
這個做業的要求是 結對項目做業
個人教學班級 006
結對隊友博客 favourLZH
結對項目的GitHub地址 IntersectDualProj
我對這項做業的目標是 提升我的程序開發素質,在與同伴的結對編程中不斷配合、成長,
一同寫出高性能程序

做業要求

本次做業爲我的項目中求解交點的增量擴展,主要目的是爲了讓同窗們經過身體力行了解如下三點:軟件需求的變動,封裝,接口與鬆耦合,以及錯誤處理。html

  1. 軟件需求的變動
    在我的項目的基礎上額外支持兩個幾何對象:線段和射線
  2. 封裝
    把求解交點的功能能獨立出來,成爲一個獨立的模塊(class library, DLL,或其它),這樣命令行和 GUI 的程序都能使用同一份代碼
    咱們稱之爲計算核心「core 模塊」,這個模塊至少能夠可被用於如下地方:
    1. 命令行測試程序中用於提供核心功能;
    2. 在單元測試框架下驗證模塊正確性;
    3. 與數據可視化部分結合使用提供核心功能
  3. 接口
    咱們知道軟件並不是只有計算核心,實際的軟件是交付給最終用戶的軟件,除了計算核心外,還須要有必定的界面和必要的輔助功能。那麼這個 core 模塊和使用它的其餘模塊之間須要如何進行交流呢?答案是經過必定的 API(Application Programming Interface),一般也可稱做接口。經過明確模塊之間的接口,則模塊 A 對於模塊 B 的認知則只有 B 提供的接口,而與 B 的實現無關。
  4. 鬆耦合
    系統的組件對於其它的組件只具備不多的認知或者沒有認知
  5. 錯誤與異常處理
    對於一個真實的軟件來講,來自用戶的非法輸入是難以免的。若是程序的輸入出現了錯誤,好比命令行參數是其餘字符,或者有多個無心義參數等等,要怎樣才能告訴函數的調用者「你錯了」?又該如何方便地告訴函數的調用者「哪裏錯了」?在這種時候,咱們通常會定義各類異常(Exception),讓模塊在碰到各類異常狀況的時候,能給調用者充分的錯誤信息。
    在本次做業中,將要求同窗們進行錯誤處理,體會一個真實軟件在錯誤處理上的相關考慮。

做業完成功能劃分

  • 助教小哥哥小姐姐很是友善的爲咱們的代碼提交規定了幾個step(這樣也方便審覈~
    • 因此咱們的軟件開發各步驟的順序和內容就肯定以下了
  1. 基本功能實現及測試:單元測試、並要求獲得90%以上覆蓋率
  2. 拓展功能後封裝:看書相關內容,並說明命令行、GUI接口設計的理由,並構造測試
  3. 支持異常處理:異常處理設計及其測試
  4. 增長界面模塊:開發UI模塊
  5. 鬆耦合:不一樣小組間交換核心模塊與界面模塊測試

PSP

  1. 在開始實現程序以前,在下述 PSP 表格記錄下你估計將在程序的各個模塊的開發上耗費的時間。(0.5')

14.在你實現完程序以後,在附錄提供的PSP表格記錄下你在程序的各個模塊上實際花費的時間。(0.5')node

  • PSP
PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃
- Estimate - 估計這個任務須要多少時間 10 10
Development 開發
- Analysis - 需求分析 (包括學習新技術) 1. 5
2. 45
3. 45
4. 30
5. 30
總計:155
1. 75
2. 30
3. 25
4. 120
5. 120
總計:370
- Design Spec - 生成設計文檔 20 40
- Design Review - 設計複審 (和同事審覈設計文檔) 30 10
- Coding Standard - 代碼規範 (爲目前的開發制定合適的規範) 20 15
- Design - 具體設計 1. 15
2. 30
3. 20
4. 45
5. 30
總計:140
1. 15
2. 30
3. 30
4. 45
5. 30
總計:150
- Coding - 具體編碼 1. 20
2. 30
3. 60
4. 90
5. 30
總計:230
1. 90
2. 75
3. 75
4. 120
5. 30
總計:390
- Code Review - 代碼複審 60
上述5步驟各10min
30
- Test - 測試(自我測試,修改代碼,提交修改) 1. 40
2. 60
3. 60
4. 90
5. 30
總計:230
1. 120
2. 45
3. 120
4. 120
5. 30
總計:435
Reporting 報告
- Test Report - 測試報告 15 15
- Size Measurement - 計算工做量 15 15
- Postmortem & Process Improvement Plan - 過後總結, 並提出過程改進計劃 15 15
合計 940 1495

開發

  • 按照做業要求實現的功能,開發順序以下

Step 1 基本功能實現及測試

需求分析與學習

  • 此次結對編程,咱們採用隊友我的項目的程序做爲基礎程序,c++

    • 緣由是,上次她用了更多的時間去編寫和測試,完成了附加題,代碼完整度和性能都應該比個人更好,因此採用
  • 而我在閱讀她的代碼過程當中,對代碼作出瞭如下幾點改進git

    1. 計算類、圖形類、基礎函數類分離
    2. 在程序中加入一些TODO標記:(1)命令行錯誤處理;(2)直線boundary以融入線段和射線;(3)運行時間分析;(4)更換部分數據結構以優化性能
  • 而且下載學習了覆蓋性測試的軟件OpenCppCoverage Plugingithub

設計

4.計算模塊接口的設計與實現過程。設計包括代碼如何組織,好比會有幾個類,幾個函數,他們之間關係如何,關鍵函數是否須要畫出流程圖?說明你的算法的關鍵(沒必要列出源代碼),以及獨到之處。(7')算法

  • 一共6個類(除開def,內有通用函數,好比double精度比較)
3class
  • 類與類之間關係
graph LR A[Point] --> B[Line] A --> C[Circle] A --> D[Calcalator] B --> D C --> D D --> E[IOinterface] D --> F[Exception] E --> F E --> G[GUI調用] E --> H[cmd調用]
  • 關鍵部分說明,因爲交點計算的關鍵函數實現都在上一次做業博客中說明了,此次不作特別闡述,着重說明新增功能的拓展。
  • Calculator 類
    • 這是因爲新增射線和射線須要作比較多改進的地方
    • 其中Line與line的交點計算的預判(用到叉乘的方法) 參見 博客,沒法預判的內容好比射線,則先計算交點,再判斷是否在射線上
    • 直線和射線與圓的問題處理,計劃先算出交點,再判斷點是否在line上
    • 線段與圓關係進行預判,好比線段兩點都在圓內,來必定程度的下降時間複雜度
    • 圓與圓的交點無需改動
class Calculator {
public:
	Calculator();
	inline double xmult(Point v1, Point v2);
	double xmult(Point o, Point a, Point b);
	//判斷點是否在line上 在line上則return true
	bool pOnLine(Point p, Line l);
	// 圓內 return true;  相切/相離  return false;
	bool pInCircle(Point p, Circle c);
	bool isParallel(Line l1, Line l2);//判斷兩線是否平行 (並捕捉 重疊的異常)	 
	int haveIntersection(Line l1, Line l2, set<Point>& nodeSet);
	int haveIntersection(Circle c, Line l, set<Point>& nodeSet);
	int haveIntersection(Circle c1, Circle c2, set<Point>& nodeSet);
	//計算所有交點
	int countAllinsect(vector<Line> lVec, vector<Circle> cVec, set<Point> &nodeSet);
};
  • Line類
    • 其中設計新增射線和線段的功能
// 'L' -> line;
// 'R' -> radio;
// 'S' -> segment;
class Line
{
public:
	Line();
	Line(char newType, double x1, double y1, double x2, double y2);
	char getType();
	double getA();
	double getB();
	double getC();
  double getSlope();
	Point  getP1();
	Point  getP2();

private:
	char type;
	Point p1;
	Point p2;
	double A;
	double B;
	double C;
  double slope;
};
  • Point類 & Circle類的設計沿用上一次做業設計

測試

  1. 計算模塊部分單元測試展現。展現出項目部分單元測試代碼,並說明測試的函數,構造測試數據的思路。並將單元測試獲得的測試覆蓋率截圖,發表在博客中。要求整體覆蓋率到 90% 以上,不然單元測試部分視做無效。(6')
  • 因爲openCppCoverage在VS中下載緩慢,能夠選擇在marketplace中下載,目前尚未發現openCppCoverage插件能夠用於檢測單元測試覆蓋率的功能,只能用它來檢測總體代碼覆蓋率,得出覆蓋率以下

3testCoverage

  • 在網上查找,VS的單元測試覆蓋率可能須要旗艦版才能完成,因此目前沒有得出單元測試的結果,可是咱們本身的單元測試編寫的有層次有條理,咱們對單元測試有100%覆蓋的信心shell

  • 總體測試架構數據庫

    • 根據設計的幾大類,採用bottom-up的方式進行測試程序的編寫
  • 對於每一個類的每個函數都進行了細密的測試編程

    • 好比下面展現的對於直線類的測試,細緻到每個函數
TEST_CLASS(LineTest) {
public:
	TEST_METHOD(lineTestBasic) {
		Line l1('L', 1, 1, 2, 2);
		Line l2('R', 0, 2, 1, 0);
		Line l3('S', 1, 0, 5, 0);

		// test getType
		Assert::IsTrue(l1.getType() == 'L');
		Assert::IsTrue(l2.getType() == 'R');
		Assert::IsTrue(l3.getType() == 'S');

		// test get abc
		Assert::IsTrue((l1.getA() == 1 && l1.getB() == -1) ||
			(l1.getA() == -1 && l1.getB() == 1)
			&& l1.getC() == 0);
		Assert::IsTrue((l2.getA() == -2 && l2.getB() == -1 && l2.getC() == 2) ||
			(l2.getA() == 2 && l2.getB() == 1 && l2.getC() == -2));

		// test get p1 p2;
		Point p1(1, 1);
		Point p2(1, 0);
		Point p3(5, 0);
		Assert::IsTrue(l1.getP1() == p1);
		Assert::IsTrue(l2.getP2() == p2);
		Assert::IsTrue(l3.getP2() == p3);

	}
};
  • 對於求交點的重要複雜部分,咱們的測試也作的更細緻
    • 好比直線相交的測試,咱們對於幾種直線間的狀況好比相交、平行、重疊,以及三種直線的狀況(線段、射線、直線)都作了很是細緻的測試
3直線測試
// test parallel
		TEST_METHOD(LinePrl)
		{
			Calculator* calc = new Calculator();
      // 三種線段
			char line = 'L';
			char radio = 'R';
			char segment = 'S';
			Line lTD(line, 1, 3, 2, 3);
			Line rTD(radio, 2, 5, 4, 5);
			Line sTD(segment, 51, 6, 24, 6);
			Calculator* cal = new Calculator();
			Assert::IsTrue(cal->isParallerl(lTD, rTD));
			Assert::IsTrue(cal->isParallerl(lTD, sTD));
			Assert::IsTrue(cal->isParallerl(rTD, sTD));

			Line l1(line, 3, 3, 5, 5);
			Line r1(radio, 6, 5, -100, -101);
			Line s1(segment, 0, 1, 100, 101);
			Assert::IsTrue(cal->isParallerl(l1, r1));
			Assert::IsTrue(cal->isParallerl(l1, s1));
			Assert::IsTrue(cal->isParallerl(r1, s1));
			Assert::IsFalse(cal->isParallerl(l1, sTD));
			Assert::IsFalse(cal->isParallerl(r1, sTD));
			Assert::IsFalse(cal->isParallerl(s1, rTD));
		}

Step 2 拓展功能後封裝

接口設計

  1. 看教科書和其它資料中關於 Information Hiding,Interface Design,Loose Coupling 的章節,說明大家在結對編程中是如何利用這些方法對接口進行設計的。(5')
  • 針對Information hiding原則(參考),咱們對step1中的程序作了以下改進:canvas

    • 外部沒法訪問原則,全部容器訪問改成迭代器訪問,而不是數組訪問
    • 人機交互邏輯,集中到一個單獨的類、包或者子系統中,這爲咱們的UI接口設計肯定了理論基礎
    • 具體數字常量化/宏,咱們對於精度的設定就採用了宏定義的方式#define EPS (1e-8) 這樣在修改具體精度時,只用一處修改便可
    • 信息訪問限制,咱們在對點、線、圓等對象有嚴格的訪問限制,全部的內部屬性爲private,在構造時即肯定內部屬性,以後只能讀取,不能修改
  • Interface Design

    • 咱們的接口設計聽從高內聚,低耦合的設計思路:單一模塊只實現一個功能,模塊和模塊之間的依賴儘量小甚至沒有,模塊間的接口也是考慮全局而精簡設計
    • 好比在Calculator類的設計中,咱們充分解析了交點計算過程當中的關鍵計算流程,設計出如下7個分層次的計算方法,分別對點在線上、點在圓內、平行判斷、交點計算(線與線、線與圓、圓與圓、彙總計算)進行各模塊的編寫
    • 使得總體複雜的計算過程充分解耦,下降計算過程的代碼編寫複雜度,減小bug出現的機會,提升計算交點過程的總體可靠性。
// Calculator.h	
  //判斷點是否在line上 在line上則return true
	bool pOnLine(Point p, Line l);
	// 圓內 return true;  相切/相離  return false;
	bool pInCircle(Point p, Circle c);
	// TODO: add slope class
	bool isParallel(Line l1, Line l2);//判斷兩線是否平行 (並捕捉 重疊的異常)	 
	int haveIntersection(Line l1, Line l2, set<Point>& nodeSet);
	int haveIntersection(Circle c, Line l, set<Point>& nodeSet);
	int haveIntersection(Circle c1, Circle c2, set<Point>& nodeSet);
	//計算所有交點
	int countAllinsect(vector<Line> lVec, vector<Circle> cVec, set<Point> &nodeSet);
  • Loose Coupling,此次附加題是對這個原則的一個理論實踐
    • 學習以後,總結爲以下幾點:
      1. 接口和抽象類的正確使用
      2. 增長中間層
      3. 減小對象之間的依賴
    • 這個部分和interface design所要求的內容有一些重合,不過很是值得學習和實踐的是增長中間層這部份內容
    • 上學期經過數據庫理論的學習,我明白了數據庫從應用層到中間層再到底層邏輯,其中三層任何一層的修改,都不會涉及到其餘層次的變更,這是由於中間兩級鏡像層發揮了巨大的做用
    • 而咱們此次的計算核心模塊和GUI以及命令行的對接,也要有中間轉換層,來實現系統的核心模塊和用戶交互層的完全解耦。下面的代碼就是咱們核心模塊面向GUI和cmd的中間層設計。
    • 內部計算實現完全隱藏,只註明外部調用接口規範,這個內容會在GUI設計部分詳細說明。
// IOinterface.h
IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points, std::string msg);
IMPORT_DLL void cmdProcess(int argc, char* argv[]);

UML

5.閱讀有關 UML 的內容:https://en.wikipedia.org/wiki/Unified_Modeling_Language ,畫出 UML 圖顯示計算模塊部分各個實體之間的關係(畫一個圖便可)。(2’)

  • 使用VS類圖導出的UML以下
    • 其中cmd和GUI接口部分使用函數編寫,不在類圖中。其主要根據UI指令,構造並維護各類圖形集合,調用Calculaor類計算交點並返回給用戶,並處理上述過程的異常拋出。
      3UML

性能分析

6.計算模塊接口部分的性能改進。記錄在改進計算模塊性能上所花費的時間,描述你改進的思路,並展現一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),並展現你程序中消耗最大的函數。(3')

  • 因爲此次做業主要是有上次做業的基礎的,因此總體性能改進相比起上次「女媧補天」式的提高,此次只是小修小改。記錄幾個修改:

    1. 直線平行判斷優化,咱們將直線的slope屬性存儲在直線class中,判斷平行時直接調用判斷相等,而不用屢次計算斜率
    2. 部分小函數宏定義優化,對於double相等以及大小比較的小型可是屢次調用的函數,咱們將其設定爲宏定義,減小函數調用的時間消耗
    3. 以及一些小的修改,根據函數調用的局部性原理,將分支語句中更多調用的語句提早之類
    4. 學習了助教發的sweep line提示文檔,對比其中內容和自行編寫的代碼,因爲sweep line彷佛只能優化線段的內容,做業時間有限,也就沒有作這個部分的優化。
  • 最終性能分析圖片以下

    • 佔用性能最多的函數仍是求直線交點函數,該函數中交點集合的插入部分是佔用CPU最多的數據操做語句
      3performance improvement

Design by contract & Code contract

7.看 Design by Contract,Code Contract 的內容:

http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx

描述這些作法的優缺點,說明你是如何把它們融入結對做業中的。(5')

  • design by contract & code constract
    • 設計即要確保交付性能,並且類的完成者和使用者、函數的調用者和被調用者等等,各類API的開發和調用,均需根據contract開發,也就是說明外界保證什麼(傳入什麼參數),內部向外界提供什麼(返回什麼結果),在處理過程當中維護什麼樣的不變性
    • 以上理解,參考wiki中的敘述

Similarly, if the method of a class in object-oriented programming provides a certain functionality, it may:

  • Expect a certain condition to be guaranteed on entry by any client module that calls it: the method's precondition—an obligation for the client, and a benefit for the supplier (the method itself), as it frees it from having to handle cases outside of the precondition.
  • Guarantee a certain property on exit: the method's postcondition—an obligation for the supplier, and obviously a benefit (the main benefit of calling the method) for the client.
  • Maintain a certain property, assumed on entry and guaranteed on exit: the class invariant.
  • The contract is semantically equivalent to a Hoare triple which formalises the obligations. This can be summarised by the "three questions" that the designer must repeatedly answer in the contract:
    • What does the contract expect?
    • What does the contract guarantee?
    • What does the contract maintain?

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.

  • 我認爲以上原則在團隊或者結對編程過程當中是很是有助於團體開發效率的,按照constract開發並交付
    • 但問題在於這種開發方式在面對大的需求變化時候重構時候可能過於死板,constract的細密度和維護設計都須要額外的開銷
  • 而咱們的做業在實際設計中,尤爲是GUI接口的設計,也最遵循了這一原則
    • 接口以下
    • gui提供直線和圓的msg信息和返回交點的容器,guiProcess函數保證返回交點數目並把交點寫入容器,guiProcess在運行過程當中使用異常處理的方法保證了直線、圓、線段等內部特性的穩定
    • 同理cmdprocess須要參數個數和參數字符數組,向輸出文件返回交點數目,運行過程當中維持直線、圓、線段等內部特性
// IOinterface.h
IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points, std::string msg);
IMPORT_DLL void cmdProcess(int argc, char* argv[]);

Step 3 支持異常處理

注意事項
當前階段,題目中描述了若干對於輸入的保證,規定了合法輸入的含義。在後續的異常處理中,全部關於輸入的保證均會被去除

  1. 計算模塊部分異常處理說明。在博客中詳細介紹每種異常的設計目標。每種異常都要選擇一個單元測試樣例發佈在博客中,並指明錯誤對應的場景。(5')

異常處理設計

  • 幾種異常
  1. 命令行輸入異常(參數、文件名)
  2. 輸入文件異常(輸入曲線不符合格式,輸入線段數目,「亂碼」輸入)
  3. 直線異常
  4. 曲線異常

異常處理測試

1.1. 命令行輸入——參數異常

intersect.exe -n 
intersect.exe -i input.txt -o output.txt -h

1.2. 命令行輸入——文件名異常

intersect.exe -i test.txt -o output.txt
intersect.exe -i input.txt -o out.txt
  • 根據以上測試,獲得異常處理測試結果

3expCmd測試)

2.1. 輸入文件內容——輸入曲線不符合格式

## 1. 直線輸入錯誤
R 0 43 9 -3 98

# 2. 輸入幾何對象參數含有前導0
S 09 12 45 89

# 3.  多個字母
S S 3 2 1 

# 4. 只有數字
3 1 5 2 76

# 5. 字母數字亂序
5 L 1 4 6

# 6. -後不接數字
L - - - -

# 7. 錯誤數字
L 98-736 92 0 82

2.2. 輸入線段數目異常

# 1. 輸入線段 < 1
0
-94

# 2. 輸入線段與實際線段數不一樣
1
L 0 10 8 83
R 91 46 2 0

4
L 56 28 82 4
R 19 41 34 56
C 56 168 5

2.3.曲線輸入文件沒法打開

3.1. 直線不符合標準

## 1. 輸入兩點重合
L 0 1 0 1 

## 2. 輸入數字超範圍
R 100000 0 0 0
L -100000 4897 278 1
S -100005 3784 942 61

3.2.有無窮多交點

#1.  正確狀況
3
S 1 1 3 3
S 5 5 100 100
R 0 0 -55 -55


# 2. 異常
2
S 0 1 7 8
R -4 -3 -3 -2

2
S 0 1 7 8
L 9 10 100 101

2
R -4 -5 0 -1
L -99 -100 -50 -51

2 
S 1 0 3 0
S 2 0 4 0 

2
S 1 0 3 0
S 2 0 3 0 

2 
S 1 0 3 0
S 1 0 5 0 

2
S 1 0 3 0
S 0 0 5 0

4.1. 圓不符合標準

## 1. 輸入圓半徑小於1
C 0 0 0

C 84 72 -23

## 2. 輸入數字超範圍
C -100000 4897 278
  • 對於以上的樣例,分別寫了測試樣例,獲得測試結果以下:

3exception

Step 4 增長界面模塊

  1. 界面模塊的詳細設計過程。在博客中詳細介紹界面模塊是如何設計的,並寫一些必要的代碼說明解釋實現過程。(5')

此次的GUI咱們是用Qt實現的。Qt的良好封裝機制使得Qt的模塊化程度很是高,可重用性較好,對於用戶開發來講比較方便。 Qt提供了一種稱爲signals/slots的安全類型來替代 callback,使得各個元件之間的協同工做變得十分簡單。

(1)界面設計

  • 咱們先經過集成在 Qt Creator 中的 Qt Designer 對窗體進行可視化設計。
    • 最終界面以下:

pairProjectGUI

  • 爲了方便用戶操做、減小用戶記憶負擔、減小用戶錯誤信息,咱們的設計作了以下改良:

    • 關文件導入:咱們實現的「..."按鈕可直接瀏覽文件夾選擇文件,無需手動輸入路徑。
    • 添加操做:因爲幾何對象的參數要求爲在(-100000,100000)之間的不含前導零的整數,咱們設置了SpinBox,其限制了正確的參數形式,避免手動輸入帶來的參數格式錯誤問題。對於幾何體的類型,咱們實現了下拉選框。
    • 刪除操做:爲了減小用戶的記憶負擔,咱們在listWidget中實時呈現了當前幾何圖形。爲了不刪除操做的錯誤信息和比較過程的繁瑣,咱們設置了複選框。用戶選擇列表中的幾何圖形,點擊「刪除幾何圖形」便可。這樣既方便了用戶,也減小了咱們異常處理的負擔。
    • 求解交點:點擊「求解交點」按鈕,則會計算出全部交點的左邊,並在下方顯示求解的交點數。
    • 繪製:在用戶完成所有對當前幾何圖形的修改和求解交點後,點擊「繪製幾何圖形和交點」按鈕,咱們將統一更新畫布。這樣避免了用戶頻繁的階段性操做帶來的無用計算。
  • 界面使用注意點:

    • 在點擊求解交點後,方有交點的數據,才能繪製出交點。
    • 在點擊繪製幾何圖形和交點後,纔會更新當前幾何圖形的繪製。

(2)主要代碼說明

  • Qt使用了信號和槽來代替回調函數,實現對象間的通訊。當一個特定的事件發生時,信號會被髮送出去。Qt的窗體部件(widget)擁有衆多預先定義好的信號。槽,則是對一個特定的信號進行的反饋。咱們此次的實現主要是建立窗體部件(widget)的子類並添加自定義槽,以便對感興趣的信號進行處理。

  • 咱們實現的類中,有兩個重要的屬性vector<string> figuresvector<pair> points,分別存放當前幾何對象當前交點

  • a.文件導入

//點擊"..."按鈕,瀏覽文件夾
void IntersectGUI::on_findFileButton_clicked() 
{
	filePath =
		QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Save path"), QDir::currentPath()));  //文件路徑
	if (!filePath.isEmpty())
	{
		if (ui.fileBox->findText(filePath) == -1)
			ui.fileBox->addItem(filePath);//在comboBox中顯示文件路徑
	}
}
//點擊"輸入文件"按鈕,導入文件數據
void IntersectGUI::on_infileButton_clicked()
{
	QFile* file = new QFile;   //申請一個文件指針
	file->setFileName(filePath);   //設置文件路徑
	bool ok = file->open(QIODevice::ReadOnly);
	if (ok)
	{
	……//讀入文件並將文件中的數據處理後存入figures中	
		}
		file->close();
	}
}
  • b.求解交點
    • 點擊「求解交點」按鈕,將當前幾何體的數據轉換成相應的接口處的數據,調用dll中的函數,計算交點並返回。具體接口設計,下一部分詳細介紹。
int IntersectGUI::on_calcPButton_clicked()
{
	points.clear();
	std::string input;
	size_t n = figures.size();
	……//將figures中的幾何體數據轉換成相應的接口中的數據input
	int cnt = 0;
	//cnt = guiProcess(&points,figures);
	try {
		cnt = guiProcess(&points, input);
	}
	catch (std::exception e) {
		QString dlgTitle = QString::fromLocal8Bit("計算出現錯誤");
		QMessageBox::critical(this, dlgTitle, e.what());
		return 0;

	} {
	}
	ui.lineEdit->setText(QString::number(cnt));//反饋交點數
	return cnt;
}
  • c.圖形繪製
    • 這一部分,咱們重寫了paintEvent()方法。點擊「繪製幾何圖形和交點」的按鈕時,調用update()函數,從新繪製。
void IntersectGUI::paintEvent(QPaintEvent*)
{
	init_canvas(); //初始化畫布 (底色和座標軸)
	if (figures.size() != 0) {
		for (vector<string>::iterator iter = figures.begin(); iter != figures.end(); ++iter) {
			draw_figures_from_str(*iter);//繪製幾何圖形
		}
		draw_points();//繪製交點
	}
}

void IntersectGUI::on_drawFigureButton_clicked()
{
	update();
}

//將不一樣的string數據繪製成不一樣的幾何圖形
void IntersectGUI::draw_figures_from_str(string str)
{
	QStringList list = (QString::fromStdString(str)).split(" ");
	QString type = list.at(0);
	……
	if (type == QString::fromLocal8Bit("L")) {
		draw_line(x1, y1, x2, y2);
	}
	else if (type == QString::fromLocal8Bit("S")) {
		draw_seg(x1, y1, x2, y2);
	}
	else if (type == QString::fromLocal8Bit("R")) {
		draw_ray(x1, y1, x2, y2);
	}
	else {
		draw_circle(x1, y1, r);
	}
}
  • 因爲Qt中的drawLine()方法,只能繪製兩點間的線段。因此在實現繪製直線和射線的時候,咱們計算了當前線與畫布邊界的交點。代碼簡單,可是很長,在這裏就不展現了。
  1. 界面模塊與計算模塊的對接。詳細地描述 UI 模塊的設計與兩個模塊的對接,並在博客中截圖實現的功能。(4')

(1)接口數據格式介紹

  • 計算模塊與界面模塊的對接,用到了此接口:
    • msg存放的是當前幾何圖形的信息,數據格式與文件中讀取的格式相同。
    • points存放求解的交點。
#ifdef IMPORT_DLL
#else
#define IMPORT_DLL extern "C" _declspec(dllimport)
#endif
IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);
/* string in msg
4
C 3 3 3
S 2 4 3 2
L -1 4 5 2
R 2 5 -1 2
*/

(2)GUI導入dll的方式以下:

#pragma comment(lib,"calcInterface.lib")
_declspec(dllexport)  extern "C"  int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);

(3)具體代碼實現

  • GUI相關的代碼只在求解交點處調用了dll的guiProcess()方法。
int IntersectGUI::on_calcPButton_clicked()
{
	points.clear();
	std::string input;
	size_t n = figures.size();
    //轉換數據
	input += std::to_string(n) + "\n";
	for (size_t i = 0; i < n; i++) {
		input += figures.at(i) + "\n";
	}
	
	int cnt = 0;
	try {
		cnt = guiProcess(&points, input);
	}
	catch (std::exception e) {
		...
	} {
	}
	ui.lineEdit->setText(QString::number(cnt));//界面反饋交點總數
	return cnt;
}
  • IntersectGUI類中的vector<std::pair<double, double>> points屬性存放求解的交點。在調用guiProcess前,清空當前points中的元素,傳入points的引用。將figures中的數據轉換爲接口對應類型的數據傳入。guiProcess()會將求解的交點寫入points中,並返回交點數。
  • guiProcess()代碼以下:
int guiProcess(std::vector<std::pair<double, double>>* points, 
	std::string msg) {
	try {
		vector<Line> lVec;
		vector<Circle> cVec;
        //將msg中的幾何信息解析並存入lVec和cVec中
		istringstream input(msg);
		fileExcHandler(input, lVec, cVec);
        //計算交點
		set<Point> pointSet = getAllIntersect(lVec, cVec);
        //將交點信息寫入points中
		for (auto iter = pointSet.begin(); iter != pointSet.end(); iter++) {
			Point p = (Point)* iter;
			points->push_back(make_pair(p.getX(), p.getY()));
		}
        //返回交點總數
		return (int)pointSet.size();
	} catch (fileException& fileError) {
		cout << fileError.what() << endl;
	}
	catch (lineException& lineError) {
		cout << lineError.what() << endl;
	}
	catch (CircleException& circleError) {
		cout << circleError.what() << endl;
	}
	return -1;
}

(4)對接相關功能實現

  • 未點擊「求解交點」按鈕時,繪製的幾何圖形。

1584982017704

  • 點擊「求解交點」按鈕後,再繪製

GUI3

  • 繪製交點並返回交點數。

Step 5 鬆耦合測試

  • 和咱們(A組)進行鬆耦合對接的是1602108817373439結對編程(B)小組
  • 鬆耦合開發過程以下:
    • dll導出,即新建dll導出項目,在pch.h 文件中導入頭文件,並在每個*.cpp文件中include "pch.h文件,以後生成dll便可
    • 以後與GUI和cmd程序分別對接,其中有一個特別須要注意的問題,也是咱們最初對接出現的問題,就是dll導出的編譯器須要和導入文件的編譯器相同,主要是和GUI對應的編譯器相同。不然就會出現沒法導入的狀況,咱們兩個小組在都採用的VS IDE上的release x64版本的編譯器以後,導出的dll能夠互相調用。
  • 下面展現鬆耦合實際測試(測試展現中兩個組的dll文件名不一樣,但以後同一命名爲calcInterface上傳到GitHubdev-combine分支中)

cmd鬆耦合

  • cmd鬆耦合測試主要代碼
typedef int (*pGui)(vector<pair<double,double>>* points, string a);
	typedef void (*pCmd)(int argc, char* argv[]);
  HMODULE hDLL = LoadLibrary(TEXT("calcInterface.dll"));
	vector<pair<double, double>>points;
	string tmp = "2\nL 1 1 0 1\nL 1 1 1 0\n";
	if (hDLL) {
		pGui guiProcess = (pGui)GetProcAddress(hDLL, "guiProcess");
		pCmd cmdProcess = (pCmd)GetProcAddress(hDLL, "cmdProcess");
		try {
			int ans1 = guiProcess(&points, tmp);
			for (int i = 0; i < points.size(); i++) {
				cout << points[i].first << " " << points[i].second << endl;
			}
			cout << ans1 << endl;
		}
		catch (exception t) {
			cout << t.what() << endl;
		}
		cmdProcess(argc, argv);
	}
  • A組dll導入B組cmd程序

cmdAB

  • B組dll導入A組cmd程序

cmdBA

GUI的鬆耦合

  • GUI導入dll主要函數以下
#pragma comment(lib,"calcInterface.lib")
_declspec(dllexport)  extern "C"  int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);
  • A組dll導入B組GUI程序

GUI_AB

  • B組dll導入A組GUI程序

GUI_BA

結對編程

  1. 描述結對的過程,提供兩人在討論的結對圖像資料(好比 Live Share 的截圖)。關於如何遠程進行結對參見做業最後的注意事項。(1')
  2. 看教科書和其它參考書,網站中關於結對編程的章節,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,說明結對編程的優勢和缺點。同時描述結對的每個人的優勢和缺點在哪裏(要列出至少三個優勢和一個缺點)。(5')

結對編程時間表

時間 事項
3.12晚 討論結對編程整體規劃
3.12-3.14 獨立進行需求分析、學習和設計的工做,輔以資源交流
3.14下午 討論代碼設計,肯定最終設計和實際結對編程的各類方法和規範
3.14晚-3.23上午 進行合做編程,完成全部功能及其測試
3.23-3.24 完成博客撰寫

遠程結對編程工具與經驗

  • liveShare進行遠程合做,其中左邊欄爲遠程桌面通訊欄,中間代碼區域爲我審覈step1編寫的測試樣例以及隊友核心代碼編程,右邊代碼區域爲隊友正在編寫step1代碼核心功能
    • 一個注意事項:在一次遠程liveShare結束以後,再開啓新鏈接,須要關閉會話再次開啓會話生成新的共享連接才能連上,否則會一直鏈接不上...(咱們第一個晚上鍊接不上簡直崩潰。)
  • GitHub上將結對隊友設置爲contributor,一塊兒貢獻整個項目
  • 騰訊會議,能夠進行屏幕共享和實時聊天,方便代碼複覈
  • 微信電話、語音和聊天,一塊兒共同制定每日計劃、完成每日總結,一同攻克難關
  • 部分截圖以下:

3liveShare

結對過程當中的優缺點

  • 參考了關於結對編程的介紹博客以後,總結結對編程的優缺點以下:
  • 優勢
    1. 一人編碼一人審覈,互相監督,提升了編程效率,減小了犯錯概率
    2. 兩人一塊兒開發、設計、編寫,能夠互相學習取長補短
    3. 在面對困難時,兩人一塊兒開發,能夠互相借鑑思路,加速問題的解決
  • 缺點
    1. 兩人合做須要有相對久的磨合期,並且磨合以後可否達成良好的合做也未可知
    2. 對於一下相對基礎的軟件開發,結對編程的意義可能不能體現

結對編程每個人的優缺點

  • 隊友
    • 優勢
      1. 學習能力強,負責此次GUI開發工做,在短期裏寫出了一個精美的GUI界面,並完成了所有功能
      2. 富有巧思編碼能力強,向量的計算引入、GUI的設計,都是隊友的功勞
      3. 作事認真負責並且耐心,追求完美,測試的編寫、readme和博客的寫做,隊友都作到盡善盡美
    • 缺點
      1. 作事情時常缺乏規劃,計劃的事兒有時候延期完成
    • 優勢
      1. 對項目能提出計劃,而且能按照計劃完成任務
      2. 擅於與人溝通,與GUI對接的API是我與另外一位同窗商議肯定
      3. 樂於學習新知識新技術,樂於嘗試liveShare、OpenCppCoverage、VS類圖等工具,輔助結對開發
    • 缺點
      1. 編程能力略弱,對於C++和VS編程平臺瞭解不夠

源代碼管理 - 警告徹底消除

3codeQualityAnalysis

相關文章
相關標籤/搜索