軟件工程結對項目

軟件工程結對博客

1、教學班級和github地址

項目 內容
這個做業屬於哪一個課程 2020計算機學院軟件工程(羅傑 任健)
這個做業的要求在哪裏 結隊項目做業
教學班級 006
項目地址 結隊項目做業

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

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 100 100
· Estimate · 估計這個任務須要多少時間 2500 3000
Development 開發 2000 2000
· Analysis · 需求分析 (包括學習新技術) 500 1000
· Design Spec · 生成設計文檔 300 300
· Design Review · 設計複審 (和同事審覈設計文檔) 100 100
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 30 35
· Design · 具體設計 500 800
· Coding · 具體編碼 1000 1000
· Code Review · 代碼複審 100 100
· Test · 測試(自我測試,修改代碼,提交修改) 200 200
Reporting 報告
· Test Report · 測試報告 100 100
· Size Measurement · 計算工做量 100 100
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 50 50
合計 2530 2835

3、看教科書和其它資料中關於 Information Hiding,Interface Design,Loose Coupling 的章節,說明大家在結對編程中是如何利用這些方法對接口進行設計的。(5')

Information Hiding:

信息隱藏的設計意圖爲將數據進行封裝,不讓外部訪問,保證安全。本次做業將不一樣的數據結構封裝成爲不一樣的類,可是對於交點的求解未封裝進類中而是採用了全局函數的形式,具體細節在後文介紹。html

Interface Design:

不一樣的類只暴露相應的接口,接口的設計是爲了讓可以很好的鉚接程序的各個部分,所謂的面向接口編程就是隻關注接口的參數和返回值,至於接口內部實現當作黑盒並不關心。咱們本次結對做業的主要接口爲5個全局變量,前端與後端的主要交互手段爲5個存儲着交點、直線、線段、射線、圓的容器,前端負責繪圖,後端負責計算,以此達到一個較好的鉚接。原本想利用重載,可是後來又從新編寫了不一樣的函數名,見名知義,對傳入參數的不一樣,使用不一樣的計算函數。具體設計會在後文介紹。前端

Loose Coupling:

咱們本次做業採用了先後端分離的模式,後端負責計算數據,前端負責展現,可是因爲未和其餘隊伍造成一個良好的接口匹配,因此沒法向外部體現這一點。可是在結對過程當中咱們是分工明確的,後端編寫者不知道前端編寫者具體的邏輯,只知道提供接口便可,前端編寫者亦然。node

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

本次做業一共使用了6個類,其中1個類爲UI類,剩下5個類爲圖形類,分別爲點、直線、射線、線段、圓。git

函數一共分爲5大類,函數的交互方法以下圖(箭頭爲調用關係):
github

算法關鍵:算法

咱們算法的關鍵在於,計算交點的方法時間複雜度爲:\(O(n^2)\),利用map並重載運算符保證點的惟一性,最後使用先後端分離的方法,前端接受請求,後端處理請求並返回給前端相應數據最終實現需求。編程

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

實體關係:
後端

其中帶有箭頭之間的實體表明具備交互,如圖所示,前端想要獲取後端數據必須經過後端提供的接口。安全

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

在2000條測試數據下:read_file()函數爲總的計算花費,而originGeo()函數和show()函數分別是圖形的添加和圖形的繪製,本次做業的性能瓶頸爲圖像的繪製,由於採用第三方庫的緣故,因此很難進行更進一步的優化。
微信

能夠看到圖像繪製佔用了整體CPU時間的31.8%

7、看 Design by Contract,Code Contract 的內容,描述這些作法的優缺點,說明你是如何把它們融入結對做業中的。(5')

按合同編程的優勢:

  • 有較強的規則,不易出現工做重疊或者接口不匹配的狀況
  • 可以在開發之初就計算出工做的大體時間,以及工做內容的複雜度

按合同編程的缺點:

  • 容易限制思惟,按照固定的方式進行開發容易被侷限
  • 有些小工程也許構建合同就顯得性價比不高

本次編程雖然沒有將合同文本化,可是仍是作了較多的交流好比固定接口,如下是微信交流確認接口的界面

後來的接口交流補充文件截圖:

8、計算模塊部分單元測試展現。展現出項目部分單元測試代碼,並說明測試的函數,構造測試數據的思路。並將單元測試獲得的測試覆蓋率截圖,發表在博客中。要求整體覆蓋率到 90% 以上,不然單元測試部分視做無效。(6')

測試數據爲2000條直線,測試結果以下:

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

異常處理模塊咱們也採用了先後端分離的模式,分爲幾種情形:

  • 當第一次文件讀入存在錯誤時,不繪製UI界面,直接結束程序

輸入文件爲

效果如圖所示:

  • 當從UI界面提交文件格式錯誤時,輸出「wrong input」,

輸入樣例同上

輸出爲:

  • 當輸入實際圖形少於給出的數據總數時,測試以下:

輸入數據:

輸出結果:

  • 當輸入文件找不到時,報錯以下:

輸出結果:

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

咱們的界面設計使用了QT庫,仍是用了QCustomplot這樣一個第三方庫,部分代碼借鑑了Qcustomplot的樣例模板,界面佈局以下圖:

大部分功能使用了按鍵與槽函數的結合,代碼舉例以下:

//可拖動
customPlot->setInteraction(QCP::iRangeDrag, true);

//可縮放
customPlot->setInteraction(QCP::iRangeZoom, true);

//曲線可選
customPlot->setInteraction(QCP::iSelectPlottables, true);

//曲線 ctrl 多選
customPlot->setInteraction(QCP::iMultiSelect, true);

//座標軸可選
customPlot->setInteraction(QCP::iSelectAxes, true);
//圖例可選
customPlot->setInteraction(QCP::iSelectLegend, true);
// 選中軸時上下軸一塊兒被選中
connect(customPlot, SIGNAL(selectionChangedByUser()), this,SLOT(selectionChanged()));
// 選中軸時,鼠標拖動智能移動一邊的軸,滾輪也是同樣
connect(customPlot, SIGNAL(mousePress(QMouseEvent*)), this,SLOT(mousePress()));
connect(customPlot, SIGNAL(mouseWheel(QWheelEvent*)), this,SLOT(mouseWheel()));

//多選框切換時,直線輸入切換成圓輸入
connect(ui->comboBox,SIGNAL(activated(int)),this,SLOT(switchGeo()));

connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(addGeo()));

//文件輸入
connect(ui->pushButton_2, SIGNAL(clicked()), this,SLOT(fileInput()));

11、界面模塊與計算模塊的對接。詳細地描述 UI 模塊的設計與兩個模塊的對接,並在博客中截圖實現的功能。(4')

前端後端對接使用了5個容器:

map <node, int>* nodes;
vector<line>* lines;
vector<rays>* rayss;
vector<lise>* lises;
vector<Cycle>* cycles;

5個指針指向後端的全局變量,獲得計算出的交點、直線、圓的全部信息,繪製圖形的代碼以下:

void MainWindow::originGeo() {
	
	//畫交點
	QVector<double> nodex;
	QVector<double> nodey;
	for (map <node, int>::iterator iter = (*nodes).begin(); iter != (*nodes).end(); iter++) {
		node temp = iter->first;
		double x = temp.getX();
		double y = temp.getY();
		nodex.push_back(x);
		nodey.push_back(y);
	}
	QCPGraph* graph = customPlot->addGraph();
	graph->setData(nodex, nodey);
	graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ScatterShape::ssDisc, 5));
	QPen graphPen;
	graphPen.setColor(QColor(255,255,255));
	graphPen.setWidthF(rand() / (double)RAND_MAX * 2 + 1);
	graph->setPen(graphPen);
	graph->setName("交點集合");

	int cnt = (*nodes).size();
	ui->lineEdit_8->setText(QString::number(cnt));

	if (cnt >= 20) {
		customPlot->legend->setVisible(true);
	}

	//----------------------------------------
	//畫直線
	int linesize = (*lines).size();
	for (int i = 0; i < linesize; i++) {
		line l = (*lines)[i];
		int x1 = l.getNode1().getX();
		int y1 = l.getNode1().getY();
		int x2 = l.getNode2().getX();
		int y2 = l.getNode2().getY();
		if (l.getExitK()) {
			double k = l.getK();
			double b = (-1) * l.getC() / l.getB();
			for (int i = 0; i < 10000; i++) {
				(*valueY)[i] = k * (*indexX)[i] + b;
			}
			QCPGraph* graph = customPlot->addGraph();
			graph->setData(*indexX, *valueY);
		}
		else {
			//斜率不存在時不支持畫圖
			continue;
		}
		QPen graphPen;
		graphPen.setColor(QColor(rand() % 245 + 10, rand() % 245 + 10, rand() % 245 + 10));
		graphPen.setWidthF(rand() / (double)RAND_MAX * 2 + 1);
		customPlot->graph()->setPen(graphPen);
		//customPlot->graph()->setName("line(編號:" + QString::number(customPlot->graphCount()) + ")");
		const QString name = "L " + QString::number(x1) + " " + QString::number(y1) + " " + QString::number(x2) + " " + QString::number(y2);
		customPlot->graph()->setName(name);
	}

	//----------------------------------------
	//畫射線
	int raysize = (*rayss).size();
	for (int i = 0; i < raysize; i++) {
		rays ray = (*rayss)[i];
		int x1 = ray.getStart().getX();
		int y1 = ray.getStart().getY();
		int x2 = ray.getN().getX();
		int y2 = ray.getN().getY();
		if (ray.getExitK()) {
			double k = ray.getK();
			double b = (-1) * ray.getC() / ray.getB();
			QVector<double> fx;
			QVector<double> fy;
			for (int i = 0; i < 10000; i++) {
				(*valueY)[i] = k * (*indexX)[i] + b;
				node tempnode((*indexX)[i], (*valueY)[i]);
				if (ray.judge(tempnode)) {
					fx.push_back((*indexX)[i]);
					fy.push_back((*valueY)[i]);
				}
			}
			QCPGraph* graph = customPlot->addGraph();
			graph->setData(fx, fy);
		}
		else {
			//斜率不存在時不支持畫圖
			continue;
		}
		QPen graphPen;
		graphPen.setColor(QColor(rand() % 245 + 10, rand() % 245 + 10, rand() % 245 + 10));
		graphPen.setWidthF(rand() / (double)RAND_MAX * 2 + 1);
		customPlot->graph()->setPen(graphPen);
		const QString name = "R " + QString::number(x1) + " " + QString::number(y1) + " " + QString::number(x2) + " " + QString::number(y2);
		//customPlot->graph()->setName("ray(編號:" + QString::number(customPlot->graphCount()) + ")");
		customPlot->graph()->setName(name);
	}

	//----------------------------------------
	//畫線段
	int lisesize = (*lises).size();
	for (int i = 0; i < lisesize; i++) {
		lise ls = (*lises)[i];
		int x1 = ls.getNode1().getX();
		int y1 = ls.getNode1().getY();
		int x2 = ls.getNode2().getX();
		int y2 = ls.getNode2().getY();
		if (ls.getExitK()) {
			double k = ls.getK();
			double b = (-1) * ls.getC() / ls.getB();
			QVector<double> fx;
			QVector<double> fy;
			for (int i = 0; i < 10000; i++) {
				(*valueY)[i] = k * (*indexX)[i] + b;
				node tempnode((*indexX)[i], (*valueY)[i]);
				if (ls.judge(tempnode)) {
					fx.push_back((*indexX)[i]);
					fy.push_back((*valueY)[i]);
				}
			}
			QCPGraph* graph = customPlot->addGraph();
			graph->setData(fx, fy);
		}
		else {
			//斜率不存在時不支持畫圖
			continue;
		}
		QPen graphPen;
		graphPen.setColor(QColor(rand() % 245 + 10, rand() % 245 + 10, rand() % 245 + 10));
		graphPen.setWidthF(rand() / (double)RAND_MAX * 2 + 1);
		customPlot->graph()->setPen(graphPen);
		const QString name = "S " + QString::number(x1) + " " + QString::number(y1) + " " + QString::number(x2) + " " + QString::number(y2);
		customPlot->graph()->setName(name);
		
	}

	//----------------------------------------
	//畫圓
	int circlesize = (*cycles).size();
	valueY1 = new QVector<double>(10000);
	for (int i = 0; i < circlesize; i++) {
		Cycle c = (*cycles)[i];
		int x = c.getC().getX();
		int y = c.getC().getY();
		int r = c.getR();
		const QString name = "C " + QString::number(x) + " " + QString::number(y) + " " + QString::number(r);
		r = r * r;
		double temp = 0;
		double temp1 = 0;
		for (int i = 0; i < 10000; i++) {
			temp = ((*indexX)[i] - x) * ((*indexX)[i] - x);
			temp1 = sqrt(r - temp);
			(*valueY)[i] = y + temp1;
			(*valueY1)[i] = y - temp1;
		}
		//int cnt = customPlot->graphCount();


		customPlot->addGraph();
		customPlot->graph()->setData(*indexX, *valueY);
		customPlot->graph()->setName(name);
		customPlot->addGraph();
		customPlot->graph()->setData(*indexX, *valueY1);
		customPlot->graph()->setName(name);
	}
	
	customPlot->replot();
	return;
}

功能包括:

  • 能夠在圖像上直接選擇圖形進行刪除,刪除兩條直線後以下圖(交點個數隨之變化):

  • 右側欄能夠添加直線,並更新交點,操做以下圖:

  • 右側欄能夠讀取文件,文件內容在白色框中顯示,以下圖:

  • 圖像點擊後提示點擊所在的位置座標,以下圖

  • 圖像能夠進行伸縮和拉伸,單機軸能夠進行拖動,雙擊軸能夠進行修改軸名

12、描述結對的過程,提供兩人在討論的結對圖像資料(好比 Live Share 的截圖)。關於如何遠程進行結對參見做業最後的注意事項。(1')

結對過程當中咱們進行了詳細的分工,黎正宇負責後端邏輯,我負責前端UI設計。商量好接口後咱們進行了語音而後一塊兒編程。

十3、看教科書和其它參考書,網站中關於結對編程的章節,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,說明結對編程的優勢和缺點。同時描述結對的每個人的優勢和缺點在哪裏(要列出至少三個優勢和一個缺點)。(5')

結對編程的優勢:

  • 能夠很好的分工協做,兩我的分工並深刻本身的一小部分能夠很大的提升效率
  • 兩我的思惟的碰撞能夠產生出更好的設計
  • 結對編程能夠很好的鍛鍊溝通協做能力
  • 兩我的互相學習,分別提升了我的的編程能力

結對編程的缺點:

  • 兩我的作同一件事,溝通很差的狀況下容易衝突
  • 兩我的容易工做量分配不均,能力有高有低,工程付出程度不一樣。
相關文章
相關標籤/搜索