1.相关信息
Q | A |
---|---|
这个作业属于哪个课程 | 2020春季计算机学院软件工程(罗杰 任健) |
这个作业的要求在哪里 | 结对项目作业 |
我在这个课程的目标是 | 系统地学习软件工程开发知识,掌握相关流程和技术,提升工程化开发的能力 |
这个作业在哪个具体方面帮助我实现目标 | 了解熟悉结对开发流程,为之后的组队项目做好铺垫 |
教学班级 | 005 |
项目地址 | https://github.com/NoSameRain/BUAA_SE_IntersectPrj_Pair |
2.PSP表格:
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 200 | 300 |
· Design Spec | · 生成设计文档 | 60 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 40 |
· Design | · 具体设计 | 30 | 40 |
· Coding | · 具体编码 | 300 | 360 |
· Code Review | · 代码复审 | 150 | 300 |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | 300 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 120 | 120 |
· Size Measurement | · 计算工作量 | 40 | 40 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 90 |
合计 | 1370 | 1720 |
3.看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。
Information Hiding:信息隐藏是使用编程语言功能(如私有变量)或显式导出策略来防止类或软件组件的某些方面对其客户端可访问的能力。例如,可以隐藏产生给定结果的计算。它遵循可以描述为一种信息隐藏类型的功能模型。信息隐藏的一个优点是具有灵活性,例如允许程序员更容易地修改程序。也可以通过将源代码放置在模块中以在将来随着程序的发展和发展轻松访问而完成。
Interface Design:这里的接口应该主要指的是API,即Application Programming Interface(应用程序接口),它是一些预先定义的函数,或指软件系统不同组成部分衔接的约定。 目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问原码,或理解内部工作机制的细节。
Loose Coupling:在编程中,耦合是指一个组件具有另一组件的直接知识程度,也就是软件模块之间的相互依赖程度。耦合通常与内聚形成对比。低耦合通常与高内聚相关,反之亦然。低耦合通常是软件结构良好和设计良好的标志,并且与高内聚性结合使用时,可以达到较高的可读性和可维护性的总体目标。耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
在这次结对编程中,我们将计算和错误处理这两种功能模块进行了封装,在实现UI部分的时候,由于只需要调用相应的函数,而不需要知道函数是如何实现的,就可以体现出信息隐藏这一特征。但是前提是被封装模块一定要保证正确性,否则在之后调用的过程中再去修改就会带来很多麻烦。由于一开始就确定了之后要实现的功能,所以接口的设计也就应运而生。像UI模块中的增加几何对象就要调用计算模块的addline()来进行操作。所以,接口的设计应该是根据功能来定义的。这也能一定程度保证数据的安全性。在耦合度上,我们的思路是让每一个模块都只实现特定的功能,各做各的事情,比如计算模块负责计算交点,那UI模块就不能再做相关的工作,保持每个模块的独立性。
4.计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。
本次作业里有三个类(Point,Line,WFLine),Line是存储计算交点所需的直接信息,WFLine是为处理有无穷多交点即Line重合设计的类,包含3个参数$a,b,c$,即采用$ax+by+c=0$的直线表达式;$a,b,c$是由两个点的坐标计算而来,故WFLine继承自Point。关于Point和Line,WFLine的函数声明都放置在assis.h中。
需要实现的重要函数有以下:1.确定直线参数;2.求直线交点;3.判断交点是否在线段或者射线上;4.错误输入处理;5.判断是否有无穷多交点;6.set用于去重的运算符重载函数;
我们设计的接口如下:
DLL_API void readin(string FileName);//读入数据
DLL_API void clear();//清空所有容器
DLL_API void cnt_coor_num(); //计算交点个数
DLL_API set<Point> getPoints();//返回交点集合
DLL_API vector<line> getLine();//返回直线集合
DLL_API void addLine(line l);//添加直线
DLL_API void delLine(line l);//删除直线
DLL_API void solve();//求解
DLL_API void write(string FileName);//写入文件
DLL_API string inputHandler(string FileName);
//从文件读入数据时进行错误处理 正常应返回 "Everything is ok!"
DLL_API string InfinitePoints();
//判断线之间是否重合 正常应返回"NO InfinitePoints!"
DLL_API int getP_cnt();//获得交点个数
DLL_API int getPointNum();//points size
程序开始时,各函数调用顺序如下:
在addLine之后需要调用InfinitePoints()以判断是否有无穷多交点,再调用交点计算模块进行计算。
5.阅读有关 UML 的内容:UML。画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)。
由于此次作业中类与类之间的关系未做过多设计,故在starUML中画出的图比较简略:
6.计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。
性能改进:40min.
主要的性能改进是在存储交点方面,一开始我们采用的是将节点的Xpoint和Ypoint转化为string以达到去重的目的,并存储在map容器中,经过性能分析发现to_string和insert函数开销特别大,所以换成了直接存储结点对象(含有两个double属性)到set容器中,同样可以达到去重的目的。改进前性能分析图如下:
可以看出to_string函数和map容器的insert函数开销极大。改进后:
主要的CPU开销是在set的insert操作。
7.看 Design by Contract,Code Contract 的内容:描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。
- 优点:降低错误率,便于明晰代码设计思路,每个人都分配好自己的任务,各司其职,更提高整体工作效率
- 缺点:真正实行起来有一定难度,有可能适得其反
- 这次作业中,两个人都按照一定“契约”进行任务,比如两人各自开发要实现的模块,然后进行对接,同时也负责对对方模块进行测试的工作,既有执行契约,又有核查契约。
8.计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。
本次单元测试考虑了一下几个部分的内容:
-
图形类型L/R/S:将L-L,L-R,L-S,R-R,R-S,R-L,S-S,S-R,S-S这几种情况组合形成更复杂的情况。因为本次作业新增了射线和线段,所以其中要特别考虑其延长线相交但交点不在射线/线段上的情况。
TEST_METHOD(testMethod16) { //S-S-R
clear();
N = 3;
line l1, l2, l3;
l1.store_coor("S 1 2 4 5");
l2.store_coor("S 2 5 3 1");
l3.store_coor("R 1 0 5 2");
coor_4_line.push_back(l1);
coor_4_line.push_back(l2);
coor_4_line.push_back(l3);
cnt_coor_num();
Assert::AreEqual(intersection.size(), (size_t)2);
} TEST_METHOD(testMethod22) {//S-S 其中一个垂直且假交点在垂直延伸线上的
clear();
N = 2;
line l1;
line l2;
l1.store_coor("S 1 0 0 1");
l2.store_coor("S 1 1 1 2");
coor_4_line.push_back(l1);
coor_4_line.push_back(l2);
cnt_coor_num();
Assert::AreEqual(intersection.size(), (size_t)0);
} -
图形之间的位置关系:考虑L、R、S两两平行、相交的情况。从斜率角度出发,考虑任意两个图形,一个有斜率另一个斜率不存在,两个斜率都存在(斜率相等、不相等),两个斜率都不存在的情况以及上述情况的组合。
TEST_METHOD(testMethod21) { //有斜率且斜率相同端点相交的S-S
clear();
N = 2;
line l1;
line l2;
l1.store_coor("S 0 0 1 1");
l2.store_coor("S 1 1 2 2");
coor_4_line.push_back(l1);
coor_4_line.push_back(l2);
cnt_coor_num();
Assert::AreEqual(intersection.size(), (size_t)1);
} -
边界条件:如坐标点存在100000,-100000的情况
TEST_METHOD(testMethod15) {
clear();
N = 3;
line l1, l2, l3;
l1.store_coor("L 100000 100000 -3 19999");
l2.store_coor("L 99999 100000 4 8366");
l3.store_coor("L -6542 768 9999 -4");
coor_4_line.push_back(l1);
coor_4_line.push_back(l2);
coor_4_line.push_back(l3);
cnt_coor_num();
Assert::AreEqual(intersection.size(), (size_t)3);
} -
零点正负号问题:如之前出现了将(0,0)和(0,-0)判断为两个点的情况,并通过单元测试进行了排查
TEST_METHOD(testMethod20) {
clear();
N = 4;
line l1, l2,l3,l4;
l1.store_coor("S 0 0 -1 1");
l2.store_coor("R 1 1 2 2");
l3.store_coor("R 1 1 0 0");
l4.store_coor("R 0 0 1 -1");
coor_4_line.push_back(l1);
coor_4_line.push_back(l2);
coor_4_line.push_back(l3);
coor_4_line.push_back(l4);
cnt_coor_num();
Assert::AreEqual(intersection.size(), (size_t)2);
} 精度问题 : 让任意两个,只在小数点后十位或某一位数值上有差别的点来作为交点,并设计出相应的几何图形,测试这两个交点有没有被判定为同一个交点。但我们在这里实现的并不是很好,所以未展示出.之后会继续学习其他同学的博客来进行改正.
将以上情况加以集合并以此作为覆盖率测试的依据,最后得到覆盖率以及测试结果如下:
9.计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。
错误类型 | 输出 | 测试样例 |
---|---|---|
类型不符合L S R | Please input line type as "L","S","R" at line | 2 K 1 2 4 5 L a 0 0 1 1 |
‘-’后未紧跟数字 | Make sure ‘-’ is followed by number.Error at line | 3 L 0 0 1 1 R 3 2 0 5 S 9 - 7 8 |
坐标范围超限 | Make sure that the range of points is(-100000,100000).Error at line | 2 L 1000001 4 5 1 R 3 2 0 5 |
line中两点重合 | Please input two different points.Error at line | 3 L 1 4 5 1 R 3 2 0 3 S 7 8 7 8 |
输入数据不完整 | TOO SHORT!Please input as "L\S\R int_x1 int_y1 int_x2 int_y2" for each line.at line | 4 L 1 4 5 1 R 3 2 0 3 L 6 4 3 2 S 9 7 5 |
输入过多元素 | TOO LONG!Please input as "L\S\R int_x1 int_y1 int_x2 int_y2" for each line.Error at line | 3 L 0 0 1 1 R 3 2 0 5 S 9 5 6 7 8 |
输入非整数 | Please input integers.Error at line | 3 L 0 0 1 1 R 3 2 0 5 S 9 5 6 7 R 4.45 6 7 6 |
找不到输入文件 | File is Not Found | input.txt移除 |
第一行不是整数 | Make sure the first line is an Integer that greater than or equal to 1. | a L 0 0 1 1 R 3 2 0 5 |
第一行小于1 | Make sure N is an integer greater than or equal to 1. | 0 L 0 0 1 1 R 3 2 0 5 |
type和数字没有空格 | Make sure there is a space between number and letter. | 2 L 0 1 1 3 R 3 2 0 5 |
以下是一部分运行截图:
10.界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。(5')
本次界面模块的实现用到了Qt插件.UI设计这一部分由于知识点比较繁杂,花费了非常多的时间,首先是阅读官方文档,对基本的类和函数有一个大致的了解.比如信号槽机制以及一些绘图控件.前期准备大概花了一两天,实际写代码并没有花费太久(主要还是负责封装的同学接口写的好,调用起来非常顺畅,整体上功能的实现思路也就比较清晰)后期解决qt的各种报错又花了很多时间,主要还是对lib,dll文件的引用不够了解,导致在属性设置上走了很多弯路.
现在回过头来看代码部分其实思路很简单,功能也就那几个.接下来讲一下我的设计流程以及思路.
设计UI我是从功能的角度出发的,首先明确要实现那些功能:添加几何对象,删除几何对象,从文件导入几何对象的描述,绘制几何对象,绘制几何对象交点,显示交点个数,抛出异常信息,退出界面.
大致明确了要实现这些功能后,我首先利用Qt Designer对窗体进行了大致的可视化设计.
Designer(界面设计器)设计 ui 界面最终转换为 C++ 代码(ui_类名.h),和我们写的 Qt 代码是大同小异的,假如我们对某些部件操作不熟悉,不知道该如何用其相应函数,这时候我们通过 Designer(界面设计器)拖拽此部件,修改其所需属性,接着编译,看其自动转换的 C++ 代码如何实现,这样可学习其相应函数的用法.
以上步骤完成后开始利用代码进行具体的功能设计与实现.首先在源文件中对几个功能按钮进行了信号槽设置,当butoon被按下后就会触发相应的功能,这里我学到了一点就是,按照qt的命名方法对函数进行命名可以省去connect部分的代码,实现button与相应功能的直接连接.以下为几个主要的功能,分别对应5个button(添加对象、删除对象、绘制几何对象、绘制交点、添加文件)
private slots:
void on_AddLine_clicked();
void on_DelLine_clicked();
void on_GenerateGraph_clicked();
void on_GeneratePoint_clicked();
void on_AddFile_clicked();
然后是几个具体要实现的功能.添加几何对象和删除几何对象部分很简单,就是调用了intersect模块的addline和delline接口,并且进行了对输入的信息异常以及输入的几何对象有无穷交点进行了判断,这里展示部分代码,一些细节部分都使用星号略去
void intersect_ui::on_AddLine_clicked()
{
*****
QString text = ui.InputStr->text();
if (text.size() == 0)
{
ui.error_messege->setText("please input text");
}
else
{
*****
addLine(l1);//添加直线
*****
if (message1 != "NO InfinitePoints!")
{
ui.error_messege->setText(qmessage1);
delLine(l1);//删除直线
}
}
ui.InputStr->clear();
}
void intersect_ui::on_DelLine_clicked()
{
*****
QString text = ui.InputStr->text();
if (text.size() == 0)
{
ui.error_messege->setText("please input text");
}
else
{
*****
delLine(l1);//删除直线
}
ui.InputStr->clear();
}
绘制几何对象部分花费了比较长的时间.这里用到了QT-Qcustomplot来实现基础坐标轴功能以及图像的绘制.其中直线,线段,射线的绘制我查阅了QCPAbstractItem官方文档用到了QCPItemStraightLine以及QCPItemLine类来实现.这里以绘制直线为例进行展示,射线和线段部分略去
void intersect_ui::on_GenerateGraph_clicked()
{
*****
vector<line> lines = getLine();//返回直线集合
for (int i = 0;i < lines.size();i++)
{
if (lines[i].type == "L")
{
QCPItemStraightLine* l = new QCPItemStraightLine(ui.widget);//构造直线
ui.widget->addItem(l);//添加到图中
l->setPen(QPen(Qt::blue)); //设置画笔
l->point1->setCoords(lines[i].x1, lines[i].y1);
l->point2->setCoords(lines[i].x2, lines[i].y2);
ui.widget->replot();
}
else if (lines[i].type == "R")
{
*****
}
else if (lines[i].type == "S")
{
*****
}
}
//设置可拖拽 滚轮放大缩小 图像可选择
ui.widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
}
通过查阅资料我在坐标轴中实现了拖拽,滚轮放大缩小功能,便于查看生成的几何对象.
生成交点与这里大同小异,就是调用了intersect模块中的返回交点坐标接口然后根据坐标点信息进行绘制,这里不再详述.
输入文件部分调用核心模块中的读文件接口和异常处理接口,实现也非常简单.
void intersect_ui::on_AddFile_clicked()
{
QString text = ui.InputFileName->toPlainText();
string str = text.toStdString();
string message = inputHandler(str);
QString qmessage = QString::fromStdString(message);
if (message == "Everything is ok!")
{
string message1 = InfinitePoints();
QString qmessage1 = QString::fromStdString(message1);
if (message1 == "NO InfinitePoints!")
{
readin(str);//读入数据
}
else
{
ui.error_messege->setText(qmessage1);
}
}
else
{
ui.error_messege->setText(qmessage);
}
11.界面模块与计算模块的对接。详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')
模块对接其实我在上一点中也已经提到了.我们的思路很简单,实现什么功能的槽函数就对接实现什么功能的计算模块的接口.
举例来讲,就是UI模块的以下函数
private slots:
void on_AddLine_clicked(); //添加几何对象
void on_DelLine_clicked(); //删除几何对象
void on_GenerateGraph_clicked(); //绘制几何对象图形
void on_GeneratePoint_clicked(); //绘制交点
void on_AddFile_clicked(); //读文件
分别对应调用核心模块中以下函数
DLL_API void addLine(line l);//添加几何对象
DLL_API void delLine(line l);//删除几何对象
DLL_API vector<line> getLine();//返回几何对象集合
DLL_API set<Point> getPoints();//返回交点
DLL_API void readin(string FileName);//从文件读入数据
同时考虑到错误处理,还调用了用于检查文件格式,判断是否有无穷交点等的函数.具体怎么调用,在什么时候调用,上面的代码部分都进行了展示.从总体的逻辑上来看就是以下三步:
- 槽函数响应事件
- UI模块中相应函数被执行
- UI模块中函数调用核心模块中函数来实现特定功能并更新核心模块数据
功能展示截图如下:
- 绘制几何对象及交点,显示交点个数,缩放大小
- 显示异常信息(这里展示了输入为空、找不到该文件、某两个几何对象有无穷交点的错误信息)
12.描述结对的过程,提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。(1')
远程结对感觉还是挺有挑战的,在作业的第一个星期因为我和队友都有冯如杯需要准备还有别的科目任务,所以交流比较少,大家是分头行动,比如增加功能和测试,各司其职;在第二个周开始共享屏幕,视频交流,效率还是提升很多,对接口的设计和使用也更为快捷。
这次项目我和队友的结对分工是,我负责step1扩展功能和step4UI模块,队友负责step2封装和step3错误处理。并且每个人写好各自的部分后都要交给对方测试与检查。所以整个任务的分配并不是完全割裂开来的,彼此都要对对方的任务做到心里有数,要大致浏览对方的代码。
我非常庆幸的是队友的目标很明确,没有仅仅就一个模块埋头写代码,比如她在封装的时候就会非常体贴地考虑UI要怎么和其他模块对接,这为我之后的工作带来了非常大的便利。
虽然留下了些小遗憾,但总体而言,两人结对的过程是非常宝贵且有意义的,我从这次项目学到了非常多东西(不仅限于编程上的知识,还有如何与人合作)当然也和队友结下很深厚的友谊。
屏幕共享截图:
13.看教科书和其它参考书,网站中关于结对编程的章节,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。(5')
优点:
- 两人在一起编程,更能集中讨论问题,发现问题,提高效率
- 一人设计一人审核,使得代码质量更高,减少错误率
- 两人在一起相互鼓励,更有动力
- 二人编程更利于发现对方代码中的漏洞,和一些盲点
缺点:
- 两人编程习惯不同可能导致需要很多时间适应对方风格、阅读彼此代码也会有些困难
- 两人在前期没有发现系统版本不匹配,而各干各的,到后期对接时会出现很多问题
我的优点与缺点:
- 优:心态稳如老狗,遇到Bug会一直de下去
- 优:不抛弃、不放弃,在遇到瓶颈的时候安慰鼓励队友
- 优:查找资料能力比较好
- 缺:前期懈怠后期玩命
队友的优点与缺点:
- 优:push主力,一直带动我推进(谢谢队友)我找bug找到半夜还开着语音陪我
- 优:认真、负责,每一个任务都很用心的在做好
- 优:细心,经常找到一些我发现不了的bug
- 优:人美,声音好听
- 缺:遇到瓶颈的时候有点小情绪,不过很可爱
14.在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。(0.5')
见上。