天天看点

软工结对项目作业

软工结对项目作业

个人项目作业——交点检测

项目 内容
这个作业属于哪个班级 2020春季计算机学院软件工程(罗杰 任健)
这个作业的要求在哪里 结对项目作业
教学班级 005
GitHub项目地址 https://github.com/SpookyDreamer/Intersect2

PSP表格

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

看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。

  • Information Hiding,是指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。在设计的过程中,我们使每一个类的属性都是private的,外部不能直接访问,需要通过get函数来获取属性的值。
  • Interface Design,应遵循以下原则:单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则、开闭原则等。我们在设计的过程中,对每一个类的函数和需要的属性,对外部开放了接口来使用。
  • Loose Coupling,通常是基于消息的系统,此时客户端和远程服务并不知道对方是如何实现的。客户端和服务之间的通讯由消息的架构支配。只要消息符合协商的架构,则客户端或服务的实现就可以根据需要进行更改,而不必担心会破坏对方。

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

与上一次的项目相比,本次项目在

Point

类里并没有什么变化。在

Line

类里,我们对

Line

类对象新增了两个

Point

类的属性和一个

char

类型的属性,

Point

类的属性用来存储输入的两个点,

char

类的属性用来存储直线的类型。

与此同时,我们增加了几个方法:

  • isOnline

    函数,用于判断一个点是否在直线上。
  • getType

    函数,用于获取type属性。
  • getPoint1

    getPoint2

    函数,用于分别获取

    point1

    point2

  • equals

    函数,用于判断两条直线是否相同。
  • inOneLine

    函数,用于判断两条直线是否共线。
class Line
{
protected:
	char type;
	double A = 0;
	double B = 0;
	double C = 0;
	Point* point1;
	Point* point2;

public:
	Line(Point* point1, Point* point2);

	virtual bool isParallel(Line* line);

	virtual Point* intersect(Line* line);

	virtual bool isOnline(Point* point);

	virtual bool equals(Line* line);

	virtual char getType();

	virtual bool inOneLine(Line* l);

	virtual Point* getPoint1();

	virtual Point* getPoint2();
};
           

对于射线

Radial

类,我们让它继承于

Line

类。重写了Line类的

isOnline

inOneLine

equals

函数。同时新增了一个方法。

  • countIntersectinOneLine

    函数,用于判断两条射线是有唯一交点或是没有交点或是有无数交点。
class Radial :public Line
{
public:
	Radial(Point* point1, Point* point2) :Line(point1, point2) {
		type = 'R';
	}

	virtual bool isOnline(Point* point);

	virtual bool equals(Line* line);

	virtual bool inOneLine(Radial* r);

	virtual int countIntersectinOneLine(Radial* r);
};
           

线段Segment类则继承于Radial类。重写了Radial类中的

isOnline

equals

countIntersectinOneLine

三个函数。

class Segment :public Radial
{
public:
	Segment(Point* point1, Point* point2) :Radial(point1, point2){
		type = 'S';
	}

	virtual bool isOnline(Point* point);

	virtual bool equals(Line* line);

	virtual int countIntersectinOneLine(Radial* r);

};
           

UML类图

由于类的数量比较多,在这里对其中的方法进行了折叠。

软工结对项目作业

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

构造单元测试时基本思路是对每一个类中的方法进行测试,注重代码的细节逻辑,争取覆盖全面。

部分单元测试的代码:

  • 测试同一直线上两线交点个数
软工结对项目作业
  • 测试容器类的:
软工结对项目作业

单元测试通过截图:

软工结对项目作业

单元测试覆盖率截图:没有找到合适的单元测试覆盖率的检查工具。

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

在异常处理部分,通过自定义4种异常类,我们完成了4种异常的处理

WrongFormatException //格式错误:文件中的几何对象描述不正确,不能被正确的解析。
UnableToConstructException //给出的几何对象上两点重复,无法构造直线/线段/射线。
CoordinateOutOfRangeException //坐标超过了题目要求的范围。
InfiniteIntersectionPointsException //当插入一个新的几何图形时,会导致无穷个交点的出现。 
           

异常的应用场景以及单元测试。

  • WrongFormatException

    这个异常出现在从文件中导入集合对象的描述时,描述的格式与题目要求不符。
    char type;
    double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
    		try {
    			if (getline(input, strLine)) {
    				//split line
    				vector<string> res;
    				stringstream aline(strLine);
    				string result;
    				while (aline >> result)
    					res.push_back(result);
    				//exception
    				if (res.size() != 5) {
    					throw WrongFormatException();
    				}
    				stringstream countstr(res[0]);
    				if (!(countstr >> type)) {
    					throw WrongFormatException();
    				}
    				if ((type!='L') && (type != 'R') && (type != 'S')) {
    					throw WrongFormatException();
    				}
    				stringstream x1str(res[1]), y1str(res[2]), x2str(res[3]), y2str(res[4]);
    				if ((!(x1str >> x1)) || (!(y1str >> y1)) || (!(x2str >> x2)) || (!(y2str >> y2))) {
    					throw WrongFormatException();
    				}
    			}
    			else {
    				throw WrongFormatException();
    			}
               
    这类异常没有进行单元测试。进行的测试主要是改变input.txt中的内容,通过命令行的运行,观察输出的错误信息是否一致。
  • UnableToConstructException

    这个异常出现在构造几何对象(直线/线段/射线)时。当出现这种错误时,无法构造直线/线段/射线。
    if (point1->equals(point2)) {
    		throw UnableToConstructException();
    	}
               
    单元测试:
    Point* pe4 = new Point(0, 0);
    			try {
    				Line* l3 = new Line(pe4, pe4);
    				Assert::IsFalse(true);
    			}
    			catch (UnableToConstructException& e) {
    				Assert::AreEqual(e.what(), "ERROR! Since the two points given coincide, we cannot construct a line.\n");
    			}
               
  • CoordinateOutOfRangeException

    这个异常出现在构造几何对象(直线/线段/射线)时。这种情况不符合题目要求
    if (x1 >= 100000 || x1 <= -100000 || y1 >= 100000 || y1 <= -100000 || x2 >= 100000 || x2 <= -100000 || y2 >= 100000 || y2 <= -100000) {
    		throw CoordinateOutOfRangeException();
    	}
               
    Point* pe1 = new Point(0, 0);
    Point* pe2 = new Point(-100000, 0);			
    	try {
            Line* l1 = new Line(pe1, pe2);
    		Assert::IsFalse(true);
    	}
    	catch (CoordinateOutOfRangeException& e) {
    				Assert::AreEqual(e.what(), "ERROR! Both horizontal and vertical values should be within  (-100000, 100000).\n");
    			}
               
  • InfiniteIntersectionPointsException

    这个异常出现插入新的几何对象时。当新插入的这个图形会导致目前的几何对象集会出现无穷多个交点的时候,则不应该将其插入集合中。
    char type1 = l->getType();
    if (!line->inOneLine(l)) {
    	continue;
    }
    if (line->getType() == 'L' || l->getType() == 'L') {
    	throw InfiniteIntersectionPointsException();
    }
    Radial* r0 = (Radial*)line;
    Radial* r1 = (Radial*)l;
    int tmp = r0->countIntersectinOneLine(r1);
    if (tmp < 0) {
    	throw InfiniteIntersectionPointsException();
    }
               
    LineSeries* ls0 = new LineSeries();
    Point* pe5 = new Point(0, 0);
    Point* pe6 = new Point(1, 1);
    Line* le1 = new Line(pe5, pe6);
    Segment* se1 = new Segment(pe5, pe6);
    ls0->add_line(se1);
    try {
    	ls0->add_line(le1);
    	Assert::IsFalse(true);
    }
    catch (InfiniteIntersectionPointsException& e) {
    	Assert::AreEqual(e.what(), "ERROR!There are infinite points of intersection between geometric objects .\n");
    }
               

Design by Contract,Code Contract

契约式设计或者Design by Contract (DbC)是一种设计计算机软件的方法。这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。这种方法的名字里用到的“契约”或者说“契约”是一种比喻,因为它和商业契约的情况有点类似。

  • 优点
    • 获得更优秀的设计
    • 提高可靠性
    • 更出色的文档
    • 简化调试
    • 支持复用
  • 开销和限制
    • 开发这需要时间学习撰写良好契约的思想和技术
    • 需要大量的实践

界面模块的详细设计过程

在UI模块的设计上,我们使用了vs基于Qt的扩展。在图像的绘制上面,我使用了

QCustomPlot

类中的

AbstractItem

对图像进行直接的绘制。最后完成的UI界面如图所示:

软工结对项目作业

UI界面一共有6个按钮,具体功能分别为:

  • Init

    :清空容器;
  • File

    :读取文件,并在下方输出读取文件的内容;
  • Add

    :添加下方所描述的直线;
  • Delete

    :删除下方所描述的直线;
  • Calculate

    :计算交点数量并在下方输出;
  • Paint

    :画出目前容器中所有直线的图像。

所对应的槽函数如下所示:

private slots:

	void init();

	void addLine();

	void deleteLine();

	void calculate();

	void readFile();

	void paintLine();
           

下面我将以

addLine()

为例,来具体地讨论槽函数的实现:

void QtGuiApplication1::addLine()
{
	QString str = ui.text->toPlainText();//读取textedit中的内容
	QStringList list = str.split(" ");//用空格将读取到的字符串分割

	double x1 = list.at(1).toDouble();
	double y1 = list.at(2).toDouble();
	double x2 = list.at(3).toDouble();
	double y2 = list.at(4).toDouble();
	Point* point1 = new Point(x1, y1);
	Point* point2 = new Point(x2, y2);//创建Point类对象
	
	//分类讨论直线类型,创建相应的对象,并将其加入到容器中
	if (QString::compare(list.at(0), "L") == 0) {
		Line* line = new Line(point1, point2);
		ls->add_line(line);
	}
	else if (QString::compare(list.at(0), "S") == 0) {
		Line* line = new Segment(point1, point2);
		ls->add_line(line);
	}
	else if (QString::compare(list.at(0), "R") == 0) {
		Line* line = new Radial(point1, point2);
		ls->add_line(line);
	}
	//更新calculate和paint的结果
	calculate();
	paintLine();
}
           

首先读取

Qtextedit

工具里的字符串并将其分割,保存在

list

中。然后根据得到的输入的类型,分类讨论所输入的直线属于哪一类,并调用

core

模块的

add_list

函数,将其加入到容器中。最后更新

calculate

paint

的结果。

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

在计算模块中,我们设计了一个容器类

LineSeries

并在其中设计了与UI模块的接口。计算模块的接口如下所示:

LineSeries();

virtual void add_line(Line* line);

virtual void delete_line(Line* line);

virtual void clear();

virtual int cal_intersects();

virtual std::unordered_set<Point*, Hash_point, Equal_point> getIntersects();

virtual std::vector<Line*> getLines();
           
  • LineSeries()

    为容器类,负责存储直线和交点以及进行下面的处理;
  • add_line(Line* line)

    ,添加直线;
  • delete_line(Line* line)

    ,删除直线;
  • clear()

    ,清空容器;
  • cal_intersects()

    ,计算交点并返回交点个数;
  • getIntersects()

    ,获取交点集合;
  • getLines()

    ,获取直线集合;

在UI模块的设计中,我们调用了计算模块的接口。最终实现了要求的功能:

  1. 支持从文件导入几何对象的描述。
  2. 支持几何对象的添加、删除。
  3. 支持绘制现有几何对象。
  4. 支持求解现有几何对象交点。

结对过程的描述

在进行结对项目的过程中,我们使用了VS2019自带的

liveshare

功能:

软工结对项目作业

并且通过微信进行了语音和文字两种方式的交流:

软工结对项目作业

结对编程的优缺点

  • 结对编程
      • 在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作能有更强的解决问题的能力。
      • 对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
      • 在心理上, 当有另一个人在你身边和你紧密配合, 做同样一件事情的时候, 你不好意思开小差, 也不好意思糊弄。
      • 在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。因为一个人的知识已经被其他人共享。
    • 缺点
      • 两个人的学习习惯和编程习惯不同,需要磨合。
      • 容易产生“过讨论”的状况,花费过多的时间在讨论上面而没有切实地进行工作。
      • 学习能力较强,对于新鲜事物的接受能力比较好。
      • 编程习惯比较好。
      • 执行力强。
      • c++基础比较差。
      • 遇到困难容易气馁,耐心不足。
  • 结对伙伴
      • c++基础较好。
      • 思考较为缜密,在测试方面比较细心。
      • 比较有耐心。
      • 交流不够主动。