- 在文章开头给出教学班级和可克隆的 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 | 完成博客撰写 |