结对项目
Posted nocturne
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结对项目相关的知识,希望对你有一定的参考价值。
一、项目地址
- 教学班级:006
二、开发耗时评估
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 15 | 10 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 60 | 90 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0 | 0 |
· Design | · 具体设计 | 90 | 100 |
· Coding | · 具体编码 | 420 | 500 |
· Code Review | · 代码复审 | 120 | 140 |
· Test | · 测试(自我测试,修改代码,提交修改) | 90 | 150 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 0 | 0 |
· Size Measurement | · 计算工作量 | 5 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 0 | 0 |
合计 |
三、接口设计
这次的项目涉及到了前后端两个部分,为了实现前后端的分离,我们首先将前后端的职责进行了明确的划分。
- 前端:仅负责做数据输入、获取输出、根据获取的输出进行图形绘制
- 后端:负责接收前端传入的输入、对输入进行合法性检查、提供核心计算功能以及数据获取的接口
由此,这个项目的前后端就清晰的分离开了,具体表现在
- 前端不依赖具体后端:即使后端更换了实现,只要提供相应的数据和计算接口,前端不需要做任何更改
- 后端不需要了解前端:后端只需提供自身的数据以及计算功能,不需要知道前端如何使用自身的数据,即使脱离前端,后端的计算功能依旧可以独自使用
然后,根据最终产品的功能需求,我们在后端模块中设计了如下几个核心接口
- 添加数据的接口,前端只需将原始输入传入即可
- 计算功能接口,前端通过调用可以让后端开始计算
- 数据获取接口,前端通过调用来获取后端内部存储的数据
- 删除接口,前端通过调用让后端对相应的数据进行删除
四、计算模块接口的设计和实现
相比于上次的实现,这次的计算模块只增加了一个接口类,用于供前端调用
相比于上次的内容,这次增加了线段和射线,其本质是对直线的左右边界做了限定,所以我们采用扩展原有的Line
类的方法来支持本次对扩展。
本次扩展对Line
类添加了左右边界的属性
- 直线:左右边界都为无穷(使用INT_MAX表示)
- 线段:左右边界都为确定值
- 射线:单边界为确定值,另一边界为无穷
这样,原有的核心计算方法依然可以正常工作,只需在计算出具体Point
交点时对其坐标进行判断即可,即所有线都按直线计算,然后再使用自身的边界对结果进行筛选
此外,这次添加了异常处理功能
这部分是使用c++
自身的异常机制来进行实现的。
我们首先针对题意分析出了一系列可能的异常情况,然后分别在可能出现异常的代码段进行了异常抛出,同时,我们在后端抛出异常时会设定好其中的异常信息,从而使得前端只需捕获异常,然后向用户回显其中的异常信息即可
而对于输入处理方面,我们采用了C++11
的正则表达式来处理,根据题目的要求,我们指定了一系列的正则表达式,对每种图形的输入都做了检查,当检查失败时同样会采用抛出异常的方式来进行处理
五、计算模块UML
六、计算模块接口性能改进
对比上次的实现,这次对浮点数的处理做了一些优化
之前采用的是long double
直接hash
和==
运算,不仅hash
耗时较长,而且会存在精度的问题,为了解决这些方面的问题,这次对浮点数的处理进行了优化
首先根据题目给出的数据范围,我们大致估计出了数据精度只需在float
即可,所以我们首先使用了float
来代替long double
进行hash
操作
虽然这样可以加快hash
操作的速度,但是精度问题依然没有解决,会出现0.999999999
和1.000000000
的问题,所以为了解决这个问题,我们采用了(epsilon)的计算方式。
即对每个浮点数先在高精度上加一个极小值,然后再将其截断为float
,这样像上面提到的问题就解决了。
通过上面一系列处理,不仅hash
计算耗时问题解决了,同时也解决了精度问题,一举两得。
上面是VS性能检测器的分析结果
在上一次个人项目中,count_line_with_line
的主要耗时点在compare
和hash
上,而如今在计算函数内容没有大变动的情况下,get_intersection_with
和compare
与hash
的耗时基本持平,由此可见这个优化带来的收益还是很大的。
七、Design by Contract与Code Contract
契约式编程在多人开发中使用较为广泛,其主要特点就是开发建立在规约之上,所有人共同制定统一的代码规约,其一般包括
- 输入参数的规约
- 输出结果的规约
- 不变性的规约
基于团队定制的一系列规约,每个人都只需要注意自身是否完成了规约中规定的内容即可,而不需额外的顾虑。
这种方式的好处是显而易见的,每个人根据自身开发范围相关的规约进行开发,而无需考虑额外的事情,由此肯定会带来代码耦合度的降低以及团队开发效率的提升。
但同时这个方式也存在一些弊端,首先,规范的制定是一件很难的事情,规范强调其稳定性,如果规范频繁发生变动,那么所有相关的开发人员都要受到影响。其次,为了是自身完成的代码满足规约,一般会采用一种防御式编程的方式进行开发,这种方式的特点为广用Assert断言,会使得代码变得比较冗杂。
而在我们这次结对开发中,我们在前后端接口上使用了这种方式,我们先根据需要完成的功能共同探讨了后端需要给前端提供的接口,制定了接口的输入输出以及异常的规范,然后再进行开发,前后端可以同时进行,效率很高。
八、计算模块单元测试
针对这次的项目新增的一些功能以及异常处理方面,我们也对第一阶段的项目的测试做了很多补充和扩展
上面唯一没有测试的类是main所在的类,因为在UI方面和exe方面进行了相应的测试,也因为和文件的输入输入相关,单元测试起来会比较受环境影响,所以这里并没有进行相关的测试。
/*
两个线段,他们可能只有一个端点相交
*/
TEST_METHOD(TestTwoSegmentIntersecInEnd) {
Line line1("S 0 0 1 1");
Line line2("S 0 0 1 -1");
std::vector<Point> result = line1.get_intersection_with(line2);
Assert::AreEqual(1, (int)result.size());
}
/*
两线段共线,且有一个公共交点(端点)
*/
TEST_METHOD(TestTwoSegmentInSameLineHaveOneIntersection) {
Line line1("S 0 0 1 1");
Line line2("S 1 1 2 2");
std::vector<Point> result = line1.get_intersection_with(line2);
Assert::AreEqual(1, (int)result.size());
}
/*
两线段共线,且无交点
*/
TEST_METHOD(TestTwoSegmentInSameLineHaveNoIntersection) {
Line line1("S 0 0 1 1");
Line line2("S 2 2 3 3");
std::vector<Point> result = line1.get_intersection_with(line2);
Assert::AreEqual(0, (int)result.size());
}
上面是一部分测试代码,可以看到,我们针对一些边缘情况进行了较为全面的测试,涵盖了每一种组合情况,同时,为了便于出错时进行bug修复,我们对比较重要的测试进行了注释解释,从而便于后期维护。
九、计算模块异常处理
这次的异常处理我们主要考虑了下面几种
- 参数格式不正确(错误字符、多个空格)
- 圆半径r<=0
- 坐标超限
- 输入点重合
- 有无限个交点(图形重合)
针对上面这几种异常情况,我们都会将相应的错误信息包装在exception中抛出,让UI做回显
/*
圆半径异常
*/
TEST_METHOD(IllegalCircleRedix1) {
auto func = [] {
Solution s;
s.add_component("C 0 0 0");
};
Assert::ExpectException<std::exception>(func);
}
/*
非法字符输入(小写字母,特殊符号)
*/
TEST_METHOD(IllegalCharacterInput1) {
auto func = [] {
Solution s;
s.add_component("l 0 0 1 1");
};
Assert::ExpectException<std::exception>(func);
}
/*
输入点重合
*/
TEST_METHOD(PointCollision1) {
auto func = [] {
Solution s;
s.add_component("L 0 0 0 0");
};
Assert::ExpectException<std::exception>(func);
}
/*
两线段共线,且有多个交点(部分重合),期望抛出异常
*/
TEST_METHOD(TestTwoSegmentCoverPart) {
auto func = [] {
Line line1("S 0 0 3 3");
Line line2("S 1 1 5 5");
line1.get_intersection_with(line2);
};
Assert::ExpectException<std::exception>(func);
}
/*
各种坐标超限
*/
TEST_METHOD(ArgumentOutOfBound1) {
auto func = [] {
Solution s;
s.add_component("C 100001 0 1");
};
Assert::ExpectException<std::exception>(func);
}
十、界面模块设计说明
十一、界面模块与计算模块的对接
十二、结对过程
关于这次结对编程,我们采用的是VS的Live Share和QQ屏幕共享来进行的。
首先是Live Share,这个功能很强,但是因为网络的问题,一般40-60分钟持续连接后就会出现卡顿,优点是可以同时开发以及编码。
而QQ屏幕共享的优点是基本没有卡顿,但是缺点是画质较差且不能同时编辑,指出具体某行代码时也有些麻烦
十三、结对编程的优缺点
结对编程的优点:
- 结对编程可以两人写一份代码,一份代码经过两人的审查,可靠性更高,不容易出问题
- 结对编程的两人可以能力互补,相互弥补彼此能力的不足
- 结对编程可以起到教育作用,有经验者可以在开发的过程中对新手进行教学
- 结对编程中两人可以交流和共同设计,有利于设计出更优的实现
结对编程的缺点:
- 若两人水平相差较多,可能熟练者会花费相对较多的时间在教学中,效率不如单人开发
- 若结对两人的关系不和,也可能存在配合不默契或者无法合作的情况
- 有的人可能不喜欢自己编程时有他人观看,这时可能会对开发者造成心理上的不适
我的优点:
- 编码速度较快,能够较快将设计转化为实现
- 重视测试,一般是先写接口再写测试最后写实现
- 设计能力较好,能够较清晰的划分系统各个模块的关系和依赖,降低系统的耦合度
我的缺点:
- 细节问题思考不够到位,常出现细节问题没有考虑到
- 核心算法的设计能力较弱
搭档的优点:
- 善于聆听,特别表现在能够聆听我的想法然后提出自己的意见,乐于接受别人的建议
- 善于学习,能够较快学习和使用工作中所需要的技术和工具
- 靠谱,所谓靠谱,就是事事有回应,每次我去找他沟通他基本都是秒回应,从不敷衍咕咕咕
搭档的缺点:
- 信心不足,有时面对一些挑战表现的有些怀疑和犹豫,但最终其实都可以战胜,所以缺乏自信
以上是关于结对项目的主要内容,如果未能解决你的问题,请参考以下文章