- 在文章開頭給出教學班級和可克隆的 Github 項目位址(例子如下)。(1')
項目 | 内容 |
---|---|
這個作業屬于 | 2020春季計算機學院軟體工程(羅傑 任健) |
這個作業的要求是 | 結對項目作業 |
我的教學班級 | 006 |
結對隊友部落格 | favourLZH |
結對項目的GitHub位址 | IntersectDualProj |
我對這項作業的目标是 | 提高個人程式開發素質,在與同伴的結對程式設計中不斷配合、成長, 一同寫出高性能程式 |
作業要求
本次作業為個人項目中求解交點的增量擴充,主要目的是為了讓同學們通過身體力行了解以下三點:軟體需求的變更,封裝,接口與松耦合,以及錯誤處理。
-
軟體需求的變更
在個人項目的基礎上額外支援兩個幾何對象:線段和射線
-
封裝
把求解交點的功能能獨立出來,成為一個獨立的子產品(class library, DLL,或其它),這樣指令行和 GUI 的程式都能使用同一份代碼
我們稱之為計算核心“core 子產品”,這個子產品至少可以可被用于以下地方:
- 指令行測試程式中用于提供核心功能;
- 在單元測試架構下驗證子產品正确性;
- 與資料可視化部分結合使用提供核心功能
-
接口
我們知道軟體并非隻有計算核心,實際的軟體是傳遞給最終使用者的軟體,除了計算核心外,還需要有一定的界面和必要的輔助功能。那麼這個 core 子產品和使用它的其他子產品之間需要如何進行交流呢?答案是通過一定的 API(Application Programming Interface),通常也可稱作接口。通過明确子產品之間的接口,則子產品 A 對于子產品 B 的認知則隻有 B 提供的接口,而與 B 的實作無關。
-
松耦合
系統的元件對于其它的元件隻具有很少的認知或者沒有認知
-
錯誤與異常處理
對于一個真實的軟體來說,來自使用者的非法輸入是難以避免的。如果程式的輸入出現了錯誤,比如指令行參數是其他字元,或者有多個無意義參數等等,要怎樣才能告訴函數的調用者“你錯了”?又該如何友善地告訴函數的調用者“哪裡錯了”?在這種時候,我們一般會定義各種異常(Exception),讓子產品在碰到各種異常情況的時候,能給調用者充分的錯誤資訊。
在本次作業中,将要求同學們進行錯誤處理,體會一個真實軟體在錯誤處理上的相關考慮。
作業完成功能劃分
- 助教小哥哥小姐姐非常友善的為我們的代碼送出規定了幾個step(這樣也友善稽核~
- 是以我們的軟體開發各步驟的順序和内容就确定如下了
- 基本功能實作及測試:單元測試、并要求得到90%以上覆寫率
- 拓展功能後封裝:看書相關内容,并說明指令行、GUI接口設計的理由,并構造測試
- 支援異常處理:異常處理設計及其測試
- 增加界面子產品:開發UI子產品
- 松耦合:不同小組間交換核心子產品與界面子產品測試
PSP
14.在你實作完程式之後,在附錄提供的PSP表格記錄下你在程式的各個子產品上實際花費的時間。(0.5')
- 在開始實作程式之前,在下述 PSP 表格記錄下你估計将在程式的各個子產品的開發上耗費的時間。(0.5')
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | ||
- Estimate | - 估計這個任務需要多少時間 | 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 | |
- Coding Standard | - 代碼規範 (為目前的開發制定合适的規範) | 15 | |
- Design | - 具體設計 | 1. 15 3. 20 4. 45 總計:140 | 3. 30 總計:150 |
- Coding | - 具體編碼 | 1. 20 3. 60 4. 90 總計:230 | 1. 90 2. 75 3. 75 總計:390 |
- Code Review | - 代碼複審 | 60 上述5步驟各10min | |
- Test | - 測試(自我測試,修改代碼,送出修改) | 1. 40 2. 60 | 1. 120 3. 120 總計:435 |
Reporting | 報告 | ||
- Test Report | - 測試報告 | ||
- Size Measurement | - 計算工作量 | ||
- Postmortem & Process Improvement Plan | - 事後總結, 并提出過程改進計劃 | ||
合計 | 940 | 1495 |
- 按照作業要求實作的功能,開發順序如下
Step 1 基本功能實作及測試
需求分析與學習
- 這次結對程式設計,我們采用隊友個人項目的程式作為基礎程式,
- 原因是,上次她用了更多的時間去編寫和測試,完成了附加題,代碼完整度和性能都應該比我的更好,是以采用
- 而我在閱讀她的代碼過程中,對代碼做出了以下幾點改進
- 計算類、圖形類、基礎函數類分離
- 在程式中加入一些TODO标記:(1)指令行錯誤處理;(2)直線boundary以融入線段和射線;(3)運作時間分析;(4)更換部分資料結構以優化性能
- 并且下載下傳學習了覆寫性測試的軟體OpenCppCoverage Plugin
設計
4.計算子產品接口的設計與實作過程。設計包括代碼如何組織,比如會有幾個類,幾個函數,他們之間關系如何,關鍵函數是否需要畫出流程圖?說明你的算法的關鍵(不必列出源代碼),以及獨到之處。(7')
- 一共6個類(除開def,内有通用函數,比如double精度比較)

- 類與類之間關系
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類的設計沿用上一次作業設計
測試
- 計算子產品部分單元測試展示。展示出項目部分單元測試代碼,并說明測試的函數,構造測試資料的思路。并将單元測試得到的測試覆寫率截圖,發表在部落格中。要求總體覆寫率到 90% 以上,否則單元測試部分視作無效。(6')
- 由于openCppCoverage在VS中下載下傳緩慢,可以選擇在marketplace中下載下傳,目前還沒有發現openCppCoverage插件可以用于檢測單元測試覆寫率的功能,隻能用它來檢測整體代碼覆寫率,得出覆寫率如下
- 在網上查找,VS的單元測試覆寫率可能需要旗艦版才能完成,是以目前沒有得出單元測試的結果,但是我們自己的單元測試編寫的有層次有條理,我們對單元測試有100%覆寫的信心
- 整體測試架構
- 根據設計的幾大類,采用bottom-up的方式進行測試程式的編寫
軟體工程作業——結對程式設計實踐
- 根據設計的幾大類,采用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);
}
};
- 對于求交點的重要複雜部分,我們的測試也做的更細緻
- 比如直線相交的測試,我們對于幾種直線間的情況比如相交、平行、重疊,以及三種直線的情況(線段、射線、直線)都做了非常細緻的測試
// 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 拓展功能後封裝
接口設計
- 看教科書和其它資料中關于 Information Hiding,Interface Design,Loose Coupling 的章節,說明你們在結對程式設計中是如何利用這些方法對接口進行設計的。(5')
- 針對Information hiding原則(參考),我們對step1中的程式做了如下改進:
- 外部無法通路原則,所有容器通路改為疊代器通路,而不是數組通路
- 人機互動邏輯,集中到一個單獨的類、包或者子系統中,這為我們的UI接口設計确定了理論基礎
- 具體數字常量化/宏,我們對于精度的設定就采用了宏定義的方式
這樣在修改具體精度時,隻用一處修改即可#define EPS (1e-8)
- 資訊通路限制,我們在對點、線、圓等對象有嚴格的通路限制,所有的内部屬性為
,在構造時即确定内部屬性,之後隻能讀取,不能修改private
- Interface Design
- 我們的接口設計遵從高内聚,低耦合的設計思路:單一子產品隻實作一個功能,子產品和子產品之間的依賴盡可能小甚至沒有,子產品間的接口也是考慮全局而精簡設計
- 比如在
類的設計中,我們充分解析了交點計算過程中的關鍵計算流程,設計出以下7個分層次的計算方法,分别對點線上上、點在圓内、平行判斷、交點計算(線與線、線與圓、圓與圓、彙總計算)進行各子產品的編寫Calculator
- 使得整體複雜的計算過程充分解耦,降低計算過程的代碼編寫複雜度,減少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,這次附加題是對這個原則的一個理論實踐
- 學習之後,總結為如下幾點:
- 接口和抽象類的正确使用
- 增加中間層
- 減少對象之間的依賴
- 這個部分和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類計算交點并傳回給使用者,并處理上述過程的異常抛出。
軟體工程作業——結對程式設計實踐
- 其中cmd和GUI接口部分使用函數編寫,不在類圖中。其主要根據UI指令,構造并維護各種圖形集合,調用Calculaor類計算交點并傳回給使用者,并處理上述過程的異常抛出。
性能分析
6.計算子產品接口部分的性能改進。記錄在改進計算子產品性能上所花費的時間,描述你改進的思路,并展示一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),并展示你程式中消耗最大的函數。(3')
- 由于這次作業主要是有上次作業的基礎的,是以整體性能改進相比起上次“女娲補天”式的提升,這次隻是小修小改。記錄幾個修改:
- 直線平行判斷優化,我們将直線的slope屬性存儲在直線class中,判斷平行時直接調用判斷相等,而不用多次計算斜率
- 部分小函數宏定義優化,對于double相等以及大小比較的小型但是多次調用的函數,我們将其設定為宏定義,減少函數調用的時間消耗
- 以及一些小的修改,根據函數調用的局部性原理,将分支語句中更多調用的語句提前之類
- 學習了助教發的sweep line提示文檔,對比其中内容和自行編寫的代碼,由于sweep line似乎隻能優化線段的内容,作業時間有限,也就沒有做這個部分的優化。
- 最終性能分析圖檔如下
- 占用性能最多的函數還是求直線交點函數,該函數中交點集合的插入部分是占用CPU最多的資料操作語句
軟體工程作業——結對程式設計實踐
- 占用性能最多的函數還是求直線交點函數,該函數中交點集合的插入部分是占用CPU最多的資料操作語句
Design by contract & Code contract
7.看 Design by Contract,Code Contract 的内容:描述這些做法的優缺點,說明你是如何把它們融入結對作業中的。(5')http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
- 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?
- 以及參考Code Contracts for .NET的描述
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 支援異常處理
注意事項
目前階段,題目中描述了若幹對于輸入的保證,規定了合法輸入的含義。在後續的異常進行中,所有關于輸入的保證均會被去除
- 計算子產品部分異常處理說明。在部落格中詳細介紹每種異常的設計目标。每種異常都要選擇一個單元測試樣例釋出在部落格中,并指明錯誤對應的場景。(5')
異常處理設計
- 幾種異常
- 指令行輸入異常(參數、檔案名)
- 輸入檔案異常(輸入曲線不符合格式,輸入線段數目,“亂碼”輸入)
- 直線異常
- 曲線異常
異常處理測試
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
- 根據以上測試,得到異常處理測試結果
)
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
- 對于以上的樣例,分别寫了測試樣例,得到測試結果如下:
Step 4 增加界面子產品
- 界面子產品的詳細設計過程。在部落格中詳細介紹界面子產品是如何設計的,并寫一些必要的代碼說明解釋實作過程。(5')
這次的GUI我們是用Qt實作的。Qt的良好封裝機制使得Qt的子產品化程度非常高,可重用性較好,對于使用者開發來說比較友善。 Qt提供了一種稱為signals/slots的安全類型來替代 callback,使得各個元件之間的協同工作變得十分簡單。
(1)界面設計
- 我們先通過內建在 Qt Creator 中的 Qt Designer 對窗體進行可視化設計。
- 最終界面如下:
- 為了友善使用者操作、減少使用者記憶負擔、減少使用者錯誤資訊,我們的設計做了如下改良:
- 關檔案導入:我們實作的“..."按鈕可直接浏覽檔案夾選擇檔案,無需手動輸入路徑。
- 添加操作:由于幾何對象的參數要求為在(-100000,100000)之間的不含前導零的整數,我們設定了SpinBox,其限制了正确的參數形式,避免手動輸入帶來的參數格式錯誤問題。對于幾何體的類型,我們實作了下拉選框。
- 删除操作:為了減少使用者的記憶負擔,我們在listWidget中實時呈現了目前幾何圖形。為了避免删除操作的錯誤資訊和比較過程的繁瑣,我們設定了複選框。使用者選擇清單中的幾何圖形,點選“删除幾何圖形”即可。這樣既友善了使用者,也減少了我們異常處理的負擔。
- 求解交點:點選“求解交點”按鈕,則會計算出所有交點的左邊,并在下方顯示求解的交點數。
- 繪制:在使用者完成全部對目前幾何圖形的修改和求解交點後,點選“繪制幾何圖形和交點”按鈕,我們将統一更新畫布。這樣避免了使用者頻繁的階段性操作帶來的無用計算。
- 界面使用注意點:
- 在點選求解交點後,方有交點的資料,才能繪制出交點。
- 在點選繪制幾何圖形和交點後,才會更新目前幾何圖形的繪制。
(2)主要代碼說明
- Qt使用了信号和槽來代替回調函數,實作對象間的通信。當一個特定的事件發生時,信号會被發送出去。Qt的窗體部件(widget)擁有衆多預先定義好的信号。槽,則是對一個特定的信号進行的回報。我們這次的實作主要是建立窗體部件(widget)的子類并添加自定義槽,以便對感興趣的信号進行處理。
- 我們實作的類中,有兩個重要的屬性
和vector<string> figures
,分别存放目前幾何對象和目前交點。vector<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()方法,隻能繪制兩點間的線段。是以在實作繪制直線和射線的時候,我們計算了目前線與畫布邊界的交點。代碼簡單,但是很長,在這裡就不展示了。
- 界面子產品與計算子產品的對接。詳細地描述 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類中的
屬性存放求解的交點。在調用guiProcess前,清空目前points中的元素,傳入points的引用。将figures中的資料轉換為接口對應類型的資料傳入。guiProcess()會将求解的交點寫入points中,并傳回交點數。vector<std::pair<double, double>> 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)對接相關功能實作
- 未點選“求解交點”按鈕時,繪制的幾何圖形。
- 點選“求解交點”按鈕後,再繪制
- 繪制交點并傳回交點數。
Step 5 松耦合測試
- 和我們(A組)進行松耦合對接的是16021088 和 17373439的結對程式設計(B)小組
- 松耦合開發過程如下:
- dll導出,即建立dll導出項目,在
檔案中導入頭檔案,并在每一個pch.h
檔案中*.cpp
檔案,之後生成dll即可include "pch.h
- 之後與GUI和cmd程式分别對接,其中有一個特别需要注意的問題,也是我們最初對接出現的問題,就是dll導出的編譯器需要和導入檔案的編譯器相同,主要是和GUI對應的編譯器相同。否則就會出現無法導入的情況,我們兩個小組在都采用的VS IDE上的release x64版本的編譯器之後,導出的dll可以互相調用。
- dll導出,即建立dll導出項目,在
- 下面展示松耦合實際測試(測試展示中兩個組的dll檔案名不同,但之後同一命名為calcInterface上傳到GitHub
分支中)dev-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程式
- B組dll導入A組cmd程式
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程式
- B組dll導入A組GUI程式
結對程式設計
- 描述結對的過程,提供兩人在讨論的結對圖像資料(比如 Live Share 的截圖)。關于如何遠端進行結對參見作業最後的注意事項。(1')
- 看教科書和其它參考書,網站中關于結對程式設計的章節,例如: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,一起貢獻整個項目
- 騰訊會議,可以進行螢幕共享和實時聊天,友善代碼複核
- 微信電話、語音和聊天,一起共同制定每日計劃、完成每日總結,一同攻克難關
- 部分截圖如下:
結對過程中的優缺點
- 參考了關于結對程式設計的介紹部落格之後,總結結對程式設計的優缺點如下:
- 優點
- 一人編碼一人稽核,互相監督,提高了程式設計效率,減少了犯錯幾率
- 兩人一起開發、設計、編寫,可以互相學習取長補短
- 在面對困難時,兩人一起開發,可以互相借鑒思路,加速問題的解決
- 缺點
- 兩人合作需要有相對久的磨合期,而且磨合之後能否達成良好的合作也未可知
- 對于一下相對基礎的軟體開發,結對程式設計的意義可能不能展現
結對程式設計每一個人的優缺點
- 隊友
-
- 學習能力強,負責這次GUI開發工作,在短時間裡寫出了一個精美的GUI界面,并完成了全部功能
- 富有巧思編碼能力強,向量的計算引入、GUI的設計,都是隊友的功勞
- 做事認真負責而且耐心,追求完美,測試的編寫、readme和部落格的寫作,隊友都做到盡善盡美
-
- 做事情時常缺少規劃,計劃的事兒有時候延期完成
-
- 我
-
- 對項目能提出計劃,并且能按照計劃完成任務
- 擅于與人溝通,與GUI對接的API是我與另一位同學商議确定
- 樂于學習新知識新技術,樂于嘗試liveShare、OpenCppCoverage、VS類圖等工具,輔助結對開發
-
- 程式設計能力略弱,對于C++和VS程式設計平台了解不夠
-