BUAA 2020 软件工程 结对项目作业

Author: 17373051 郭骏

3.28添加:4.计算模块接口的设计与实现过程部分,PairCore实现的细节

项目 内容
这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里 结对项目作业
我在这个课程的目标是 学习软件工程的开发知识,培养工程化开发能力
这个作业在哪个具体方面帮助我实现目标 通过实操掌握结对开发基础

1.前言

给定 N 个几何图形,询问平面中有多少个点在至少 2 个给定的图形上。

在此处先展示Code Quality Analysis的零警告图片。

2.PSP表格

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

3.接口设计思想

  • 信息隐藏

    这条原则指导我们,应当将类的属性私有化,通过访问函数来实现,并且使用接口来连接层与层。我们在设计的过程中,做到了将类的属性全部私有化。接口的实现方面,我们使用类的部分公有函数作为接口,做到了类与类之间的交互。

  • 接口设计

    我们在设计过程中,通过Point—Line/Circle—Core三层类的结构环环相扣,每两层之间使用有限的公有函数进行交互。Line和Circle有互相使用对方求交点的方法,能够轻松的调用交点求解程序,Line也包含了线段、射线和直线,在外层写程序时不用关心到底是哪两种图形在求交点。

  • 松耦合

    类与类、层与层之间有着较好的隔离,如果出现问题或者需要增加需求,只需要修改一个类中的代码,而不需要去修改其调用的其他类。如Line和Circle,虽然需要互相求交点,但是修改其中一个类的代码,可以无需改动另外一个类,因为接口是完善的,类之间的耦合度也是低的。

在C++中,我们并没有使用抽象类的特性来帮助我们构建接口,因为抽象类在作为函数参数时的支持并不够方便。在此提到的“接口”,指的是每个类的公有方法。

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

我们的程序由Point类打底,代表解题过程中的点,可以是线段/射线的端点,也可以是图形之间的交点。我们为其预设了比较函数和赋值方法,作为程序的基础。

然后是Line类和Circle类。Line类包含线段、射线和直线,作为基础题部分的解题骨干。Line类的核心方法是Line::getIntersect(Line l),可以帮助我们轻松求出两条线之间的交点。Circle类是圆类,用于附加题。该类有对直线和圆求交点的方法。

主类为PairCore。这个类中包含了命令行参数分析函数,输入正则匹配函数,以及求交点输出到文件的函数。有PairCore::parser(int argc, char* argv[])方法,用于处理命令行参数的输入,解析方式是简单的字符串相等的条件判断。PairCore::text_handle()方法是用于从文件中读入数据,按行读入,第一行是数字,之后的每一行采用正则表达式的方式进行读取,如果格式与正则表达式不匹配则返回报错。同时我们也需要对输入范围进行特判。PairCore::getIntersectionCount()是用于计算几何图形交点的函数,其中包含三个循环,分别用于计算线与线、线与圆、圆与圆的交点。

本次作业中的核心函数,也是和上次作业很不一样的地方,就是判断求出来的点是否是两个图形的交点。由于线段、射线的范围有限制,所以我们需要判断点是否在线上。我们有函数Line::isOnline(Point p)来判断点是否在线上。判断的方法是与线的两个端点进行比较。

同时,我们需要判断输入的线是否重合,即便在同一条直线上,也有可能是没有交点或者有一个交点。我们有函数Line::relation(Line l)来判断两条线的关系。如果两条线在同一直线上,则我们会对两条线的端点坐标进行判断,从而能够判断出他们真实的交点个数。

算法的独到之处在于,早早为Point类写好了比较函数,将此后的许多位置关系判断转化为端点坐标的位置判断,简化了问题。

5.UML图

UML类图如下所示。

6.性能改进

我们使用VS的性能分析器对2000条线、500个圆的情况进行了分析,得到的图如下所示:

可以看到,在Line::getIntersect这个方法上耗费的CPU达到32%,让我们感到不可思议。我们进入代码之后,查看了语句的CPU使用率。

可以看到,语句大部分时间花在了对vector的创建、修改和返回上。由于两条线之间的交点只能是1个或者0个,所以我们可以不用vector<Point>作为返回值,而是用Point作为返回值,并判断该Point是否存在。修改之后的占比如图所示。

我们在优化上大概耗费了20分钟。

7.契约式设计

契约式设计要求设计者对软件设计正式、准确、可验证的接口规范,定义了先验条件、后验条件和不变式,这些规范称为“契约”。

这种方法的优点:

  • 通过规范化的注释,能够直接验证程序正确性。
  • 设计时着重功能而非具体实现,不用担心具体的实现流程。
  • 明确接口的功能之后,设计者和开发者都能够得到足够的信息。

这种方法的缺点:

  • 程序的正确性验证有时代价会非常大,甚至可能无法检错。
  • 正确而规范的设计十分困难,会出现满足设计而不满足功能的情况。
  • 存在一些难以或无法用契约设计的功能。

在结对作业中,这种思想确实可以让我们写出正确性较为完备的程序,但是却难以帮助我们对程序进行优化。同时,在从设计到实现的过程中,我们常常需要绞尽脑汁,甚至不得已去修改设计。因而,在结对的过程中,我们没有过分依赖契约式设计,只是做到了一般的设计—开发的流程。

8.单元测试展示

部分单元测试代码展示:

单元测试通过截图:

单元测试覆盖率截图:

我们构造测试数据的思路是:从一般到特殊,从简单到复杂,从白箱到黑箱。

我们的测试代码有题目的样例代码、最简单情况的代码、特殊情况(重合、平行、端点相交、射线相背等)的样例设计,以及一些能够触发异常的样例。此外,我们还使用随机数据生成器生成了一点随机且复杂的情况加入测试,用于检测我们的考虑是否完备。

9.异常处理说明

我们设计了13种异常,每种异常都有其对应的错误码。错误码和异常的对应关系在程序中有所体现,代码如下:

错误代码 错误信息 测试样例
-1 线段之间存在重合 2
S 0 0 2 2
S 1 1 3 3
-2 线段和射线之间存在重合 2
S 0 0 2 2
R 1 1 3 3
-3 射线之间存在重合 2
S 0 0 2 2
S 1 1 3 3
-4 直线与某条线重合 2
L 0 0 2 2
S -1 -1 -2 -2
-5 圆与圆重合 2
C 0 0 2
C 0 0 2
-6 输入的两个交点重复 1
L 1 1 1 1
-7 坐标超出(-100000,100000)的范围 1
L 100000 1 1 1
-8 输入圆的半径不大于0 1
C 0 0 -2
-9 分析图形信息时出错 1
asdfg
-10 分析图形个数时出错 L 1 1 2 2
-11 几何图形个数与输入的数目不匹配 2
L 1 1 2 2
-12 多余的不在末尾的换行符 2
L 2 2 3 3

L 0 5 5 0
-13 命令行参数错误 intersect.exe -p point.txt

如果在命令行模式下发生异常,则程序会将错误信息输出到同目录下的error.txt中。

10.界面模块设计

界面模块采用C#的Winform来开发。由于这是我们第一次使用C#,也是第一次开发GUI程序,在设计上难免会存在一些不足。

界面设计如图所示,我们的界面模块有两个窗口。

第一个窗口(Form1)是输入输出图形信息的窗口。如果GUI使用不当,会有相应的错误提示。右边的列表是现有的图形信息,可以通过鼠标点选来进行删除。

从文件导入之后不会直接开始绘制,而是将文件中的点信息加入到现有的点列表中,提供了相对高的灵活性和可操作性。

我们确实支持图形的绘制,也支持图形的添加和删除,但是这两个过程在我们的界面中是分离的,即没有做到像GeoGebra一样能够实时绘制图形和交点。

第二个窗口(Form2)是我们的绘制结果窗口。该窗口使用Winform的Panel进行绘制,整个绘制过程为造轮子过程,即没有成熟的坐标系模块供我们使用,所以效果相对简陋,且不支持缩放和平移。不过由于结对编程只有短短的两周,且中间经历了很多别的作业,所以没有在此处过于苛求。程序默认将所有的图形都画进窗口中,所以如果图形的横纵轴跨度较大,则观感会欠佳。

轮子中的关键代码是如何掌握好窗口的边界位置。我们相当于要将所有图形的坐标范围(minx,miny)-(maxx,maxy)映射到我们的画板(0,0)-(720,720)上。映射过程的核心代码如下:

float k, dx, dy;
if (maxx - minx < 1 && maxy - miny < 1)
{
    k = 1;
    dx = minx;
    dy = miny;
}
else
{
    if ((maxx - minx) > (maxy - miny))
    {
        k = maxx - minx;
        dx = minx;
        dy = miny - (maxx - maxy - minx + miny) / 2;
    }
    else
    {
        k = maxy - miny;
        dx = minx - (-maxx + maxy + minx - miny) / 2;
        dy = miny;
    }
}

这里的k是缩放比例,dx和dy是x轴和y轴的平移距离。对于每个点的坐标,我们只需要进行如下变换:x = (x0 - dx) * 720 / k, y = (y0 - dy) * 720 / k即可。

11.模块对接

dll为前端留下了两个接口,分别用于GUI展示和命令行测试。

/*GUI展示
dll从同目录下的lines.pair按格式读入几何图形的信息,
求解后将得到的点的坐标存入同目录下的points.pair文件。
函数的返回值>=0,代表交点个数。<0则代表出现异常,返回异常代码。
*/
int solve();
/*命令行展示
dll从同目录下的commands.pair按格式读入命令行参数,按要求处理。
函数的返回值>=0,代表交点个数。<0则代表出现异常,返回异常代码,不写文件。
报错部分的代码由前端完成,将错误信息存在同目录下error.txt中。
*/
int command();

可以看到这两个接口的交互几乎全部依赖于文件和返回值,没有进行参数传递。

主要的原因在于,C#和C++的标准有诸多不同,比如C#不支持指针,char为两个字节长度,string类和C++的string类不能公用等。我们尝试无论怎么传参数,都无法达到我们想要的效果,只能传一些简单的数字。所以我们干脆制定了文件读入的标准。

如果我们使用C++的GUI开发,可能不会出现这样的问题,这是我们需要反思的一点。

12.结对过程

结对过程主要通过QQ语音+Live Share的方式完成。由于网络原因,常常出现Live Share断线的情况,极大的影响了我们的工作效率。不过在我们的不懈努力之下,最终还是完成了本次作业。证据如下所示:

13.结对编程

  • 优点
    • 编程过程互相监督,不会存在摸鱼的情况,随时思考随时交流,思维更加集中
    • 两人共审同一份代码,对代码的质量和正确性都有更高的保证。
    • 两人一起工作,集思广益,对于困难的问题可能会更快产出解答。
  • 缺点
    • 度过磨合期很困难,要互相习惯对方的编程速度、思维、风格。
    • 对于“驾驶员”来说,写代码的“领航员”过度插手可能会带来反效果。
结对人员 队友
优点 思维较快,思考更迅速连贯 思考全面,单元测试完善
缺点 难以习惯队友的代码风格、编程习惯等,磨合期较难受 节奏难以同步

14.附加题:支持松耦合

我们选择交换dll的团队是(17373052, 17373053)团队。

他们的dll设计与我们大致相同,除了文件的命名方式以外,其他大致相同。

合并时遇到的问题有:

  • 他们并非由GUI和command两个函数构成,而是只有一个函数run(),由命令行参数进行区分。无论是否需要绘制,都会将点的信息存在points.txt中。我们修改了我们的读写方式,以适配他们的核心模块。
  • 他们的异常处理返回信息和我们不一样。他们的run()函数只定义了两种异常:输入异常和重合异常。我们为此修改了异常的返回信息。

经测试,命令行和GUI都可以正常使用。

后记:一点体会

posted @ 2020-03-23 19:16  sharinka  阅读(228)  评论(2编辑  收藏  举报