软工结对作业
软工结对作业
一、简介
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2020春季计算机学院软件工程(罗杰 任建) |
这个作业的要求在哪里 | 结对项目作业 |
我在这个课程的目标是 | 学习软件工程相关知识,培养自己独立和团队开发能力 |
这个作业在哪个具体方面帮助我实现目标 | 将理论课上所学的结对编程知识付诸实践,初步熟悉与别人合作开发软件的流程 |
教学班级 | 005 |
项目地址 | https://github.com/yangjiuchun123/SE_pair_homework |
二、PSP 2.1表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 30 | 25 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 600 | 720 |
· Design Spec | · 生成设计文档 | 150 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 40 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 5 |
· Design | · 具体设计 | 60 | 120 |
· Coding | · 具体编码 | 300 | 600 |
· Code Review | · 代码复审 | 60 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 200 | 240 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 120 | 180 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 1640 | 2150 |
三、接口设计
封装是我进行的,对于函数的接口,首先我们对直线类(包括直线、射线和线段)和圆类进行属性的私有化,即将其中的成员属性设置为private,并提供一些get
函数来取得这些值,以及set
函数这样做符合信息隐藏(「Information Hiding」)的思想,可以避免类的属性被随意修改。此外,考虑到之后还需要接入错误处理和GUI
模块,因此对于输入的处理也需要实现一个类中的方法,我改写了一个input
函数,而不是像第一次作业那样直接放在main
函数里。
对于松耦合(「Loose Coupling」),这点在dll
文件的使用中可以体现。dll
是Dynamic Link Library的缩写,即动态链接库。由于本题中涉及到多个模块,因此可以将其中最核心的计算模块做好之后生成dll
文件,其他模块只要调用这个dll
文件即可实现对计算模块的调用。如果计算模块以后需要更改,那么更改完后只需重新生成并更新dll
文件即可,不需要改动其他模块的代码。这体现了不同代码块之间的松耦合。
对于dll
文件根据要求封装了两个版本,一个包含异常处理、一个只是简单的计算(供UI
使用)。
四、计算模块接口的设计与实现过程
本次作业基本上是以我上次作业的代码为基础,进行一些增量修改得来的。本次作业与之前个人项目对计算模块的要求的区别在于添加了射线和线段的输入,对于这两个新的需求,我们考虑仍然使用个人作业中的两直线交点计算方式,首先都先将这两个射线、线段都视为直线,计算出如果是直线,那么它们的交点坐标是多少。然后再根据射线的起点和线段的端点坐标,与求出的交点坐标进行比较,以确定这个交点在不在射线、线段上。由于射线和线段一定是单调递增或单调递减的,因此可以通过简单的横纵坐标比较来得出结论。需要注意的是,一般情况下比较横坐标即可得出结论,但在斜率为0时需要对纵坐标进行比较才能判断。
对于两条线的交点,需要判断这个交点是不是同时在这两条线上;而对于线与圆的交点,只需判断这个交点是不是在线上即可。如果判断结果为真,即可将这个交点加入到交点集合中,否则不加入到集合中。
独到之处倒是谈不上,不过个人认为这种判断方式还是比较简单暴力的。诚然也可以使用一些方法来判断两条线段或射线是否有交点,之后再进行交点坐标的计算,但那样做可能会使代码的逻辑更加复杂,测试起来的难度也更高一些。
为供GUI
使用,我在计算模块中添加了对图像的增减函数(addList
,delList
),重新进行了封装。
五、计算模块的UML图
UML图如下(Line类中一些不是很重要的属性及方法已省略):
六、计算模块接口部分的性能改进
在计算交点的部分,能改进的地方并不是很多(虽然这部分是时间开销最大的),这一点也在个人作业中提到过了。这部分做的改进就只是将一些需要重复计算的值先计算出来,以便后续直接使用,但这样的优化效果也有限。
另外,我们还做的一点优化是使用list而不是数组来存储输入的图形。固然,数组的存储、取值操作肯定是要比list快的,这也是我在个人作业中采用数组来存储图形的原因,但是这次考虑到UI的模块有添加和删除对象的需求,我们知道数组在插入和删除元素时是非常复杂的,尤其是删除元素,需要将被删除元素的后面所有元素都向前移动,这不但实现起来复杂,还可能会出现bug。因此我们认为使用list是一个较好的选择,c++的list使用双向链表实现,使得它在插入和删除元素的时候更加简单。这一点与其说是性能上的改进,不如说是因需求的改变而导致的必要的措施。
七、契约式设计
契约式设计或者Design by Contract (DbC)是一种设计计算机软件的方法。这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。这种方法的名字里用到的“契约”或者说“契约”是一种比喻,因为它和商业契约的情况有点类似。
契约式设计的优点在于,它可以让程序员对自己所要实现的接口的输入、输出有更加明确的认识,只要先验条件被满足,接口实现起来就可以少考虑很多错误的输入,同样,在输出方面,接口还需要保证自己的输出满足后验条件,以确保其他代码能接收到正确的结果。从这方面讲,契约式设计也给程序的测试带来了很大的方便,测试时,可以通过检查契约有没有被破坏,来确定程序是否有输入输出格式上的bug。
契约式设计的缺点在于,撰写契约本身可能会花上一段时间,而且如果在设计接口时并没有想好这个接口的契约应该如何设计,那么之后不但需要花更多时间去改契约,之后才能去改代码实现。这样一来开发的效率就会降低。
因为我是负责UI界面的设计,所以对计算部分提供的接口的要求比较高,就会和我的队友仔细沟通,告诉他我需要什么函数,比如计算交点的函数,添加、减少几何对象数目的函数,让他按照我的需求进行修改,然后我还负责封装,就能够二次检测接口是否符合我的要求,最后我才能进行UI设计。
八、计算模块部分单元测试
单元测试还是采用构造各种不同类型的输入来进行,例如直线与直线、直线与线段、射线与线段、射线与圆、线段与圆等情况来构造。构造的情景有利两线相交、平行,两圆相交、相切、相离等。还构造了一些需要特殊处理的情况,如线段的斜率为0、线段的斜率不存在等情况,这些情况在程序中是需要特判的。
测试代码及测试覆盖率截图如下:
九、计算模块部分的异常处理
异常处理部分,我们考虑了的异常和对应的提示信息如下表所示:
异常 | 提示信息 |
---|---|
输入格式错误——首行格式错误 | Wrong input format: expect a positive integer in the first line! |
输入格式错误——其他行格式错误 | Wrong input format: expect "L/R/S (integer) (integer) (integer) (integer)" or "C (integer) (integer) (integer)" |
点的坐标范围超限 | Invalid input: point out of range(-100000, 100000)! |
直线的两点重合 | Invalid input: you entered two same point for a line! |
有无穷多交点 | Error when computing intersections: there are infinite intersections! |
这部分单元测试的样例如下:
十、界面模块的详细设计过程
首先因为之前没有前端开发经验,所以,在网上搜集了许多资料,最后选择QT作为开发工具,并且购买了一本入门级的参考书。经过一系列曲折,决定采用dialog
和button
的形式实现。
第一个对话框实现按钮,即具体功能,见下图:
通过get Address
获得需要打开的文件,通过img
获得几何图像(显示新的dialog),通过add obj
添加新的几何对象,通过delete number
减少新的几何对象,点击number of intersets
来获得交点数。
关于这部分的两个对话框代码设计如下:
这是第一个窗口的设计代码:
class calculate :public QDialog {
Q_OBJECT
public:
calculate(QWidget* parent = 0);
signals:
private slots:
void getFile();
void removeObj();
void addObj();
void showResult();
void showImg();
private:
QString Filename;
QPushButton* getAddress;
QPushButton* num;
QPushButton* del;
QPushButton* add;
QPushButton* img;
QLineEdit* info;
QLineEdit* delnum;
QLineEdit* result;
};
这是第二个窗口的代码:
class picture :public QDialog {
public:
picture(QWidget* parent = 0);
private:
void paintEvent(QPaintEvent*);
};
只是重构了paintEvent
函数,用于在第二个dialog
实现画图。
十一、界面模块与计算模块的对接
主要使用了core.dll
中的Solve::solve
和Solve::addList
,以及Solve::deleteList
这三个方法
void calculate::removeObj() {
QString input = delnum->text();
int temp = input.toInt();
sol->removeList(temp);
}
void calculate::addObj() {
QString input = info->text();
string information = input.toStdString();
char type;
int x1, y1, x2, y2, x, y, r;
if (information[0] == 'C') {
if (sscanf(information.c_str(), "%c %d %d %d", &type, &x, &y, &r) == NULL) {
return;
}
Circle* newcircle = new Circle(x, y, r);
sol->addShape(newcircle);
}
else {
if (sscanf(information.c_str(), "%c %d %d %d %d", &type, &x1, &y1, &x2, &y2) == NULL) {
return;
}
Line* newline = new Line(type, x1, y1, x2, y2);
sol->addShape(newline);
}
}
void calculate::showResult() {
result->setText(QString::number(sol->getSolve()));
}
十二、结对的过程
结对过程中,我们主要是用腾讯会议来进行两个人的沟通,同时使用屏幕共享功能来互相阐述自己的想法和思路。整个项目基本上是一人写代码,另一人复审,过一段时间再两人互换角色这样进行的。
会议的截图如下:
十三、结对编程的优缺点
结对编程的优点在于:
(1)两个人可以交流,遇到不懂的地方可以互相询问,思路可以互通;
(2)两个人可以互相监督,避免了拖延症;
(3)可以发挥出两个人不同的特质,即扬长避短。
缺点在于:
(1)两个人如果交流不当,可能会导致思路的混乱,拖慢进度;
(2)在统一思路的基础上,理解对方的代码也要时间;
(3)这样的学术活动没那么纯粹,涉及到人际关系。
我的优点在于:
(1)思路缜密,能够很好的发现同伴以及自己代码的bug;
(2)具有较强的自学能力,独自搜集资料解决UI
问题;
(3)性格开朗面对队友新的要求的时候能够很快的完成;
我的缺点在于:
(1)表达能力不行,常常无法表达自己真正的想法;
(2)对代码的美观以及安全性考虑不足。
我的结对伙伴的优点在于:
(1)能够很好的统筹安排时间;
(2)代码的结构优美,思路清晰;
(3)易于交流,当我觉得设计UI时间过长,无暇测试,他同意了我的请求,大大减小了我的压力。
他的不足在于:
(1)要求不能一次都提完,在我封装完之后,还会再次提一些新的要求。