代码改变世界

软工结对项目

2020-03-24 17:25  gzhBuaa  阅读(144)  评论(1编辑  收藏  举报
项目 内容
课程:北航计算机学院2020春季软件工程 班级博客地址
作业:结对编程项目——求解几何对象交点 项目要求
我在这个课程的目标是 提高编程能力,积累项目经验,提高团队协作能力
这个作业在哪个具体方面帮助我实现目标 提高协作能力,学习新的软件开发知识

1.项目地址

2.项目PSP表格

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

3.接口设计

  • Information Hiding: 只提供几何对象类、计算类等类的信息,包括成员变量、成员函数,类内部函数实现细节(包括构造函数、求解交点函数等)对外隐藏。这一过程是通过使用动态链接库实现的,我设计了intersectDll动态链接库,将负责求解交点的模块封装起来。调用者可以使用其内部的类,比如Line(表示直线、射线、线段的类)、Circle(表示圆)、Intersect(内置一系列函数,负责计算不同几何对象的交点)。
  • Interface Design: intersectDll暴露所有类的成员函数:Line(int,int,int,int,char),Circle(int,int,int),lineCrossLine(Line,Line),lineCrossCircle(Line,Circle),circleCrossCircle(Circle,Circle)等。调用者可以随意创建、使用几何对象,并通过调用Intersect类的一系列函数,求解不同几何对象的交点坐标。
  • Loose Coupling: 我的这种设计其实并不符合松耦合的要求,因为计算模块的类没有对外隐藏,而是可以被引用,所以计算模块对外暴露的信息较多。这样导致模块之间的相互依赖程度较高,也是我设计的缺点所在。

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

  • 我设计了一个Intersect类,其内部包含三个函数:lineCrossLine(Line,Line),lineCrossCircle(Line,Circle),circleCrossCircle(Circle,Circle)。三个函数分别用来求解线(L,R,S型线)与线的交点、线与圆的交点、圆与圆的交点。将构造好的Line与Circle对象传入Intersect类的方法中,返回一个包含交点对象Node的容器vector。几种几何对象类只与Intersect类具有联系,相互之间没有依赖关系。
  • 本次作业的关键在于直线类型产生了扩展,新增了射线与线段类型。对此,并没有必要构造新的集合对象类,只需要为Line对象增添一个标识kind,来代表它是直线、射线、线段这三种类型的哪一种。对于射线与线段类型,没有必要对求解的过程进行更改,可以把它们都看作是直线,先求解交点。得到求解的结果vector之后,判定容器中的Node对象是否合法:对于射线,交点的横坐标x和x2应该在x1的同侧,纵坐标y和y2应该在y1的同侧;对于线段,焦点的横、纵坐标x和y,应该分别在x1和x2、y1和y2之间。如果合法,则保留该交点;不合法,则删除。
  • 这样设计的好处,在于改动较少。只需要在Line中添加一个标识kind,以及一个判定交点是否合法的函数judgeLegal(),就可以完成扩展的功能。

5.UML图

  • UML图如下所示:

6.计算接口部分的性能改进

  • 本次作业中,在计算模块性能改进方面没有做太多的工作。只是将需要重复使用的几何对象的参数,在一开始提取出来,保存在局部变量里,算是做了一点小优化。性能分析图如下:

  • 由于测试样例规模不大,耗时主要集中在文件IO函数上。除此之外,计算类的三个函数lineCrossLine()、lineCrossCircle()、circleCrossCircle()耗时较多,其中耗时最多的是lineCrossCircle()函数,即求解线与圆的交点。这是由于数学上的运算复杂度造成的,相比来说求解线和线的交点、圆和圆的交点,在数学上运算的步骤更少。找到一种开销更小的方法,或是在细节上优化其求解过程,成为突破性能瓶颈的关键。

7.契约式设计和代码协定

  • 之前在oo课程中,曾经学习过JML语言,接触了一些关于契约式设计的相关知识。JML的重要内容是方法规格,包括核心三个方面:前置条件、后置条件和副作用约定。这对应着契约式设计的三大部分:前置条件、后置条件、类不变项。
  • 优点: 对于编程者来说,契约式设计方法可以让设计思路更加清晰、严谨。逻辑严格的规格,让代码实现过程更加规范,有利于减少编码上的漏洞。对于维护者来说,代码具有规格说明,有助于理解代码,提高可维护性。
  • 缺点: 规格设计需要耗费大量时间,降低工作效率,除非对于正确性要求极高,在实际工程中很难真正做到,且没有必要进行严格地契约式设计。
  • 在项目中的体现:我在具体的代码实现过程中,附加了很多注释来说明函数的功能、数据的约束等,我觉得这从某种程度上体现了契约式设计的准则。例子如下:
class _declspec(dllexport) Node {
public:
	//judgeCross指示该交点是否有几何意义,其取值只能为0或1。
	//如果judgeCross=0,说明该交点没有几何意义。适用于以下情况:两几何对象虽没有交点,
	//但求解交点的函数仍须返回Node对象,以保证函数返回值的统一性;
	//如果judgeCross=1,说明该交点是两几何对象的交点。
	int judgeCross = 0;
	double x = 0.0, y = 0.0;

	//重载 < 运算符,目的是使用unordered_set
	bool operator < (const Node node) const;

	Node(int judgeCross);
	Node(int judgeCross, double x, double y);
};

8.计算模块部分单元测试展示

  • 部分代码如下:
Line* line1 = new Line(0, 0, 1, 1, 'L');
Line* line2 = new Line(1, 1, 3, 0, 'R');
Line* line3 = new Line(2, 0, 2, -1123,'S');
Circle* circle4 = new Circle(10, 0, 1);
Circle* circle5 = new Circle(0, 0, 4);
Circle* circle6 = new Circle(3, 0, 1);
Circle* circle7 = new Circle(0, 0, 1);
Intersect i;
vector<Node> v0 = i.lineCrossLine(*line1, *line2);
vector<Node> v1 = i.lineCrossLine(*line1, *line3);
vector<Node> v2 = i.lineCrossCircle(*line1, *circle4);
vector<Node> v3 = i.circleCrossCircle(*circle5, *circle6);
vector<Node> v4 = i.circleCrossCircle(*circle5, *circle7);
Assert::AreEqual((int)v0.size(), 1);
Assert::AreEqual((int)v1.at(0).judgeCross, 0);
Assert::AreEqual((int)v2.at(0).judgeCross, 0);
Assert::AreEqual((int)(v3.size()), 1);
Assert::AreEqual(v3.at(0).x, 4.0);
Assert::AreEqual((int)(v4.at(0).judgeCross), 0); 
  • 单元测试覆盖求解线(L,R,S型线)与线的交点、线与圆的交点、圆与圆的交点的函数。并且构造特殊的线类型(与x轴垂直),与线(L,R,S型)和圆分别求交点。此外,构造相互平行的线,判断交点个数。对于求线与圆交点的情况,分别考虑相离、相切、相交;求圆与圆交点的情况,分别考虑相离、外切、相交、内切、内含。

9.计算模块部分异常处理说明

  • 我们考虑到的异常情况主要有以下几大类:
    1、几何对象类别异常,即输入了除'L'、'R'、'S'、'C'以外的字符来标识类别。
    2、输入点的范围异常,即输入了(-100000, 100000)范围之外的坐标点。
    3、输入点重复,即输入线类型的两个坐标点重合。
    4、圆的半径异常,即输入了小于或等于0的半径。

10.界面模块的详细设计过程

  • 我是用win32来实现界面模块的。在初始界面的menu中,添加一些输入几何参数的选项。每个输入选项绑定一个dialog,上面带有文本框来接收用户的输入。整个界面没有添加别的组件,用来绘制坐标轴、几何图形、交点坐标等。
  • 核心的函数是CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM),负责处理主窗口的消息。主窗口menu中设置了“确定”选项,当参数输入完毕、点击确定之后,弹出消息窗口,如果输入合法,则调用InvalidateRect()函数重新绘制界面中的所有对象。代码如下:
case ID_QUEREN:
{
		if ((lines[numofobj][0] == lines[numofobj][2]) &&
		(lines[numofobj][1] == lines[numofobj][3])) {
			MessageBox(hWnd, _T("输入了重复的两个点,请重新输入!"), _T("Coordinate error!"), MB_OK);
			break;
		}
		numofobj++;
		MessageBox(hWnd, _T("输入成功!"), _T("输入成功!"), MB_OK | MB_ICONINFORMATION);
		InvalidateRect(hWnd, NULL, TRUE);
}
break;
  • 绘制图像的代码,在处理WM_PAINT消息的模块之中。每当WndProc()接收了WM_PAINT消息,会调用如下代码:
case WM_PAINT:
{
    hdc = BeginPaint(hWnd, &ps);
	initPaint(hWnd);
	SetBkMode(hdc, TRANSPARENT);
	for (int i = 0; i < numofobj; i++) {
		paintLine(hWnd, i,0);
	}
	int a, b;
	nodeset.clear();
	for (a = 0; a < numofobj - 1; a++) {
		for (b = a + 1; b < numofobj; b++) {
			intersect(hWnd,a, b);
		}
	}
	//输出交点个数
	string s = "当前交点个数:" + to_string(nodeset.size());
	TCHAR S[64];
	SetBkMode(GetDC(hWnd), TRANSPARENT);
	MultiByteToWideChar(CP_ACP, 0, const_cast<char*>(s.c_str()), 64, S, 64);
	TextOut(GetDC(hWnd), 50, 50, S, lstrlen(S));
	EndPaint(hWnd, &ps); 
}
break;
  • 界面截图:

  • 删除射线之后,界面如下:

11.界面模块与计算模块的对接

  • 界面模块与计算模块对接的位置,是在intersect()函数中,计算两个几何对象的交点,并在交点的位置绘制出坐标。几何对象的所有参数在之前的WndProc()中被接收,并保存在lines二维数组中。由于计算模块的所有类(几何对象类、计算类)对界面模块公开,所以可以根据lines中的参数构造相应几何对象(Line、Circle),之后调用Intersect类的一些函数,求出交点坐标。
  • 下面是当A、B两几何对象均为线时,求解交点的对应代码:
	Line* line1 = new Line(lines[i][0], lines[i][1], lines[i][2], lines[i][3]);
	Line* line2 = new Line(lines[j][0], lines[j][1], lines[j][2], lines[j][3]);
	Intersect* inter = new Intersect();
	result = inter->lineCrossLine(*line1,*line2);
	//判断交点是否合法
	result = judgeLegalll(A, B, *line1, *line2, result);
	//绘制交点坐标
	paintIntersect(hwnd, result);
  • 值得注意的是judgeLegalll和paintIntersect函数,这两个函数是在界面模块内部实现的。前者用于判断当几何对象为线段或射线时,求出的交点是否在几何对象上(求解过程中把它们都当作直线来计算)。后者是在界面中的相应位置绘制出交点坐标,为了避免绘制的字符串的背景覆盖几何图形,需要设置背景色为透明,即SetBkMode(hdc, TRANSPARENT)。paintIntersect函数存在一个设计上的问题,就是当交点的位置比较集中时,绘制的坐标可能会相互重叠。目前还没有想出一个比较好的解决办法。

12.描述结对的过程

  • 由于网络原因,Live Share等实时视频交流方式效果较差。所以我和搭档的结对方式是分工合作,将任务划分为几个模块,各自完成相应的部分。之后通过微信语音、聊天等线上交流方式,进行问题的研讨、模块的整合。截图如下:

13.结对编程的优缺点

  • 结对编程的优点,在于采用领航员-驾驶员的模式,一人负责编码、一人负责审核与指导,相当于不断地进行代码复审,有助于提高代码质量。结对过程中,两人的角色每隔一段时间进行轮换,这使得编码人员在工作一段时间之后可以放松、活动身体,而指导人员则以较好的身体与精神状态进行编码,有助于保持编码与指导过程的高效率、高质量。但是结对编程也有一定的缺点,比如结对的两人有时会发生建议冲突,或是要花费时间理解对方的思路。特别是如果结对的两人水平差距较大,配合不好,可能会产生负效益,造成工作效率低下。
  • 结对二人的优缺点:
人员 优点 缺点
1.目标明晰,2.不断钻研摸索,3.有耐心 时间安排不合理,赶DDL
许天立 1.写代码很快,2.有钻研精神,3.合作能力强 和我一样,赶DDL