结对项目

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.9999999991.000000000的问题,所以为了解决这个问题,我们采用了(epsilon)的计算方式。

即对每个浮点数先在高精度上加一个极小值,然后再将其截断为float,这样像上面提到的问题就解决了。

通过上面一系列处理,不仅hash计算耗时问题解决了,同时也解决了精度问题,一举两得。

上面是VS性能检测器的分析结果

在上一次个人项目中,count_line_with_line的主要耗时点在comparehash上,而如今在计算函数内容没有大变动的情况下,get_intersection_withcomparehash的耗时基本持平,由此可见这个优化带来的收益还是很大的。

七、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屏幕共享的优点是基本没有卡顿,但是缺点是画质较差且不能同时编辑,指出具体某行代码时也有些麻烦

十三、结对编程的优缺点

结对编程的优点

  • 结对编程可以两人写一份代码,一份代码经过两人的审查,可靠性更高,不容易出问题
  • 结对编程的两人可以能力互补,相互弥补彼此能力的不足
  • 结对编程可以起到教育作用,有经验者可以在开发的过程中对新手进行教学
  • 结对编程中两人可以交流和共同设计,有利于设计出更优的实现

结对编程的缺点

  • 若两人水平相差较多,可能熟练者会花费相对较多的时间在教学中,效率不如单人开发
  • 若结对两人的关系不和,也可能存在配合不默契或者无法合作的情况
  • 有的人可能不喜欢自己编程时有他人观看,这时可能会对开发者造成心理上的不适

我的优点

  • 编码速度较快,能够较快将设计转化为实现
  • 重视测试,一般是先写接口再写测试最后写实现
  • 设计能力较好,能够较清晰的划分系统各个模块的关系和依赖,降低系统的耦合度

我的缺点

  • 细节问题思考不够到位,常出现细节问题没有考虑到
  • 核心算法的设计能力较弱

搭档的优点

  • 善于聆听,特别表现在能够聆听我的想法然后提出自己的意见,乐于接受别人的建议
  • 善于学习,能够较快学习和使用工作中所需要的技术和工具
  • 靠谱,所谓靠谱,就是事事有回应,每次我去找他沟通他基本都是秒回应,从不敷衍咕咕咕

搭档的缺点

  • 信心不足,有时面对一些挑战表现的有些怀疑和犹豫,但最终其实都可以战胜,所以缺乏自信

以上是关于结对项目的主要内容,如果未能解决你的问题,请参考以下文章

个人项目复用代码实现结对编程项目

结对-英文词频分析-结对项目总结

总结如何复用个人项目实现结对编程项目,以及结对编程的经验教训。

结对项目

结对编程项目报告

从结对编程初项目窥团队合作编程