2020软件工程-结对项目做业

2020软件工程-结对项目做业

1. 项目简介

github连接 html

项目 内容
这个做业属于哪一个课程 https://edu.cnblogs.com/campus/buaa/BUAA_SE_2020_LJ
这个做业的要求在哪里 https://edu.cnblogs.com/campus/buaa/BUAA_SE_2020_LJ/homework/10466)
我在这个课程的目标是 提高软件开发能力
这个做业在哪一个具体方面帮助我实现目标 提高了我的编程能力, 测试和提高代码性能的能力

2. PSP时间花费

PSP2.1 Personal SoftWare Process Stages 预计耗时 实际耗时
Planning 计划 120 60
Estimte 估计这个任务须要多少时间 1800 1700
Development 开发 1100 1000
Analysis 需求分析 0 0
Desing Spaec 生成设计文档 100 40
Design Review 设计复审 100 10
Coding Standard 代码规范 0 0
Design 具体设计 60 40
Coding 具体编码 1100 180
Code Review 代码复审 60 60
Test 测试 180 180
Reporting 报告 100 120
Test Report 测试报告 0 0
Size Measurement 计算工做量 5 0
Postmortem & Process Improvement 过后总结 60 50
- 合计 1800 1700

3. 接口设计

通过查阅资料和学习, 我了解到在接口设计中有经常使用的一些原则, 本次做业的设计中, 咱们用到了这些原则。node

  • 单一职责原则c++

    Single Responsibility Principle: There shuld never be more than one reason for class to changegit

    单一职责原则要求一个接口, 一个功能, 若是在private 方法和public接口中, 改动过多, 那么容易引发错误。 在结对编程的设计中, 计算模块里咱们尽可能保证一个函数只作一个事情, 好比Line Circle Point分出三个类, 每一个求交点的函数只处理一种类型等。github

  • 里氏替换原则算法

    Liskov Substitution Principle: Functions that use pointers or references to base classed must be able to use objecsts of derived classes without knowing it编程

    这一法则是多态的体现, 全部父指针的使用点均可以用子指针, 这要求了子类只扩展父类, 可是不能够用在覆写父类函数的时候让函数的应用范围减小。 在最初的版本中, 咱们采用了Line Circle继承父类的方式实现, 后来发现这种继承影响性能, 就放弃了继承。windows

  • 依赖倒置原则数据结构

    依赖倒转原则是TDD测试驱动开发的体现, 要求高层模块不依赖特定的底层模块, 而是依赖其抽象, 这要求编程者提早想好接口。架构

  • 接口隔离原则

    clients should not be forced to depend upon interfacess that they don't use

    保证接口单一, 不要过于臃肿, 在计算模块和UI模块的对接时, 咱们尽可能细化接口, 让模块的每一个按钮对应了恰好一个接口, 同时接口之间的功能达到了隔离。

  • 迪米特法则

    law of Demeter: least knowledge
    loose coupling: Loose coupling means they mostly independent

    这一原则要求两个类之间要低耦合, 达到loose coupling, 一个类不须要知道另外一个类是怎么实现的, 更不须要由于一个类内部代码的修改二更改本身的方法。 在咱们的结对编程中, 为了实现接口的高效对接, 对于接口进行和抽象和封装

4.计算模块接口的设计与实现过程

类设置

  共设置4个类,分别为表示直线、射线、线段的Line类(经过成员m_type区分),表示圆的Circle类,表示交点的Node类,以及负责维护几何对象、计算交点及输出结果的主类Intersect类。几何对象类的成员包含构造参数以及为求解交点时减小计算量而预存的衍生量。此外,Line类还包含一个判断输入的交点是否在该线上的online成员函数。

主类接口

  主类Intersect在实例化后能够经过addGeoFromFile函数从目标路径文件中读入几何对象,也能够经过addGeoByString函数由指定格式的字符串添加几何对象。还能够经过removeGeoByString函数删除容器中和指定格式的字符串所表示的几何对象相同的几何对象。主类提供CalculateIntersections函数用于触发交点的计算,这个函数经过调用相应的交点计算函数计算每两个几何体所造成的交点并保存在容器中。在交点计算完毕后能够经过GetIntersectionNumber函数获得交点个数,或使用GetGeoObjectsGetIntersections函数输出几何对象的具体信息,也可经过ViewIntersections函数打印交点至标准输出以快速调试。

  主类在实例化时生成存放几何对象及交点的容器,并在后续过程当中不断更新。UML图以下:

交点计算

  交点的计算根据代数或几何方法实现。其中,线间交点的求解依赖行列式,较为工整规范;线圆交点的求解经过将圆心平移至原点,简洁地计算出交点后再平移回去的方法实现(可视为坐标系的线性变换);圆圆的交点则经过相交部分的三角关系求解。具体以下:

LineLineIntersect

  对于\(L_1\{(x_1,y_1), (x_2,y_2)\},L_2\{(x_3,y_3),(x_4,y_4)\}\),交点为:

\[x=\frac{ \left|\begin{array}{} \left|\begin{array}{} x_1 & y_1\\ x_2 & y_2 \end{array}\right| & x_1-x_2\\ \left|\begin{array}{} x_3 & y_3\\ x_4 & y_4 \end{array}\right| & x_3-x_4 \end{array}\right| } { \left|\begin{array}{} x_1-x_2 & y_1-y_2\\ x_3-x_4 & y_3-y_4 \end{array}\right| }\\ y=\frac{ \left|\begin{array}{} \left|\begin{array}{} x_1 & y_1\\ x_2 & y_2 \end{array}\right| & y_1-y_2\\ \left|\begin{array}{} x_3 & y_3\\ x_4 & y_4 \end{array}\right| & y_3-y_4 \end{array}\right| } { \left|\begin{array}{} x_1-x_2 & y_1-y_2\\ x_3-x_4 & y_3-y_4 \end{array}\right| } \]

判据为\(\Delta= \left|\begin{array}{} x_1-x_2 & y_1-y_2\\ x_3-x_4 & y_3-y_4 \end{array}\right|\),有:

\[\Delta\begin{cases} =0 & no\space intersection\\ \ne0 & intersection \end{cases} \]

为减小计算,读入直线时预存\(x_1-x_2,y_1-y_2\)\(\left|\begin{array}{}x_1 & x_2\\y_1 & y_2\end{array}\right|\)

LineCircleIntersect

  对于\(L\{(x_1,y_1),(x_2,y_2)\},C\{(x_c,y_c),r\}\),交点为:

\[x=\frac{ Dd_y\pm sign(d_y)d_x\sqrt{r^2{d_r}^2-D^2} }{ {d_r}^2 }+x_c\\ y=\frac{ -Dd_x\pm\left|d_y\right|\sqrt{r^2{d_r}^2-D^2} }{ {d_r}^2 }+y_c \]

其中:

\[d_x=x_2-x_2\\ d_y=y_2-y_1\\ d_r=\sqrt{{d_x}^2+{d_y}^2}\\ D=\left|\begin{array}{} x_1-x_c & x_2-x_c\\ y_1-y_c & y_2-y_c \end{array}\right|\\ sign(x)=\begin{cases} -1 & x<0\\ 1 & otherwise \end{cases} \]

判据为\(\Delta=r^2{d_r}^2-D^2\),有:

\[\Delta\begin{cases} <0 & no\space intersection\\ =0 & tangent\\ >0 & intersection \end{cases} \]

为减小计算,读入圆时预存\(r^2\)

CircleCircleIntersect

  对于\(C_1\{(x_1,y_1),r_1\},C_2\{(x_2,y_2),r_2\}\),交点为:

\[x=x_0\pm h(y_2-y_1)/d_c\\ y=y_0\mp h(x_2-x_1)/d_c \]

其中:

\[h=\sqrt{r_1^2-a^2}\\ d_c=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\\ a=({r_1}^2 - {r_2}^2+{d_c}^2)/d_c\\ x_0=x_1+a(x_2-x_1)/d_c\\ y_0=y_1+a(y_2-y_1)/d_c \]

判据为\(\Delta={r_1}^2-a^2\),有:

\[\Delta\begin{cases} <0 & no\space intersection\\ =0 & tangent\\ >0 & intersection \end{cases} \]

参考资料:

https://mathworld.wolfram.com/Line-LineIntersection.html

https://mathworld.wolfram.com/Circle-LineIntersection.html

http://paulbourke.net/geometry/circlesphere/

支持新增几何对象

  对射线和线段的支持,在不改变求交点算法的前提下实现。在求出交点后由Line对象使用成员函数online根据m_type判断交点是否在该线上,决定是否将其加入交点集m_allIntersections中。其中:

\[(x,y)\in Segment\{(x_1,y_1),(x_2,y_2)\}\space iff\space min(x_1,x_2)\le x\le max(x_1,x_2)\and min(y_1,y_2)\le y\le max(y_1,y_2)\\(x,y)\in Ray\{(x1_1,y_1),(x_2,y_2)\}\space iff\space (x_2-x_1,y_2-y_1)·(x-x_1,y-y_2)\ge0 \]

为节省计算,在构造线段时预存横纵坐标的最值。

  特别地,须要额外考虑非直线的线型对象之间的相交。当其判据\(\Delta\)为零时(向量平行)仍可能相交。若数据合法,则只可能相交于端点处,所以予以特判。

计算模块的封装

  将计算模块封装为动态链接库后,对外接口有三。一为readFile函数,用于从文件中输入几何对象;二为addGeometryObject函数,用于经过字符串输入单个几何对象;三为getResult函数,用于触发交点的计算并返回一个由包含全部几何对象描述串的列表和由全部交点构成的列表所组成的元组,形如pair<vector<string>, vector<Point>>

5. UML图

UML图是用来表示类之间设计关系的图表, 主要有如下五种关系

  • 依赖关系
  • 关联关系
  • 聚合关系
  • 组合关系
  • 继承关系

用UML图表示计算模块的关系以下图所示

一共用三个类来存数据, Line, Circle, Node(点), Intersect里面聚合了这三种数据, 进行一系列运算

6. 模块性能改进

记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展现一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展现你程序中消耗最大的函数。(3')

交点存储数据结构的选择

  经过随机生成的数据进行性能测试,占用CPU时间最多的子函数每每是与交点存储所用数据结构相关的函数,如选用unordered_set时哈希值的计算,选用set时元素之间的比较等。所以交点存储数据结构的选择对性能的提高十分关键,备选方案及可能的问题以下:

  • unordered_set:须要考虑哈希桶的数量(根据几何对象数目肯定)和哈希函数的设计。
  • set:虽然红黑树的更新较哈希表慢,但不存在rehash的问题。
  • vector+sort+unique:重复的交点较多时会超过内存限制,须要随时检测去重。

所以最终的问题在于在给定的数据条件下哪一种数据结构更优。通过比较,采用了unordered_set,并在Intersect类初始化时使用reserve函数预设哈希桶数量为\(5000000\)以免rehash开销。在随机生成的\(2000\)个几何对象的测试中(交点数在1M以上)性能分析图以下:

可见,CPU占用率最高的函数为存放交点的unordered_set调用的equal_to函数。这说明哈希值的碰撞较多,能够考虑优化哈希值的计算。查阅资料后选取了较为合适的:

\[hash(Node)=hash(Node.x)\hat{}hash(Node.y)<<1 \]

须要注意的是,浮点数的哈希会致使在精度范围内相等的数被哈希至不一样哈希桶,致使重复计算。针对这个问题将哈希函数修改成:

\[hash(Node)=hash(round(Node.x * 1.0 / EPS))\hat{}hash(round(Node.y * 1.0 / EPS)) \]

参考资料:

https://stackoverflow.com/questions/16792751/hashmap-for-2d3d-coordinates-i-e-vector-of-doubles

https://www.zhihu.com/question/52368555

针对C++程序的优化

  经过查阅如下材料,修改了原始程序中可能致使性能降低的代码,改进包含:

  • 直接实例化对象而非使用new来给指针分配堆空间,减少内存管理开销。
  • 使用emplace系列函数向容器中插入元素,避免push的拷贝开销。
  • 提早取出重复使用的成员变量避免屡次访存。
  • 取消继承,删除指针的强制转换,存储时肯定几何对象类型。
  • 循环控制使用++i;

http://www.javashuo.com/article/p-hvmxpkiy-nn.html

https://blog.csdn.net/nodeman/article/details/80771789

https://blog.csdn.net/p942005405/article/details/84764104

7. Design By Contract

Design By Contract: It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants.

在上学期的OO中, 我了解到和约设计须要定义如下的条件

  • 前置条件 函数的输入应该知足什么条件
  • 不变式 在函数计算过程当中 应该知足哪些恒定的条件
  • 后置条件 函数的结束时应知足什么条件

因为对于每一个函数用和约设计过于复杂, 并不实用, 所以咱们对于模块进行和约设计, 各自保证计算模块和UI模块的正确性, 独立测试。

8. 计算模块部分单元测试展现

展现出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试获得的测试覆盖率截图,发表在博客中。要求整体覆盖率到 90% 以上,不然单元测试部分视做无效。(6')

  本次单元测试继承了上次的测试代码,放入OldTest类中。其包含三个TEST_METHOD,分别在仅有直线、仅有圆以及线圆混合的状况下针对几何对象之间的相离、相切、相交、共交点等状况各测试\(5\)个样例。其中测试直线的部分以下:

  新增功能的测试则包含在NewTest类中,其下辖三个TEST_METHOD针对射线和线段之间的位置关系及特殊状况(平行交于端点)共测试了\(14\)个样例。其中测试线段的部分以下:

  此外还设置了精度和溢出测试。精度损失来源于sqrt操做;溢出是当坐标接近\(10^5\)时,\(\Delta=r^2{d_r}^2-D^2\)等判据可能超过long long的表示范围,所以圆相关一切计算改用double进行。

  测试样例彻底覆盖了核心计算代码:

9. 计算模块部分异常处理说明

在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。(5')

异常类型 设计目标 样例
CMDFormatException 命令行输入错误 ./intersect.exe -p xx
FileNotFoundException 输入文件不存在 ./intersect.exe -i nowhere -o out.txt
FileFromatException 文件内容格式错误 C
L 1 1 -1
CoordinateLimitExceedException 坐标超限 1
C 999999 0 1
LineDefinitionException 两个参数点重合 1
L 1 1 1 1
CircleDefinitionException 圆的半径小于等于零 1
C 0 0 -1
InfinateIntersectionException 几何对象间存在重叠 2
R 0 1 0 2
S 0 3 0 4

10. 界面模块设计

由于以前没有GUI编程的经验, 在设计界面模块以前,我首先调查了windows下有哪些GUI开发的库可使用。

1.MFC

2.QT

3.WPF

4.WTL

在调查以后, 据网上评价QT比较好用, 并且是跨平台的, 可是由于安装文件过大, 电脑给windows的分区不足, 最终选择了VS自带的MFC。

MFC有两种设计方式, 一个是dialog app一个是document app, 我选择的是dialog app, 开发一个窗口。

使用visual studio mfc生成dialog app后, 在资源文件里能够找到界面对应的文件, 打开后以下图, 能够添加组件

我主要使用了三种组件

  • Button
  • Static text
  • Edit control

Button组件用来获取用户的点击操做, 每次点击会触发一个onclick函数

void CMFCApplication1Dlg::OnBnClickedButton1()
{
CString geomLine;
edit_geo_obj.GetWindowTextW(geomLine);
CT2CA sa(geomLine);
std::string s(sa);
addGeometryObject(s);
}

函数内能够添加各类操做, 好比在个人界面中, 有如下几个按钮

  • 输入文件

  • 增长几何对象

  • 删除几何对象

  • 计算交点(而且绘制)

  • 关闭

static text组件用来展现文字

Edit control是一个文本框, 能够用来输入文字, 每一个文本框会有一个变量, 用来获取文字。

好比file.GetWindowTextW(geomline)就是把文件输入框中的文字读入到geomLine这个Ctring中

最后, 点击计算交点后, 会把从计算模块获得的交点绘制出来, 调用draw函数画在GUI的中央, 以下图所欧式

11. 界面模块与计算模块的对接

因为咱们的接口比较少, 在界面模块对接时很流畅。

获得intersectProjectDll.lib和Interface.h后, 我看到有三个接口

__declspec(dllexport) void readFile(string);
 
__declspec(dllexport) void addGeometryObject(string);

__declspec(dllexport) void removeGeometryObject(string);

__declspec(dllexport) pair<vector<string>, vector<Point>> getResult();

这三个接口是咱们以前商定好的接口, 尽可能隐藏内部实现细节, 只暴露三个接口

  • readFile从文件中读取集合对象
  • addGeometryObject添加几何对象
  • getResult用来获取当前的全部交点和几何对象, 用于画图

功能以下图

图中有三个按钮, 输入文件调用readFile, 添加几何对象调用addGeometryObject, 计算交点调用getResult 而且绘制

实现以上功能后, 对接工做完成。

12. 描述结对的过程

使用工具: 腾讯会议

具体过程:

  • 在结对编程过程当中, 咱们首先没有区分领航员和驾驶员的角色, 而是总体对于项目进行讨论, 好比用什么样的架构, UI的框架等。
  • 在明确了分工以后, 咱们进入了探索期, 查阅文档, 了解技术细节, 而且写设计文档
  • 完成了需求分析和设计后, 咱们开始了几轮的编码。 一我的编程, 另一我的负责测试和提修改意见, 通过几轮迭代后, 咱们有了基本可用的版本。
  • 在复审, 测试阶段, 咱们轮流提供测试样例, 进行压力测试
  • 最后一个阶段是代码优化, 咱们采起的模式是一我的负责找优化方法, 一我的负责实现, 及时保持沟通, 获得性能提高的反馈。

13. 结对编程优缺点

结对编程的优势

  • 两我的能够互相学习, 完善对c++语法, 标准库使用等知识
  • 结对工做能够提高代码的质量
  • 缩短了复审周期, 在公司的code review阶段, 有时由于tech lead事情繁忙, 没法给出fd, 一段code review会经历1-2天才能进入下阶段的持续开发测试, 结对编程能够加速这一流程
  • 有同伴一块儿编程可让人更专一

结对编程的缺点:

  • 两我的同时作一件事, 有些浪费时间, 不利于团队的冲刺
  • 每每领航员数量少, 驾驶员多, 成本高

在结对编程的时候, 每个人的优缺点以下:
对驾驶员的优势

  • 代码能够及时获得反馈, 架构问题能够及时调整
  • 能够找人讨论, 提高工做专一度
  • 快速学习, 可以迅速提高软件质量

对驾驶员的缺点

  • 有时候旁边有人会紧张, 影响工做状态

对领航员的优势

  • 能够了解junior一些的SDE的开发历程
  • 统一项目代码风格
  • 巩固本身代码架构, 算法知识

对领航员的缺点

  • 浪费了太多时间

14.模块互换

咱们互换的组是 http://www.javashuo.com/article/p-dumtxcfu-nn.html

咱们选择的组和咱们有类似的接口, 在替换核心模块的时候经历了一下几个步骤:

1.修改dll, lib路径, 成功引入头文件, 可以连接到库

2.修改输入文件, 添加几何对象, 计算交点的接口名称

3.运行

运行结果以下:

相关文章
相关标签/搜索