结对项目作业

项目 内容
这个作业属于哪个课程/ 2020年春季计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里? 结对项目作业
我在这个课程的目标是? 提高代码水平,熟悉团队合作
这个作业在哪个具体方面帮助我实现目标? 分析成熟软件的优缺点,从中学习软件开发、设计经验

1. 教学班级和可克隆的 Github 项目地址。

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. 计算模块接口的设计与实现过程。

  • 本次作业增加了射线、线段类。根据需求的改变,我们初步预计应添加两个类。为了尽量少地重构代码,我们选择将直线类作为射线、线段类的父类。

    • 线段类添加了startXstartYendXendY 四个属性,分别用来标记线段的起点和终点坐标。继承了构造函数,用来初始化这些属性。
    • 射线类添加了startXstartYdirect 三个属性,用来记录射线的起点和射线的方向。
    • 射线、线段、类继承了直线类的方法isInLineisRepeatdrawshow,分别用来判断点是否在直线上、判断几何图形之间是否有无数个焦点(重合)、在画布上画出几何图形、展示几何图形信息。

    圆、点类与个人项目相同,没有做更改。

  • 四个接口来实现几何图形的增删功能:addLineaddCircledelLinedelCircle

    • addLineaddCircle两接口需要传入对应几何图形的参数。``addLine首先需要传入LS或者R来区分直线、线段和射线,其次传入两个点的坐标。addCricle`只需要传入三个参数,分表代表圆心的横纵坐标以及圆的直径。
    • delLinedelCircle只需要传入一个参数,即相应几何对象在相应容器(vector)内的下标。
  • 两个接口来展示几何图形的详细信息:showLineshowCircle

    • showLine会调用LineSegmentLineRaysLine的show方法,展示相应几何图形的详细信息:直线参数,起点终点坐标,射线方向等。并展示几何图形在容器vector<Line*>lines中的下标。
    • showCircle会直接展示Circle的三个参数以及在容器vector<Circle>circles中的下标。
  • solve接口来计算几何图形之间的交点,基本保持个人项目代码不变。

  • 两个接口进行几何图形的绘制:drawdrawPoint。调用了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] = '';
      		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表格

原文地址:https://www.cnblogs.com/Pupil59/p/12559877.html