- 在文章開頭給出教學班級和可克隆的 Github 項目地址(例子以下)。(1')
項目 | 內容 |
---|---|
這個做業屬於 | 2020春季計算機學院軟件工程(羅傑 任健) |
這個做業的要求是 | 結對項目做業 |
個人教學班級 | 006 |
結對隊友博客 | favourLZH |
結對項目的GitHub地址 | IntersectDualProj |
我對這項做業的目標是 | 提升我的程序開發素質,在與同伴的結對編程中不斷配合、成長, 一同寫出高性能程序 |
本次做業爲我的項目中求解交點的增量擴展,主要目的是爲了讓同窗們經過身體力行了解如下三點:軟件需求的變動,封裝,接口與鬆耦合,以及錯誤處理。html
- 在開始實現程序以前,在下述 PSP 表格記錄下你估計將在程序的各個模塊的開發上耗費的時間。(0.5')
14.在你實現完程序以後,在附錄提供的PSP表格記錄下你在程序的各個模塊上實際花費的時間。(0.5')node
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 |
此次結對編程,咱們採用隊友我的項目的程序做爲基礎程序,c++
而我在閱讀她的代碼過程當中,對代碼作出瞭如下幾點改進git
而且下載學習了覆蓋性測試的軟件OpenCppCoverage Plugingithub
4.計算模塊接口的設計與實現過程。設計包括代碼如何組織,好比會有幾個類,幾個函數,他們之間關係如何,關鍵函數是否須要畫出流程圖?說明你的算法的關鍵(沒必要列出源代碼),以及獨到之處。(7')算法
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); };
// '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; };
- 計算模塊部分單元測試展現。展現出項目部分單元測試代碼,並說明測試的函數,構造測試數據的思路。並將單元測試獲得的測試覆蓋率截圖,發表在博客中。要求整體覆蓋率到 90% 以上,不然單元測試部分視做無效。(6')
在網上查找,VS的單元測試覆蓋率可能須要旗艦版才能完成,因此目前沒有得出單元測試的結果,可是咱們本身的單元測試編寫的有層次有條理,咱們對單元測試有100%覆蓋的信心shell
總體測試架構數據庫
對於每一個類的每個函數都進行了細密的測試編程
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)); }
- 看教科書和其它資料中關於 Information Hiding,Interface Design,Loose Coupling 的章節,說明大家在結對編程中是如何利用這些方法對接口進行設計的。(5')
針對Information hiding原則(參考),咱們對step1中的程序作了以下改進:canvas
#define EPS (1e-8)
這樣在修改具體精度時,只用一處修改便可private
,在構造時即肯定內部屬性,以後只能讀取,不能修改Interface Design
Calculator
類的設計中,咱們充分解析了交點計算過程當中的關鍵計算流程,設計出如下7個分層次的計算方法,分別對點在線上、點在圓內、平行判斷、交點計算(線與線、線與圓、圓與圓、彙總計算)進行各模塊的編寫// 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);
// IOinterface.h IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points, std::string msg); IMPORT_DLL void cmdProcess(int argc, char* argv[]);
5.閱讀有關 UML 的內容:https://en.wikipedia.org/wiki/Unified_Modeling_Language ,畫出 UML 圖顯示計算模塊部分各個實體之間的關係(畫一個圖便可)。(2’)
6.計算模塊接口部分的性能改進。記錄在改進計算模塊性能上所花費的時間,描述你改進的思路,並展現一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),並展現你程序中消耗最大的函數。(3')
因爲此次做業主要是有上次做業的基礎的,因此總體性能改進相比起上次「女媧補天」式的提高,此次只是小修小改。記錄幾個修改:
最終性能分析圖片以下
7.看 Design by Contract,Code Contract 的內容:
http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx描述這些作法的優缺點,說明你是如何把它們融入結對做業中的。(5')
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.
// IOinterface.h IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points, std::string msg); IMPORT_DLL void cmdProcess(int argc, char* argv[]);
注意事項
當前階段,題目中描述了若干對於輸入的保證,規定了合法輸入的含義。在後續的異常處理中,全部關於輸入的保證均會被去除
- 計算模塊部分異常處理說明。在博客中詳細介紹每種異常的設計目標。每種異常都要選擇一個單元測試樣例發佈在博客中,並指明錯誤對應的場景。(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
- 界面模塊的詳細設計過程。在博客中詳細介紹界面模塊是如何設計的,並寫一些必要的代碼說明解釋實現過程。(5')
此次的GUI咱們是用Qt實現的。Qt的良好封裝機制使得Qt的模塊化程度很是高,可重用性較好,對於用戶開發來講比較方便。 Qt提供了一種稱爲signals/slots的安全類型來替代 callback,使得各個元件之間的協同工做變得十分簡單。
爲了方便用戶操做、減小用戶記憶負擔、減小用戶錯誤信息,咱們的設計作了以下改良:
界面使用注意點:
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(); } }
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; }
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); } }
- 界面模塊與計算模塊的對接。詳細地描述 UI 模塊的設計與兩個模塊的對接,並在博客中截圖實現的功能。(4')
#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 */
#pragma comment(lib,"calcInterface.lib") _declspec(dllexport) extern "C" int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);
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; }
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; }
pch.h
文件中導入頭文件,並在每個*.cpp
文件中include "pch.h
文件,以後生成dll便可dev-combine
分支中)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); }
#pragma comment(lib,"calcInterface.lib") _declspec(dllexport) extern "C" int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);
- 描述結對的過程,提供兩人在討論的結對圖像資料(好比 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 | 完成博客撰寫 |