结对项目作业
项目 | 内容 |
---|---|
这个作业属于哪个课程/ | 2020年春季计算机学院软件工程(罗杰 任健) |
这个作业的要求在哪里? | 结对项目作业 |
我在这个课程的目标是? | 提高代码水平,熟悉团队合作 |
这个作业在哪个具体方面帮助我实现目标? | 分析成熟软件的优缺点,从中学习软件开发、设计经验 |
1. 教学班级和可克隆的 Github 项目地址。
- 教学班级:006
- 项目地址:https://github.com/Pupil59/PairWork
2. PSP 表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 1600 | 30 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 60 | 180 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
· Design | · 具体设计 | 60 | 120 |
· Coding | · 具体编码 | 600 | 900 |
· Code Review | · 代码复审 | 80 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | 300 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 90 | 30 |
· Size Measurement | · 计算工作量 | 30 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 20 |
合计 | 1370 | 1600 |
3. 运用到的设计方法。
- 信息隐藏:UI模块在调用计算模块的接口时,只将参数传递给了就算模块,计算模块的细节全部被隐藏。添加删除画图等等功能全部被封装在Core中,UI只负责相应用户界面。但类的属性是共有的,没有遵循信息隐藏原则。
- 松耦合:我们将判断直线是否重合、点是否在线上、画图等功能作为类的方法实现,这样只需修改相应的方法,而不用修改调用、UI模块等。
4. 计算模块接口的设计与实现过程。
-
本次作业增加了射线、线段类。根据需求的改变,我们初步预计应添加两个类。为了尽量少地重构代码,我们选择将直线类作为射线、线段类的父类。
- 线段类添加了
startX
、startY
、endX
、endY
四个属性,分别用来标记线段的起点和终点坐标。继承了构造函数,用来初始化这些属性。 - 射线类添加了
startX
、startY
、direct
三个属性,用来记录射线的起点和射线的方向。 - 射线、线段、类继承了直线类的方法
isInLine
、isRepeat
、draw
、show
,分别用来判断点是否在直线上、判断几何图形之间是否有无数个焦点(重合)、在画布上画出几何图形、展示几何图形信息。
圆、点类与个人项目相同,没有做更改。
- 线段类添加了
-
四个接口来实现几何图形的增删功能:
addLine
、addCircle
、delLine
、delCircle
。addLine
、addCircle
两接口需要传入对应几何图形的参数。``addLine首先需要传入
L、
S或者
R来区分直线、线段和射线,其次传入两个点的坐标。
addCricle`只需要传入三个参数,分表代表圆心的横纵坐标以及圆的直径。delLine
、delCircle
只需要传入一个参数,即相应几何对象在相应容器(vector
)内的下标。
-
两个接口来展示几何图形的详细信息:
showLine
、showCircle
。showLine
会调用Line
、SegmentLine
、RaysLine
的show方法,展示相应几何图形的详细信息:直线参数,起点终点坐标,射线方向等。并展示几何图形在容器vector<Line*>lines
中的下标。showCircle
会直接展示Circle
的三个参数以及在容器vector<Circle>circles
中的下标。
-
solve
接口来计算几何图形之间的交点,基本保持个人项目代码不变。 -
两个接口进行几何图形的绘制:
draw
、drawPoint
。调用了graphics.h头文件来绘制几何图形。 -
inputFile
接口用来读取指定路径下的文件。 -
算法关键
- 对本次作业而言,我们采用的求交点方法仍然与上次作业相同。首先求出线段或者射线所在的直线与其他几何对象的交点,关键之出在于判断交点是否在线段、射线上。
- 我们使用了继承,避免了大规模的代码重构,并通过类的方法
isInLine
来判断。
5. UML
6. 计算模块接口部分的性能改进
我们用时最长的函数是lineIntersectLine
函数,经过性能分析可以看出是vector的insert方法耗时最长。考虑到更换容器类需要一定规模的代码重构,1000条直线运行时间大概为5.9秒的性能基本满足了我们的需求,我们并未对此进行优化。
7. Design by Contract 与 Code Contract
契约式编程
它规定软件设计者应该为软件组件定义正式的,精确的和可验证的接口规范,该规范扩展了抽象数据类型的普通定义,包括前置条件,后置条件和不变量。
- 优点:使用者和被调用者地位平等,双方必须彼此履行义务,才可以行驶权利。调用者必须提供正确的参数,被调用者必须保证正确的结果和调用者要求的不变性。双方都有必须履行的义务,也有使用的权利,这样就保证了双方代码的质量,提高了软件工程的效率和质量。
- 缺点:契约式编程并未被标准化,因此项目之间的定义和修改各不一样,给代码造成很大混乱。
- 在结对作业中,我们没有使用到契约式编程。但在计算模块和UI模块的对接上,我们通过口头约束,规定了一些接口的参数、功能等等。
8. 计算模块部分单元测试展示。
-
射线所在的直线与圆相交,但交点不在射线上。射线分别为四个方向:若不垂直于X轴,沿X轴正方向,沿X轴负方向;若垂直于X轴,分为沿Y轴正方向,沿Y轴负方向。针对四种情况分别构建测试用例。
Line l1(0, 1, 1, 0); RaysLine r1(1, 1, 2, 2); lineIntersectLine(l1, r1); Assert::AreEqual((int)points.size(), 0); RaysLine r2(1, 1, 1, 2); lineIntersectLine(l1, r2); Assert::AreEqual((int)points.size(), 0); RaysLine r3(0, 0, -1, -1); lineIntersectLine(l1, r3); Assert::AreEqual((int)points.size(), 0); RaysLine r4(-1, -1, -1, -2); lineIntersectLine(l1, r4); Assert::AreEqual((int)points.size(), 0); points.clear();
-
线段所在的直线与圆相交,但交点不在射线上。大体分为两种情况:线段与X轴垂直,线段与X轴不垂直。其中为了测试构造函数初始化startX、startY和endX、endY,又设计了同一组参数交换前后两个点的情况。
Line l1(0, 1, 1, 0); SegmentLine s1(1, 1, 2, 2); lineIntersectLine(l1, s1); Assert::AreEqual((int)points.size(), 0); SegmentLine s2(1, 1, 1, 2); lineIntersectLine(l1, s2); Assert::AreEqual((int)points.size(), 0); SegmentLine s3(1, 2, 1, 1); lineIntersectLine(l1, s3); Assert::AreEqual((int)points.size(), 0); SegmentLine s4(-1, -1, -2, -2); lineIntersectLine(l1, s4); Assert::AreEqual((int)points.size(), 0); SegmentLine s5(-1, -1, -1, -2); lineIntersectLine(l1, s5); Assert::AreEqual((int)points.size(), 0); points.clear();
-
射线与圆相交
Circle c1(0, 0, 1); RaysLine r1(0, 1, 1, 1); lineIntersectCircle(r1, c1); Assert::AreEqual((int)points.size(), 1); Assert::AreEqual(points.begin()->x, 0.0); Assert::AreEqual(points.begin()->y, 1.0); points.clear();
-
线段与圆相交
Circle c1(0, 0, 1); SegmentLine s1(0, 1, 0, -1); lineIntersectCircle(s1, c1); Assert::AreEqual((int)points.size(), 2); set<Point>::iterator it = points.begin(); Assert::AreEqual(it->x, 0.0); Assert::AreEqual(it->y, -1.0); it++; Assert::AreEqual(it->x, 0.0); Assert::AreEqual(it->y, 1.0); points.clear();
-
inputArg函数测试
char* argv[5] = { "Intersect.exe", "-i", "in.txt", "-o", "out.txt" }; inputArg(5, argv); Assert::AreEqual((int)lines.size(), 3); Assert::AreEqual((int)circles.size(), 1); lines.clear(); circles.clear(); fin.close(); fout.close();
-
solve等函数的测试沿用上次作业的代码
-
代码覆盖率测试
9. 计算模块部分异常处理说明。
-
命令行参数
-
没有正确使用
-i
或者-o
参数,而是输入了其他-?
参数try { char* argv[5] = { "Intersect.exe", "-v", "in.txt", "-o", "out.txt" }; inputArg(5, argv); } catch (char* msg) { Assert::AreEqual(msg, "Incorrect command line parameters, please use '-i' for input, '-o' for output"); }
-
缺少
-i
参数try { char* argv[5] = { "Intersect.exe", "iii", "in.txt", "-o", "out.txt" }; inputArg(5, argv); } catch (char* msg) { Assert::AreEqual(msg, "'-i' is not found, please use '-i'"); }
-
缺少
-o
参数try { char* argv[5] = { "Intersect.exe", "-i", "in.txt", "oooo", "out.txt" }; inputArg(5, argv); } catch (char* msg) { Assert::AreEqual(msg, "'-o' is not found, please use '-o'"); }
-
未找到输入文件
try { char* path = "int.txt"; inputFile(path); } catch (char* msg) { Assert::AreEqual(msg, "can not locate input file, please check the path of the file"); fin.close(); }
-
-
文件输入参数错误
-
第一行不是整数
try { char* path = "case1.txt"; inputFile(path); } catch (char* msg) { Assert::AreEqual(msg, "Incorrect Num at line 1, the Num must be a positive integer"); fin.close(); } /*文件内容: SSSSSS C 3 3 3 S 2 4 3 2 L -1 4 5 2 R 2 5 -1 2 */
-
类型错误
try { char* path = "case2.txt"; inputFile(path); } catch (char* msg) { Assert::AreEqual(msg, "Incorrect type, correct types are L, S, R, C"); fin.close(); } /*文件内容: 4 MAA 3 3 3 A 2 4 3 2 L -1 4 5 2 R 2 5 -1 2 */
-
圆参数错误(缺参数或参数不为整数)
try { char* path = "case3.txt"; inputFile(path); } catch (char* msg) { Assert::AreEqual(msg, "Incorrect circle parameter, please input integer and check the number of parameters"); fin.close(); } /*文件内容: 4 C 3 3 a S 2 4 3 2 L -1 4 5 2 R 2 5 -1 2 */
-
直线参数错误(缺参数或参数不为整数)
try { char* path = "case4.txt"; inputFile(path); } catch (char* msg) { Assert::AreEqual(msg, "Incorrect line parameter, please input integer and check the number of parameters"); fin.close(); } /*文件内容: 4 C 3 3 3 S 2 4 3 2 L -1 4 5 2 R 2 5 */
-
几何图形参数超出范围
try { addLine("R", 1, 2, 3, 100000); } catch (char* msg) { Assert::AreEqual(msg, "parameter out of range, correct range is (-100000, 100000)"); }
-
直线参数两点重合
try { addLine("R", 1, 1, 1, 1); } catch (char* msg) { Assert::AreEqual(msg, "two points coincide in a line definition, please input two different points"); }
-
线与线(包括直线、射线、线段)重合
try { addLine("L", 1, 1, 2, 2); addLine("L", 3, 3, 4, 4); } catch (char* msg) { Assert::AreEqual(msg, "two lines coincide"); lines.clear(); } //射线与直线重合 try { addLine("R", 1, 1, 2, 2); addLine("L", 3, 3, 4, 4); } catch (char* msg) { Assert::AreEqual(msg, "two lines coincide"); lines.clear(); } //射线与射线重合 try { addLine("R", 1, 1, 2, 2); addLine("R", 3, 3, 4, 4); } catch (char* msg) { Assert::AreEqual(msg, "two lines coincide"); lines.clear(); } try { addLine("R", 0, 0, 1, 1); addLine("S", 1, 1, -1, -1); } catch (char* msg) { Assert::AreEqual(msg, "two lines coincide"); lines.clear(); } try { addLine("S", 0, 1, 0, -1); addLine("L", 0, 0, 0, 2); } catch (char* msg) { Assert::AreEqual(msg, "two lines coincide"); lines.clear(); } //线段与射线重合 try { addLine("S", 1, 1, -1, -1); addLine("R", 0, 0, 1, 1); } catch (char* msg) { Assert::AreEqual(msg, "two lines coincide"); lines.clear(); }
-
圆的半径必须是正整数
try { addCircle(1, 2, -3); } catch (char* msg) { Assert::AreEqual(msg, "radius of circle must be a positive integer"); }
-
圆重复
try { addCircle(1, 2, 3); addCircle(1, 2, 3); } catch (char* msg) { Assert::AreEqual(msg, "this circle exists"); }
-
10. 界面模块的详细设计过程。
-
UI模块我们采用了MFC
初始设计:
最终设计:
UI中,第一行可输入文件路径,点击import可导入文件。导入成功会弹出窗口表示成功。不成功会又相应的异常提示。
第二行五个窗口可分别传入五个参数,第一个敞口可传入L、R、S分别代表直线、射线、线段。后四个窗口可传入两个点的坐标。点击AddLine可将几何图形添加。添加成功会弹出出succeed窗口,不成功会有异常提示。DelLine按钮前的窗口可输入一个数字,代表容器内集合对象的下表,点击DelLine可删除该集合对象。ShowLines可查看容器内各集合对象的详细信息和下标。第三行同第二行。
Draw按钮可画出当前所有的集合对象。Solve可以求出交点,并画出交点。关闭弹出的画图界会终止整个程序。
各个按钮的代码实现
-
Draw
void CUIDlg::OnBnClickedButton1() { //直接调用接口 draw(); }
-
Solve
void CUIDlg::OnBnClickedButton2() { //直接调用接口 solve(); draw(); drawPoint(); }
-
Import
void CUIDlg::OnBnClickedButton3() { try { CString cstr; GetDlgItemText(IDC_EDIT1, cstr); int n = cstr.GetLength(); int len = WideCharToMultiByte(CP_ACP, 0, cstr, n, NULL, 0, NULL, NULL); char* path = new char[len + 1]; WideCharToMultiByte(CP_ACP, 0, cstr, n, path, len, NULL, NULL); path[len] = '\0'; inputFile((char*)path); string str = "import from file succeed!"; string cap = "Message"; MessageBox((CString)str.c_str(), (CString)cap.c_str()); } catch (const char* msg) { CString cmsg = s2c(msg); AfxMessageBox(cmsg); return; } }
-
AddLine
void CUIDlg::OnBnClickedButton4() { // 从编辑框读入数据,转化数据类型,调用接口 // 在这里对读入的数据进行了处理 // 会对空数据和错误的数据进行预处理,确保传入的参数是正确的 try { CString ctype, cx1, cy1, cx2, cy2; GetDlgItemText(IDC_EDIT2, ctype); GetDlgItemText(IDC_EDIT3, cx1); GetDlgItemText(IDC_EDIT4, cy1); GetDlgItemText(IDC_EDIT5, cx2); GetDlgItemText(IDC_EDIT6, cy2); if (ctype.GetLength() == 0 || cx1.GetLength() == 0 || cy1.GetLength() == 0 || cx2.GetLength() == 0 || cy2.GetLength() == 0) { throw "type or parameters can not be NULL"; } string type = c2s(ctype); string x1 = c2s(cx1); string y1 = c2s(cy1); string x2 = c2s(cx2); string y2 = c2s(cy2); if (type != "L" && type != "R" && type != "S") { throw "Line type must be L, R or S!"; } if (!isDigit(x1) || !isDigit(y1) || !isDigit(x2) || !isDigit(y2)) { throw "parameters must be integer!"; } addLine(type, (long long)stoi(x1), (long long)stoi(y1), (long long)stoi(x2), (long long)stoi(y2)); string str = "Add line succeed!"; string cap = "Message"; MessageBox((CString)str.c_str(), (CString)cap.c_str()); } catch (const char* msg) { CString cmsg = s2c(msg); AfxMessageBox(cmsg); } }
-
DelCircle
void CUIDlg::OnBnClickedButton8() { try { //同上 CString cindex; GetDlgItemText(IDC_EDIT11, cindex); if (cindex.GetLength() == 0) { throw "index can not be NULL"; } string index = c2s(cindex); if (!isDigit(index)) { throw "index must be integer!"; } delCircle((long long)stoi(index)); string str = "Delete Circle succeed!"; string cap = "Message"; MessageBox((CString)str.c_str(), (CString)cap.c_str()); } catch (const char* msg) { CString cmsg = s2c(msg); AfxMessageBox(cmsg); } }
-
ShowCircle
void CUIDlg::OnBnClickedButton9() { // 直接调用接口 showCircle(); }
-
11. 界面模块与计算模块的对接
-
两模块的对接如10中给出的代码。对接时直接导入dll文件和lib文件,添加头文件:
#pragma once #pragma comment(lib, "Core.lib") #include <string> using namespace std; extern "C" _declspec(dllimport) void addLine(string type, long long x1, long long y1, long long x2, long long y2); extern "C" _declspec(dllimport) void addCircle(long long x, long long y, long long r); extern "C" _declspec(dllimport) void delLine(int index); extern "C" _declspec(dllimport) void delCircle(int index); extern "C" _declspec(dllimport) void inputFile(char* path); extern "C" _declspec(dllimport) int solve(); extern "C" _declspec(dllimport) void draw(); extern "C" _declspec(dllimport) void drawPoint(); extern "C" _declspec(dllimport) void showLine(); extern "C" _declspec(dllimport) void showCircle();
生成dll文件:
导入dll文件:
-
实现的功能
从文件导入:
添加几何对象:
show:
删除几何对象:
Draw:
solve:
12. 结对过程
我与17373100窦铮 同学进行了结对
13. 结对优缺点分析
-
结队编程
- 优点:两个人可以互相监督,降低低级错误(拼错单词等);也可以轮流休息,增加效率。
- 缺点:结队过程中理念相互碰撞时比较难以调解,会降低效率。
-
合作评价
伙伴 我 优点 编程细心,bug少;脾气好,发生争执也很有耐心;时间管理到位 理解能力强;代码风格规范;能与队友高效交流 缺点 与人交流容易上头 代码bug多,基础不扎实
14. 实际花费时间
见问题2 PSP表格