目录
- 结对编程博客
- 1、GitHub项目地址
- 2、PSP表格
- 3、看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的
- 4、计算模块接口的设计与实现过程
- Input类
- PreProcess类
- DFS类
- RingDFS类
- Core类
- Error类
- 5、画出UML图显示计算模块部分各个实体之间的关系
- 6、计算模块接口部分的性能改进。 记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数
- 7、看Design by Contract, Code Contract的内容 ,描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的
- 8、计算模块部分单元测试展示。 展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路
- 9、计算模块部分异常处理说明
- 10、界面模块的详细设计过程
- 开发平台
- 1、设计界面
- 2、编写后端
- 11、界面模块与计算模块的对接
- 12、描述结对的过程
- 13、看教科书和其它参考书,网站中关于结对编程的章节, 明结对编程的优点和缺点。结对的每一个人的优点和缺点在哪里
- 14、PSP表格
- 15、松耦合
结对编程博客
1、GitHub项目地址
项目GitHub地址
2、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
Planning | 计划 | 0.5 | |
· Estimate | · 估计这个任务需要多少时间 | 20.4 | |
Development | 开发 | 18.6 | |
· Analysis | · 需求分析 (包括学习新技术) | 5 | |
· Design Spec | · 生成设计文档 | 0.5 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 0.5 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0.1 | |
· Design | · 具体设计 | 0.5 | |
· Coding | · 具体编码 | 10 | |
· Code Review | · 代码复审 | 1 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 1 | |
Reporting | 报告 | 1 | |
· Test Report | · 测试报告 | 0.2 | |
· Size Measurement | · 计算工作量 | 0.1 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 0.5 | |
合计 | 21.9 |
3、看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的
- Information Hiding:信息的隐藏和封装,其中信息的隐藏是目的,而封装是方法,这一行为是为了保证接口的安全性,避免在他人使用接口时修改了意料之外的数据,而出现问题。在结对编程中,类属性、方法等尽量定义为私有,只有其他类需要调用的方法才设置为公有。
- Interface Design:接口设计。接口作为不同模块之间相连接的部分,其设计的合理性与安全性直接关系到不同模块接合时的难易度与安全性。好的接口设计应符合诸多原则如接口隔离原则、迪米特法则、开闭原则等,其目的都是为了保证接口的安全性与实用性。结对编程中,通过事前规定、参照作业要求等,接口设计基本合理。
- Loose Coupling:松耦合,简单地说就是降低功能、函数之间的依赖程度,即一个方法尽量只完成一个功能,方面修改,避免出现一个函数修改,所有函数都需要再次修改,这样难免导致每次修改时工作量变大,且正确性存疑。可在接下来的模块中看到,结对编程中尽量符合了该要求。
4、计算模块接口的设计与实现过程
计算模块主要有6个类,Core类
,DFS类
,Error类
,Input类
,PreProcess类
,RingDfs类
。
Input类
用于从输入中提取所需要的信息,如模式、开头结尾、路径等。
PreProcess类
用于处理数据,包括分割单词,并依据分割出的单词生成图,输出结果等。
DFS类
用于寻找图中是否有环。
RingDfs类
用于根据生成的图进行搜索,寻找符合要求的最长路径。
Core类
用于封装。
Error类
用于异常时报错。
Input类
方法名 | 作用 | 调用方法 |
---|---|---|
Input(int num, char * paras[]) | 根据程序参数,提取相应信息,如各类参数,文件路径等 | 本类私有方法,Error(string str)方法 |
Input(string str); | 根据输入字符串提取相应信息 | 本类私有方法 |
int getMode() | 返回指定的单词链类型,1为单词数,2为字母数 | |
int getIfRing() | 返回是否允许环 | |
int getHead() | 返回指定的单词链开头,0为无限制,否则为字母的ASCII码值 | |
int getTail() | 返回指定单词链结尾,规则同上 | |
string getPath() | 返回输入的文件路径 | |
string getInput() | 返回输入的完整字符串,用于调试 |
PreProcess类
方法名 | 作用 | 调用方法 |
---|---|---|
PreProcess(char* wordss[], int len, int r); | 根据输入的单词表、长度、是否有环,判断是否合法并生成图 | 本地私有方法,Error(string str)方法 |
PreProcess(string str, int n, char* words[], int r) | 根据输入的包含所有单词的字符串、长度、是否有环,生成单词表与图。单词表char* []是为了满足接口要求。 | 本地私有方法 |
PreProcess(string str, int r) | 根据输入的路径与是否有环,生成单词表与图 | 本地私有方法 |
void printGraph() | 打印合并同首尾单词后的图 | |
void printRingGraph() | 答应元素 | |
void print(vector ary) | 根据输入的容器,将其中单词按顺序输出至solution.txt | |
void VecToStr(char* result[], vector ans) | 将图转化为单词表格式,以满足同接口要求 | Error(string str)方法 |
DFS类
方法名 | 作用 | 调用方法 |
---|---|---|
DFS(PreProcess pp) | 生成实例 | |
void getGraph() | 获取已生成好的图 | |
bool hasRing() | 判断图中是否有除了自环以外的环 |
RingDFS类
方法名 | 作用 | 调用方法 |
---|---|---|
RingDFS(PreProcess pp) | 生成实例,pp中含有图等信息 | |
vector initDFS(int mode, int head, int tail, int ring) | 根据输入的各类参数,以及获取到的图,搜索符合要求的单词链,并返回结果 | 本地私有方法,Error(string str)方法 |
Core类
方法名 | 作用 | 调用方法 |
---|---|---|
int gen_chain_word(char* words[], int len, char* result[], char h ead, char tail, bool enable_loop) | 根据输入的单词表、长度、参数等,搜索单词数量最多的符合要求的单词链 | Input::Input |
int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop) | 根据输入的单词表、长度、参数等,搜索字母数量最多的符合要求的单词链 | 同上 |
Error类
方法名 | 作用 | 调用方法 |
---|---|---|
void Error(std::string str) | 根据输入的报错信息抛出异常 |
算法基于bfs的改进(改进原理见第6部分),基本原理简单易懂。对于无环的情况下,建图时合并了同首尾单词,只保留字母数量最大的单词。
5、画出UML图显示计算模块部分各个实体之间的关系
6、计算模块接口部分的性能改进。 记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数
以下为无环,单词数量9900的运行情况:
可见,大部分时间花在了建图时的各种关于内存的操作上。这是因为在优化了无环的搜索算法后,将搜索部分的复杂度限制在了O(26x26),速度很快,因此关于内存的各类操作成为了最占用时间的操作。
对于算法的具体优化如下:
对于无环的搜索,按如下方法建图:因为无环,所以以26个字母为节点,单词为边,以a
开头b
结尾的单词,表现为由节点a
指向节点b
的一条边。
在搜索算法上,以递归BFS搜索
为基础,在节点b
的递归退出时,即可确保b
往后的所有路径均已遍历,因此可以得到以b
为开头,能搜索到的最长符合要求的单词链长度,保存该长度,记为L(b)
。之后若其他节点访问节点b
时,比如上述例子中的a
节点访问b
节点,不需要再次进入b
递归,只需要比较(L(b)
)与(由a
指向b
的边长度)之和,与原本的L(a)
的大小,并将大者保存为新的L(a)
即可。
这样,在所有节点递归结束后,得到了每个节点往后所能达到的符合要求的单词链的最大长度。根据此信息输出最长路径即可。
该算法确保了每条边只走一次,不会重复走同一条边(同一个单词),因此,在无环的基础上其复杂度为O(26x26)。
但是,在有环的问题中,因为环的存在以及该方法无法判断L(b)
中有哪些节点,因此可能会导致重复走同一条边,该算法无法使用。
7、看Design by Contract, Code Contract的内容 ,描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的
契约式设计的优点是,义务、责任分工明确,各自负责各自部分的正确性,简化了开发流程,明确了各自任务。契约的存在保障了双方的利益,对需求者来说,不需要担心所需部分的正确性,只需要按照契约使用即可。对于供应者来说,不需要担心提供的模块如何使用等, 只需要按照契约完成模块并确保正确性即可。
在结对编程中,我们会规定不同模块之间的接口,在发现bug后将bug交给相应模块的实现者负责修复,另一方只在实现者遇到困难时提供帮助。
8、计算模块部分单元测试展示。 展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路
部分单元测试代码如下:
这两部分测试数据,测试的是方法Input Input(string str)
,方法Input Input(int num, char* paras[])
。两个方法的作用分别是根据输入的字符串,或程序的参数提取相应的设置信息,文件路径等。
测试的思路是构造符合被测方法输入数据结构要求的数据,并尽量复杂,以达到更高的分支覆盖率。
以图中的测试用例为例,因为测试的是处理输入的方法,因此在构造输入数据时尽量构造较为复杂的输入,即尽量输入更多的参数,以覆盖较多的分支。
在catch
方法中通过判断异常信息是否符合预期的方式,来判断是否触发了所要测试的异常。
同时,在应该触发异常的代码下一行加入一条永假的断言Assert::AreEqual("Error Test Didn't Throw Error!", " ");
,以确保通过的测试,是触发了正确的异常,而不是没有触发异常导致没有进入catch
,根本没有运行catch
中的断言。
因为VS只有企业版、专业版才能查看单元测试的分支覆盖率,所以该分支覆盖率是将大部分测试中的代码转移到main
函数中运行后查看的,其覆盖率低于实际的单元测试分支覆盖率但也达到了90%。
9、计算模块部分异常处理说明
所有的异常如下:
输入错误
Chain Type Parameter Error
在已经输入过
-w
或-c
参数,即指定过单词链类型的情况下,再次输入这两个参数。单元测试:
try { Input input("-w -c ..\Pair_Programming\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Chain Type Parameter Error"; Assert::AreEqual(s, str); }
Head Duplicate Definition Error
在已经使用
-h
指令规定过开头的情况下,再次指定开头。单元测试:
try { Input input("-w -h a -h a ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Head Duplicate Definition Error"; Assert::AreEqual(s, str); }
Lack Space Error
在一次性输入所有参数时,参数之间、
-h
指令与制定的开头字母之间应有空格。没有空格时抛出次异常。单元测试:
try { Input input("-w -ha ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Lack Space Error"; Assert::AreEqual(s, str); }
Head Letter Error
指定的开头字符不是字母。
单元测试:
try { Input input("-w -h + ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Head Letter Error"; Assert::AreEqual(s, str); }
Tail Duplicate Definition Error
使用
-t
指令指定过结尾的情况下再次使用该指令。单元测试:
try { Input input("-w -t a -t a ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Tail Duplicate Definition Error"; Assert::AreEqual(s, str); }
Tail Letter Error
制定的结尾字符不是字母。
单元测试:
try { Input input("-w -t + ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Tail Letter Error"; Assert::AreEqual(s, str); }
Ring Parameter Duplicate
多次使用
-r
参数。单元测试:
try { Input input("-w -r -r ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Ring Parameter Duplicate"; Assert::AreEqual(s, str); }
Parameter Type Error
输入的参数不符合规定。
单元测试:
try { Input input("-w -z ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Parameter Type Error"; Assert::AreEqual(s, str); }
Lack Chain Type Parameter
在输入中没有使用
-w
或-c
制定指定单词链类型。单元测试:
try { Input input("-h a ..\\Pair_Programming\\DFSTest2.txt"); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Lack Chain Type Parameter"; Assert::AreEqual(s, str); }
数据错误
Input File Doesn't Exit
输入的文件路径不存在。
单元测试:
try { PreProcess pp ("noFile.txt", 0); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Input File Doesn't Exit"; Assert::AreEqual(s, str); }
Multiple Self Ring Found
在不允许有环的情况下,出现了多个自环。
单元测试:
try { PreProcess pp("..\\Pair_Programming\\PPErrorTest1.txt", 0); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Multiple Self Ring Found"; Assert::AreEqual(s, str); }
其中,
PPPErrorTest1.txt
的内容如下:bc cc cd df ff beee eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee eeeeeeeee ef fg gb
No Word Chain in File
输入的文件中单次数量小于2,不构成单词链。
单元测试:
try { PreProcess pp("..\\Pair_Programming\\PPErrorTest2.txt", 0); } catch (string str) { string s = "No Word Chain in File"; Assert::AreEqual(s, str); }
其中,
PPErrortEST2.TXT
文件内容如下:bc
Ring Detected When not Allowed
在没有使用
-r
参数,的情况下,单词链中出现了环。单元测试:
try { PreProcess pp("..\\Pair_Programming\\DFSTest1.txt", 0); DFS dfs(pp); dfs.getGraph(); dfs.hasRing(0); Assert::AreEqual("Error Test Didn't Throw Error!", " "); } catch (string str) { string s = "Ring Detected When not Allowed"; Assert::AreEqual(s, str); }
其中,
DFSTest1.txt
文件的内容如下:bc cc cd df ff beee eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee ef fg gb
10、界面模块的详细设计过程
开发平台
使用的是Qt Creator 3.4.2进行开发的,由于Qt自带了前端设计的功能,开发起来比较简单。
1、设计界面
使用的是Qt Creator自带的设计面板,操作简单只需要拖拽即可,以下是具体设计。
2、编写后端
在设计好了界面之后,只需要编写好slots函数就可在设计面板中简单的关联(代码未全部贴出)
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
bool H;
int wc;
bool R;
bool T;
QString tmp;
bool fFile;
private slots:
void setC(bool b);
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include "qtextbrowser.h"
#include
#include
#include
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//ui->hideButton->hide();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::setC(bool b){
this->wc = b?2:1;
}
编写完成后,需要做的就是将函数与组件关联起来,同样在设计面板中操作。
关于Qt的信号槽机制,具体可以看这篇博客,由于是在设计模板中关联的,所以并不需要手动编写connect函数。
11、界面模块与计算模块的对接
首先,将计算模块打包成dll
文件,为了使用方便,将统一的接口gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop)
与接口gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop)
定义为函数而非类方法。
打包好后,将生成的Core.dll
文件复制到工程的debug
文件夹或release
文件夹中,通过以下代码导入两个上述接口:
typedef int(*ptr_word)(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
typedef int(*ptr_char)(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
HINSTANCE CoreDLL = LoadLibrary("Core.dll");
ptr_word gen_chain_word = (ptr_word)GetProcAddress(CoreDLL, "gen_chain_word");
ptr_char gen_chain_char = (ptr_word)GetProcAddress(CoreDLL, "gen_chain_char");
导入后,便可以直接调用两个函数。
12、描述结对的过程
在结对的过程中,首先我们在一起预估各个难度的复杂度,之后对于部分逻辑极为简单且不重要的部分如读入等,进行分工,并行开发提高速度。对于核心的部分,如搜索算法的完成、优化等,我们按照结对编程的原则,一人写一人检查,确保没有BUG以及算法的正确性。
13、看教科书和其它参考书,网站中关于结对编程的章节, 明结对编程的优点和缺点。结对的每一个人的优点和缺点在哪里
正如《构建之法》中所述
既然代码复审能发现这么多问题,有这么好的效 果,如果我们每时每刻都处在代码复审的状态, 那不是很好么?
结对编程是将代码复审发挥到极致后的形式,也就是说结对编程完全继承了代码复审的所有优点:增强了代码的规范性,减少了bug出现的可能性,提高了代码的可维护性等等。在合理的使用方法下,结对编程的效率甚至能超过并行开发的效率,考虑到测试、维护等等问题的时间成本。但是,这样的“极限”形式也有一定的局限,即对于程序中非常简单,逻辑复杂度极地的部分,进行实时的代码复审就有了“杀鸡用牛刀”的意味,在这些部分上,结对编程显然有矫枉过正的嫌疑,其开发效率在程序的简单部分中显然没有并行开发高。
结对中我的优缺点:
优点:承担任务主动,投入时间长、任务完成的较为完整,进行了许多优化。
缺点:与同伴沟通不足。
我的同伴的优缺点:
优点:配合良好,完成任务优秀、负责,测试完整。
缺点:没有及时反馈进度与问题。
14、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
Planning | 计划 | 0.5 | 0.5 |
· Estimate | · 估计这个任务需要多少时间 | 20.4 | 55+ |
Development | 开发 | 18.6 | 50+ |
· Analysis | · 需求分析 (包括学习新技术) | 5 | 1 |
· Design Spec | · 生成设计文档 | 0.5 | 0.2 |
· Design Review | · 设计复审 (和同事审核设计文档) | 0.5 | 0.1 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0.1 | 0.1 |
· Design | · 具体设计 | 0.5 | 1 |
· Coding | · 具体编码 | 10 | 40+ |
· Code Review | · 代码复审 | 1 | 1 |
· Test | · 测试(自我测试,修改代码,提交修改) | 1 | 10+ |
Reporting | 报告 | 1 | 2 |
· Test Report | · 测试报告 | 0.2 | 0.5 |
· Size Measurement | · 计算工作量 | 0.1 | 0.1 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 0.5 | 0.5 |
合计 | 21.9 | 55+ |
15、松耦合
我们与学号16061093、16061155的队伍进行了松耦合测试,结果正确,对方使用我方Core.dll
的运行结果截图如下:
在刚开始耦合时出现了问题,因为在对于head
的定义中,我们组的逻辑是当head
不为0即认为是规定的开头字母,但接口内部没有检查其正确性,而对方认为head
不规定时为'0'
。
出现问题后,我们组在接口内部加2上了对于参数的合法性判断,之后没有其他问题。
之后我们还与16061167、16061170组进行了耦合,运行正确,没有问题。