软工结对博客
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2020春季计算机学院软件工程(罗杰 任健) (北京航空航天大学 - 计算机学院) |
这个作业的要求在哪里 | 结对项目作业 |
我的教学班级 | 005 |
项目地址 | 链接地址 |
一、PSP2.1
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 5 | 5 |
· Estimate | · 估计这个任务需要多少时间 | 5 | 5 |
Development | 开发 | 1420 | 1560 |
· Analysis | · 需求分析 (包括学习新技术) | 600 | 900 |
· Design Spec | · 生成设计文档 | 60 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 0 |
· Design | · 具体设计 | 120 | 30 |
· Coding | · 具体编码 | 300 | 300 |
· Code Review | · 代码复审 | 120 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | 180 |
Reporting | 报告 | 50 | 70 |
· Test Report | · 测试报告 | 20 | 20 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 40 |
合计 | 1475 | 1635 |
二、接口设计
- Information Hiding
这一块其实做得非常不好,没有特意屏蔽掉内部实现细节,比如circle
类,line
类当中的变量都是以public的形式暴露出来。
- Loose Coupling
virtual void upgrade_points(set& points, Line& line);
virtual void upgrade_points(set& points, Circle& circle);
所有的item都有上述方法,该方法只需要传入已有点集和所需要求解的相交线(圆),实现了低耦合的效果。
- Interface Design
DLL_API void LoadFromFile(ifstream& input, vector& lines, vector& rays,
vector& segments, vector& circles);
DLL_API void Calculate_Points(set& points, vector& lines, vector& rays,
vector& segments, vector& circles);
LoadFromFile
只传入符合要求的字符串,录入信息保存在vector
容器中。
Calculate_Points
负责计算,并将计算结果保存在保存在vector
容器中。
三、UML
四、性能计算及改进
本次作业沿用上次作业,复杂度还是O(n^2),没有太大改进。主要还是插入点时红黑树的维护占用了大量时间。
五、契约式编程的理解及应用
契约式编程要求我们在『前提条件』、『后继条件』和『不变量条件』进行契约的检查。类似的,例如检查参数,一旦参数不对,当即撕毁契约。契约所约束的,是『一个为了确保程序正常运行的条件』,一旦契约被损毁,只有一个原因,那就是程序出了Bug,一旦出现问题,应该有调用方来检查,确保调用的时候,必须是合法的。
通过契约式设计可以使程序结构更加清晰,扩展性高。可以优化程序设计,提高代码的可靠性,简化调试工作,适合团队开发。
不足之处在于契约式编程需要学习撰写良契约的思想和技术,且撰写成本较高(当然回报大于成本),需要大量实践。
在编写代码过程中,主要在处理异常时用到了契约式设计的思想。先罗列出各种异常,然后再依次实现。
六、单元测试
来自队友做的单元测试与异常处理,覆盖率如下
就代码而言谈谈我的理解:
- getIntersectPoint测试考虑了圆与线的三种情况,线与线的两种情况
//case 1:line & line 相交
line_1=new Line(0, 1, 2, 3);
line_2=new Line(0, 0, -1, 1);
points=line_1->getIntersectPoint(*line_2);
Assert::AreEqual((int)points.size(), 1);
Assert::AreEqual(points[0]==Point(-0.5,0.5),true);
//case 2:Line & line 平行
line_1=new Line(0, 2, 0, -2);
line_2=new Line(3, 4, 3,-7);
points = line_1->getIntersectPoint(*line_2);
Assert::AreEqual((int)points.size(), 0);
//case 3:Line & circle 相切
line_1 = new Line(0, 2, 0, -2);
cir_2 = new Circle(1, 1, 1);
points = line_1->getIntersectPoint(*cir_2);
Assert::AreEqual((int)points.size(), 1);
Assert::AreEqual(points[0] == Point(0, 1), true);
//case 4:Line & circle 相交
line_1 = new Line(-4, 1, 0, 1);
cir_2 = new Circle(0, 1, 1);
points = line_1->getIntersectPoint(*cir_2);
Assert::AreEqual((int)points.size(), 2);
Assert::AreEqual((points[0] == Point(-1, 1) || points[1] == Point(-1, 1)), true);
Assert::AreEqual((points[0] == Point(1, 1) || points[1] == Point(1, 1)), true);
//case 5:Line & circle 相离
line_1 = new Line(0, 2, 0, -2);
cir_2 = new Circle(2, 1, 1);
points = line_1->getIntersectPoint(*cir_2);
Assert::AreEqual((int)points.size(), 0);
- isLawful的测试覆盖了线段和射线两点之间、延长线上、端点等位置
Ray ray(0,1,0,-1);
Segment seg(1, 2, 6, 7);
Assert::AreEqual(ray.islawful(Point(0,-2)), true);
Assert::AreEqual(ray.islawful(Point(0,1)), true);
Assert::AreEqual(ray.islawful(Point(0,2)), false);
Assert::AreEqual(seg.islawful(Point(3,4)), true);
Assert::AreEqual(seg.islawful(Point(1,2)), true);
Assert::AreEqual(seg.islawful(Point(6,7)), true);
Assert::AreEqual(seg.islawful(Point(-1,0)), false);
- 还有对异常处理的覆盖测试,包括类别码非法、数值超限、定义线段端点重合、线区间重合等
七、异常处理
- 点的坐标值超限
TEST_METHOD(excep9) {
//数值超限
ifstream fin;
fin.open("excep9.txt");
vector lines;
vector rays;
vector segs;
vector cirs;
bool flag = false;
/* 1
L - 100001 4 -1 4
*/
try {
LoadFromFile(fin, lines, rays, segs, cirs);
}
catch (exception& e) {
if ((string)e.what() == "value of point out of range(-100000,100000)") flag = true;
}
Assert::AreEqual(true, flag);
}
- 线段的两点相同
TEST_METHOD(excep8) {
//定义线的两个端点相同
ifstream fin;
fin.open("excep8.txt");
vector lines;
vector rays;
vector segs;
vector cirs;
bool flag = false;
/*2
L -1 4 -1 4
R 2 5 - 1 2*/
try {
LoadFromFile(fin, lines, rays, segs, cirs);
}
catch(exception& e){
if ((string)e.what() == "Use same points to define line/ray/segment") flag = true;
}
Assert::AreEqual(true,flag);
}
- 线有重合部分
TEST_METHOD(excep4) {
//ray & seg 区间重合
bool flag = false;
Ray* r1 = new Ray(0, 0, 1, 1);
Segment* s2=new Segment(-1,-1,1,1);
try {
exception_if_InfPoints(*r1, *s2);
}
catch (exception& e) {
if ((string)e.what() == "Has infinite points") flag = true;
}
Assert::AreEqual(true, flag);
}
- n<=0
TEST_METHOD(excep10) {
//illegal n
ifstream fin;
fin.open("excep10.txt");
vector lines;
vector rays;
vector segs;
vector cirs;
bool flag = false;
/* 0
L - 100 4 -1 4
*/
try {
LoadFromFile(fin, lines, rays, segs, cirs);
}
catch (exception& e) {
if ((string)e.what() == "illegal n!") flag = true;
}
Assert::AreEqual(true, flag);
}
- 其他非法类别码
TEST_METHOD(excep12) {
//类别码非法
ifstream fin;
fin.open("excep12.txt");
vector lines;
vector rays;
vector segs;
vector cirs;
bool flag = false;
try {
LoadFromFile(fin, lines, rays, segs, cirs);
}
catch (exception& e) {
if ((string)e.what() == "typecode should be upppercase!") flag = true;
}
Assert::AreEqual(true, flag);
}
- 圆的半径小于等于0
TEST_METHOD(excep11) {
//圆半径<=0
ifstream fin;
fin.open("excep11.txt");
vector lines;
vector rays;
vector segs;
vector cirs;
bool flag = false;
/* 1
C - 100 4 0
*/
try {
LoadFromFile(fin, lines, rays, segs, cirs);
}
catch (exception& e) {
if ((string)e.what() == "radius of circle <=0") flag = true;
}
Assert::AreEqual(true, flag);
}
八、界面设计
- 定义导入按键:
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,画点
九、模块对接
DLL_API void LoadFromFile(ifstream& input, vector& lines, vector& rays,
vector& segments, vector& circles);
DLL_API void Calculate_Points(set& points, vector& lines, vector& rays,
vector& segments, vector& circles);
将计算模块导出为dll,向UI模块提供LoadFromFile和Calculate_Points接口;
LoadFromFile函数需传入文件流的引用和各几何对象容器的引用;
Calculate_Points函数传入各几何对象容器的引用和交点集的引用
十、结对过程
基本上是队友扛着我往前跑,第一次交流之前就基本完成了异常处理和单元测试,以及UI的部分工作。由于剩下的工作量比较少,我比较偏执地试图用QGraphicview完成图像的绘制,但是遇到很多很多困难,队友在关键时刻给了我很多帮助,及时地共享资料(虽然最后来不及实现),培养了我与人交流的能力。
结对编程优缺点
对象 | 优点 | 缺点 |
---|---|---|
结对编程 | 1、效率提高 ; 2、更容易发现bug ; 3、可以从队友身上学到很多; 4、代码的交流有助于提高代码能力 | 1、相互不太了解,磨合成本高;2、初期对队友的代码很难理解;3、可能会排斥队友的代码风格 |
我 | 1、细致严谨;2、做事前准备充分;3、追求完美 | 动手慢,一直在找资料,做练习题 |
队友 | 1、码力十足;2、动手快;3、勤于交流;4、善于鼓励;5、对bug反应灵敏 | 函数封装随意,public泛滥,接口高耦合 |