项目 内容
这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里 结对项目作业
我在这个课程的目标是 进一步提高自己的编码能力,工程能力,团队协作能力
这个作业在哪个具体方面帮助我实现目标 学习了c++模块化方法,以及图形化界面的编写方式
教学班级 006
项目地址 https://github.com/NSun-S/BUAA_SE_PairWork.git

目录

一、写在前面

本次作业功能实现比较简单,但是我第一次对一个项目进行封装,所以在后面封装和实现GUI的时候感觉比较困难。还有在进行模块松耦合的时候,由于事先没跟对接的组做好商议,后面浪费了很长时间去修改。

不过这次作业带给我的收获是巨大的,首先,我学会了模块封装,其次我学会了用Qt怎么写UI界面。我还知道了模块要实现松耦合要注意哪些问题。这些收获为后面团队项目打下了重要的基础。

下述 PSP 表格记录了我在程序的各个模块的开发上耗费的时间:

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 30 30
Development 开发
· Analysis · 需求分析 (包括学习新技术) 200 300
· Design Spec · 生成设计文档 30 50
· Design Review · 设计复审 (和同事审核设计文档) 20 20
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 60 120
· Design · 具体设计 30 30
· Coding · 具体编码 540 500
· Code Review · 代码复审 120 200
· Test · 测试(自我测试,修改代码,提交修改) 240 300
Reporting 报告
· Test Report · 测试报告 50 120
· Size Measurement · 计算工作量 20 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 10
合计 1360 1700

二、关于Information Hiding,Interface Design,Loose Coupling的实现

Information Hiding(信息隐藏原则): 这是David Parnas在1972年最早提出信息隐藏的观点,他在其论文中指出:代码模块应该采用定义良好的接口来封装,这些模块的内部结构应该是程序员的私有财产,外部是不可见的。所以我们在作业中设计接口的时候,接口的输入输出信息都是string、int、double这种类型,完全不会体现出我们设计的Line类和Circle类。

Interface Design(接口设计): 通过实现addLine(),addCircle(),deleteLine(),deleteCircle()函数,可以对内部的数据结构进行添加删除操作,这保证了虽然内部信息是隐藏的,但是却可以通过接口来进行更改。

Loose Coupling(松耦合): 我们和另一个小组通过接口的统一,实现了模块松耦合,替换核心计算模块以后,程序仍然可以正常运行。

三、计算模块的实现

1. 内部设计

在这一部分我们经过讨论,沿用了上次我的设计,在我个人作业基础上进行了拓展,仍然保持了上次作业的两个类——直线类和圆类,在直线类中,我们新加了一个属性类型,用来区分直线是直线型、射线型还是线段型,因为在求解交点的过程中,三种“直线”的求解方式是完全一样的,我们只需要根据其类型判断交点在不在其对应的区域上即可,这个类中除了上次的求解直线和直线交点的函数,增加了一个判断交点是不是在“直线”上的函数;在圆类中,和上次作业几乎完全一致,仍然是求垂足、求直线和圆的距离、求圆和圆的交点、求圆和直线的交点四个方法,区别在于在求垂足时需要暂时将直线类型统一设置为直线型,来避免垂足被判为不在直线上。直线类和圆类在功能上是一种协同关系。

2. 外部接口

我们将项目的主类作为对外的接口,在里面实现了solve,ioHandler等函数接口,用于求解交点,以及添加、删除几何对象,从文件中读取几何对象,在类内创建了保存现有几何对象的容器,在每个函数内调用直线类或圆类的方法来实现功能。这个类可以说是一个顶层类,是连接外部和内部的枢纽。

3. 流程图(以ioHandler和solve为例)

4. 算法关键及独到之处

算法的关键在于各类图形间交点的求解,这是我们的任务所在,也是整个模块功能的核心。我认为算法中的独到之处如下:

  • 3类直线型对象的统一处理,在交点求解时以统一的函数来求解交点,在求解之后判断点在不在对应的几何对象上,短短数十行代码就实现了功能的拓展。
  • 交点存储方式的选取,在交点的存储上,我们选择了用vector存储后排序去重的方式,其性能上的优势非常明显,对于一个10000个几何对象2900w+交点的数据,在未去重时,我和另一组同学的运行时间分别为4.97s和5.21s,在排序去重后运行时间分别为10s和31s,可见我们这种处理方式的优势是非常明显的。

四、计算模块的UML图

五、计算模块的性能改进

1. 性能的改进

这一部分与其说是性能的改进,不如说是bug的修复,在性能上我们已经取得了不错的效果,但有两个问题一开始处理的比较差,下面我们来分析一下这两方面的问题。

  • 判断点在线段、射线上。我们一开始采用了计算距离的方式,理论上说这种方式没有任何问题,在计算精确度足够高的条件下,这种方法自然没有问题,但在我们完成作业后和同学进行比对时,发现在一个6000+条直线类几何对象上,我们的交点数目能相差40000多个,不愿意相信的是,就是这个判断点在不在直线上的函数造成的,距离的计算引入了1e-6级别的误差,让两个原本应该重合的点不再重合,将判断条件改成之间判断横纵坐标(都是整数),问题才得以解决。
  • 判断圆相切。这个问题和上面的问题一样,同样是精度问题,但这个问题的解决过程要漫长的多,在600w+个交点上我们的结果多了两个,经过一晚上复杂的排查,锁定了两组几何对象,下面以其中一组进行说明。这是一个直线和圆相切的例子,(L, -272, 469, 673, 973)和(-401, 968, 501),我们的程序计算出两个交点,原因是直接使用==来判断相切,这带来了1e-5级别的误差,我们使用wolfram平台绘图验证了其相切,并计算出了交点(见下图)。最后改变了相切判断条件,问题才得以解决。

2. 性能分析

下图是我们使用VS性能分析工具分析的结果。

可以看出,对vector的排序花费了较多的时间。其中,消耗最大的函数是slove,因为全部的交点求解过程都是在这个函数里面完成的,下面是这个函数的代码。

六、关于Design by Contract,Code Contract

契约式设计就是按照某种规定对一些数据等做出约定,如果超出约定,程序将不再运行,例如要求输入的参数必须满足某种条件。在我们的作业中,接口的设计以及松耦合的实现均使用了契约式设计原则。这个原则的好处是可以预先定义好接口,方便把握软件的整体架构,也方便开发者和使用者进行对接,还有就是方便维护,在维护的同时原有功能可以继续使用,维护完成后替换核心功能部分代码即可。

七、计算模块的单元测试

在这次作业中我们切实感受到了单元测试的重要性,在每次增加新功能后进行回归测试,可以很容易的发现问题,设计上主要就是对新添功能的测试,以及一些边缘问题的测试。

1. 部分单元测试代码展示

TEST_CLASS(testinterface_solve)
{
TEST_METHOD(method1)
{
deleteAll();
vector<pair<double, double>> myIntersections;
ioHandler("../testinput2.txt");
solve(myIntersections);
int answer = myIntersections.size();
Assert::AreEqual(26, answer);
}
};

这是在我们写好ioHandler和solve接口后进行单元测试的样例,测试了通过接口进行交点计算。

TEST_CLASS(testinterface_ad)
{
TEST_METHOD(method1)
{
vector<pair<double, double>> myIntersections;
//ioHandler("../testinput2.txt");
addLine(-1, 4, 5, 2, LINE);
addLine(2, 4, 3, 2, SEGMENT);
addLine(2, 5, -1, 2, RAY);
addCircle(3, 3, 3);
solve(myIntersections);
int answer = myIntersections.size();
Assert::AreEqual(5, answer);
}
TEST_METHOD(method2)
{
vector<pair<double, double>> myIntersections;
//ioHandler("../testinput2.txt");
deleteCircle(3,3,3);
deleteLine(2, 5, -1, 2, RAY);
solve(myIntersections);
int answer = myIntersections.size();
Assert::AreEqual(1, answer);
}
};

这是我们写好addLine(Circle)和deleteLine(Circle)接口后进行单元测试的样例,测试了通过接口进行几何对象的增添和删除。同时消除代码中的所有Warning。

2. 单元测试覆盖率截图

从图中可以看出我们单元测试覆盖了93%的内容,剩下没有覆盖的部分大多为函数头和main函数中的内容。

八、计算模块的错误处理

直线型对象给定两点重复

TEST_METHOD(method1)
{
try
{
addLine(-1, 4, -1, 4, LINE);
}
catch (const char* msg)
{
Assert::AreEqual("Error: two points of a line should be different", msg);
}
}

这一错误是对于直线型几何对象,其输入的两点坐标重合。

坐标值越界

TEST_METHOD(method2)
{
try
{
addLine(-1000000, 4, -1, 4, LINE);
}
catch (const char* msg)
{
Assert::AreEqual("Warning: your coordinate value is out of bound", msg);
}
}

这一错误针对所有几何对象,正确的数据坐标值应限定在(-100000,100000)。

圆半径出现非正数

TEST_METHOD(method3)
{
try
{
addCircle(-10, 4, -1);
}
catch (const char* msg)
{
Assert::AreEqual("Error: circle's radius should be a positive integer", msg);
}
}

这一类错误针对圆类型几何对象,圆的半径应该为正值。

未定义类型标识

TEST_METHOD(method5)
{
deleteAll();
vector<pair<double, double>> myIntersections;
try
{
ioHandler("../undefined.txt");
}
catch (const char* msg)
{
Assert::AreEqual("Error: unexcepted type mark", msg);
}
}

这一类错误针对出现除'L','S','R','C'之外的类型标识符。

九、界面模块的设计

本次作业的界面模块,我们使用了Qt进行设计,并使用了Qt的开源库QCustomPlot进行图像绘制。主要包括6个函数。

QioHandler():

从文件中读取数据并进行计算。

void myGUI::QioHandler()
{
string input = ui.fileInput->toPlainText().toStdString();
ioHandler(input);
fstream inputfile(input);
int n;
inputfile >> n;
for (int i = 0; i < n; i++)
{
char type;
inputfile >> type;
if (type == 'L' || type == 'R' || type == 'S')
{
int tempType = -1;
if (type == 'L') tempType = LINE;
else if (type == 'R') tempType = RAY;
else if (type == 'S') tempType = SEGMENT;
double x1, x2, y1, y2;
inputfile >> x1 >> y1 >> x2 >> y2;
lines.push_back(UILine(x1, y1, x2, y2, tempType));
}
else if (type == 'C')
{
double c1, c2, r;
inputfile >> c1 >> c2 >> r;
circles.push_back(UICircle(c1, c2, r));
}
}
Qsolve();
}

QdeleteAll():

删除所有几何对象。

void myGUI::QdeleteAll()
{
deleteAll();
ui.widget->clearItems();
ui.widget->clearGraphs();
lines.clear();
circles.clear();
Qsolve();
}

QaddLine():

添加直线。

void myGUI::QaddLine()
{
string newType = ui.newType->toPlainText().toStdString();
//通过ui读入两个点
double x1 = ui.newX1->toPlainText().toDouble();
double y1 = ui.newY1->toPlainText().toDouble();
double x2 = ui.newX2->toPlainText().toDouble();
double y2 = ui.newY2->toPlainText().toDouble();
//判断类型
int type = -1;
if (newType == "L") {
type = LINE;
}
else if (newType == "R") {
type = RAY;
}
else if (newType == "S") {
type = SEGMENT;
}
//执行接口的addLine函数
addLine(x1, y1, x2, y2, type);
lines.push_back(UILine(x1, y1, x2, y2, type));
//重新计算交点
Qsolve();
}

QdeleteLine():

删除直线。

void myGUI::QdeleteLine()
{
string newType = ui.newType->toPlainText().toStdString();
double x1 = ui.newX1->toPlainText().toDouble();
double y1 = ui.newY1->toPlainText().toDouble();
double x2 = ui.newX2->toPlainText().toDouble();
double y2 = ui.newY2->toPlainText().toDouble();
int type = -1;
if (newType == "L") type = LINE;
else if (newType == "R") type = RAY;
else if (newType == "S") type = SEGMENT;
deleteLine(x1, y1, x2, y2, type);
for (auto iter = lines.begin(); iter != lines.end(); iter++)
{
if (iter->x1 == x1 && iter->y1 == y1 && iter->x2 == x2 && iter->y2 == y2 && iter->type == type)
{
lines.erase(iter);
break;
}
}
//在删除线后清屏
ui.widget->clearItems();
ui.widget->clearGraphs();
//重新绘制几何对象并求解交点
Qsolve();
}

圆的函数和直线类似,就不再赘述了。这里面的Qsolve()函数功能是使用QCustomPlot中的QCPItem模块绘制所有几何图形,并执行接口中的solve()函数,根据结果绘制交点。所以每次添加删除几何对象后都执行Qsolve()函数,可以实现图像的自动更新。

十、界面模块和计算模块的对接

本次作业我们采用动态链接库(dll)的方式进行模块对接。

首先在计算模块中实现这些函数:

void solve(vector<pair<double, double>> & realIntersections) throw(const char*);
void ioHandler(string input) throw(const char*);
void addLine(double x1, double y1, double x2, double y2, int type) throw(const char*);
void deleteLine(double x1, double y1, double x2, double y2, int type);
void addCircle(double c1, double c2, double r) throw(const char*);
void deleteCircle(double c1, double c2, double r);
void deleteAll();

然后再函数声明前加_declspec(dllexport),就可以在dll中实现这些函数的接口,然后界面模块导入计算模块的dll,即可使用这些函数。

如上一节所述,我们写好了界面模块的几个函数,然后在ui的按钮中添加对这些函数的链接,即可在ui中实现点击功能。如图:

在编译运行后,从input.txt中导入图形,即可实现交点求解和图像绘制功能。如图:

十一、模块松耦合的实现

合作小组两位同学:17373456,17373459

我方运行对方core.dll成功的截图:

对方运行我方core.dll成功的截图:

我方使用命令行运行对方core.dll的截图:

对接过程中出现的问题:

  • GUI.exe和core.dll的编译方式不一致,导致互换core.dll之后无法运行。

    • 解决方法:统一使用Release x64的模式进行编译。
  • 接口函数的关键词不一致,我方采用__cdecl,对方使用的默认。
    • 解决方法:对方将函数声明添加__cdecl关键词,即可正常运行。

十二、描述结对的过程

由于疫情原因,这次结对项目不能面对面进行讨论。所以在结对的过程中我们经历了大致几个阶段,使用live share+腾讯会议阶段,这一阶段一开始感觉很新奇,在计算模块的设计过程中我们都采用这一模式,后来在图形界面设计时由于live share编译时非常不稳定,且双方都没有接触过QT设计图形界面,共同探索效率较低,因此我们采用了腾讯会议连线加桌面共享的方式,这样有问题可以随时讨论,也可以方便展示最新的成果,下面是我们两个阶段的截图。

此外,我们通过热身作业中学到的pull request操作,共同维护了一个GitHub仓库,实现了代码的交互管理。

十三、对结对编程的评价

结对编程的优点:首先在面对困难的时候可以集思广益,更快地解决问题,比如一开始写图形界面的时候我们都不知道怎么写,然后我们都上网上去找资料,最后把我们找到的信息合并起来,就完成了图形界面的编写,如果一个人写的话可能会写一部分但是另一部分不会写,效率就很低了。其次可以减少细小错误的发生,因为大部分时间都是一个人写另一个人去检查他的代码,所以这在一定程度上保证了代码的质量。

结对编程的缺点:结对编程需要代码理解能力很强,我们不仅要知道自己在写什么,还要知道队友在写什么,他的逻辑是什么样的,以及是否正确,所以整个过程比较累。同时,写个人项目时我们自己有一个完整的思维流程,但这在结对项目中是不适用的,我们总是要根据队友的思维变化而不断调整我们的思维,难以形成对项目的整体把控。

我的优点:对工程文件的组织能力强,对代码结构的把控比较好,比较适合框架设计。

我的缺点:算法设计不如队友。

队友的优点:做事耐心,有责任心,对算法设计比较好

队友的缺点:有时会比较粗心,代码中会犯一些小错误。

BUAA2020软工作业(四)——结对项目的更多相关文章

  1. BUAA2020软工作业——提问回顾与个人总结

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 提问回顾与个人总结 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方 ...

  2. BUAA2020软工作业(三)——个人项目

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 个人项目作业 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方面帮助 ...

  3. BUAA2020软工作业(五)——软件案例分析

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 软件案例分析作业 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方面 ...

  4. BUAA2020软工作业(二)——对软件工程的初步理解

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 个人博客作业 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方面帮助 ...

  5. BUAA2020软工作业(一)——谈谈我和计算机的缘分

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 第一次作业-热身! 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方 ...

  6. BUAA2020软工团队beta得分总表

    BUAA2020软工团队beta得分总表 [TOC] 零.团队博客目录及beta阶段各部分博客地址 团队博客 计划与设计博客 测试报告博客 发布声明博客 事后分析博客 敏 杰 开 发♂ https:/ ...

  7. [软工作业]-软件案例分析-CSDN

    [软工作业]-软件案例分析-CSDN(app) 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 个人博客作业-软件案例分析 我在这个课程的目标是 ...

  8. 软工作业-----Alpha版本第一周小结

            软工作业-----Alpha版本第一周小结   Part1.第一周周计划记录 姓名 学号 周前计划安排 每周工作记录 自我打分 yrz(队长) 1417 1.进行任务分析 2.任务分配 ...

  9. FZU软工第四次作业-团队介绍

    目录 团队展示----旅法师 团队成员 队名----旅法师 拟作的团队项目描述 队员风采 团队首次合照 团队的特色描述 团队展示----旅法师 本次作业链接 团队成员 031602305 陈玮 031 ...

随机推荐

  1. charles 抓包修改app页面数据

    1,首先给手机安装Charles证书,安装官方的来,在无线网配置项目,输入手动代理地址,后开启飞行模式刷新网络, 2,在浏览器输入chls.pro/ssl 下载并安装证书,此时电脑端charles 会 ...

  2. Apollo 配置中心详细教程

    一.简介 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理 ...

  3. CodeForce-812B Sagheer, the Hausmeister(DFS)

    Sagheer, the Hausmeister CodeForces - 812B 题意:有一栋楼房,里面有很多盏灯没关,为了节约用电小L决定把这些灯都关了. 这楼有 n 层,最左边和最右边有楼梯. ...

  4. learn git(本地仓库)

    #本地 在Windows上安装Git 在Windows上使用Git,可以从Git官网直接https://git-scm.com/downloads下载,然后按默认选项安装即可. 装完成后,在开始菜单里 ...

  5. JS HTML5仿微信朋友圈特效

    完美! 图片相册翻页可定位在第几张,右上角可关闭. 源代码下载地址: 链接: https://pan.baidu.com/s/1o7PA7wu 密码: asyt

  6. lumen-phpunit 单元测试

    lumen-框架5.8为例 1,把vendor下的bin目录放到环境变量里面: 2,设置路由 $router->get('syn', ['uses' => 'syn\syn@diction ...

  7. HttpRunner3.X - 全面讲解如何落地项目实战

    一.前言 接触httprunner框架有一段时间了,也一直探索如何更好的落地到项目上,本篇主要讲述如何应用到实际的项目中,达到提升测试效率的目的. 1.项目难题 这个月开始忙起来了,接了个大项目,苦不 ...

  8. 专访阿里云 Serverless 负责人:无服务器不会让后端失业

    2012 年,云基础设施服务提供商 Iron.io 的副总裁 Ken 谈到软件开发行业的未来,首次提出了 Serverless 的概念,为云中运行的应用程序描述了一种全新的系统体系架构.此后,以 AW ...

  9. Python Pandas的使用 !!!!!详解

     Pandas是一个基于python中Numpy模块的一个模块 Python在数据处理和准备⽅⾯⼀直做得很好,但在数据分析和建模⽅⾯就差⼀些.pandas帮助填补了这⼀空⽩,使您能够在Python中执 ...

  10. 点击按钮改变div背景色,再次点击恢复 -- 原生JS

    如果对您有帮助,记得点个赞哦!