Author: 17373051 郭駿
3.28添加:4.計算子產品接口的設計與實作過程部分,PairCore實作的細節
項目 | 内容 |
---|---|
這個作業屬于哪個課程 | 2020春季計算機學院軟體工程(羅傑 任健) |
這個作業的要求在哪裡 | 結對項目作業 |
我在這個課程的目标是 | 學習軟體工程的開發知識,培養工程化開發能力 |
這個作業在哪個具體方面幫助我實作目标 | 通過實操掌握結對開發基礎 |
目錄
- 1.前言
- 2.PSP表格
- 3.接口設計思想
- 4.計算子產品接口的設計與實作過程
- 5.UML圖
- 6.性能改進
- 7.契約式設計
- 8.單元測試展示
- 9.異常處理說明
- 10.界面子產品設計
- 11.子產品對接
- 12.結對過程
- 13.結對程式設計
- 14.附加題:支援松耦合
- 後記:一點體會
- 教學班級:005
- 項目位址:https://github.com/abTaoTao/Pair_project_git
給定 N 個幾何圖形,詢問平面中有多少個點在至少 2 個給定的圖形上。
在此處先展示Code Quality Analysis的零警告圖檔。

PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 5 | |
· Estimate | · 估計這個任務需要多少時間 | ||
Development | 開發 | 390 | 570 |
· Analysis | · 需求分析 (包括學習新技術) | 60 | |
· Design Spec | · 生成設計文檔 | 20 | |
· Design Review | · 設計複審 (和同僚稽核設計文檔) | ||
· Coding Standard | · 代碼規範 (為目前的開發制定合适的規範) | ||
· Design | · 具體設計 | 120 | |
· Coding | · 具體編碼 | ||
· Code Review | · 代碼複審 | ||
· Test | · 測試(自我測試,修改代碼,送出修改) | 180 | |
Reporting | 報告 | 70 | 100 |
· Test Report | · 測試報告 | 30 | |
· Size Measurement | · 計算工作量 | 10 | |
· Postmortem & Process Improvement Plan | · 事後總結, 并提出過程改進計劃 | ||
合計 | 465 | 675 |
-
資訊隐藏
這條原則指導我們,應當将類的屬性私有化,通過通路函數來實作,并且使用接口來連接配接層與層。我們在設計的過程中,做到了将類的屬性全部私有化。接口的實作方面,我們使用類的部分公有函數作為接口,做到了類與類之間的互動。
-
接口設計
我們在設計過程中,通過Point—Line/Circle—Core三層類的結構環環相扣,每兩層之間使用有限的公有函數進行互動。Line和Circle有互相使用對方求交點的方法,能夠輕松的調用交點求解程式,Line也包含了線段、射線和直線,在外層寫程式時不用關心到底是哪兩種圖形在求交點。
-
松耦合
類與類、層與層之間有着較好的隔離,如果出現問題或者需要增加需求,隻需要修改一個類中的代碼,而不需要去修改其調用的其他類。如Line和Circle,雖然需要互相求交點,但是修改其中一個類的代碼,可以無需改動另外一個類,因為接口是完善的,類之間的耦合度也是低的。
在C++中,我們并沒有使用抽象類的特性來幫助我們建構接口,因為抽象類在作為函數參數時的支援并不夠友善。在此提到的“接口”,指的是每個類的公有方法。
我們的程式由Point類打底,代表解題過程中的點,可以是線段/射線的端點,也可以是圖形之間的交點。我們為其預設了比較函數和指派方法,作為程式的基礎。
然後是Line類和Circle類。Line類包含線段、射線和直線,作為基礎題部分的解題骨幹。Line類的核心方法是
Line::getIntersect(Line l)
,可以幫助我們輕松求出兩條線之間的交點。Circle類是圓類,用于附加題。該類有對直線和圓求交點的方法。
主類為PairCore。這個類中包含了指令行參數分析函數,輸入正則比對函數,以及求交點輸出到檔案的函數。有
PairCore::parser(int argc, char* argv[])
方法,用于處理指令行參數的輸入,解析方式是簡單的字元串相等的條件判斷。
PairCore::text_handle()
方法是用于從檔案中讀入資料,按行讀入,第一行是數字,之後的每一行采用正規表達式的方式進行讀取,如果格式與正規表達式不比對則傳回報錯。同時我們也需要對輸入範圍進行特判。
PairCore::getIntersectionCount()
是用于計算幾何圖形交點的函數,其中包含三個循環,分别用于計算線與線、線與圓、圓與圓的交點。
本次作業中的核心函數,也是和上次作業很不一樣的地方,就是判斷求出來的點是否是兩個圖形的交點。由于線段、射線的範圍有限制,是以我們需要判斷點是否線上上。我們有函數
Line::isOnline(Point p)
來判斷點是否線上上。判斷的方法是與線的兩個端點進行比較。
同時,我們需要判斷輸入的線是否重合,即便在同一條直線上,也有可能是沒有交點或者有一個交點。我們有函數
Line::relation(Line l)
來判斷兩條線的關系。如果兩條線在同一直線上,則我們會對兩條線的端點坐标進行判斷,進而能夠判斷出他們真實的交點個數。
算法的獨到之處在于,早早為Point類寫好了比較函數,将此後的許多位置關系判斷轉化為端點坐标的位置判斷,簡化了問題。
UML類圖如下所示。
我們使用VS的性能分析器對2000條線、500個圓的情況進行了分析,得到的圖如下所示:
可以看到,在
Line::getIntersect
這個方法上耗費的CPU達到32%,讓我們感到不可思議。我們進入代碼之後,檢視了語句的CPU使用率。
可以看到,語句大部分時間花在了對vector的建立、修改和傳回上。由于兩條線之間的交點隻能是1個或者0個,是以我們可以不用vector<Point>作為傳回值,而是用Point作為傳回值,并判斷該Point是否存在。修改之後的占比如圖所示。
我們在優化上大概耗費了20分鐘。
契約式設計要求設計者對軟體設計正式、準确、可驗證的接口規範,定義了先驗條件、後驗條件和不變式,這些規範稱為“契約”。
這種方法的優點:
- 通過規範化的注釋,能夠直接驗證程式正确性。
- 設計時着重功能而非具體實作,不用擔心具體的實作流程。
- 明确接口的功能之後,設計者和開發者都能夠得到足夠的資訊。
這種方法的缺點:
- 程式的正确性驗證有時代價會非常大,甚至可能無法檢錯。
- 正确而規範的設計十分困難,會出現滿足設計而不滿足功能的情況。
- 存在一些難以或無法用契約設計的功能。
在結對作業中,這種思想确實可以讓我們寫出正确性較為完備的程式,但是卻難以幫助我們對程式進行優化。同時,在從設計到實作的過程中,我們常常需要絞盡腦汁,甚至不得已去修改設計。因而,在結對的過程中,我們沒有過分依賴契約式設計,隻是做到了一般的設計—開發的流程。
部分單元測試代碼展示:
單元測試通過截圖:
單元測試覆寫率截圖:
我們構造測試資料的思路是:從一般到特殊,從簡單到複雜,從白箱到黑箱。
我們的測試代碼有題目的樣例代碼、最簡單情況的代碼、特殊情況(重合、平行、端點相交、射線相背等)的樣例設計,以及一些能夠觸發異常的樣例。此外,我們還使用随機資料生成器生成了一點随機且複雜的情況加入測試,用于檢測我們的考慮是否完備。
我們設計了13種異常,每種異常都有其對應的錯誤碼。錯誤碼和異常的對應關系在程式中有所展現,代碼如下:
錯誤代碼 | 錯誤資訊 | 測試樣例 |
---|---|---|
-1 | 線段之間存在重合 | 2 S 0 0 2 2 S 1 1 3 3 |
-2 | 線段和射線之間存在重合 | R 1 1 3 3 |
-3 | 射線之間存在重合 | |
-4 | 直線與某條線重合 | L 0 0 2 2 S -1 -1 -2 -2 |
-5 | 圓與圓重合 | C 0 0 2 |
-6 | 輸入的兩個交點重複 | 1 L 1 1 1 1 |
-7 | 坐标超出(-100000,100000)的範圍 | L 100000 1 1 1 |
-8 | 輸入圓的半徑不大于0 | C 0 0 -2 |
-9 | 分析圖形資訊時出錯 | asdfg |
-10 | 分析圖形個數時出錯 | L 1 1 2 2 |
-11 | 幾何圖形個數與輸入的數目不比對 | |
-12 | 多餘的不在末尾的換行符 | L 2 2 3 3 L 0 5 5 0 |
-13 | 指令行參數錯誤 | intersect.exe -p point.txt |
如果在指令行模式下發生異常,則程式會将錯誤資訊輸出到同目錄下的error.txt中。
界面子產品采用C#的Winform來開發。由于這是我們第一次使用C#,也是第一次開發GUI程式,在設計上難免會存在一些不足。
界面設計如圖所示,我們的界面子產品有兩個視窗。
第一個視窗(Form1)是輸入輸出圖形資訊的視窗。如果GUI使用不當,會有相應的錯誤提示。右邊的清單是現有的圖形資訊,可以通過滑鼠點選來進行删除。
從檔案導入之後不會直接開始繪制,而是将檔案中的點資訊加入到現有的點清單中,提供了相對高的靈活性和可操作性。
我們确實支援圖形的繪制,也支援圖形的添加和删除,但是這兩個過程在我們的界面中是分離的,即沒有做到像GeoGebra一樣能夠實時繪制圖形和交點。
第二個視窗(Form2)是我們的繪制結果視窗。該視窗使用Winform的Panel進行繪制,整個繪制過程為造輪子過程,即沒有成熟的坐标系子產品供我們使用,是以效果相對簡陋,且不支援縮放和平移。不過由于結對程式設計隻有短短的兩周,且中間經曆了很多别的作業,是以沒有在此處過于苛求。程式預設将所有的圖形都畫進視窗中,是以如果圖形的橫縱軸跨度較大,則觀感會欠佳。
輪子中的關鍵代碼是如何掌握好視窗的邊界位置。我們相當于要将所有圖形的坐标範圍(minx,miny)-(maxx,maxy)映射到我們的畫闆(0,0)-(720,720)上。映射過程的核心代碼如下:
float k, dx, dy;
if (maxx - minx < 1 && maxy - miny < 1)
{
k = 1;
dx = minx;
dy = miny;
}
else
{
if ((maxx - minx) > (maxy - miny))
{
k = maxx - minx;
dx = minx;
dy = miny - (maxx - maxy - minx + miny) / 2;
}
else
{
k = maxy - miny;
dx = minx - (-maxx + maxy + minx - miny) / 2;
dy = miny;
}
}
這裡的k是縮放比例,dx和dy是x軸和y軸的平移距離。對于每個點的坐标,我們隻需要進行如下變換:
x = (x0 - dx) * 720 / k, y = (y0 - dy) * 720 / k
即可。
dll為前端留下了兩個接口,分别用于GUI展示和指令行測試。
/*GUI展示
dll從同目錄下的lines.pair按格式讀入幾何圖形的資訊,
求解後将得到的點的坐标存入同目錄下的points.pair檔案。
函數的傳回值>=0,代表交點個數。<0則代表出現異常,傳回異常代碼。
*/
int solve();
/*指令行展示
dll從同目錄下的commands.pair按格式讀入指令行參數,按要求處理。
函數的傳回值>=0,代表交點個數。<0則代表出現異常,傳回異常代碼,不寫檔案。
報錯部分的代碼由前端完成,将錯誤資訊存在同目錄下error.txt中。
*/
int command();
可以看到這兩個接口的互動幾乎全部依賴于檔案和傳回值,沒有進行參數傳遞。
主要的原因在于,C#和C++的标準有諸多不同,比如C#不支援指針,char為兩個位元組長度,string類和C++的string類不能公用等。我們嘗試無論怎麼傳參數,都無法達到我們想要的效果,隻能傳一些簡單的數字。是以我們幹脆制定了檔案讀入的标準。
如果我們使用C++的GUI開發,可能不會出現這樣的問題,這是我們需要反思的一點。
結對過程主要通過QQ語音+Live Share的方式完成。由于網絡原因,常常出現Live Share斷線的情況,極大的影響了我們的工作效率。不過在我們的不懈努力之下,最終還是完成了本次作業。證據如下所示:
- 優點
- 程式設計過程互相監督,不會存在摸魚的情況,随時思考随時交流,思維更加集中
- 兩人共審同一份代碼,對代碼的品質和正确性都有更高的保證。
- 兩人一起工作,集思廣益,對于困難的問題可能會更快産出解答。
- 缺點
- 度過磨合期很困難,要互相習慣對方的程式設計速度、思維、風格。
- 對于“駕駛員”來說,寫代碼的“領航員”過度插手可能會帶來反效果。
結對人員 | 我 | 隊友 |
---|---|---|
思維較快,思考更迅速連貫 | 思考全面,單元測試完善 | |
難以習慣隊友的代碼風格、程式設計習慣等,磨合期較難受 | 節奏難以同步 |
我們選擇交換dll的團隊是(17373052, 17373053)團隊。
他們的dll設計與我們大緻相同,除了檔案的命名方式以外,其他大緻相同。
合并時遇到的問題有:
- 他們并非由GUI和command兩個函數構成,而是隻有一個函數
,由指令行參數進行區分。無論是否需要繪制,都會将點的資訊存在points.txt中。我們修改了我們的讀寫方式,以适配他們的核心子產品。run()
- 他們的異常處理傳回資訊和我們不一樣。他們的
函數隻定義了兩種異常:輸入異常和重合異常。我們為此修改了異常的傳回資訊。run()
經測試,指令行和GUI都可以正常使用。