第一次结对项目

第一次结对项目

项目 内容
这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里 结对项目作业
我在这个课程的目标是 通过这门课强化软件开发能力,熟悉与他人合作的能力
这个作业在哪个具体方面帮助我实现目标 使用结对编程的模式
  1. 在文章开头给出教学班级和可克隆的 Github 项目地址(例子如下)。(1')

  2. 在开始实现程序之前,在下述 PSP 表格记录下你估计将在程序的各个模块的开发上耗费的时间。(0.5')(独立完成)

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划
    · Estimate · 估计这个任务需要多少时间 10 10
    Development 开发
    · Analysis · 需求分析 (包括学习新技术) 30 40
    · Design Spec · 生成设计文档 26 25
    · Design Review · 设计复审 (和同事审核设计文档) 10 9
    · Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 10
    · Design · 具体设计 20 21
    · Coding · 具体编码 480 500
    · Code Review · 代码复审 50 40
    · Test · 测试(自我测试,修改代码,提交修改) 50 30
    Reporting 报告
    · Test Report · 测试报告 60 55
    · Size Measurement · 计算工作量 20 30
    · Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 10 10
    合计 776 780
  3. 看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。(5')(独立完成)

    1. Information Hiding

    ​ Information hiding is part of the foundation of both structured design and object-oriented design. In structured design, the notion of “black boxes” comes from information hiding. In object-oriented design, it gives rise to the concepts of encapsulation and modularity, and it is associated with the concept of abstraction.

    ​ ——From Code Complete Section 5.3

    ​ 当我们使用到Object Oriented的程序设计,常常会提及信息隐藏(Information Hiding)这一概念。在面向对象的封装、继承、多态中,信息隐藏是一个十分基础的要素。在我看来,信息隐藏主要降低了程序复杂性。隐藏信息的主要机制之一是封装 -组合元素以创建更大的实体。然后,程序员可以专注于新对象,而不必担心隐藏的细节。当我们实现新的功能时,只需要调用原来的接口即可实现基础的功能,并且保证了安全性和健壮性。

    ​ 信息隐藏的原则主要包括:

    ​ a. 多层设计中的层与层之间加入接口层;

    ​ b. 所有类与类之间都通过接口类访问;

    ​ c. 类的所有数据成员都是private,所有访问都是通过访问函数实现的;

    ​ 在我们这次的编程工作中,广泛应用了信息隐藏原则,例如:

    class Container {
    private:
    	int id;
    	string name;
    }
    
    1. Interface Design & Loose Coupling

      面向接口编程(Interface Design)在这次编程中起到了非常重要的作用。GUI和Core两套模块,通过core提供的run() 接口,实现无缝兼用。先把主要的任务逻辑线提取出来,作为接口,core具体实现通过该接口的实现类来完成。

      面向接口编程能够实现loose coupling(松耦合),降低出现bug的概率,使得我们拓展功能更加方便。

      int run(int argc, char** argv) {
      	int n;
      	char P;
      	ofstream outFile;
      	string inFileName, outFileName;
      	inFileName = argv[2];
      	outFileName = argv[4];
      	ifstream inFile(inFileName);
      	outFile.open(outFileName);
      	if (!inFile.is_open() || !outFile.is_open()) {
      		cerr << "core exception : -2, file not opened" << endl;
      		return -2;
      	}
      	inFile >> n;
      	try {
      		while (n--) {
      			inFile >> P;
      			insertShape(inFile, P, container);
      		}
      	} catch (exception & e){
      		cerr << "core exception : -1, overlap" << endl;
      		return -1;
      	}
      	cout << container->lineVec.size() << endl;
      	cout << container->circleVec.size() << endl;
      	unordered_set<Point, myHash>* pointSet = finalGetCrossPoints(container->lineVec,
      		container->circleVec, debug, testPerform);
      	cout << pointSet->size() << endl;
      	outFile << pointSet->size();
      	writePoints(pointSet);
      	return 0;
      }
      

      如上为最主要的接口,实现读取文件、计算交点并输出的核心功能。在GUI部分,只需要调用.dll文件,即可完成核心功能的使用。

  4. **计算模块接口的设计与实现过程。**设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。(7')

    1. 类图:

      8HgXKP.png

      类包括:Shape, Point, Line, Circle, Container.

      ​ 其中,Line, CircleShape的子类,采用面向对象的方法来维护可拓展性、可维护性良好的程序结构。

      • Shape: 二维几何形的类。具有基本的共有属性和方法。
      • Point:点类。

      ​ 具有属性:坐标x, y,脏位 valid

      ​ 具有方法:

      • Line:直线类。

      ​ 具有属性:斜率k,截距b,垂直布尔值vertical,垂直截距vertical_x,线类型 lineType(表明是直线、线段还是射线)。

      ​ 具有方法:

      show():显示对象属性。

      getCross(Line* l2):获得与另一条直线的交点。

      setX(double x):获得直线上横坐标为x的点。

      • Circle:圆类。

      ​ 具有属性:圆心P,半径r

      ​ 具有方法:

      bool isCross(Line l):判断圆是否与直线相交。

      bool isCross(Circle c2):判断两圆是否相交。

      pointPair intersections(Circle* c):获取两圆的交点。

      pointPair getCrossPoints(Circle* cir, Line* l):获取圆和直线的交点。

    数据的组织

    使用STL中的vector来管理Line。为了不使点重复,使用set管理获得的点。但是set的性能较慢,需要进行排序,而这一点在本次作业中没有要求。改为使用unordered_set来管理Point

    同时使用 Container类储存图形对象:

    class Container {
    private:
    	int id;
    	string name;
    
    public:
    	unordered_multimap<double, Shape*> map;
    	vector<Line*> lineVec;
    	vector<Circle*> circleVec;
    	bool equal(const Shape &s1, const Shape &s2);
    	Shape* find(const Shape &shape);
    	bool insert(Shape &shape);
    
    	double myHashFunc(const Shape &obj);
    };
    

    算法

    题目的实际需求就是求解多条直线/线段/射线的交点。除了正确性的要求之外,还要尽可能提高性能。那么,首先考虑如何求解:

    • 若已知两条直线方程 \(L_1: y = k_1x + b_1, L_2: y = k_2x + b_2\),且\(k_1 \neq k_2\),可直接联立得到解。

    • 若有一条直线是垂直的,\(L_1: x = x_0, L_2: y = k_2x + b_2\),可带入求解。

    • 若两条直线平行,没有交点。

      直线方程的公式有以下几种形式

      斜截式:

      \[\begin{equation}y=kx+b\end{equation} \]

    ​ 截距式:

    ​ $$\begin{equation}x/a+y/b=1\end{equation}$$

    ​ 两点式:

    ​ $$\begin{equation}(x-x1)/(x2-x1)=(y-y1)/(y2-y1)\end{equation}$$

    ​ 一般式:

    ​ $$\begin{equation}ax+by+c=0\end{equation}$$ (可以表达任意直线)

    ​ 在这里采用斜截式,只需要建立直线的时候计算\(k, b\)(或垂直\(x\)轴),空间复杂度小,在之后的计算中也非常简洁。

    ​ 以上为基本求解方法。

    ​ 那么,如何对所有直线求解呢?

    ​ 最直观的想法是对所有直线分别求交点,时间复杂度为\(O(n^2)\)。但是,这样粗暴的解法的性能无疑是很低的。在网络上查找资料,想到平行线可以作为一个集合,集合内不用求交点。但是,要求平行线集合仍然要遍历它们的斜率,和直接求交点的时间复杂度并无太大区别,因为在求交点的时候,若斜率相同,那么可以直接跳过。

    ​ 另外,由于存在多条直线交于同一点的情况,实际上对于所有可能的交点,都要求出具体的交点坐标。那么,平行线集合的优势也被进一步削弱了。所以总体上,这一类做法的复杂度都是\(O(n^2)\)

    ​ 在这一基础上,可以做一些初步的优化,即已经求过交点的两条直线不再求解。如:有直线\(L_1, L_2\),在求解\(L1\)\(L_2\)的交点之后,不需要反过来求交点。如此可以节省一半的时间。

    ​ 其次,在确定算法之后,考虑数据的精度问题。在题目直线参数为(-100000, 100000)的情况下,float精度是达不到要求的。只有使用double才能满足精度约束。

    ​ 在求解直线交点的基础上,可以对射线、线段用类似的方法求解。假设为直线求出交点,然后判断其有效性(根据是否是真实的交点)。

  5. 阅读有关 UML 的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)。(2’)(独立完成)

    8bOlgP.jpg

  6. **计算模块接口部分的性能改进。**记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。(3')

    本项目做出的优化:

    1. 采用unordered_set保存数据,避免排序带来的性能消耗。
    2. 跳过已经计算过的几何形,避免重复计算。

    构思优化消耗时间10min,实现优化消耗25min左右。

    关于为什么没有做平行线的优化:

    ​ 考虑平行线之间不用互相计算交点,节省了时间。但是得到平行线集合需要消耗不小的时间,而且多条线交于同一点要求所有几何形之间都要计算出准确的交点位置。平行线优化并没有展现出优势,但是会加大程序的复杂度。

    性能分析:

    ​ 所有分析采用随机生成的 \(N = 2500\) 组数据。

    CPU性能分析:

    8HrFv8.png

    占用最大CPU的为hash函数,这一项难以优化。

    内存占用分析:

    8HrP8P.png

  7. 看 Design by Contract,Code Contract 的内容:

    描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。(5')(独立完成)

    这两个页面讲述的内容主要为契约式设计。类似于企业协作:

    • 供应商必须提供一定的结果(义务),并有权期望客户满足必要的条件(利益)。
    • 客户必须满足这些条件(义务),并有权获得结果(收益)。
    • 双方必须遵守适用于所有合同的某些一般法律和法规。

    Design by Contract将这些元素变成软件文本的组成部分:前提条件(客户义务,供应商收益);后置条件(客户利益,供应商义务);和不变量(一般规则)。这些概念存在于各个级别:需求,设计,实施,测试。

    优点:

    • 自动的高质量编程。
    • 内置的框架,可进行集中且有效的测试。
    • 无需描述实现细节即可传达清晰的设计的能力。
    • 整个软件开发过程的清晰,简单。
    • 项目管理更加直接、稳定。
    • 有利于构建无错误的面向对象系统。
    • 有助于程序员更好地理解和控制继承机制。
    • 可为异常处理提供安全有效的语言构造。

    缺点:

    • 更改的开发成本
    • 更多的开发时间
    • 潜在的开发错误(由于程序员不熟悉Design by Contract)

    在我们的程序中:

    bool checkOneLine(string s) {
    	vector<string> ss;
    	split(s, ss);
    	if (ss.size() == 0) {
    		cerr << "console exception:-4, checkOneLine" << endl;
    		return false;
    	}
    	string types = ss.at(0);
    	if (typeSet.find(types) == typeSet.end()) return false;
    	try {
    		if (types == "L" || types == "R" || types == "S") {
    			myassert(5, (int)ss.size());
    		} else {
    			myassert(4, (int)ss.size());
    		}
    		for (int i = 1; i < ss.size(); i ++) {
    			myassert(checkNumber(ss.at(i)), true);
    		}
    	} catch (exception e) {
    		cerr << "console exception:-4, checkOneLine" << endl;
    		return false;
    	}
    	return true;
    }
    

    广泛使用动态检查,对输入内容和输出完成契约式的设计。从而确保了在程序运行过程中的安全性和稳定性。

  8. 计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。(6')

  9. **计算模块部分异常处理说明。**在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。(5')

  10. **界面模块的详细设计过程。**在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。(5')

  11. **界面模块与计算模块的对接。**详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')

  12. 描述结对的过程,提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。(1')

    结对使用腾讯会议软件进行实时投屏和通话,保证交流的高效率。一人写代码,一人审查。

    结对过程中,我们大体上是积极的、充满干劲、相互督促的。在一人编码过程中,另一人负责检查的监督,确保代码的质量和bug的最小化。同时,我们技术观点相近,结对非常愉快。结对使用了:一个人写,一个人看;或者一个人写实现,一个人写测试两种方式。一段时间后进行交换。

    [截图]

  13. 看教科书和其它参考书,网站中关于结对编程的章节,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。(5')(独立完成)

    结对编程的优点:

    1. 有利于知识的分享和传递。当一方不熟悉某一知识领域时,另一方可以进行协助。
    2. 清晰分工。在整个过程中,二人都有核心的事情要做,但是会在一些边缘的事情上,看情况调节工作。这样清晰的分工可以让每个人专注在自己的事情上,快速高效的输出。
    3. 互相学习。除了编程上的姿势,更重要的是学习对方做事的态度和方式,

    结对编程的缺点:

    1. 结对编程非常消耗精力。要求结对的双方都要保证有充沛的精力。
    2. 结对的双方如果脾气对味,技术观点相近,结对会很愉快,而且碰撞出很多火花,效率有明显提高。反之,就可能陷入很多的争吵,而导致进度停滞不前。甚至影响团队协作。
    3. 不是所有的工作都适合结对。技术验证和架构设计都不适合结对。结对比较适合在需求和架构设计明确后的实现阶段。
    4. 结对编程不能替代代码评审。虽然结对编程对代码的审核程度比代码评审细致的多。但两个结对的人有明显的思维趋同性,从而忽略同样的问题或者犯下同样的错误。

    本人的优点:

    1. 对程序的框架有清晰的规划
    2. 较强的问题分析处理能力
    3. 坚强的意志,不惧怕程序编写中的困难

    本人的缺点:

    1. 在编程的细致程度上有所欠缺

    队友的优点:

    1. 旺盛的编程精力
    2. 乐于学习新知识
    3. 强大的编码能力

    队友的缺点:

    1. 偶尔出现粗心的编程错误
  14. 在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。(0.5')(独立完成)

    见最上方表格。

:结对小组中两个人发布独立博客,其中 2、3、5、7、13、14 部分请独立完成,不允许雷同。项目的测试分数两人共享,博客的分数各自独立。附加题的相关要求请按附加题的要求补充在博客中。

猜你喜欢

转载自www.cnblogs.com/kidogu/p/12558125.html