一.教学班级和github地址
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2020计算机学院软件工程(罗杰 任健) |
这个作业的要求在哪里 | 结队项目作业 |
教学班级 | 006 |
项目地址 | 结队项目作业 |
二.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | ||
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 30 | 60 |
· Design Spec | · 生成设计文档 | 20 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 15 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 35 |
· Design | · 具体设计 | 90 | 180 |
· Coding | · 具体编码 | 150 | 270 |
· Code Review | · 代码复审 | 40 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 20 | 30 |
· Size Measurement | · 计算工作量 | 10 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 515 | 870 |
三.看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。
- Information Hiding,信息隐藏原则。在结构化函数的概念和面向对象的封装思想中,都深刻体现这一原则。在结构化函数层面,我们把实现具体内容的函数设置为private,把实现一个整体功能的函数设置为public,这样的话我们从外部调用一个模块的功能就会非常直观。面向对象方面,我们的整个工程分为了三个模块,模块之间的数据关系,调用依赖都是非常的独立。也就是说,每一次工作的时候,模块之间只会通信基本的控制信息和数据流,对各个模块具体的内容没有任何影响,这也符合了信息隐藏原则。
- Interface Design ,接口设计。接口设计是实现程序的结构化和信息隐藏原则的最重要的方法,其将模型的方法抽象出来,作为一个函数或者类的方法供外部或自己调用。小到一个解决某个问题的程序,大到一个工程项目,都离不开接口设计。这次作业,我们设计了:
Core
类,里面定义了方法addGeomrties(string input)
,外部只需要传入一个字符串就可以完成添加命令,因此这个函数可以在ui,命令行,控制台都方便的调用。方法intersect()
只需要在添加集合体之后调用这个函数,core模块就会自动完成所有计算,不需要外界任何调用。painter
类,里面定义了方法paintEvent(QPainteEvent *event)
,外界不需要任何的调用,在我们的框架下,系统会自动根据窗口大小,集合数据自动重新画图,甚至不需要传入参数的过程。Geometry
对象,里面有一个枚举类包含了圆,直线,射线等所有不同的几何体,统一的定义了计算的接口完成计算,避免过多的分类讨论。
Loose Coupling
,松耦合。松耦合是指减少程序各个功能模块之间的依赖程度。我们通过对直线类、圆类封装并提供相关的接口来实现对直线或者圆的初始化、某两个几何对象交点求解等功能;此外,我们还将总的求解方法封装在core中,实现整体的松耦合。
四.计算模块接口的设计与实现过程
设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。(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* 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* 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 geomrties;
Line *l;
geomrties.push_back(*l);
Core类:封装的计算核心模块,方便测试。
class DLL3_API Core {
public:
set intersections; //交点集合
vector geomrties; //几何体
vector 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}\)。一种特殊情况是线段、射线所在直线重合时,由于自身长度有限可能并不重合。
五.UML类图
阅读有关 UML 的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)。(2’)
六.性能改进
计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')
我们采用了\(O(n^2)\)的交点求解算法。虽然求线段交点可以采用Bentley & Ottmann提出的基于扫描线的算法,将复杂度降到\(O(nlogn)\),但是我们的问题中有多种几何体,只优化线段与线段效果有些鸡肋。
消耗最大的函数是getIntersection_ll,计算直线交点的函数,因为输入中只有直线类的几何体。在getIntersection_ll中最耗费时间的是set::insert函数。
七.看 Design by Contract,Code Contract 的内容,描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。
-
契约式设计,是一种设计计算机软件的方法。这种方法要求软件设计这为软件组建定义正式的、精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。
- 前置条件:为了调用函数,必须为真的条件,在其违反时,函数决不调用,传递好数据是调用者的责任。
- 后置条件:函数保证能做到的事情,函数完成时的状态,函数有这一事实表示它会结束,不会无休止的循环
- 不变式:从调用者的角度来看,该条件总是为真,在函数的内部处理过程中,不变项可以为变,但在函数结束后,控制返回调用者时,不变项必须为真。
-
优缺点:
- 优点:
- 获得更优秀的设计
- 提高代码的可靠性,有利于测试
- 为不同模块约定其与其他模块的接口及其应该有的性质,方便模块之间的整合
- 契约式设计可以明显降低调试的代价,能比较准确地定位错误
- 支持复用
- 缺点:
- 契约式设计需要比较大的时间开销。
- 小型项目中契约式设计似乎成为了一种限制,因为契约式设计较大的时间开销拖慢了整个小项目的进度。
- 优点:
-
在本次结对项目中的运用:
- 在本项目中主要通过注释的方法设计开发的计划,以便保证两个人的模块能正常的对接,一下是我们部分的约定注释。
- 但是在实际运用中有的地方架构变化比较大,注释的方式可能存在过于死板的问题。
八.计算模块部分单元测试展示
计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。(6')
单元测试代码
分别摘取了一个功能测试和一个异常测试,其中异常测试中测了判断输入格式的函数。
//圆与直线相切
TEST_METHOD(TestMethod9)
{
Core core;
core.addGeomrtie("C 0 0 1");
core.addGeomrtie("L 1 0 1 1");
core.intersect();
set::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);
}
测试思路
功能测试:直线相交,直线平行,斜率无穷;两圆相离,嵌套,相切,相交;圆与直线相离,相切,相交;直线与线段相离;直线与射线相交,相离;圆内含线段,内含射线端点,圆与射线相离。
异常测试:输入图形类别出错;输参数为小数;参数个数出错;圆的半径小于零;坐标越界;直线两点相同;两直线重合;两线段重合;(在同一直线上的)两线段不重合,两线段连接;两射线重合;(在同一直线上的)两射线不重合,两射线连接。
覆盖率截图
九.计算模块部分异常处理说明
-
我们定义了4种异常,如下图所示:
对每一种异常,我们都给其定义一个返回值
其中两个几何体之间存在交点是最复杂的,需要考虑射线的方向,线段的端点。
十.界面模块的详细设计过程。
界面模块采用QT来开发。界面如下图所示,有一个窗口,三个部分:
-
最左边是功能控制面板。从上到下依次为控制按钮,图形选择器,输入文本框,状态反馈窗口,调整画图版。用户首先在输入文本输入想要添加的几何体,使用语法和控制台程序一样。然后点击添加按钮,该几何体就会出现在图形选择器界面中,同时在状态反馈窗口中提示用户输入是否合法,如果合法,则会在画板中更新。除此之外,用户可以通过删除按钮,导入文件按钮来删除几何体和从文件导入几何体。
-
中间的部分是画图版,每一次添加删除几何体,改变窗体大小都会重新绘制图像。画图内容包括坐标轴及其刻度,几何图形的轮廓,交点。
-
右边部分是我们的显示交点信息的界面,顶部显示交点的数量,下面显示交点的数值。
-
设计过程:这是我们第一次接触UI设计的领域,严格的说之前还有设计网页UI的经历,但不同的是,软件的UI需要从头到尾自己编码而不是简单的调用模版。我们先认真的分析了用户的使用习惯,得出了一下思路:
- 尽量简洁,避免多余的点击操作
- 把所有信息显示在一个平面上
- 实时提供反馈
因此我们这样设计了这个软件,具体的实施过程中我们通过看样例,搜索网络资料,自己实验等方法,从无到有积累经验完成了ui的制作。
十一.界面模块与计算模块的对接。
由于我们的core模块在本次调用中有两个部分,一个是错误处理函数,一个是core的主题对象,因此并不需要设计特别复杂的对接。
十二.描述结队的过程。
-
我们一开始尝试过LIVE SHARE进行编程,但是在实际体验中发现这样对我们的效率并没有特别的提升,因此我们最终采用分工合作,主要是我来写前段部分,队友写后端部分
-
我们在合作过程中遇到的最大的困难还是平台方面的困难,由于某些未知的原因,导致我可以运行生成的一些程序在我的队友的电脑上就会报错。我们花了大量的时间解决这样的问题,最后基本都解决了,但是还是有一些没有成功,只能采用我单独完成这部分的工作。
十三.看教科书和其它参考书,网站中关于结对编程的章节,说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。
-
结对编程的优缺点:
- 优点:
- 相互学习,每个人的代码都有很实用的技巧构思,看队友的代码可以学到很多东西。
- 相互监督,在一起工作的时候更有责任感,需要对自己开发的东西负责。
- 相互检查,有的问题一个人可能并不能发现,需要两个人一起检查才能发现。
- 缺点:
- 两个人工作的进度和节奏可能存在一些差别,需要调整磨合适应。
- 优点:
-
我与他的优缺点:
我 她(gj) 优点 比较注重代码细节;思考慎重,比较细心;能比较快速地查找到代码的bug。 比较细心,能够发程序的bug并提出优化方案。 缺点 缺少耐心。 面对一些困难会耗费很多时间解决。