1
这个作业属于哪个课程 | 2020春北航计算机学院软件工程(罗杰 任健) |
这个作业的要求在哪里 | 个人项目作业 |
我在这个课程的目标是 | 增强软件开发能力,增强沟通表达能力 |
这个作业在哪个具体方面帮助我实现目标 | 完成个人项目,接触软件开发流程 |
教学班级 | 006 |
github地址 | https://github.com/Therp-GY/Pair_intersect |
2
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 400 | 500 |
· Design Spec | · 生成设计文档 | 30 | 25 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 25 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 40 |
· Design | · 具体设计 | 60 | 160 |
· Coding | · 具体编码 | 500 | 600 |
· Code Review | · 代码复审 | 100 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 200 | 500 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 20 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 15 | 15 |
合计 | 1415 | 2055 |
3.Information Hiding,Interface Design,Loose Coupling
Information Hiding:Circle、Point类的数据成员都为private,Line的数据成员为protected仅供继承它的Ray、Seg类使用
Interface Design:
SRP:接口的职责尽量单一。接口要么是求交点,要么是求距离,职责单一
LOD:仅和成员变量、方法的输入输出参数中的类运算,降低耦合。
loose coupling:在GUI和CORE中设置中间层intersect,整合CORE中的所有核心模块(各种求交点、增加几何体、删除几何体)操作,完成和GUI的对接,降低耦合度
4.计算模块接口的设计与实现过程
计算模块的类:
Point:
double distance(const Point& point)const; // 求点与点之间距离
Line:
// 线线交点
int find_intersection(const Line &line, Point *p) const; // 圆线交点 int find_intersection(const Circle& circle, Point* p) const;
Circle:
// 圆线交点
int find_intersection(const Line& line , Point *p)const;
// 圆圆交点 int find_intersection(const Circle& circle, Point* p)const;
关键函数比较离散,并不复杂。线线、圆线都是独立求的,圆圆会转化为圆线相交,逻辑比较简单。
算法关键:
本次结对项目引入了线段和射线
- 因此当计算线线交点和线圆交点时,所得到的点可能并不在线类(Line、Ray、Seg)上,因此需要增加判断点是否在线上的函数contain()。引入contain()后,即将求出的交点一一用contain()判定,不在线上则舍去。
contain():由于我是使用double类型并引入eps精度,因此若直接带入点,有可能出现如下情况:虽然点确实是在线上,但是由于坐标数值过大带入所 得到的值可能超过精度值,· 从而判定点不在直线上。因此我没有采用带入点的方法。当初始化线类时会有两个初始点start_point和end_point,我计算其方 向向量((end_point - start_point)/ |(end_point - start_point)|)= v并存入线类中。判断某个点是否在线上,先判断是否等于两个初始点中的一个, 相等则在线上,若不等:
计算该点和start_point的方向向量v'
1.线为Line:若|v'| == v 则在线上。
2.线为Ray:若 v' == v 则在射线上。
3.线为Reg:若 v' == v 则在射线上且该点坐标介于start_point和end_point之间,则在线段上。
- 判断两条线有无限交点时,不能单纯靠线的a、b、c参数判定,我引入 coincide()函数
coincide():线l1的两个初始点start_point1、end_point1 ,线l2的两个初始点start_point2、end_point2,则判断start_point1、end_point1是否在l2上,start_point2、end_point2是否在l1 上,若有三个点即以上满足条件,则两条线有无数个交点。若有两个点,则判断这两个点是否为同一个点,否,则 两条线有无数个交点。
5.UML 图
以上为uml图
6.计算模块接口部分的性能改进
30min。由于本次项目时间大部分花在接口的设计上,因此在性能上没有做出很多改进。
性能分析图:
更改思路:结果得出,contain()函数是消耗时间最多的。回到程序中,我发现由于增加了Ray和Seg,我在每一次求交点(线线、圆线)时,我都需要调用contain()判断交点是否在线上,然而实际上当线类为直线时,我并不需要判定,因此我做出如下改动
更改前:
Point t0 = p[0]; Point t1 = p[1]; n = 0; if (line.contain(t0)) { p[n++] = t0; } if (line.contain(t1)) { p[n++] = t1; } return n;
更改后:
Point t0 = p[0]; Point t1 = p[1]; n = 0; if (line.get_type() == 'L' || line.contain(t0)) { //判断是否为直线 p[n++] = t0; } if (line.get_type() == 'L' || line.contain(t1)) { p[n++] = t1; } return n;
更改后性能图:
可见contain()函数所占时间百分比大大降低,性能得到提升。
*(然而当线类都为射线或者线段时,无法避免调用contain()函数,会消耗大量运行时间)
7.Design By Contract、Code Contract
契约式设计:契约式设计的提出,主要基于软件可靠性(正确性、健壮性)方面的考虑。
特点
- 期望所有调用它的客户模块都保证一定的进入条件:这就是函数的先验条件—客户的义务和供应商的权利,这样它就不用去处理不满足先验条件的情况。
- 保证退出时给出特定的属性:这就是函数的后验条件—供应商的义务,显然也是客户的权利。
- 在进入时假定,并在退出时保持一些特定的属性:不变式。
在本次结对项目中,契约式设计体现在接口的设计上。
例如core的接口,它的权力,是要求输入参数是符合输入格式的几何体对象,以void add_object(char type, double x1, double y1, double x2, double y2)为标准,这是它给GUI作出的规范,当程序因为输入参数不符合规范而导致的错误,需要查看的是gui是否有错而并非core。
而core接口的义务,是计算几何体的交点,生成交点列,同时能对重合的几何体、相同的端点等现象抛出异常。
这种契约式设计,有效的规范了本次项目gui和core的责任。
8.计算模块单元测试
测试代码:
vector<std::pair<double, double>> point_gui_list; Intersect intersect; const char* s = "input.txt"; intersect.read_from_file(s); /* 正常输入数据 4 C -3 3 3 C -3 2 2 S 2 4 3 2 L -1 4 5 2 */ intersect.read_from_console(); /* 3 C 3 3 3 C 3 3 3 圆重合异常 R 3 0 5 3 R 5 3 7 6 线重合异常 L 0 0 0 0 S 1 0 1000000 20 坐标范围异常 C 6 7 3 */ intersect.print_intersect(); intersect.delete_object('S', 2, 4, 3, 2); // 删除线类 intersect.print_intersect(); intersect.add_object('S', 2, 4, 3, 2); // 添加线类 intersect.print_intersect(); intersect.delete_object('C' ,3, 3, 3); // 删除圆类 intersect.print_intersect(); intersect.add_object('C', 3, 3, 3); // 添加圆类 intersect.print_intersect(); point_gui_list = intersect.get_point_gui(); // 得到交点坐标 // cout << 14 cout << point_gui_list.size();
测试思路:先从文件中读取数据,再从命令行读取数据(通过命令行读取时的错误数据输入覆盖异常测试),覆盖测试读取数据函数。然后增加删除几何体(线、圆),覆盖所有核心计算模块(线线交点、线圆交点、圆圆交点)。最后得到交点list,打印交点个数,和实际个数吻合
覆盖截图:
总覆盖率超过90%
9.异常处理
异常目标:
同端点异常 same_point_error:当输入线类时,两个端点相同,则抛出异常。
超范围异常 out_index_error:当输入数据(点坐标、圆半径)、交点坐标超过100000,抛出异常。
无限交点异常 Inf_intersection_error:当存在两个几何体的交点有无数个,抛出异常。
删除异常 no_delete_object_error:当删除一个并不存在的几何体(即输入时并没有添加的几何体),抛出异常
异常单元测试:
- same_point_error
int a = 0; try { Point a1(-1000, 0); Point b1(-1000, 0); Seg l1(a1, b1); // 相同端点,应抛出异常 } catch (const same_point_error s) { a = 1; } Assert::AreEqual(1, a);
- out_index_error
a = 0; try { Circle r1(Point(-1, 200), 100002); // 圆半径超过范围,应抛出异常 } catch (const out_index_error o) { a = 1; } Assert::AreEqual(1, a);
- Inf_intersection_error
Point p[2]; int a = 0; try { Point a1(0, 0); Point b1(2, 2); Seg l1(a1, b1); Point a2(1, 1); Point b2(3, 3); Ray l2(a2, b2); l1.find_intersection(l2,p); // 线段l1和射线l2有无限个交点,应抛出异常 } catch (const Inf_intersection_error i) { a = 1; } Assert::AreEqual(1, a);
- no_delete_object_error
Intersect intersect; int a = 0; try { Line l(Point(1, 1), Point(2, 2)); intersect.add_object('L',1,1,2,2); intersect.delete_object('L', 1, 1, 2, 3); // intersect中并没有('L', 1, 1, 2, 3)这一条直线,应抛出异常 } catch (const no_delete_object_error n) { a = 1; } Assert::AreEqual(1, a);
10.界面模块的详细设计过程
在这次的界面模块设计中,我们采用的是QT软件,基于c++开发的UI模块。在此基础上,我们学习了一下QT的基本使用方法,以及在QT中需要用到的数据类型和方法来构建页面。最后通过设计,我们得到了一个建议的坐标轴界面,可以进行对几何对象的绘制,同时支持添加和删除几何对象,以及调用计算交点个数并绘制。
- 首先,模块从input.txt文件中导入几何对象的坐标信息,利用相应的数据结构储存坐标点信息
QFile file("E:/QT-GUI/UI/input.txt"); file.open(QIODevice::ReadOnly | QIODevice::Text); QTextStream in(&file); QString line = in.readLine(); qint16 num = line.toInt(); //几何对象个数 while (!line.isNull()) { line = in.readLine(); QList<QString> list = line.split(" "); if (list[0].compare("L") == 0) { QPoint point1 = QPoint(list[1].toInt(), list[2].toInt()); QPoint point2 = QPoint(list[3].toInt(), list[4].toInt()); lines_1.append(point1); lines_2.append(point2); } else if (list[0].compare("C") == 0) { .... } else if (list[0].compare("S") == 0) { .... } else if (list[0].compare("R") == 0) { .... } }
- 在窗口设计方面,首先是构建好一个建议的坐标轴系统,由于无法精确坐标轴的刻度大小范围,我们采用一个size_window参数来对几何对象的坐标大小信息记录,根据size_window参数来自动调节坐标轴的刻度大小范围。
int side = qMin(width(),height()); //创建窗口宽高参数 QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true); //开启抗锯齿 painter.translate(width() / 2,height() / 2); //坐标系统平移变换,把原点平移到窗口中心 painter.scale(side / (3.0 * size_window),side / (3.0 * size_window)); //坐标系统比例变换,使绘制的图形随窗口的放大而放大 painter.scale(1,-1); //Y轴向上翻转,翻转成正常平面直角坐标系 painter.setPen(QPen(Qt::black,height() / 2000)); painter.drawLine(-200000,0,200000,0); painter.drawLine(0,150000,0,-150000); painter.setPen(QPen(Qt::black, height() / 2000));
- 在绘制部分,我们采用一些QT的QPainter中的方法,比如drawLine,drawEllipse来绘制直线,圆等几何对象,对于绘制坐标点,我们实际上也根据坐标轴刻度绘制一个以交点为中心的小圆圈以便观察。
- 对于添加和删除功能,我们设计了两个槽函数来对输入的坐标信息进行处理:
- 最后是对交点的绘制,我们通过访问core模块,得到相应的返回值后,对返回的数据结构进行相应的处理,可以得到所有交点的坐标信息。
std::vector<std::pair<double, double>> points_1 = intersect.get_point_gui(); ui->label_2->setText(tempStr.setNum(points_1.size())); for (int i = 0; i < points_1.size(); i++) { points.append(QPointF(points_1.at(i).first, points_1.at(i).second)); }
11.界面模块与计算模块的对接
- 我们首先将core计算模块封装为dll文件,其中还有相应的头文件和.lib文件,将其作为外部库导入到界面模块GUI中,在GUI的调用过中,通过接口类Intersect传入参数得到相应的反馈。
- 在实际的对接过程中,我们首先创建一个Intersect类,通过intersect导入文件的几何对象坐标描述,在此同时intersect在core中构建出几何图像的交点信息,在UI模块添加和删除几何对象时,也调用intersect相应的方法添加和删除几何体。最后,GUI如要获取交点信息,通过get_point_gui()得到一个包含所有交点信息的数据返回值。
Intersect intersect; const char* s = "input.txt"; intersect.read_from_file(s);
intersect.add_object('S', list[1].toDouble(), list[2].toDouble(), list[3].toDouble(), list[4].toDouble());
intersect.delete_object('L', list[1].toDouble(), list[2].toDouble(), list[3].toDouble(), list[4].toDouble());
std::vector<std::pair<double, double>> points_1 = intersect.get_point_gui(); ui->label_2->setText(tempStr.setNum(points_1.size())); for (int i = 0; i < points_1.size(); i++) { points.append(QPointF(points_1.at(i).first, points_1.at(i).second)); }
12.结对过程
结对方式:主要通过微信语音交流和腾讯会议远程协作。
截图:
忘记截图了....
13.结对优缺点
优点:
1.代码处于不断复审过程中,减少bug出现率,提高代码质量
2.双方处于不断交流过程中,有利于
在设计层次得理解
3.由于旁边有人,不好意思开小差,更加投入认真
缺点:
1.初次结对时,因为不够熟悉可能导致交流障碍,降低结对效率
2.当双方水平有较大差距时结对效率还不一定比得上单人工作效率
队友张擂:
- 优点:擅长与人沟通。很少抱怨。比较认真。
- 缺点:代码不够规范。
我:
- 优点:学习能力不错。擅长与人沟通。很少抱怨。
- 缺点:代码能力一般。不够细心。
14.错误警告
无错误和警告