第一次结对项目
第一次结对项目
-
在文章开头给出教学班级和可克隆的 Github 项目地址(例子如下)。(1')
- 教学班级:005
- 项目地址:https://github.com/Kennnnnnji/intersect-plus
-
在开始实现程序之前,在下述 PSP 表格记录下你估计将在程序的各个模块的开发上耗费的时间。(0.5')(独立完成)
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 30 30 · Estimate · 估计这个任务需要多少时间 30 30 Development 开发 995 1415 · Analysis · 需求分析 (包括学习新技术) 120 300 · Design Spec · 生成设计文档 30 30 · Design Review · 设计复审 (和同事审核设计文档) 5 5 · Coding Standard · 代码规范 (为目前的开发制定合适的规范) 0 0 · Design · 具体设计 60 60 · Coding · 具体编码 480 600 · Code Review · 代码复审 60 60 · Test · 测试(自我测试,修改代码,提交修改) 240 360 Reporting 报告 50 75 · Test Report · 测试报告 30 60 · Size Measurement · 计算工作量 10 10 · Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 10 5 合计 1075 1520 -
看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。(5')(独立完成)
- 模块之间是独立运行的,GUI和core之间没有具体的数据交流,没有使用一些数据结构传递信息,只是传递里输入输出文件所在位置的字符串。
- core组件中,所有图形共享一个父类shape,shape中定义了接口getCross(shape &);,不同的子类分别实现对应的功能计算交点,运行的时候统一调用接口,即可,子类修改对应的实现不会影响到调用。
-
计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。(7')
类图:
类包括:
Shape, Point, Line, Circle, Container
. 其中,
Line, Circle
是Shape
的子类,采用面向对象的方法来维护可拓展性、可维护性良好的程序结构。Shape:
二维几何形的类。具有基本的共有属性和方法。Point:
点类。
具有属性:坐标
x, y
,脏位valid
。 具有方法:
Line:
直线类。
具有属性:斜率
k
,截距b
,垂直布尔值vertical
,垂直截距vertical_x
,线类型lineType
(表明是直线、线段还是射线)。 具有方法:
show():
显示对象属性。
getCross(Line* l2):
获得与另一条直线的交点。
setX(double x):
获得直线上横坐标为x
的点。Circle:
圆类。
具有属性:圆心
P
,半径r
。 具有方法:
bool isCross(Line l):
判断圆是否与直线相交。
bool isCross(Circle c2):
判断两圆是否相交。
pointPair intersections(Circle* c):
获取两圆的交点。
pointPair getCrossPoints(Circle* cir, Line* l):
获取圆和直线的交点。
数据的组织
使用STL中的vector
来管理Line
。为了不使点重复,使用set
管理获得的点。但是set
的性能较慢,需要进行排序,而这一点在本次作业中没有要求。改为使用unordered_set
来管理Point
。
同时使用 Container
类储存图形对象:
class Container {
private:
int id;
string name;
public:
unordered_multimap<double, Shape*> map;
vector<Line*> lineVec;
vector<Circle*> circleVec;
bool equal(const Shape &s1, const Shape &s2);
Shape* find(const Shape &shape);
bool insert(Shape &shape);
double myHashFunc(const Shape &obj);
};
算法
题目的实际需求就是求解多条直线/线段/射线的交点。除了正确性的要求之外,还要尽可能提高性能。那么,首先考虑如何求解:
-
若已知两条直线方程 \(L_1: y = k_1x + b_1, L_2: y = k_2x + b_2\),且\(k_1 \neq k_2\),可直接联立得到解。
-
若有一条直线是垂直的,\(L_1: x = x_0, L_2: y = k_2x + b_2\),可带入求解。
-
若两条直线平行,没有交点。
直线方程的公式有以下几种形式:
斜截式:
\[\begin{equation}y=kx+b\end{equation} \]
截距式:
$$\begin{equation}x/a+y/b=1\end{equation}$$
两点式:
$$\begin{equation}(x-x1)/(x2-x1)=(y-y1)/(y2-y1)\end{equation}$$
一般式:
$$\begin{equation}ax+by+c=0\end{equation}$$ (可以表达任意直线)
在这里采用斜截式,只需要建立直线的时候计算\(k, b\)(或垂直\(x\)轴),空间复杂度小,在之后的计算中也非常简洁。
以上为基本求解方法。
那么,如何对所有直线求解呢?
最直观的想法是对所有直线分别求交点,时间复杂度为\(O(n^2)\)。但是,这样粗暴的解法的性能无疑是很低的。在网络上查找资料,想到平行线可以作为一个集合,集合内不用求交点。但是,要求平行线集合仍然要遍历它们的斜率,和直接求交点的时间复杂度并无太大区别,因为在求交点的时候,若斜率相同,那么可以直接跳过。
另外,由于存在多条直线交于同一点的情况,实际上对于所有可能的交点,都要求出具体的交点坐标。那么,平行线集合的优势也被进一步削弱了。所以总体上,这一类做法的复杂度都是\(O(n^2)\)。
在这一基础上,可以做一些初步的优化,即已经求过交点的两条直线不再求解。如:有直线\(L_1, L_2\),在求解\(L1\)对\(L_2\)的交点之后,不需要反过来求交点。如此可以节省一半的时间。
其次,在确定算法之后,考虑数据的精度问题。在题目直线参数为(-100000, 100000)的情况下,float
精度是达不到要求的。只有使用double
才能满足精度约束。
在求解直线交点的基础上,可以对射线、线段用类似的方法求解。假设为直线求出交点,然后判断其有效性(根据是否是真实的交点)。
-
阅读有关 UML 的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)。(2’)(独立完成)
多个子类公用一个父类和接口 -
计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')
本项目做出的优化:
- 采用
unordered_set
保存数据,避免排序带来的性能消耗。 - 跳过已经计算过的几何形,避免重复计算。
构思优化消耗时间10min,实现优化消耗25min左右。
关于为什么没有做平行线的优化:
考虑平行线之间不用互相计算交点,节省了时间。但是得到平行线集合需要消耗不小的时间,而且多条线交于同一点要求所有几何形之间都要计算出准确的交点位置。平行线优化并没有展现出优势,但是会加大程序的复杂度。
性能分析:
所有分析采用随机生成的 \(N = 2500\) 组数据。
CPU性能分析:
占用最大CPU的为hash函数,这一项难以优化。
内存占用分析:
- 采用
-
看 Design by Contract,Code Contract 的内容:
- http://en.wikipedia.org/wiki/Design_by_contract
- http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。(5')(独立完成)
我们的程序中更多的是使用动态检查功能,保证运行时的稳定性。
-
计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。(6')
如图所示,单元测试的时候要考虑各种组合情况,比如这个函数是测试图形之间是否相互重叠导致有无数个交点的,这个函数是考察直线直线、直线射线、射线射线之间重叠和不重叠的情况,还要考虑线段与线段,线段与直线,线段与射线,之间重叠,部分重叠和线段完全被包含在其中等等条件。除此之外,还要考虑边界条件,比如数据上下界,-99999,99999这种条件。
在测试完计算功能之后是测试错误处理部分,有些部分仅涉及函数,有些涉及文件,因此不但要写单元测试代码,还得准备测试用的样例文件。
我使用的是vs2019社区版,社区版中没有统计单元测试覆盖率的功能。OpenCppCoverage Plugin也不行,这个插件只能检测运行一次调试时的代码覆盖率,无法检测单元测试的覆盖率,而由于有错误处理,我们没办法在一次调试中覆盖90%的代码,这个插件与单元测试无关。
-
计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。(5')
- 输入的文件中有非法字符
10. 界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。(5')
GUI部分主要借助QT来实现的,QT可以通过拖拽组件的方式来进行UI设计,并可以使用Signals/Slots的方式实现模块之间的联动。
主要设计如图所示,所有组件统一展示在窗口中。按钮和输入的窗口上方有相应的文字说明。正上方占主要部分的是进行展示的窗口,右下角的交点坐标框用来展示计算出来的交点。
-
界面模块与计算模块的对接。详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')
两个模块主要通过文件进行数据传输,由于计算的核心组件已经封装成DLL文件,所以UI模块将添加或删除的直线写入临时文件"temp.txt"中,在点击绘制按钮后,通过调用函数的方式调用核心组件计算交点,并写入另一个临时文件"points.txt"中,完成之后从读取文件"temp.txt“中图形,展示到上方的窗口中,在从临时文件"points.txt"中读取交点坐标展示到右下角的窗口中。
下图展示的是槽的功能,即点集添加按钮就会运行下列代码。
-
描述结对的过程,提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。(1')
结对使用腾讯会议软件进行实时投屏和通话,保证交流的高效率。一人写代码,一人审查。
结对过程中,我们大体上是积极的、充满干劲、相互督促的。在一人编码过程中,另一人负责检查的监督,确保代码的质量和bug的最小化。同时,我们技术观点相近,结对非常愉快。结对使用了:一个人写,一个人看;或者一个人写实现,一个人写测试两种方式。一段时间后进行交换。
-
看教科书和其它参考书,网站中关于结对编程的章节,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。(5')(独立完成)
优点:
1.每个人的精力集中的时间有限,双方交替编程和复审代码,可以由长时间高效的代码产出。
2.编程的同时在进行复审,可以及时纠正代码的错误,提高代码质量。
3.双方可以说同时进行编程,比双方分开编程有更好的代码理解,减少理解代码的时间,便于后续测试文件的编写等。
4.水平系相对较低的一方可以从水平较高的一方学习到编程的技巧。
缺点:- 效率不一定比两人单独编程高,双方缺乏默契,没有统一编程规范反而会拖慢效率。
- 双方能力、地位反而会会陷入一方为主导,另一方无法及时纠正,违背结对编程的初衷。
-
在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。(0.5')(独立完成)
注:结对小组中两个人发布独立博客,其中 2、3、5、7、13、14 部分请独立完成,不允许雷同。项目的测试分数两人共享,博客的分数各自独立。附加题的相关要求请按附加题的要求补充在博客中。