1. 项目地址
项目 | 内容 |
---|---|
课程 | 软件工程 |
作业 | 结对项目作业 |
教学班级 | 005 |
项目地址 | https://github.com/huangjihui511/IntersectProject2 |
2. PSP 表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 120 | 120 |
· Design Spec | · 生成设计文档 | 40 | 40 |
· Design Review | · 设计复审 (和同事审核设计文档) | 40 | 20 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
· Design | · 具体设计 | 80 | 120 |
· Coding | · 具体编码 | 360 | |
· Code Review | · 代码复审 | 90 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 990 | 1030 |
3. 接口设计
看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。(5')
信息隐蔽是开发整体程序结构时使用的法则,即将每个程序的成分隐蔽或封装在一个单一的设计模块中,定义每一个模块时尽可能少地显露其内部的处理。信息隐蔽原则对提高软件的可修改性、可测试性和可移植性都有重要的作用。
以下列举了一些信息隐藏原则的应用。
1 多层设计中的层与层之间加入接口层;
2 所有类与类之间都通过接口类访问;
3 类的所有数据成员都是private,所有访问都是通过访问函数实现的;
藕合度是度量一个代码单元在使用时与其他单元的关系。松耦合是一个单元无需其他代码单元特别的配合而可以使用。
我们运用了单例模式的思想封装出一个core类,相当于计算处理器,用vector存储几何体,拥有计算交点的方法。这样后端就被完整封装,和ui之间的耦合很松散,只需要在ui中创建core对象。
我们写了一个Geometry结构体用来存储直线和圆的实例(为了在core中形成一个几何体容器,不区分直线和圆的具体类别),使用void getObj(Circle& obj)或void getObj(Line& obj)的方法根据传入参数的类型得到对应实例,体现了信息的隐藏。
但是我们Line,Circle类的属性是public的,这一点不够谨慎,没有充分隐藏信息。
4. 计算模块接口的设计与实现过程
设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。(7')
Point类:继承自pair,表示坐标点。
Line类:表示直线、射线、线段,类别由属性type标识。包含计算直线交点的函数getIntersection_ll。
class Line {
public:
double a;
double b;
double c;
GType type;
Point e; //方向向量
Point p1; //输入点
Point p2;
Line();
Line(Point source, Point target, GType type);
int getIntersection_ll(set<Point>* intersections, Line l1, Line l2);
void operator=(const Line& line);
};
Circle类:表示圆。包含计算两圆交点的函数getIntersection_cc。
class Circle {
public:
Point c;
double r;
Circle();
Circle(Point c, double r);
void operator=(const Circle& circle);
int getIntersection_cc(set<Point>* intersections, Circle c1, Circle c2);
};
Geometry结构体:包含一个Line或Circle类的实体,和一个指示类别的Gflag(取值为L或C)。这样就可以将Line和Circle的对象加入一个vector
struct Geometry {
GType Gflag;
union {
Line lObj;
Circle cObj;
};
Geometry(Line l);
Geometry(Circle c);
void getObj(Line& obj);
void getObj(Circle& obj);
void operator=(const Geometry& g);
};
举例,将Line加入vector
vector<Geometry> geomrties;
Line *l;
geomrties.push_back(*l);
Core类:封装的计算核心模块,方便测试。
class DLL3_API Core {
public:
set<Point> intersections; //交点集合
vector<Geometry> geomrties; //几何体
vector<string> errorInformations; //错误信息
int isValid = 1;
void addGeomrties(ifstream *fin); //从文件增加几何体
void addGeomrtie(string text); //增加单个几何体
int intersect(); //求geomrties内几何体的交点
int addError(string input); //增加错误信息
};
函数与类的关系:
关键方法基本都封装在了类里面。先调用core.addGeomrties()函数增加几何体,再调用core.intersect()计算交点。addGeomrties函数是通过遍历文件调用若干addGeomrtie实现的,在测试阶段可以这样调用addGeomrtie("L 0 0 1 1")直接增加输入。intersect函数计算每两个几何体之间的交点,根据几何体类别分别调用getIntersection_ll、getIntersection_cc、getIntersection_cl。
算法关键:
圆与直线的交点算法与作业一相同,不再赘述。为了实现线段和射线的扩展,只需要把他们当作直线计算交点,然后判断交点是否在线段或者直线上。由于交点一定在线段或者射线延伸出来的直线上,只需判断交点的横坐标是否在线段端点之间,或者射线无限延伸的那一边。
判断交点与直线位置关系
错误处理中判断“无限交点”这一项,判断直线类的几何体是否重合条件比较复杂。普通直线的重合判断公式是(frac{A1}{A2}=frac{B1}{B2}=frac{C1}{C2})。一种特殊情况是线段、射线所在直线重合时,由于自身长度有限可能并不重合。
5. UML图
阅读有关 UML 的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)。(2’)
6. 性能改进
计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')
我们采用了(O(n^2))的交点求解算法。虽然求线段交点可以采用Bentley & Ottmann提出的基于扫描线的算法,将复杂度降到(O(nlogn)),但是我们的问题中有多种几何体,只优化线段与线段效果有些鸡肋。
消耗最大的函数是getIntersection_ll,计算直线交点的函数,因为输入中只有直线类的几何体。在getIntersection_ll中最耗费时间的是set::insert函数。
7. Design by Contract,Code Contract
看 Design by Contract,Code Contract 的内容:
http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。(5')
Code Contract规定软件设计人员应为软件组件定义正式,精确和可验证的接口规范,该规范应使用前提条件,后置条件和不变式来扩展抽象数据类型的普通定义。根据对商业合同的条件和义务的概念隐喻,这些规范被称为“contract”(合同)。
优点是
定义了精确的接口规范,从而使得程序移植性好,降低出错的机率。
缺点是
一些简单的函数也使用接口规范,会增加开发的负担。
我们对接口返回值的规定,用注释标注在了函数定义之前。由于大多数函数比较简单,只写了个别的函数,比如检查错误的函数返回值的含义。
8. 单元测试
计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。(6')
单元测试代码
分别摘取了一个功能测试和一个异常测试,其中异常测试中测了判断输入格式的函数。
//圆与直线相切
TEST_METHOD(TestMethod9)
{
Core core;
core.addGeomrtie("C 0 0 1");
core.addGeomrtie("L 1 0 1 1");
core.intersect();
set<Point>::iterator it = core.intersections.begin();
Assert::AreEqual((int)core.intersections.size(), 1);
Assert::AreEqual(it->first, 1.0);
Assert::AreEqual(it->second, 0.0);
}
//坐标越界
TEST_METHOD(TestMethod13)
{
Assert::AreEqual(checkRange("L 0 0 100000 0"), 1);
Assert::AreEqual(checkRange("L 0 0 -100000 0"), 1);
Assert::AreEqual(checkRange("L 0 0 100001 0"), 1);
Assert::AreEqual(checkRange("L 0 0 -100001 0"), 1);
}
测试思路
功能测试:直线相交,直线平行,斜率无穷;两圆相离,嵌套,相切,相交;圆与直线相离,相切,相交;直线与线段相离;直线与射线相交,相离;圆内含线段,内含射线端点,圆与射线相离。
异常测试:输入图形类别出错;输参数为小数;参数个数出错;圆的半径小于零;坐标越界;直线两点相同;两直线重合;两线段重合;(在同一直线上的)两线段不重合,两线段连接;两射线重合;(在同一直线上的)两射线不重合,两射线连接。
覆盖率截图
9. 异常处理说明
计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。(5')
-
我们定义了4种异常,如下图所示:
-
输入的语句不符合语法:
-
图形的数值不符合范围:
-
图形两点存在重合:
-
两个几何体有无穷交点:
对每一种异常,我们都给其定义一个返回值
其中两个几何体之间存在交点是最复杂的,需要考虑射线的方向,线段的端点。
-
10. 界面模块
界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。(5')
界面模块采用QT来开发。界面如下图所示,有一个窗口,三个部分:
-
最左边是功能控制面板。从上到下依次为控制按钮,图形选择器,输入文本框,状态反馈窗口,调整画图版。用户首先在输入文本输入想要添加的几何体,使用语法和控制台程序一样。然后点击添加按钮,该几何体就会出现在图形选择器界面中,同时在状态反馈窗口中提示用户输入是否合法,如果合法,则会在画板中更新。除此之外,用户可以通过删除按钮,导入文件按钮来删除几何体和从文件导入几何体。
-
中间的部分是画图版,每一次添加删除几何体,改变窗体大小都会重新绘制图像。画图内容包括坐标轴及其刻度,几何图形的轮廓,交点。
-
右边部分是我们的显示交点信息的界面,顶部显示交点的数量,下面显示交点的数值。
-
设计过程:这是我们第一次接触UI设计的领域,严格的说之前还有设计网页UI的经历,但不同的是,软件的UI需要从头到尾自己编码而不是简单的调用模版。我们先认真的分析了用户的使用习惯,得出了一下思路:
- 尽量简洁,避免多余的点击操作
- 把所有信息显示在一个平面上
- 实时提供反馈
因此我们这样设计了这个软件,具体的实施过程中我们通过看样例,搜索网络资料,自己实验等方法,从无到有积累经验完成了ui的制作。
11. 界面模块与计算模块的对接
详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')
由于我们的core模块在本次调用中有两个部分,一个是错误处理函数,一个是core的主题对象,因此并不需要设计特别复杂的对接。
12. 结对的过程
描述结对的过程,提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。(1')
尝试过Code Share,最后觉得微信交流更有效率。
13. 结对编程的优点和缺点
看教科书和其它参考书,网站中关于结对编程的章节,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。(5')
结对编程的优点:能得到更高的投入产出比
- 开发层次,能提供更好的设计及质量和代码质量,有更强解决问题的能力
- 对开发人员自身,能带来更多信心,以及高质量产出的满足感
- 在心里上,结对的人不好意思开小差
- 再企业管理层次,结对能更有效交流,相互学习和传递经验。
结对编程的缺点:
- 能力较强者和较弱者需要磨合,强者不可避免地要多付出一些
我的优缺点:细心;思路比较周全;对ui的效果有些执着;缺点是对编译环境引发的问题一无所知
队友的优缺点:效率高!解决问题的能力强!代码结构好!暂时没有缺点