结对项目
结对项目作业
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2020春季计算机学院软件工程(罗杰 任健) |
教学班级 | 005 |
项目地址 | Team Intersect |
PSP 表格记录
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 15 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | - | - |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 120 |
· Design Spec | · 生成设计文档 | 60 | 80 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 40 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
· Design | · 具体设计 | 30 | 60 |
· Coding | · 具体编码 | 720 | 1200 |
· Code Review | · 代码复审 | 40 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | 20 | 20 |
· Test Report | · 测试报告 | 20 | 20 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 40 | 120 |
合计 | 1195 | 1920 |
花了很多时间学习QT,碰到了许多问题,由于代码结构不好,写的不规范,报了许多没见过的错误,耗费了相当长的时间。
接口设计说明
由于我和队友对C++的接口不是特别熟悉,因此这一次写的代码,体现的接口的概念不是特别清晰,也因此重复造了很多次轮子。其实在开始扩展代码时,有过使用接口的想法,当时不知道这种想法是“接口”。比如对于新增的线段,射线,其实都是直线的一种,如果在写直线类时留有一些接口,在实现另外的线的类时就会轻松一些,代码内容也会紧凑一些;或者比如直线,线段,圆这些都属于一个图形类。大体思想应该是类似的,只是这次做了很多重复工作,让代码看起来很臃肿。(头文件写的也不是很规范,写的有点像.java
文件)
另外,一开始写的时候并没有刻意去考虑使用接口,尝试之后报了一些错误。于是为了防止一些不必要的麻烦,我们还是采取了一些简单的做法,哪怕是复制粘贴,先保证能写出正确的代码。
由于对接口的认识不够,因此就从三方面总结一下这次的不足和以后需要注意的点。
Information Hiding
- 设计不符合面向对象的规范,没有将数据封装好。每个类中的成员变量不应设置为public,没有为其配备相应的get方法,尽管在使用时直接使用
.
或->
比较方便。但是就这一次而言,应该不算是大问题,如果工程更大的话,这种细节问题就必须引起重视。 - 多层设计中的层与层之间加入接口层。
- 所有类与类之间都通过接口类访问。
Interface Design
- 没有设计好接口层,代码框架较差。
- 接口应该将内部实现细节封装起来,外部用户通过预留的接口可以使用接口的功能而不需要知晓内部具体细节。
- 可以采用纯虚函数的方式来实现。这样做的好处是能够实现封装和多态。
Loose Coupling
- 由于本身写法就比较蠢,所以这次所写的类与类之间的依赖性比较低。
- 对于底层函数,功能尽量单一,尽量避免修改底层函数。功能相近的函数,可以设计2个以上,不要为了减少代码量,把一个函数的功能设计的太多。
这次作业没有用好接口,以后要做出改进,让代码变得灵活。
计算模块接口的设计与实现过程
起初的想法是,每种几何对象都是一个类。因此会有圆,直线,射线,线段这四个类,并且有最基础的类Point
。由于上次已经完成了圆和直线类,因此这次首先考虑了射线,线段与直线的相似性,希望使用继承等方法尽量简化代码,避免不必要的工作。对于射线和线段,其实和直线类似,算出结果后只需要判断一下该点是否在射线或线段上(是否符合条件)即可。对于新增的两个类,应该有着与直线类同样的求交点方法,同时在圆类里配置求交点的方法,因此三种求交点函数就足够,再根据具体情况写一些判断函数。
按照这种想法进行后,发现由于对C++还不是很熟悉,会犯许多错误,甚至不符合语法规范,最终还是决定先按照笨办法完成代码。
算法的关键之一是判断所求出的点,是否确实在我们的几何对象上,主要涉及线段和射线,这两类线由于长度限制显得比较特殊。另外就是三种求交点的方法。
独到之处在于记录了直线的斜率k和截距b,在后续操作中提供了很大的方便,并且求交点的方法是根据图形及方程得到的公式,易于理解。
UML 图显示计算模块部分各个实体之间的关系
使用VS自带的类设计器画出的UML图如下:
没有用到太复杂的关系,各个类比较独立,函数重复性太强,值得反思。
计算模块接口部分的性能改进
由于对于自己写的hash没有保证,因此没有选择将set
改为unordered_set
。
生成1000个随机的几何对象,一次性能分析如下:
能够看出来,消耗最大的还是set
关于红黑树的构建,这是使用此容器难以避免的。
3000个随机几何对象,得到239万个交点,性能分析如下:
依然是红黑树的构建部分消耗最大。
看Design by Contract,Code Contract的内容,描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。
契约式设计有三个关键词,规定了各方的一种“责任”或是“义务”。
- 前置条件:为了调用函数,必须为真的条件,在其违反时,函数决不调用,传递好数据是调用者的责任。
- 后置条件:函数保证能做到的事情,函数完成时的状态,函数有这一事实表示它会结束,不会无休止的循环。
- 类不变项:从调用者的角度来看,该条件总是为真,在函数的内部处理过程中,不变项可以为变,但在函数结束后,控制返回调用者时,不变项必须为真。
它能够:
- 帮助开发者获得更优秀的设计,让设计变得更加清楚,简单,系统。
- 帮助开发者理解代码,更好地测试代码。
- 对开发者和调用者(用户)负责,因为是一种强制的规定。
但是,它也有很多缺点,比如:
- 撰写合格的契约(如JML)有一定难度,需要长时间的学习。
- 撰写契约很耗费时间,而且可能比代码还复杂。
- 过于复杂的契约可能对开发者没有好处。
在本次作业中这种思想体现的不是太明显,因为用到了许多容器,其实这些容器可能本身就是一种契约,规定某个容器只存某种对象,在写UI模块时直接调用即可。
计算模块部分单元测试展示
单元测试部分代码如下:
构造测试数据时主要考虑了可能实现的10种情况,在每种相交或其他情况之中再考虑比较特殊的情况,如圆与圆内含,内切,外切;射线的端点在直线上;直线平行等。最后设置了同时存在四种几何对象的情况,一共11个Test,每个Test之中再进行细分,同时根据测试覆盖率再修改一些测试数据。
单元测试运行如上,代码覆盖率如下:
消除Code Quality Analysis中的所有警告
计算模块部分异常处理说明
错误类型 | 描述 | 期望输出 |
---|---|---|
输入文件输入格式错误 | 包括几何类型不存在,坐标出现字母,没有给出N等情况。(支持坐标为小数,关于坐标必须是整数也没有硬性规定) | WRONG FORMAT |
线类给定的两点重合 | 确定线的两个点相同,无法确定一条线。 | CANNOT SOLVE THE SAME POINTS |
无穷个交点 | 包括圆和圆重合,直线与直线重合,直线与射线重合,线段与线段的重合等情况。(准确来说也不是一种错误) | INFINITE INTERSECTANT POINTS |
圆的半径小于0 | 显而易见。 | RADIUS CANNOT BE LESS THAN ZERO |
命令行参数不正确 | 包括参数错误,缺少参数。 | COMMAND ERROR |
坐标超出范围 | 坐标不在(-100000,100000)内 | COORDINATE EXCEEDS LIMIT |
一共定义了六种异常,针对某些异常会有提示信息,一些测试样例及输出如下表:
测试样例 | 期望输出 |
---|---|
L 0 0 1 1 R 0 2 4 8 |
WRONG FORMAT Please tell me how many figures do you want first And please ensure your input is a right number... |
2 L 0 0 1 1 R 0 2 HelloWorld 8 |
WRONG FORMAT If you want a line,plase input as: L x1 y1 x2 y2 If you want a circle,plase input as: C x y r And please ensure your input is a number... |
2 L 0 0 1 1 Z 0 2 2 8 |
WRONG FORMAT Sorry,we do not support this type yet... |
6 R 3 3 5 5 R 3 3 -9 -9 L 0 1 4 1 C 0 0 2 C 0 0 20 |
WRONG FORMAT Sorry,your input N is wrong... |
3 L 0 2 0 2 L 5 4 8 7 R 8 7 9 8 |
CANNOT SOLVE THE SAME POINTS |
2 L 1 0 2 0 R -8 0 0 0 |
INFINITE INTERSECTANT POINTS |
3 C 0 1 5 C 0 1 5 L 1 0 5 4 |
INFINITE INTERSECTANT POINTS |
1 C 0 0 -5 |
RADIUS CANNOT BE LESS THAN ZERO |
更改命令行参数为: input.txt |
COMMAND ERROR |
2 L 1 2 3 4 S 2 3 4 100001 |
COORDINATE EXCEEDS LIMIT Coordinate should be within -100000 and 100000 |
界面模块的详细设计过程
界面模块与计算模块的对接
之前完全没有接触过UI设计和模块封装,所以这两部分完成的不尽人意,碰到了许多困难。总的来说就是有一些可行的想法,但对实现过程很不了解,在尝试的过程中又出现了无数难以解决的问题。
首先在学习将写好的cpp文件及头文件封装花费了大量的时间,不知道应该怎么将函数或是接口导出到dll中。在网上学习后通过给函数声明前加_declspec(dllexport)
,成功生成了.lib
及.dll
文件。但具体怎么引用,引用之后如何使用都不太清楚。在原先的代码中,几何图形都是用C++的STL容器存储的,在引用dll的时候是否可以使用原先的容器?尝试后发现并不可行,只能对引用相应的接口函数,那么能否通过接口将要添加的几何图形添加到之前的容器中?······说到底还是对编译,链接的概念不够清楚,才会问这种简单的问题。
假设可以的话,我的想法就是通过接口将UI输入的图形添加到原先的容器中,最后调用intersect()
方法返回交点值,并且接口中应当有传回交点容器及其他图形容器的功能,从而在UI模块进行绘制。但是这两部分怎么数据互通,现在也一头雾水。
另外对于如何调用dll中的接口也不是很清楚,最后查到需要在声明前加_declspec(dllimport)
。
在封装时,由于代码本身写的不规范,头文件中的函数全部都是定义而不是声明,因此给后续发展带来了许多问题。在QT中引用封装好的dll和lib时,由于一些头文件的include会碰到各种奇妙的编译错误,比如某个函数或是变量在*.obj
中已经定义等重复定义问题,最后竟然报了800多个错误,实在是心有余而力不足了。
对于Qt的学习,实际上增加一些按钮函数不是很困难。但由于无法和计算模块对接,这部分的实现也显得很吃力,总是会报错。
最终,UI界面只有一个初步的设计框架。起初通过引用第三方的qcustomplot
库还可以通过VS编译,画出来的UI界面还有坐标显示,但最后不知道动了哪里,编译也通不过了,而且VS和Qt似乎本身就有很多不太兼容出错的地方。
描述结对的过程
这两部分过VS2019自带的Live Share插件以及微信语音一起讨论并写代码。
由于Live Share插件过于不稳定,延迟非常高,因此有时一方修改代码时另一方看不到,甚至会出行错行等奇怪的错误。为了解决此问题,只能将代码发至微信后尽量只由一个人修改。
结对编程评价
结对编程 | 我 | 队友 | |
---|---|---|---|
优点 | 1.两个人写代码速度更快,对问题的考虑也更全面; 2.互相监督,工作效率高,不容易摸; 3.对于互相学习很有利,能够取长补短。 |
能够耐心听取队友的意见,并给出一些意见;代码书写较规范,可读性强。 | 对问题思考全面,交流积极,能够很快指出我对问题思考的不足之处。 |
缺点 | 1.如果两人有分歧或矛盾,会对编程很不利; 2.可能会出现大佬歧视新手的情况; 3.适用范围有限,如一些小型且简单的编程。 |
对C++不够熟悉,思考不全面,容易写出bug。 | 也不熟悉C++,代码风格需要改进。 |