项目 内容
这个作业属于哪个课程 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. Java并发之Synchronized机制详解

    带着问题阅读 1.Synchronized如何使用,加锁的粒度分别是什么 2.Synchronized的实现机制是什么 3.Synchronized是公平锁吗 4.Java对Synchronized做 ...

  2. Vue组件传值(二)之 非父子组件传值

    Vue中非父子组件之间是如何实现通信的? 本章主要讲的是非父子组件传值,父子组件传值请看上一篇文章. 1.创建新的Vue实例引入项目中,通过$emit.$on来实现非父子组件传值: 1 <!DO ...

  3. Linux find命令实例教程 15个find命令用法

    除了在一个目录结构下查找文件这种基本的操作,你还可以用find命令实现一些实用的操作,使你的命令行之旅更加简易.本文将介绍15种无论是于新手还是老鸟都非常有用的Linux find命令.首先,在你的h ...

  4. scrum项目冲刺_day03总结

    摘要:今日完成任务. 1.图像识别已完成,但是较为卡顿,仍需优化 2.语音输入正在进行 3.搜索功能正在进行 总任务: 一.appUI页面(已完成) 二.首页功能: 1.图像识别功能(基本完成) 2. ...

  5. 为什么不推荐Python初学者直接看项目源码

    无论是有没有其他语言的经验,入门Python都很简单.Python拥有简单直观的语法,方便的语法糖,以及丰富的第三方库.只要一个基础的Python教程,大家基本上都能无障碍的入门.在入门之后,很多人对 ...

  6. Java基础系列(32)- 递归讲解

    递归 A方法调用B方法,我们很容易理解 递归就是:A方法调用A方法!就是自己调用自己 利用递归可以用简单的程序来解决一些复杂的问题.它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题 ...

  7. verifycode验证码模版

    # -*- coding:utf-8 -*- from django.shortcuts import HttpResponse def verifycode(request): # 引入绘图模块 f ...

  8. nginx与mysql安装

    yum install -y wget vim gcc-c++ bash-completion wget http://nginx.org/download/nginx-1.14.0.tar.gzta ...

  9. 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 百篇博客分析OpenHarmony源码 | v55.01

    百篇博客系列篇.本篇为: v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程 ...

  10. 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 百篇博客分析OpenHarmony源码 | v36.04

    百篇博客系列篇.本篇为: v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CP ...