软工结队作业
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2020春季计算机学院软件工程(罗杰 任健) (北京航空航天大学 - 计算机学院) |
这个作业的要求在哪里 | 结对项目作业 |
我的教学班级 | 005 |
项目地址 | https://github.com/heruideid/test |
2.在下述 PSP 表格记录下你估计将在程序的各个模块的开发上耗费的时间
PSP2.1 | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 0.1 | 0.1 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 12 | 24 |
· Design Spec | · 生成设计文档 | 0.5 | 0.5 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0 | 0 |
· Design | · 具体设计 | 1 | 2 |
· Coding | · 具体编码 | 10 | 15 |
· Code Review | · 代码复审 | 2 | 2 |
· Test | · 测试(自我测试,修改代码,提交修改) | 2 | 5 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 0 | 0 |
· Size Measurement | · 计算工作量 | 0.1 | 0.1 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 0.1 | 0.1 |
合计 | 27.8 | 48.8 |
3. 看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。
-
接口设计方面(见下文代码):
定义了Line和Circle两大父类,而Ray类和Segment类继承自Line类。
各类调用统一的接口upgrade_points更新交点集。
对于射线、线段和直线,upgrade_point函数通过调用相同的getIntersectPoint函数求交点,再用各自的isLawfula函数判断点是否在线上。
射线、线段类继承了直线类求交点的方法,重载了isLawful方法和upgrade_point方法。实现了较高的代码重用和简洁的接口。
-
信息隐藏部分
因本次作业中类中的成员变量和方法偏少,本着快速开发的目的,对类的各变量和方法都声明在了public域中,方便调用和进行单元测试
-
松耦合方面
大概就是按着要求把计算模块封装成了dll文件,向UI模块提供从文件导入和求交点两个接口。这样好处就是任一模块坏掉了都不会影响到另一个模块,使得问题局部化,便于维护
//class Line
string display(); //为UI部分删除几何对象提供的函数,我们设计的删除对象是从下拉栏选定已有对象并删除,因此需调用此函数将“L 0 0 1 1”这样的字符串存储在下拉栏中供用户选择几何对象
vector<Point> getIntersectPoint(Line& line);
vector<Point> getIntersectPoint(Circle& circle);
virtual bool islawful(Point& point) { return true; }
virtual void upgrade_points(set<Point>& points, Line& line);
virtual void upgrade_points(set<Point>& points, Circle& circle);
//class Ray
bool islawful(Point& point);
string display();
void upgrade_points(set<Point>& points, Line& line);
void upgrade_points(set<Point>& points, Circle& circle);
//class Segment
bool islawful(Point& point);
string display();
void upgrade_points(set<Point>& points, Line& line);
void upgrade_points(set<Point>& points, Circle& circle);
//class circle
string display();
void upgrade_points(set<Point>& points, Circle& circle);
4.计算模块接口的设计与实现过程。设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。
关于类的组织和主要成员函数、成员函数间的调用关系上一点已经说过。计算模块没有做太多优化,用vector存储Line、Ray、Circle、Segment,求交点时遍历一遍,O(n^2)复杂度,算是最中规(tou)中矩(lan)的做法了。至于增减几何对象是UI方需求,考虑到UI部分展示的几何对象不会太多,性能可以不用考虑,就直接调用接口函数重新计(tou)算(lan)了一遍。
5. 阅读有关 UML 的内容,画出 UML 图显示计算模块部分各个实体之间的关系
6. 计算模块接口部分的性能改进。记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数。
本次作业对性能部分优化有限,基本复杂度还是O(n^2)。由上图可以看出Line::getIntersect函数调用较多,以及插入点时红黑树的维护占用了较多的时间。
7.看 Design by Contract,Code Contract 的内容, 描述这些做法的优缺点,说明你是如何把它们融入结对作业中的
查阅维基百科可知,契约式编程的大概意思就是软件设计者应该为软件定义正式的、精确的和可验证的接口。OO里边的JML就是一种契约编程的范例,基于使用JML的经验来说,
优点:
明确好各接口的前提是理清项目的架构,这对于团队开发来说至关重要。
在分工以后,明确好接口各自就能通过接口明确责任范围,避免扯皮
缺点:
契约编程对接口的定义要求无二义性,有时一些功能可能用自然语言描述很简洁但要按标准来描述就比较麻烦
基于敏捷开发的目的和项目规模不大的情况,我们前后端定义的接口的功能很简单,就是提供读取文件的方法和计算交点集两个函数。因此这一块用得不多
8.计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效
鉴于vs2017社区版不太方便测试单元覆盖测试率,而能便捷获取到运行时覆盖率。在征得助教的同意下,我们跑了较为复杂的input,并使得代码覆盖率到达了90%
单元测试代码:
getIntersectPoint:构造直线与直线相交、平行、线圆相交、相离相切等5种情况测试
isLawful的检测:构造点在线段或射线延长线上、端点等位置,看返回bool值与预期是否一致
Calculate_point函数构造几何对象,然后算算跟预期是否一致
异常处理:构造异常输入,看能否catch
部分截图:
9. 计算模块部分异常处理说明。在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。
1.点的坐标值超限
2.定义线的两点相同
3.有无穷交点
4.n<=0
5.类别码小写
6.其他非法类别码
7.圆的半径小于等于0
无穷点异常:
定义线的两点相同:
数值超限:
非法n:
圆半径<0和非法类别码:
10. 界面模块的详细设计过程。在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程
UI运行截图:
代码较长,有兴趣可在github上自行浏览。下以伪代码描述
定义从文件导入按键:
connect(ui.btn_loadfile, &QPushButton::clicked, [=]() {
//获取当前路径
//在当前路径打开弹窗选择文件
//返回选取的文件的绝对路径
//调用LoadFromFile更新本地的几何对象容器
//更新UI中下拉栏已有的几何对象,建立映射
});
定义添加对象按键:
//添加几何对象,假设UI输入的几何对象与已有几何对象不重合(不查重)
connect(ui.btn_add, &QPushButton::clicked, [=]() {
//提取几何对象类别码
QString str = ui.comboBox_type->currentText();
//提取输入的四个或三个整数
//检查输入合法性
try {
exception_if_outBorder(fir_num);
exception_if_outBorder(sec_num);
exception_if_outBorder(thi_num);
exception_if_outBorder(four_num);
if (str == QString("circle")) { exception_if_illegalRadius(thi_num); }
else { exception_if_samePoints(fir_num, sec_num, thi_num, four_num); }
}
catch (exception& e) {
QMessageBox::critical(this,"critical",e.what());
return;
}
//创建对象,并更新UI中下拉栏已有的几何对象,建立映射
});
定义删除对象按键:
//删除几何对象
connect(ui.btn_delete, &QPushButton::clicked, [=]() {
//更新本地几何对象集合
QString cur_str = ui.comboBox_delete->currentText();
this->delete_obj(cur_str);//删除容器中的几何对象
//删除映射
int cur_index = ui.comboBox_delete->currentIndex();
ui.comboBox_delete->removeItem(cur_index);
//重置points
points.clear();
//调用计算模块,更新points
try {
Calculate_Points(points, lines, rays, segments, circles);
}
catch (exception& e) {
QMessageBox::critical(this, "critical", e.what());
}
});
定义绘制按键:
connect(ui.btn_paint, &QPushButton::clicked, [=]() {
update();//重新触发PaintEvent
//更新UI显示的交点数
QString str;
str.setNum(points.size());
ui.lineEdit_ans->setText(str);
});
定义画图事件PaintEvent
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);//抗锯齿,美化图形
painter.translate(500, 500);//更改坐标原点
//定义画布区域 y>=280,O坐标(500,500)
//绘制坐标轴
painter.drawLine(QLineF(QPoint(-500, 0), QPoint(500, 0)));
painter.drawLine(QLineF(QPoint(0, 500), QPoint(0, -220)));
//比例尺20:1
//绘制几何对象
//设置画笔颜色和宽度
QPen pen((QColor)Qt::darkCyan);
pen.setWidth(3);
painter.setPen(pen);
//绘制直线
for (auto& line : lines) {
double A = line.A, B = line.B, C = line.C;
double x1, y1 = 11, x2, y2 = -15;
if (A == 0) {
y1 = y2 = -C / B;
}
else {
x1 = (-C - B * y1) / A;
x2 = (-C - B * y2) / A;
}
painter.drawLine(QPoint(x1 * 20, -y1 * 20), QPoint(x2 * 20, -y2 * 20));
//y=11;x1
//y=-15,x2;
}
//同理,遍历rays和segments,计算画布边界的相关点,然后绘制
//遍历points,画点
11. 界面模块与计算模块的对接。详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能
运行截图见上一问。
将计算模块导出为dll,向UI模块提供LoadFromFile和Calculate_Points接口
LoadFromFile函数需传入文件流的引用和各几何对象容器的引用
Calculate_Points函数传入各几何对象容器的引用和交点集的引用
12. 描述结对的过程,提供两人在讨论的结对图像资料(比如 Live Share 的截图)。
主要就是针对各自开发的代码进行交流,以函数为单位进行代码审查。我们这一次的UI用了Qt来开发,需要学习大量Qt里自定义的类和相关函数,我们通过QQ提供的共享屏幕的功能在代码复审之外还进行了技术、信息的交流,这对我们解决UI开发过程中的各种困难提供了许多有力的帮助。
13. 说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)
结对编程的优点:两个人互相探讨有助于思维的扩展,代码的Bug也会更少
缺点:新人之间需要磨合成本,经过一段时间的熟悉开发才会进入正轨,初期会有较多不便
我 | 对方 | |
---|---|---|
优点 | 执行力强、爱coding、 | 细心、善于沟通、考虑周全善于发现隐藏Bug |
缺点 | coding速度偏慢,码力有待提升 | 拖延症晚期患者orz |