软件工程做业——结对编程实践

  1. 在文章开头给出教学班级和可克隆的 Github 项目地址(例子以下)。(1')
项目 内容
这个做业属于 2020春季计算机学院软件工程(罗杰 任健)
这个做业的要求是 结对项目做业
个人教学班级 006
结对队友博客 favourLZH
结对项目的GitHub地址 IntersectDualProj
我对这项做业的目标是 提升我的程序开发素质,在与同伴的结对编程中不断配合、成长,
一同写出高性能程序

做业要求

本次做业为我的项目中求解交点的增量扩展,主要目的是为了让同窗们经过身体力行了解如下三点:软件需求的变动,封装,接口与松耦合,以及错误处理。html

  1. 软件需求的变动
    在我的项目的基础上额外支持两个几何对象:线段和射线
  2. 封装
    把求解交点的功能能独立出来,成为一个独立的模块(class library, DLL,或其它),这样命令行和 GUI 的程序都能使用同一份代码
    咱们称之为计算核心“core 模块”,这个模块至少能够可被用于如下地方:
    1. 命令行测试程序中用于提供核心功能;
    2. 在单元测试框架下验证模块正确性;
    3. 与数据可视化部分结合使用提供核心功能
  3. 接口
    咱们知道软件并不是只有计算核心,实际的软件是交付给最终用户的软件,除了计算核心外,还须要有必定的界面和必要的辅助功能。那么这个 core 模块和使用它的其余模块之间须要如何进行交流呢?答案是经过必定的 API(Application Programming Interface),一般也可称做接口。经过明确模块之间的接口,则模块 A 对于模块 B 的认知则只有 B 提供的接口,而与 B 的实现无关。
  4. 松耦合
    系统的组件对于其它的组件只具备不多的认知或者没有认知
  5. 错误与异常处理
    对于一个真实的软件来讲,来自用户的非法输入是难以免的。若是程序的输入出现了错误,好比命令行参数是其余字符,或者有多个无心义参数等等,要怎样才能告诉函数的调用者“你错了”?又该如何方便地告诉函数的调用者“哪里错了”?在这种时候,咱们通常会定义各类异常(Exception),让模块在碰到各类异常状况的时候,能给调用者充分的错误信息。
    在本次做业中,将要求同窗们进行错误处理,体会一个真实软件在错误处理上的相关考虑。

做业完成功能划分

  • 助教小哥哥小姐姐很是友善的为咱们的代码提交规定了几个step(这样也方便审核~
    • 因此咱们的软件开发各步骤的顺序和内容就肯定以下了
  1. 基本功能实现及测试:单元测试、并要求获得90%以上覆盖率
  2. 拓展功能后封装:看书相关内容,并说明命令行、GUI接口设计的理由,并构造测试
  3. 支持异常处理:异常处理设计及其测试
  4. 增长界面模块:开发UI模块
  5. 松耦合:不一样小组间交换核心模块与界面模块测试

PSP

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

14.在你实现完程序以后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。(0.5')node

  • PSP
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
- Estimate - 估计这个任务须要多少时间 10 10
Development 开发
- Analysis - 需求分析 (包括学习新技术) 1. 5
2. 45
3. 45
4. 30
5. 30
总计:155
1. 75
2. 30
3. 25
4. 120
5. 120
总计:370
- Design Spec - 生成设计文档 20 40
- Design Review - 设计复审 (和同事审核设计文档) 30 10
- Coding Standard - 代码规范 (为目前的开发制定合适的规范) 20 15
- Design - 具体设计 1. 15
2. 30
3. 20
4. 45
5. 30
总计:140
1. 15
2. 30
3. 30
4. 45
5. 30
总计:150
- Coding - 具体编码 1. 20
2. 30
3. 60
4. 90
5. 30
总计:230
1. 90
2. 75
3. 75
4. 120
5. 30
总计:390
- Code Review - 代码复审 60
上述5步骤各10min
30
- Test - 测试(自我测试,修改代码,提交修改) 1. 40
2. 60
3. 60
4. 90
5. 30
总计:230
1. 120
2. 45
3. 120
4. 120
5. 30
总计:435
Reporting 报告
- Test Report - 测试报告 15 15
- Size Measurement - 计算工做量 15 15
- Postmortem & Process Improvement Plan - 过后总结, 并提出过程改进计划 15 15
合计 940 1495

开发

  • 按照做业要求实现的功能,开发顺序以下

Step 1 基本功能实现及测试

需求分析与学习

  • 此次结对编程,咱们采用队友我的项目的程序做为基础程序,c++

    • 缘由是,上次她用了更多的时间去编写和测试,完成了附加题,代码完整度和性能都应该比个人更好,因此采用
  • 而我在阅读她的代码过程当中,对代码作出了如下几点改进git

    1. 计算类、图形类、基础函数类分离
    2. 在程序中加入一些TODO标记:(1)命令行错误处理;(2)直线boundary以融入线段和射线;(3)运行时间分析;(4)更换部分数据结构以优化性能
  • 而且下载学习了覆盖性测试的软件OpenCppCoverage Plugingithub

设计

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

  • 一共6个类(除开def,内有通用函数,好比double精度比较)
3class
  • 类与类之间关系
graph LR A[Point] --> B[Line] A --> C[Circle] A --> D[Calcalator] B --> D C --> D D --> E[IOinterface] D --> F[Exception] E --> F E --> G[GUI调用] E --> H[cmd调用]
  • 关键部分说明,因为交点计算的关键函数实现都在上一次做业博客中说明了,此次不作特别阐述,着重说明新增功能的拓展。
  • Calculator 类
    • 这是因为新增射线和射线须要作比较多改进的地方
    • 其中Line与line的交点计算的预判(用到叉乘的方法) 参见 博客,没法预判的内容好比射线,则先计算交点,再判断是否在射线上
    • 直线和射线与圆的问题处理,计划先算出交点,再判断点是否在line上
    • 线段与圆关系进行预判,好比线段两点都在圆内,来必定程度的下降时间复杂度
    • 圆与圆的交点无需改动
class Calculator {
public:
	Calculator();
	inline double xmult(Point v1, Point v2);
	double xmult(Point o, Point a, Point b);
	//判断点是否在line上 在line上则return true
	bool pOnLine(Point p, Line l);
	// 圆内 return true;  相切/相离  return false;
	bool pInCircle(Point p, Circle c);
	bool isParallel(Line l1, Line l2);//判断两线是否平行 (并捕捉 重叠的异常)	 
	int haveIntersection(Line l1, Line l2, set<Point>& nodeSet);
	int haveIntersection(Circle c, Line l, set<Point>& nodeSet);
	int haveIntersection(Circle c1, Circle c2, set<Point>& nodeSet);
	//计算所有交点
	int countAllinsect(vector<Line> lVec, vector<Circle> cVec, set<Point> &nodeSet);
};
  • Line类
    • 其中设计新增射线和线段的功能
// 'L' -> line;
// 'R' -> radio;
// 'S' -> segment;
class Line
{
public:
	Line();
	Line(char newType, double x1, double y1, double x2, double y2);
	char getType();
	double getA();
	double getB();
	double getC();
  double getSlope();
	Point  getP1();
	Point  getP2();

private:
	char type;
	Point p1;
	Point p2;
	double A;
	double B;
	double C;
  double slope;
};
  • Point类 & Circle类的设计沿用上一次做业设计

测试

  1. 计算模块部分单元测试展现。展现出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试获得的测试覆盖率截图,发表在博客中。要求整体覆盖率到 90% 以上,不然单元测试部分视做无效。(6')
  • 因为openCppCoverage在VS中下载缓慢,能够选择在marketplace中下载,目前尚未发现openCppCoverage插件能够用于检测单元测试覆盖率的功能,只能用它来检测总体代码覆盖率,得出覆盖率以下

3testCoverage

  • 在网上查找,VS的单元测试覆盖率可能须要旗舰版才能完成,因此目前没有得出单元测试的结果,可是咱们本身的单元测试编写的有层次有条理,咱们对单元测试有100%覆盖的信心shell

  • 总体测试架构数据库

    • 根据设计的几大类,采用bottom-up的方式进行测试程序的编写
  • 对于每一个类的每个函数都进行了细密的测试编程

    • 好比下面展现的对于直线类的测试,细致到每个函数
TEST_CLASS(LineTest) {
public:
	TEST_METHOD(lineTestBasic) {
		Line l1('L', 1, 1, 2, 2);
		Line l2('R', 0, 2, 1, 0);
		Line l3('S', 1, 0, 5, 0);

		// test getType
		Assert::IsTrue(l1.getType() == 'L');
		Assert::IsTrue(l2.getType() == 'R');
		Assert::IsTrue(l3.getType() == 'S');

		// test get abc
		Assert::IsTrue((l1.getA() == 1 && l1.getB() == -1) ||
			(l1.getA() == -1 && l1.getB() == 1)
			&& l1.getC() == 0);
		Assert::IsTrue((l2.getA() == -2 && l2.getB() == -1 && l2.getC() == 2) ||
			(l2.getA() == 2 && l2.getB() == 1 && l2.getC() == -2));

		// test get p1 p2;
		Point p1(1, 1);
		Point p2(1, 0);
		Point p3(5, 0);
		Assert::IsTrue(l1.getP1() == p1);
		Assert::IsTrue(l2.getP2() == p2);
		Assert::IsTrue(l3.getP2() == p3);

	}
};
  • 对于求交点的重要复杂部分,咱们的测试也作的更细致
    • 好比直线相交的测试,咱们对于几种直线间的状况好比相交、平行、重叠,以及三种直线的状况(线段、射线、直线)都作了很是细致的测试
3直线测试
// test parallel
		TEST_METHOD(LinePrl)
		{
			Calculator* calc = new Calculator();
      // 三种线段
			char line = 'L';
			char radio = 'R';
			char segment = 'S';
			Line lTD(line, 1, 3, 2, 3);
			Line rTD(radio, 2, 5, 4, 5);
			Line sTD(segment, 51, 6, 24, 6);
			Calculator* cal = new Calculator();
			Assert::IsTrue(cal->isParallerl(lTD, rTD));
			Assert::IsTrue(cal->isParallerl(lTD, sTD));
			Assert::IsTrue(cal->isParallerl(rTD, sTD));

			Line l1(line, 3, 3, 5, 5);
			Line r1(radio, 6, 5, -100, -101);
			Line s1(segment, 0, 1, 100, 101);
			Assert::IsTrue(cal->isParallerl(l1, r1));
			Assert::IsTrue(cal->isParallerl(l1, s1));
			Assert::IsTrue(cal->isParallerl(r1, s1));
			Assert::IsFalse(cal->isParallerl(l1, sTD));
			Assert::IsFalse(cal->isParallerl(r1, sTD));
			Assert::IsFalse(cal->isParallerl(s1, rTD));
		}

Step 2 拓展功能后封装

接口设计

  1. 看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明大家在结对编程中是如何利用这些方法对接口进行设计的。(5')
  • 针对Information hiding原则(参考),咱们对step1中的程序作了以下改进:canvas

    • 外部没法访问原则,全部容器访问改成迭代器访问,而不是数组访问
    • 人机交互逻辑,集中到一个单独的类、包或者子系统中,这为咱们的UI接口设计肯定了理论基础
    • 具体数字常量化/宏,咱们对于精度的设定就采用了宏定义的方式#define EPS (1e-8) 这样在修改具体精度时,只用一处修改便可
    • 信息访问限制,咱们在对点、线、圆等对象有严格的访问限制,全部的内部属性为private,在构造时即肯定内部属性,以后只能读取,不能修改
  • Interface Design

    • 咱们的接口设计听从高内聚,低耦合的设计思路:单一模块只实现一个功能,模块和模块之间的依赖尽量小甚至没有,模块间的接口也是考虑全局而精简设计
    • 好比在Calculator类的设计中,咱们充分解析了交点计算过程当中的关键计算流程,设计出如下7个分层次的计算方法,分别对点在线上、点在圆内、平行判断、交点计算(线与线、线与圆、圆与圆、汇总计算)进行各模块的编写
    • 使得总体复杂的计算过程充分解耦,下降计算过程的代码编写复杂度,减小bug出现的机会,提升计算交点过程的总体可靠性。
// Calculator.h	
  //判断点是否在line上 在line上则return true
	bool pOnLine(Point p, Line l);
	// 圆内 return true;  相切/相离  return false;
	bool pInCircle(Point p, Circle c);
	// TODO: add slope class
	bool isParallel(Line l1, Line l2);//判断两线是否平行 (并捕捉 重叠的异常)	 
	int haveIntersection(Line l1, Line l2, set<Point>& nodeSet);
	int haveIntersection(Circle c, Line l, set<Point>& nodeSet);
	int haveIntersection(Circle c1, Circle c2, set<Point>& nodeSet);
	//计算所有交点
	int countAllinsect(vector<Line> lVec, vector<Circle> cVec, set<Point> &nodeSet);
  • Loose Coupling,此次附加题是对这个原则的一个理论实践
    • 学习以后,总结为以下几点:
      1. 接口和抽象类的正确使用
      2. 增长中间层
      3. 减小对象之间的依赖
    • 这个部分和interface design所要求的内容有一些重合,不过很是值得学习和实践的是增长中间层这部份内容
    • 上学期经过数据库理论的学习,我明白了数据库从应用层到中间层再到底层逻辑,其中三层任何一层的修改,都不会涉及到其余层次的变更,这是由于中间两级镜像层发挥了巨大的做用
    • 而咱们此次的计算核心模块和GUI以及命令行的对接,也要有中间转换层,来实现系统的核心模块和用户交互层的完全解耦。下面的代码就是咱们核心模块面向GUI和cmd的中间层设计。
    • 内部计算实现完全隐藏,只注明外部调用接口规范,这个内容会在GUI设计部分详细说明。
// IOinterface.h
IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points, std::string msg);
IMPORT_DLL void cmdProcess(int argc, char* argv[]);

UML

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

  • 使用VS类图导出的UML以下
    • 其中cmd和GUI接口部分使用函数编写,不在类图中。其主要根据UI指令,构造并维护各类图形集合,调用Calculaor类计算交点并返回给用户,并处理上述过程的异常抛出。
      3UML

性能分析

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

  • 因为此次做业主要是有上次做业的基础的,因此总体性能改进相比起上次“女娲补天”式的提高,此次只是小修小改。记录几个修改:

    1. 直线平行判断优化,咱们将直线的slope属性存储在直线class中,判断平行时直接调用判断相等,而不用屡次计算斜率
    2. 部分小函数宏定义优化,对于double相等以及大小比较的小型可是屡次调用的函数,咱们将其设定为宏定义,减小函数调用的时间消耗
    3. 以及一些小的修改,根据函数调用的局部性原理,将分支语句中更多调用的语句提早之类
    4. 学习了助教发的sweep line提示文档,对比其中内容和自行编写的代码,因为sweep line彷佛只能优化线段的内容,做业时间有限,也就没有作这个部分的优化。
  • 最终性能分析图片以下

    • 占用性能最多的函数仍是求直线交点函数,该函数中交点集合的插入部分是占用CPU最多的数据操做语句
      3performance improvement

Design by contract & Code contract

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

http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx

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

  • design by contract & code constract
    • 设计即要确保交付性能,并且类的完成者和使用者、函数的调用者和被调用者等等,各类API的开发和调用,均需根据contract开发,也就是说明外界保证什么(传入什么参数),内部向外界提供什么(返回什么结果),在处理过程当中维护什么样的不变性
    • 以上理解,参考wiki中的叙述

Similarly, if the method of a class in object-oriented programming provides a certain functionality, it may:

  • Expect a certain condition to be guaranteed on entry by any client module that calls it: the method's precondition—an obligation for the client, and a benefit for the supplier (the method itself), as it frees it from having to handle cases outside of the precondition.
  • Guarantee a certain property on exit: the method's postcondition—an obligation for the supplier, and obviously a benefit (the main benefit of calling the method) for the client.
  • Maintain a certain property, assumed on entry and guaranteed on exit: the class invariant.
  • The contract is semantically equivalent to a Hoare triple which formalises the obligations. This can be summarised by the "three questions" that the designer must repeatedly answer in the contract:
    • What does the contract expect?
    • What does the contract guarantee?
    • What does the contract maintain?

The contracts take the form of pre-conditions, post-conditions, and object invariants. Contracts act as checked documentation of your external and internal APIs. The contracts are used to improve testing via runtime checking, enable static contract verification, and documentation generation.

  • 我认为以上原则在团队或者结对编程过程当中是很是有助于团体开发效率的,按照constract开发并交付
    • 但问题在于这种开发方式在面对大的需求变化时候重构时候可能过于死板,constract的细密度和维护设计都须要额外的开销
  • 而咱们的做业在实际设计中,尤为是GUI接口的设计,也最遵循了这一原则
    • 接口以下
    • gui提供直线和圆的msg信息和返回交点的容器,guiProcess函数保证返回交点数目并把交点写入容器,guiProcess在运行过程当中使用异常处理的方法保证了直线、圆、线段等内部特性的稳定
    • 同理cmdprocess须要参数个数和参数字符数组,向输出文件返回交点数目,运行过程当中维持直线、圆、线段等内部特性
// IOinterface.h
IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>>* points, std::string msg);
IMPORT_DLL void cmdProcess(int argc, char* argv[]);

Step 3 支持异常处理

注意事项
当前阶段,题目中描述了若干对于输入的保证,规定了合法输入的含义。在后续的异常处理中,全部关于输入的保证均会被去除

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

异常处理设计

  • 几种异常
  1. 命令行输入异常(参数、文件名)
  2. 输入文件异常(输入曲线不符合格式,输入线段数目,“乱码”输入)
  3. 直线异常
  4. 曲线异常

异常处理测试

1.1. 命令行输入——参数异常

intersect.exe -n 
intersect.exe -i input.txt -o output.txt -h

1.2. 命令行输入——文件名异常

intersect.exe -i test.txt -o output.txt
intersect.exe -i input.txt -o out.txt
  • 根据以上测试,获得异常处理测试结果

3expCmd测试)

2.1. 输入文件内容——输入曲线不符合格式

## 1. 直线输入错误
R 0 43 9 -3 98

# 2. 输入几何对象参数含有前导0
S 09 12 45 89

# 3.  多个字母
S S 3 2 1 

# 4. 只有数字
3 1 5 2 76

# 5. 字母数字乱序
5 L 1 4 6

# 6. -后不接数字
L - - - -

# 7. 错误数字
L 98-736 92 0 82

2.2. 输入线段数目异常

# 1. 输入线段 < 1
0
-94

# 2. 输入线段与实际线段数不一样
1
L 0 10 8 83
R 91 46 2 0

4
L 56 28 82 4
R 19 41 34 56
C 56 168 5

2.3.曲线输入文件没法打开

3.1. 直线不符合标准

## 1. 输入两点重合
L 0 1 0 1 

## 2. 输入数字超范围
R 100000 0 0 0
L -100000 4897 278 1
S -100005 3784 942 61

3.2.有无穷多交点

#1.  正确状况
3
S 1 1 3 3
S 5 5 100 100
R 0 0 -55 -55


# 2. 异常
2
S 0 1 7 8
R -4 -3 -3 -2

2
S 0 1 7 8
L 9 10 100 101

2
R -4 -5 0 -1
L -99 -100 -50 -51

2 
S 1 0 3 0
S 2 0 4 0 

2
S 1 0 3 0
S 2 0 3 0 

2 
S 1 0 3 0
S 1 0 5 0 

2
S 1 0 3 0
S 0 0 5 0

4.1. 圆不符合标准

## 1. 输入圆半径小于1
C 0 0 0

C 84 72 -23

## 2. 输入数字超范围
C -100000 4897 278
  • 对于以上的样例,分别写了测试样例,获得测试结果以下:

3exception

Step 4 增长界面模块

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

此次的GUI咱们是用Qt实现的。Qt的良好封装机制使得Qt的模块化程度很是高,可重用性较好,对于用户开发来讲比较方便。 Qt提供了一种称为signals/slots的安全类型来替代 callback,使得各个元件之间的协同工做变得十分简单。

(1)界面设计

  • 咱们先经过集成在 Qt Creator 中的 Qt Designer 对窗体进行可视化设计。
    • 最终界面以下:

pairProjectGUI

  • 为了方便用户操做、减小用户记忆负担、减小用户错误信息,咱们的设计作了以下改良:

    • 关文件导入:咱们实现的“..."按钮可直接浏览文件夹选择文件,无需手动输入路径。
    • 添加操做:因为几何对象的参数要求为在(-100000,100000)之间的不含前导零的整数,咱们设置了SpinBox,其限制了正确的参数形式,避免手动输入带来的参数格式错误问题。对于几何体的类型,咱们实现了下拉选框。
    • 删除操做:为了减小用户的记忆负担,咱们在listWidget中实时呈现了当前几何图形。为了不删除操做的错误信息和比较过程的繁琐,咱们设置了复选框。用户选择列表中的几何图形,点击“删除几何图形”便可。这样既方便了用户,也减小了咱们异常处理的负担。
    • 求解交点:点击“求解交点”按钮,则会计算出全部交点的左边,并在下方显示求解的交点数。
    • 绘制:在用户完成所有对当前几何图形的修改和求解交点后,点击“绘制几何图形和交点”按钮,咱们将统一更新画布。这样避免了用户频繁的阶段性操做带来的无用计算。
  • 界面使用注意点:

    • 在点击求解交点后,方有交点的数据,才能绘制出交点。
    • 在点击绘制几何图形和交点后,才会更新当前几何图形的绘制。

(2)主要代码说明

  • Qt使用了信号和槽来代替回调函数,实现对象间的通讯。当一个特定的事件发生时,信号会被发送出去。Qt的窗体部件(widget)拥有众多预先定义好的信号。槽,则是对一个特定的信号进行的反馈。咱们此次的实现主要是建立窗体部件(widget)的子类并添加自定义槽,以便对感兴趣的信号进行处理。

  • 咱们实现的类中,有两个重要的属性vector<string> figuresvector<pair> points,分别存放当前几何对象当前交点

  • a.文件导入

//点击"..."按钮,浏览文件夹
void IntersectGUI::on_findFileButton_clicked() 
{
	filePath =
		QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Save path"), QDir::currentPath()));  //文件路径
	if (!filePath.isEmpty())
	{
		if (ui.fileBox->findText(filePath) == -1)
			ui.fileBox->addItem(filePath);//在comboBox中显示文件路径
	}
}
//点击"输入文件"按钮,导入文件数据
void IntersectGUI::on_infileButton_clicked()
{
	QFile* file = new QFile;   //申请一个文件指针
	file->setFileName(filePath);   //设置文件路径
	bool ok = file->open(QIODevice::ReadOnly);
	if (ok)
	{
	……//读入文件并将文件中的数据处理后存入figures中	
		}
		file->close();
	}
}
  • b.求解交点
    • 点击“求解交点”按钮,将当前几何体的数据转换成相应的接口处的数据,调用dll中的函数,计算交点并返回。具体接口设计,下一部分详细介绍。
int IntersectGUI::on_calcPButton_clicked()
{
	points.clear();
	std::string input;
	size_t n = figures.size();
	……//将figures中的几何体数据转换成相应的接口中的数据input
	int cnt = 0;
	//cnt = guiProcess(&points,figures);
	try {
		cnt = guiProcess(&points, input);
	}
	catch (std::exception e) {
		QString dlgTitle = QString::fromLocal8Bit("计算出现错误");
		QMessageBox::critical(this, dlgTitle, e.what());
		return 0;

	} {
	}
	ui.lineEdit->setText(QString::number(cnt));//反馈交点数
	return cnt;
}
  • c.图形绘制
    • 这一部分,咱们重写了paintEvent()方法。点击“绘制几何图形和交点”的按钮时,调用update()函数,从新绘制。
void IntersectGUI::paintEvent(QPaintEvent*)
{
	init_canvas(); //初始化画布 (底色和坐标轴)
	if (figures.size() != 0) {
		for (vector<string>::iterator iter = figures.begin(); iter != figures.end(); ++iter) {
			draw_figures_from_str(*iter);//绘制几何图形
		}
		draw_points();//绘制交点
	}
}

void IntersectGUI::on_drawFigureButton_clicked()
{
	update();
}

//将不一样的string数据绘制成不一样的几何图形
void IntersectGUI::draw_figures_from_str(string str)
{
	QStringList list = (QString::fromStdString(str)).split(" ");
	QString type = list.at(0);
	……
	if (type == QString::fromLocal8Bit("L")) {
		draw_line(x1, y1, x2, y2);
	}
	else if (type == QString::fromLocal8Bit("S")) {
		draw_seg(x1, y1, x2, y2);
	}
	else if (type == QString::fromLocal8Bit("R")) {
		draw_ray(x1, y1, x2, y2);
	}
	else {
		draw_circle(x1, y1, r);
	}
}
  • 因为Qt中的drawLine()方法,只能绘制两点间的线段。因此在实现绘制直线和射线的时候,咱们计算了当前线与画布边界的交点。代码简单,可是很长,在这里就不展现了。
  1. 界面模块与计算模块的对接。详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。(4')

(1)接口数据格式介绍

  • 计算模块与界面模块的对接,用到了此接口:
    • msg存放的是当前几何图形的信息,数据格式与文件中读取的格式相同。
    • points存放求解的交点。
#ifdef IMPORT_DLL
#else
#define IMPORT_DLL extern "C" _declspec(dllimport)
#endif
IMPORT_DLL int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);
/* string in msg
4
C 3 3 3
S 2 4 3 2
L -1 4 5 2
R 2 5 -1 2
*/

(2)GUI导入dll的方式以下:

#pragma comment(lib,"calcInterface.lib")
_declspec(dllexport)  extern "C"  int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);

(3)具体代码实现

  • GUI相关的代码只在求解交点处调用了dll的guiProcess()方法。
int IntersectGUI::on_calcPButton_clicked()
{
	points.clear();
	std::string input;
	size_t n = figures.size();
    //转换数据
	input += std::to_string(n) + "\n";
	for (size_t i = 0; i < n; i++) {
		input += figures.at(i) + "\n";
	}
	
	int cnt = 0;
	try {
		cnt = guiProcess(&points, input);
	}
	catch (std::exception e) {
		...
	} {
	}
	ui.lineEdit->setText(QString::number(cnt));//界面反馈交点总数
	return cnt;
}
  • IntersectGUI类中的vector<std::pair<double, double>> points属性存放求解的交点。在调用guiProcess前,清空当前points中的元素,传入points的引用。将figures中的数据转换为接口对应类型的数据传入。guiProcess()会将求解的交点写入points中,并返回交点数。
  • guiProcess()代码以下:
int guiProcess(std::vector<std::pair<double, double>>* points, 
	std::string msg) {
	try {
		vector<Line> lVec;
		vector<Circle> cVec;
        //将msg中的几何信息解析并存入lVec和cVec中
		istringstream input(msg);
		fileExcHandler(input, lVec, cVec);
        //计算交点
		set<Point> pointSet = getAllIntersect(lVec, cVec);
        //将交点信息写入points中
		for (auto iter = pointSet.begin(); iter != pointSet.end(); iter++) {
			Point p = (Point)* iter;
			points->push_back(make_pair(p.getX(), p.getY()));
		}
        //返回交点总数
		return (int)pointSet.size();
	} catch (fileException& fileError) {
		cout << fileError.what() << endl;
	}
	catch (lineException& lineError) {
		cout << lineError.what() << endl;
	}
	catch (CircleException& circleError) {
		cout << circleError.what() << endl;
	}
	return -1;
}

(4)对接相关功能实现

  • 未点击“求解交点”按钮时,绘制的几何图形。

1584982017704

  • 点击“求解交点”按钮后,再绘制

GUI3

  • 绘制交点并返回交点数。

Step 5 松耦合测试

  • 和咱们(A组)进行松耦合对接的是1602108817373439结对编程(B)小组
  • 松耦合开发过程以下:
    • dll导出,即新建dll导出项目,在pch.h 文件中导入头文件,并在每个*.cpp文件中include "pch.h文件,以后生成dll便可
    • 以后与GUI和cmd程序分别对接,其中有一个特别须要注意的问题,也是咱们最初对接出现的问题,就是dll导出的编译器须要和导入文件的编译器相同,主要是和GUI对应的编译器相同。不然就会出现没法导入的状况,咱们两个小组在都采用的VS IDE上的release x64版本的编译器以后,导出的dll能够互相调用。
  • 下面展现松耦合实际测试(测试展现中两个组的dll文件名不一样,但以后同一命名为calcInterface上传到GitHubdev-combine分支中)

cmd松耦合

  • cmd松耦合测试主要代码
typedef int (*pGui)(vector<pair<double,double>>* points, string a);
	typedef void (*pCmd)(int argc, char* argv[]);
  HMODULE hDLL = LoadLibrary(TEXT("calcInterface.dll"));
	vector<pair<double, double>>points;
	string tmp = "2\nL 1 1 0 1\nL 1 1 1 0\n";
	if (hDLL) {
		pGui guiProcess = (pGui)GetProcAddress(hDLL, "guiProcess");
		pCmd cmdProcess = (pCmd)GetProcAddress(hDLL, "cmdProcess");
		try {
			int ans1 = guiProcess(&points, tmp);
			for (int i = 0; i < points.size(); i++) {
				cout << points[i].first << " " << points[i].second << endl;
			}
			cout << ans1 << endl;
		}
		catch (exception t) {
			cout << t.what() << endl;
		}
		cmdProcess(argc, argv);
	}
  • A组dll导入B组cmd程序

cmdAB

  • B组dll导入A组cmd程序

cmdBA

GUI的松耦合

  • GUI导入dll主要函数以下
#pragma comment(lib,"calcInterface.lib")
_declspec(dllexport)  extern "C"  int guiProcess(std::vector<std::pair<double, double>> * points, std::string msg);
  • A组dll导入B组GUI程序

GUI_AB

  • B组dll导入A组GUI程序

GUI_BA

结对编程

  1. 描述结对的过程,提供两人在讨论的结对图像资料(好比 Live Share 的截图)。关于如何远程进行结对参见做业最后的注意事项。(1')
  2. 看教科书和其它参考书,网站中关于结对编程的章节,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,说明结对编程的优势和缺点。同时描述结对的每个人的优势和缺点在哪里(要列出至少三个优势和一个缺点)。(5')

结对编程时间表

时间 事项
3.12晚 讨论结对编程整体规划
3.12-3.14 独立进行需求分析、学习和设计的工做,辅以资源交流
3.14下午 讨论代码设计,肯定最终设计和实际结对编程的各类方法和规范
3.14晚-3.23上午 进行合做编程,完成全部功能及其测试
3.23-3.24 完成博客撰写

远程结对编程工具与经验

  • liveShare进行远程合做,其中左边栏为远程桌面通讯栏,中间代码区域为我审核step1编写的测试样例以及队友核心代码编程,右边代码区域为队友正在编写step1代码核心功能
    • 一个注意事项:在一次远程liveShare结束以后,再开启新链接,须要关闭会话再次开启会话生成新的共享连接才能连上,否则会一直链接不上...(咱们第一个晚上链接不上简直崩溃。)
  • GitHub上将结对队友设置为contributor,一块儿贡献整个项目
  • 腾讯会议,能够进行屏幕共享和实时聊天,方便代码复核
  • 微信电话、语音和聊天,一块儿共同制定每日计划、完成每日总结,一同攻克难关
  • 部分截图以下:

3liveShare

结对过程当中的优缺点

  • 参考了关于结对编程的介绍博客以后,总结结对编程的优缺点以下:
  • 优势
    1. 一人编码一人审核,互相监督,提升了编程效率,减小了犯错概率
    2. 两人一块儿开发、设计、编写,能够互相学习取长补短
    3. 在面对困难时,两人一块儿开发,能够互相借鉴思路,加速问题的解决
  • 缺点
    1. 两人合做须要有相对久的磨合期,并且磨合以后可否达成良好的合做也未可知
    2. 对于一下相对基础的软件开发,结对编程的意义可能不能体现

结对编程每个人的优缺点

  • 队友
    • 优势
      1. 学习能力强,负责此次GUI开发工做,在短期里写出了一个精美的GUI界面,并完成了所有功能
      2. 富有巧思编码能力强,向量的计算引入、GUI的设计,都是队友的功劳
      3. 作事认真负责并且耐心,追求完美,测试的编写、readme和博客的写做,队友都作到尽善尽美
    • 缺点
      1. 作事情时常缺乏规划,计划的事儿有时候延期完成
    • 优势
      1. 对项目能提出计划,而且能按照计划完成任务
      2. 擅于与人沟通,与GUI对接的API是我与另外一位同窗商议肯定
      3. 乐于学习新知识新技术,乐于尝试liveShare、OpenCppCoverage、VS类图等工具,辅助结对开发
    • 缺点
      1. 编程能力略弱,对于C++和VS编程平台了解不够

源代码管理 - 警告彻底消除

3codeQualityAnalysis

相关文章
相关标签/搜索