項目 | 內容 |
---|---|
這個做業屬於那個課程 | 班級博客 |
這個做業的要求在哪裏 | 做業要求 |
我在這個課程的目標是 | 學習軟件工程相關知識,鍛鍊軟件開發能力。 |
這個做業在哪一個具體方面幫我實現目標 | 完成團隊項目,體會結對編程。 |
做業正文 | 做業正文 |
在開始實現程序以前,在下述 PSP 表格記錄下你估計將在程序的各個模塊的開發上耗費的時間。(0.5' + 0.5')html
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | ||
·Estimate | 估計這個任務須要多少時間 | 30 | 30(閱讀具體要求,完成了博客框架) |
Development | 開發 | ||
·Analysis | 需求分析 (包括學習新技術) | 600 | 480(相關算法,QT/QcustomPlot學習) |
·Design Spec | 生成設計文檔 | 30 | 30 |
·Design Review | 設計複審 (和同事審覈設計文檔) | 10 | 10 |
·Coding Standard | 代碼規範 (爲目前的開發制定合適的規範) | 10 | 10 |
·Design | 具體設計 | 60 | 30(實際上沿用了上次的設計思路,增長附加圓部分,ui部分的設計則是摸索官方文檔前進的,未算入此中) |
·Coding | 具體編碼 | 480 | 240(QT+QcustomPlot比預想簡單不少) |
·Code Review | 代碼複審 | 60 | 60 |
·Test | 測試(自我測試,修改代碼,提交修改) | 60 | 90 |
Reporting | 報告 | ||
·Test Report | 測試報告 | 30 | 30 |
·Size Measurement | 計算工做量 | 30 | 20 |
·Postmortem & Process Improvement Plan | 過後總結, 並提出過程改進計劃 | 120 | 240(將寫文檔的部分包含於此處) |
合計 | 1520 | 1270 |
本次沿用了我在上次單人做業的參考架構(即《算法競賽入門經典:訓練指南》4.1.2 和 4.2.1小節》中關於計算幾何的內容),所以在簡單修改後便獲得了擴展計算交點功能的代碼。因爲兩人遠程合做的不便,剩餘的任務則是直接一分爲二,我來負責UI部分,另外一位同窗負責測試以及錯誤處理的部分。這部分表格記錄的是UI以及核心代碼計算部分的時間,相對預估減小了很多,由於在看完QT和QcustomPlot的相關資料後,發現實現起來並不複雜。c++
看教科書和其它資料中關於 Information Hiding,Interface Design,Loose Coupling 的章節,說明大家在結對編程中是如何利用這些方法對接口進行設計的。(5')git
事實上核心代碼行數在300行之內,每一個類也並沒有私有屬性,設計時只要按基本的信息隱藏和接口設計原則就能夠。
具體如第五部分UML類圖所示,因爲每一個類中的屬性與方法都是須要提供給外部交點計算函數的,所以均爲public(固然能夠把交點求解函數內置,將計算方法隱藏)。信息隱藏在此處的體現並不明顯。接口設計部分則分爲三類,一類將須要用到的運算符重載,一類提供運算方法對交點求解,以及和UI對接的接口,重點放在UI對接接口上,爲此咱們和合做小組進行了討論,並決定最終的接口設計。
至於鬆耦合部分,因爲核心代碼的每一個類僅有其固有的計算方法與屬性,實際狀況下對類中代碼進行修改也並不會影響到其餘類或是對代碼進行總體大幅度改動。github
對於更爲複雜的軟件工程項目,上述三種方法會有更大的發揮空間。算法
計算模塊接口的設計與實現過程。設計包括代碼如何組織,好比會有幾個類,幾個函數,他們之間關係如何,關鍵函數是否須要畫出流程圖?說明你的算法的關鍵(沒必要列出源代碼),以及獨到之處。(7')編程
與上次做業相似,本次做業裏只有三個類(點/向量、線、圓)及其公共經常使用函數以及三個求解函數(具體UML圖見下問),在類中只需對應屬性以及提供向量計算的相關函數,並在三個求解函數中對類的交點進行求解。微信
求解函數的關鍵代碼以下:架構
其中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) { } }
獨到之處在於:用計算幾何的方法,簡潔高效的解決了核心代碼的拓展部分。函數
閱讀有關 UML 的內容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。畫出 UML 圖顯示計算模塊部分各個實體之間的關係(畫一個圖便可)。(2’)
計算模塊接口部分的性能改進。記錄在改進計算模塊性能上所花費的時間,描述你改進的思路,並展現一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),並展現你程序中消耗最大的函數。(3')
性能改進方面依然沒有想出更優複雜度的算法,只可以進行一些細節上的優化。因爲C++的set是基於紅黑樹實現的,插入操做的複雜度爲O(logn),因此考慮了換成基於哈希表實現的unordered_set, 在哈希函數理想的狀況下,插入操做的複雜度爲O(1)。換了unordered_set以後性能確實有了提高,在1000條直線,400000個交點的狀況下,所用時間從9秒左右提高到了6秒左右。
http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
描述這些作法的優缺點,說明你是如何把它們融入結對做業中的。(5')
DBC經過(內建的或附加的)語言特性強制程序的前條件(pre- condition)、後條件(post-condition)、不變式(invariant)獲得保證,並從而使程序接口獲得進一步的明確。
其優勢在於:
(1)明確接口功能,對程序的預期行爲作檢查,便於調試、發現程序中的錯誤;
(2)明確接口用途,編寫者和使用者均可以獲得足夠的信息。
(3)便於代碼重用。
(4)檢測時間相對較短。
缺點在於:
(1)書寫過於複雜,消耗時間過長。
在面向對象課程中,曾使用並編寫過Java中的JML。在本次課程中,該點的體現主要在於核心代碼與UI交接接口的設計上。UI模塊只依賴於核心代碼的接口函數和容器便可完成繪製任務。
計算模塊部分單元測試展現。展現出項目部分單元測試代碼,並說明測試的函數,構造測試數據的思路。並將單元測試獲得的測試覆蓋率截圖,發表在博客中。要求整體覆蓋率到 90% 以上,不然單元測試部分視做無效。(6')
在構造測試數據時,兩兩組合,分爲L_L,R_R, S_S, L_R, L_S, L_C, R_S, R_C, S_C, C_C十種狀況。
首先把能想到的狀況進行了測試,好比直線相交,直線平行,射線端點,直線與圓相交,相切,相離等狀況,而後根據覆蓋率工具顯示的未覆蓋的分支,再添加對應的測試數據 。
測試覆蓋率:
在博客中詳細介紹每種異常的設計目標。每種異常都要選擇一個單元測試樣例發佈在博客中,並指明錯誤對應的場景。(5')
只設計了一個異常類,定義了五種異常類型。
WRONGTYPE: 輸入了L, R, S, C以外的不支持的類型。
WRONGFORMAT: 輸入了不符合規範的格式。
BADPOINT: 輸入的點的座標不在(-100000,100000)範圍內。
BADLINE: 輸入的直線的兩個端點重合。
BADCIRCLE: 輸入的圓的半徑r小於等於0。
界面模塊的詳細設計過程。在博客中詳細介紹界面模塊是如何設計的,並寫一些必要的代碼說明解釋實現過程。(5')
首先決定採用基於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加速,但我並未實驗)
界面模塊與計算模塊的對接。詳細地描述 UI 模塊的設計與兩個模塊的對接,並在博客中截圖實現的功能。(4')
因爲提早設計了接口
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;
所以在對接時只須要在四個按鈕對應槽中合理調用接口便可。
實現的功能:
支持從文件導入幾何對象的描述。
左上角File處能夠添加文件,並顯示在下方文本框中。
支持幾何對象的添加、刪除。
按下Add,Del按鈕會添加/刪除文本框中的集合對象
支持繪製現有幾何對象。
PaintGraph按鈕實現
支持求解現有幾何對象交點並繪製。
PaintPoint按鈕實現
座標系放縮,平移 (數量級10^-5 ~ 10^250)
描述結對的過程,提供兩人在討論的結對圖像資料(好比 Live Share 的截圖)。關於如何遠程進行結對參見做業最後的注意事項。(1')
看教科書和其它參考書,網站中關於結對編程的章節,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,說明結對編程的優勢和缺點。同時描述結對的每個人的優勢和缺點在哪裏(要列出至少三個優勢和一個缺點)。(5')
結對編程 | 我 | 結對夥伴 | |
---|---|---|---|
優勢 | 1.設計更好,Bug更少 2.在監督下能夠更有效率地完成工做 3.兩人共同解決困難問題,提升上限。 | 接受新知識較快;執行力較強;時間和任務管理較爲到位。 | 可以仔細分析軟件BUG;代碼書寫較快;思考較爲全面 |
缺點 | 1.不少實際項目不適合結對編程;2.和結對夥伴的磨合結果會直接影響結對編程結果。 | C++編碼基礎不夠紮實。 | 交流不夠主動 |
因爲和對方團隊(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可以完成任務需求。