BUAA軟體工程結對項目
小組成員:16005001,17373192
1、教學班級和項目位址
項目 | 内容 |
---|---|
這個作業屬于哪個課程 | 部落格園班級連接配接 |
這個作業的要求在哪裡 | 結對項目作業 |
我在這個課程的目标是 | 提高軟體開發能力、團隊協作能力 |
這個作業在哪個具體方面幫助我實作目标 | 感受結對開發 |
項目代碼 | https://github.com/monokuma-zhuo/Intersect |
教學班級 | 006 |
2、PSP表格
SP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | ||
· Estimate | · 估計這個任務需要多少時間 | 30 | |
Development | 開發 | ||
· Analysis | · 需求分析 (包括學習新技術) | 100 | 180 |
· Design Spec | · 生成設計文檔 | 20 | |
· Design Review | · 設計複審 (和同僚稽核設計文檔) | ||
· Coding Standard | · 代碼規範 (為目前的開發制定合适的規範) | 5 | |
· Design | · 具體設計 | 40 | 60 |
· Coding | · 具體編碼 | 200 | |
· Code Review | · 代碼複審 | ||
· Test | · 測試(自我測試,修改代碼,送出修改) | ||
Reporting | 報告 | ||
· Test Report | · 測試報告 | 10 | |
· Size Measurement | · 計算工作量 | ||
· Postmortem & Process Improvement Plan | · 事後總結, 并提出過程改進計劃 | ||
合計 | 535 | 735 |
3、看教科書和其它資料中關于 Information Hiding,Interface Design,Loose Coupling 的章節,說明你們在結對程式設計中是如何利用這些方法對接口進行設計的
接口設計時主要是減少接口之間的耦合度,確定每個接口都隻完成各自的一項功能,避免出現冗雜,進而實作松耦合。核心子產品和ui之間的接口設計就是這樣實作的,從ui程式的角度看我隻需要知道接口名字,拿到接口傳回的資料即可,而不需要關注接口内部是如何設計的。在類中沒有寫私有屬性,但是封裝成dll檔案後供ui使用本身也是一種資訊隐藏,因為ui程式并不能知道接口的源碼設計,隻通過接口名字調用即可。
4、計算子產品接口的設計與實作過程。設計包括代碼如何組織,比如會有幾個類,幾個函數,他們之間關系如何,關鍵函數是否需要畫出流程圖?說明你的算法的關鍵(不必列出源代碼),以及獨到之處
本次作業共有直線類,射線類,線段類,圓類,函數除了每個類中求交點的方法外,還寫了幾個供ui調用的接口函數。
幾何對象的表示
上次作業中,由于直線與圓無明顯的聯系,故我采用結構體來表示,但本次作業新增了射線與線段,射線與線段明顯與直線有聯系,可以看作是特殊的有界的直線,故采用繼承的方式。分别建立直線類與圓類,射線類與線段類繼承直線類。對于交點的存儲,本次作業中采用pair<double,double>的形式。
對于幾何對象,采用vector進行存儲,交點采用set進行存儲。
交點的求解
參考上次作業的求解方法,在本次作業中依舊使用類似的方法,在類中編寫與其他幾何對象計算交點的方法。
在計算交點之前,還需要判斷交點是否存在。判斷直線與直線是否平行的方法,依舊采取上次作業中提到的方法,比較$A1B2$與$A2B1$是否一緻。直線與圓則采取圓心到直線的距離與半徑的關系,圓與圓采取比較圓心距離與兩圓半徑之間的關系。線段與射線當作直線來考慮。考慮到double的精度問題,在判斷相等時采取$fabs(a-b)<1e-10$的方式進行判斷。
求解交點的方式依舊采取上次作業的方式,關于線段與射線,采取上次作業所述求解方法得到交點後,還需判斷交點是否線上段或射線上,具體做法為對求解出的點進行範圍判斷,同時還需要考慮到斜率不存在的情形。由于線段與射線與其他圖形求解得到的結果可能不存在,故在傳回值中增加标記位用來描述此解是否存在。
值得注意的是,線段與射線在共線的情況下依舊可能有解,這是與直線有着很大的差別,故需要對線段、射線的判斷平行函數進行重寫,維護一個全局變量用于存儲這個可能存在的解。
交點的去重
采用set進行去重,重寫了存儲交點的pair<double,double>的operator<的方法來保證去重的正确性,同時還考慮到了精度問題,在重寫<的時候均使用$fabs(a-b)<1e-10$先判斷是否等于關系成立。
5、閱讀有關 UML 的内容。畫出 UML 圖顯示計算子產品部分各個實體之間的關系(畫一個圖即可)。
如圖由VS生成的uml圖

6、計算子產品接口部分的性能改進。記錄在改進計算子產品性能上所花費的時間,描述你改進的思路,并展示一張性能分析圖(由VS 2015/2017的性能分析工具自動生成),并展示你程式中消耗最大的函數。
采用10000個随機生成的幾何圖形得到以下結果。
程式中消耗最大的為set的去重工作。曾經考慮過使用unordered_set來代替set,但是在換成unordered_set編寫hash函數時遇到了有效數字以及精度問題和沖突問題,最終還是決定使用set來保證正确性。
7、看 Design by Contract,Code Contract 的内容描述。這些做法的優缺點,說明你是如何把它們融入結對作業中的
DBC要求使用者和被調用者地位平等,雙方必須彼此履行義務。
優點:1、減少因自然語言的歧義等帶來的不必要麻煩。
2、保證了雙方代碼的品質。
3、提高了軟體工程的效率和品質 。
缺點:1、對程式語言有一定要求,需要支援斷言。
2、寫起來比較繁瑣,可能會帶來不必要的麻煩。
8、計算子產品部分單元測試展示。展示出項目部分單元測試代碼,并說明測試的函數,構造測試資料的思路。并将單元測試得到的測試覆寫率截圖,發表在部落格中。要求總體覆寫率到 90% 以上,否則單元測試部分視作無效。
對于程式正确性、各個方法的正确性,斜率不存在等特殊情況以及各種異常進行了測試。
這是其中一個測試正确性的函數
TEST_METHOD(TestCorrectness)
{
set<pair<double, double>> Array_dot;
Line l1 = Line(-1, 4, 5, 2);
Cycle c1 = Cycle(3, 3, 3);
Ray r1 = Ray(2, 5, -1, 2);
Segment s1 = Segment(2, 4, 3, 2);
pair<pair<double, double>, int> dot1 = l1.intersect(r1);
if (dot1.second == 1) {
Array_dot.insert(dot1.first);
}
dot1 = l1.intersect(s1);
if (dot1.second == 1) {
Array_dot.insert(dot1.first);
}
pair<pair<double, double>, pair<double, double>> dot2 = c1.line_cycle_instere(l1);
Array_dot.insert(dot2.first);
Array_dot.insert(dot2.second);
pair<pair<pair<double, double>, pair<double, double>>, pair<int, int>> dot3 = c1.ray_cycle_instere(r1);
if (dot3.second.first == 1) {
Array_dot.insert(dot3.first.first);
}
if (dot3.second.second == 1) {
Array_dot.insert(dot3.first.second);
}
dot1 = r1.intersect(s1);
if (dot1.second == 1) {
if (r1.x1 < r1.x2) {
if (r1.x1 <= dot1.first.first) {
Array_dot.insert(dot1.first);
}
}
else if (r1.x1 > r1.x2) {
if (r1.x1 >= dot1.first.first) {
Array_dot.insert(dot1.first);
}
}
else {
if (r1.y1 > r1.y2) {
if (r1.y1 >= dot1.first.second) {
Array_dot.insert(dot1.first);
}
}
else if (r1.y1 < r1.y2) {
if (r1.y1 <= dot1.first.second) {
Array_dot.insert(dot1.first);
}
}
}
}
Assert::AreEqual(5, (int)Array_dot.size());
}
這是斜率不存在的測試
TEST_METHOD(TestAequals0)
{
Line l1 = Line(0, 0, 1, 1);
Ray r1 = Ray(1, 0, 2, 0);
pair<pair<double, double>, int> dot1 = l1.intersect(r1);
Assert::AreEqual(0, (int)dot1.first.first);
Assert::AreEqual(0, (int)dot1.first.second);
Assert::AreEqual(0, (int)dot1.second);
}
對各種異常的測試采取讀入預先寫好的存在錯誤的檔案的方式
TEST_METHOD(TestOperationException) {
auto func = [] {test("input1.txt"); };
Assert::ExpectException<OperatorException>(func);
/*try {
test("input1.txt");
}
catch (OperatorException& e) {
Assert::AreEqual("Operation Exception", e.what());
}*/
}
TEST_METHOD(TestEndException) {
auto func = [] {test("input2.txt"); };
Assert::ExpectException<EndException>(func);
/*try {
test("input2.txt");
}
catch (EndException& e) {
Assert::AreEqual("End Exception", e.what());
}*/
}
TEST_METHOD(TestDefectException) {
auto func = [] {test("input3.txt"); };
Assert::ExpectException<DefectException>(func);
/*try {
test("input3.txt");
}
catch (DefectException& e) {
Assert::AreEqual("Defect Exception", e.what());
}*/
}
TEST_METHOD(TestNumberException) {
auto func = [] {test("input4.txt"); };
Assert::ExpectException<NumberException>(func);
/*try {
test("input4.txt");
}
catch (NumberException& e) {
Assert::AreEqual("Number Exception", e.what());
}*/
}
代碼覆寫率
單元測試
9、計算子產品部分異常處理說明。在部落格中詳細介紹每種異常的設計目标。每種異常都要選擇一個單元測試樣例釋出在部落格中,并指明錯誤對應的場景。
設計了如下異常
錯誤類型 | 輸入(其中一種) | 描述 |
---|---|---|
操作符錯誤 | 2 L 0 0 1 1 Z 0 0 -1 -1 | 操作符出入錯誤 |
有多餘字元 | 2 C 1 1 1 C 1 1 1 L 1 1 1 0 | 幾何對象超出數目 |
缺少字元 | 2 L 1 1 1 1 | 缺少幾何對象或數字 |
數字格式錯誤 | 1 L 10000 1000a 1 1 | |
數字越解 | 1 L 10000000 1 1 0 | 輸入數字超過100000範圍 |
直線重複 | 2 L 1 1 0 0 L 0 0 1 1 | 直線出現重合導緻無數交點,射線與線段的無數交點情形也算作此異常 |
圓重複 | 2 C 1 1 1 C 1 1 1 | 圓重複導緻無數交點 |
直線非法 | 1 L 1 1 1 1 | 直線的兩個端點相同構不成直線 |
圓非法 | 1 C 1 1 0 | 圓的半徑非法 |
異常均會在控制台輸出并且同時輸出行号友善定位
10、界面子產品的詳細設計過程。在部落格中詳細介紹界面子產品是如何設計的,并寫一些必要的代碼說明解釋實作過程。
界面子產品我們使用Qt進行編寫,由于Qt的窗格是以左上角為原點,向下為y軸正方向,是以需要重新繪制一個坐标系。我們圈了600*600區域為坐标系,同時繪制了x軸和y軸。之後設定了4個按鈕,分别為添加圖形、從檔案中添加、删除、求交點。
添加圖形時會彈出一個新窗格來輸入圖形類型和點坐标,确定後進行繪制,并調用core子產品的接口函數傳回目前交點集合并存儲。從檔案中添加也是同理,隻不過輸入的資料是從檔案中讀取。
資訊發送與接收的部分代碼
void MainWindow::on_pushButton_clicked()
{
dialog1=new Dialog;
connect(dialog1,SIGNAL(sendData(int,QString,QString,QString,QString,QString)),this,SLOT(receiveData(int,QString,QString,QString,QString,QString)));
dialog1->show();
}
void MainWindow::receiveData(int type,QString name,QString x1,QString y1,QString x2,QString y2)
{
if(type==0){
Paint_Line(x1.toDouble(),y1.toDouble(),x2.toDouble(),y2.toDouble());
}
else if(type==1){
Paint_Ray(x1.toDouble(),y1.toDouble(),x2.toDouble(),y2.toDouble());
}
else if(type==2){
Paint_Segment(x1.toDouble(),y1.toDouble(),x2.toDouble(),y2.toDouble());
}
else if(type==3){
Paint_Cycle(x1.toDouble(),y1.toDouble(),x2.toDouble());
}
}
繪制直線的代碼
void MainWindow::Paint_Line(double x1,double y1,double x2,double y2)
{
intersect_point=input_line(std::make_pair(std::make_pair(x1,y1),std::make_pair(x2,y2)));
QPainter painter(&image);
QPen pen;
pen.setColor(Qt::blue);
painter.setPen(pen);
painter.setRenderHint(QPainter::Antialiasing, true);
if(x1==x2)
{
painter.drawLine(QPointF(pointx0+x1,0),QPointF(pointx0+x2,600));
}
else if(y1==y2)
{
painter.drawLine(QPointF(0,pointy0-y1),QPointF(600,pointy0-y2));
}
else{
double k=(y2-y1)/(x2-x1);
double b=y1-k*x1;
painter.drawLine(QPointF(0,-1*(-300*k+b)+pointy0),QPointF(600,-1*(300*k+b)+pointy0));
}
}
從檔案讀取的代碼
void MainWindow::on_pushButton_3_clicked()
{
QString filename;
filename=QFileDialog::getOpenFileName(this,tr("檔案"),"",tr("text(*.txt)"));
if(!filename.isNull())
{
QFile file(filename);
if(!file.open(QFile::ReadOnly|QFile::Text))
{
QMessageBox::warning(this,tr("error"),tr("read file error:&1").arg(file.errorString()));
return;
}
QTextStream in(&file);
while(!in.atEnd())
{
QString line=in.readLine();
QList<QString> list;
list=line.split(' ');
if(list[0]=="L")
{
Paint_Line(list[1].toDouble(),list[2].toDouble(),list[3].toDouble(),list[4].toDouble());
}
else if(list[0]=="R")
{
Paint_Ray(list[1].toDouble(),list[2].toDouble(),list[3].toDouble(),list[4].toDouble());
}
else if(list[0]=="S")
{
Paint_Segment(list[1].toDouble(),list[2].toDouble(),list[3].toDouble(),list[4].toDouble());
}
else if(list[0]=="C")
{
Paint_Cycle(list[1].toDouble(),list[2].toDouble(),list[3].toDouble());
}
}
}
}
删除時需要清空目前坐标系中的幾何圖形和交點坐,并調用core子產品中的接口函數清除core中已有的幾何圖形。
void MainWindow::on_pushButton_2_clicked()
{
QList<QLabel*> array_label=this->findChildren<QLabel *>();
for(int i=0;i<array_label.size();i++)
{
array_label[i]->clear();
}
delete_all();
image.fill(Qt::white);
Paint();
update();
}
求交點時,直接根據存儲的坐标資訊在圖上setText即可。
void MainWindow::on_pushButton_4_clicked()
{
std::set<std::pair<double, double>>::iterator it;
for(it=intersect_point.begin();it!=intersect_point.end();it++)
{
QString x=QString::number((*it).first,'f',1);
QString y=QString::number((*it).second,'f',1);
QLabel *text=new QLabel(this);
text->setText("("+x+","+y+")");
text->setGeometry(300+(*it).first,300-(*it).second,100,25);
text->show();
}
}
11、界面子產品與計算子產品的對接。詳細地描述 UI 子產品的設計與兩個子產品的對接,并在部落格中截圖實作的功能。
如下為core子產品中的接口函數
IMPORT_DLL std::set<std::pair<double,double>> solve(std::vector<std::pair<char, std::pair<std::pair<int, int>, std::pair<int, int>>>> line, std::vector<std::pair<char, std::pair<std::pair<int, int>, int>>> circle);
IMPORT_DLL bool if_line_same(std::pair<std::pair<double, double>, std::pair<double, double>> a, std::pair<std::pair<double, double>, std::pair<double, double>> b);
IMPORT_DLL bool if_circle_same(std::pair<std::pair<double, double>, double> c1, std::pair<std::pair<double, double>, double> c2);
IMPORT_DLL std::set<std::pair<double, double>> input_line(std::pair<std::pair<double, double>, std::pair<double, double>> line1);
IMPORT_DLL std::set<std::pair<double, double>> input_ray(std::pair<std::pair<double, double>, std::pair<double, double>> ray1);
IMPORT_DLL std::set<std::pair<double, double>> input_segment(std::pair<std::pair<double, double>, std::pair<double, double>> segment1);
IMPORT_DLL std::set<std::pair<double, double>> input_circle(std::pair<std::pair<double, double>, double> circle1);
IMPORT_DLL long main1(int argc, char* argv[]);
IMPORT_DLL void delete_all();
在Qt的.pro工程中加入如下兩行代碼
LIBS+=D:/softwareproject/Gui3/untitled/core.lib
INCLUDEPATH +=D:/softwareproject/Gui3/untitled/pch
并将core.dll檔案放在和intersect.exe同一目錄下,即可在Qt工程中正常調用接口函數。
如圖為實作的添加圖形,顯示坐标等功能
12、描述結對的過程,提供兩人在讨論的結對圖像資料(比如 Live Share 的截圖)。
13、看教科書和其它參考書,網站中關于結對程式設計的章節,說明結對程式設計的優點和缺點。同時描述結對的每一個人的優點和缺點在哪裡(要列出至少三個優點和一個缺點)。
結對程式設計:
優點:1、兩人複查代碼,減少bug。
2、可以互相學習對方的長處,比如一些程式設計小技巧,代碼規範等
缺點:1、受時空影響,比如現在各自在家交流有時不友善
2、商讨可能會占據大量時間導緻效率降低
我:
優點:1、能夠比較快的了解問題,并想出一些解決方案
2、寫代碼效率還可以
3、能夠比較快的學習新知識并應用
缺點:1、代碼風格不好,隊友讀起來比較吃力。
隊友
優點:1、做事認真負責
2、能夠清楚的表達出自己的想法,交流比較容易
3、找bug能力強,能發現一些被忽視的bug
缺點:1、代碼架構方面寫的不是很好,會出現代碼冗雜的情況
附上無警告的截圖
附加題:松耦合
合作組同學學号:17373167,17373349
如下圖為我們組的ui運作他們組的core子產品的修改與結果:
由于我們兩組預先并沒有商量好統一的接口名字,導緻并不能無任何修改的使用對方的core子產品,是以在ui上我們需要修改檔案名和接口名。并且由于傳回的資料類型也不一樣,是以需要建立一個合适的容器來存儲他們的接口函數傳回的交點坐标。