代码改变世界

结对项目作业

2020-03-24 03:19  木杉月  阅读(245)  评论(2编辑  收藏  举报

结对项目

项目 内容
这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里 结对项目
教学班级 005
项目的GitHub地址 pairProject

1.地址

2.PSP 表格

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

这次相较于上次个人项目,增加了异常处理,ui模块。而核心模块基本与上次的相同,增加的射线与线段只用判断交点是否在射线与线段上即可。异常处理部分也是让程序更接近于软件,因为实际的软件总是有着各种意外,不能保证每个用户都能完全按照说明来使用软件。而ui也是让数据可视化,使得结果更生动形象。

3.看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。

对于信息隐藏,在我们结对编程中由于类不多,信息改变频繁,所以未使用私有变量,主要是针对类中的临时变量定义添加了下划线避免了无意中改变类中的公有变量。

而接口设计主要是为了让大部分函数能够进行复用,而只用简单的关注接口需要什么,接口返回什么,就行。而不用关注实现的细节,可以让轻松的让不同人使用。这次我们就是将计算交点的函数放在一个类中,例如,线与线的交点,圆与圆的交点等等。而将不同的几何图形分别作为一个类,保存其几何信息。

而对于松耦合其实和接口设计差不多,我们前端ui也只用调用核心模块的计算交点该类就行。而不用关注核心模块具体怎么实现的。

4.计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。

本次代码有三个类,按照接口设计的要求来做,将计算交点的计算函数归为一个类solve,而将两种类型的几何图形线和圆分别作为一个单独的类,其中solve类调用其他两个类。而函数我们也是有三个,输入,计算,输出三个函数,从而实现main函数的简洁。

由于相比较上次而言这次添加的有射线和线段。对于线与线的交点。我们是采用原来的直线和直线的交点,然后再判断交点是否在射线和线段上。而判断是否在射线和线段上我们提出了两种方法。

  • 1.例如线段与直线的交点,我们根据几何学可知,若线段与直线有交点,则线段的两点在直线的两侧。而这时我们将这两点带入直线方程ax+by+c = 0中则所得出的结果必异号。所以我们进行判断若异号则按照直线与直线的交点进行处理,若同号则无交点。而对于射线与直线的交点,就多了一种同号的情况而这时候我们只要判断终点和起点距离直线距离的大小就行。而且点到直线距离未(ax+by+c)/(sqrt(a2 + b2)),而由于底相同,所以我们也只用判断ax+by+c的绝对值大小即可。
  • 2.还有一种是先直线与直线的交点,然后再判断交点是否在相应的线段上。

而第二个方法相较于第一个方法计算量会更大一些,所以我们采用了第一个算法。

5.阅读有关 UML 的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)

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

我们明显可以看到,其中性能消耗最大的是求交点的函数而这也是我们优化的终点。而我们继续看下图。

我们可以看到其中求交点的具体函数中,set容器的插入操作消耗是最大的,占了一半多一上。所以我们曾想过将set容器改为hashmap容器,但是后面发现,用hashmap需要重写hash函数,而在这上面花费的时间比创建红黑树还多,所以我们最后还是采用了现在的这种容器。

7.看 Design by Contract,Code Contract 的内容:描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。

DBC的中心思想是调用者与被调用者都有要求,也就是说,调用函数需要提供相应的参数,而被调用者则是在调用者提供正确的参数的条件下需要返回相应的值。所以调用方和被调用方是平等的。

而其优点在于

  • 对于代码影响较小
  • 代码易复用,因为已经存在合同了,而调用者只需根据合同上要求来调用即可
  • 便于调试,提前设计好合同,若被调用函数在合同规定的输入下出现问题,则代表被调用函数有bug

缺点

  • 缺点就在于你设计好合同后,却无法自由的禁用掉。比如需要增加规定的输入,这个时候就需要修改合同,比较繁琐。

而这次结对作业中由于之前并不了解这种做法,所以并未融入。而在调试的时候我们使用的断言Assert方法,和try-catch方法。

8.计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。

在这次单元测试中,需要测试的就是solve类中的方法,有LL,LR,LS,RR,RS,SS,LC,RC,SC,CC等10种求交点的方法。而我们也是根据这10种方法来构造相应的测试数据。而对于不同的方法再根据几何性质具体进行构造数据,例如圆和圆之间又有内含,内切,相交,外切,外离等等几种情况,而我们根据这种来具体进行构造测试数据。

单元测试代码如下

测试覆盖率如下

9.计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。

  • 输入命令行参数个数不对

  • 输入命令行参数不匹配

  • 坐标范围超限//若坐标不是整数怎么办 (-100000, 100000)

  • 直线定义中两点重合

  • 圆半径不为正数

  • 有无穷多交点

  • 几何对象数目应为正整数

  • 几何图形只能为L,R,S,C

  • L,R,S只能4个参数

  • C只能为3个参数

  • 几何对象参数均为不含前导零的整数

10.界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。

首先,布局,我在界面左边有一块白色画布,右边是输入框,添加,删除,从文件导入三个按钮。首先我先画出了坐标系,然后再在坐标系里画线和圆。界面模块最重要的就是信号和槽了,而这也决定了点击相应按钮然后界面做出的反应。而添加信号槽代码如下

    connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(Paint()));
    connect(ui->pushButton_2,SIGNAL(clicked()),this,SLOT(Delete()));
    connect(ui->pushButton_3,SIGNAL(clicked()),this,SLOT(Fl()));

也就是将按钮的点击信号与自己所定义的信号槽函数所连接。而添加按钮也就对应于绘图操作,删除按钮也就对应于删除操作。

然后接下来重要的就是绘图了。而绘图中,我们需要保存界面上已有的数据,由于本次数据量较少,所以我直接就用全局变量存储几何图形的信息。然后每一次点击按钮的操作都将会刷新图像。绘制几何图形的代码如下所示

    for (int i = 0; i < lines.size(); i++)
    {
        if (lines[i].type == 'R')
        {
            painter.drawPoint(get_x(lines[i].x0), get_y(lines[i].y0));
        }
        else if (lines[i].type == 'S')
        {
        painter.drawPoint(get_x(lines[i].x0), get_y(lines[i].y0));
            painter.drawPoint(get_x(lines[i].x1), get_y(lines[i].y1));
        }
        painter.drawLine(get_x(lines[i].x0), get_y(lines[i].y0), get_x(lines[i].x1), get_y(lines[i].y1));
    }

    for (int i = 0; i < circles.size(); i++)
    {
        painter.drawEllipse(get_x(circles[i].x) - 10 * circles[i].r, get_y(circles[i].y) - 10 * circles[i].r, 20 * circles[i].r, 20 * circles[i].r);
    }

11.界面模块与计算模块的对接。详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能

而界面模块与计算模块进行对接则在这次项目中比较简单,因为界面模块只需要展示几何图形的交点即可,所以我们只用调用计算模块的接口写一个绘制交点的函数即可。部分代码如下所示

    for (auto it : intersections)
    {
        painter.drawPoint(it.first, it.second);
        QString s = "(" + QString::number(it.first) + "," + QString::number(it.second) + ")";
        painter.drawText(get_x(int(it.first)) + 20, get_y(int(it.second)), s);
    }

这里就是将已经计算好的交点容器遍历并绘制。

具体功能如下

  • 添加几何图形

  • 删除几何图形

  • 从文件导入

12.描述结对的过程,提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。

这个是我们在一起写核心模块时候的share截图。我们在结对过程中一般是手机开微信视频,而电脑用VS share,其中一个人来写代码,而另一个观察他是否编写有误,并且思考怎么写比较好,整体架构是否有问题。就如同一个是驾驶员,一个是领航员。

13.结对编程的优点和缺点,同时描述结对的每一个人的优点和缺点在哪里?

  • 在这次编程中,深深的体会到了结对编程的优点
    • 首先就是两个人一起做,整体代码架构会好许多,两个人每个人都想到了不同的好的方面,互相互补。
    • 效率高,防止摸鱼,经常一个人编程总会走神,玩会手机,而当两个人一起进行结对编程时每个人都为了不辜负对面打起十二分精神来做。
    • 不断复审,减少程序bug,可以说很多程序员都出现过无意识的bug,明明自己想的是这一方面,偏偏最后写出来却是另一方面,而两个人结对编程,就相当于不断的复审,在这次结对编程过程中也是遇到过很多次这种情况。
  • 当然,也有其缺点所在
    • 那就是两个人结对编程需要两个人都是空闲的,而这不像一个人编程那么方便,想开始写代码就开始写代码。
    • 还有就是如果两个人不是很熟悉的话,也会影响结对编程的效率,甚至没有一个人编程效率高。
  • 每个人的优点
    • 结对伙伴的优点就在于细心,思考全面所以单元测试,还有核心模块等等是让结对伙伴来做 ,我的优点在于学习新知识较快,所以我负责了新的知识ui方面的,来负责写前端方面。还有我们俩个共同的优点就是能够沉浸在编程中,不被外界干扰。当然,结对伙伴还有就是很乐观,能够以好的心态面对程序中所出现的问题,并积极的解决它。
  • 每个人的缺点
    • 我的缺点在与比较马虎,虽然写代码较快,但是容易漏掉边界信息。而结对伙伴的缺点在于对于新知识接受能力相对不强。但是其基础知识却十分牢固。