软件工程 结对项目-最长单词链
软件工程 结对项目-最长单词链
项目 | 内容
- | -
这个作业属于哪个课程 | 软件工程课程
这个作业的要求在哪里 | 结对项目-最长单词链
我在这个课程的目标是 | 掌握现代软件工程的基本知识,与团队一起进行软件开发。
这个作业在哪个具体方面帮助我实现目标 | 与另一位同学一起结对编程,初步体会了软件开发。
1.Github项目地址
2.PSP表格(预估开发耗费时间)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟)
- | - | - | -
Planning | 计划 | 20 |
·Estimate | ·估计这个任务需要多少时间 | 20 |
Development | 开发 | 1710 |
·Analysis | ·需求分析(包括学习新技术) | 20 |
·Design Spec | ·生成设计文档 | 20 |
·Design Review | ·设计复审(和同事审核设计文档) | 20 |
·Coding Standard | ·代码规范(为目前的开发制定合适的规范) | 20 |
·Design | ·具体设计 | 30 |
·Coding | ·具体编码 | 1000 |
·Code Review | ·代码复审 | 300 |
·Test | ·测试(自我测试,修改代码,提交修改) | 300 |
Reporting | 报告 | 230 |
·Test Report | ·测试报告 | 200 |
·Size Measurement | ·计算工作量 | 10 |
·Postmortem & Process Improvement Plan | ·事后总结,并提出过程改进计划 | 20 |
| | 合计 | 1960 |
3.在结对编程中如何对接口进行设计
把计算最长链的函数单独写成一个Core类,在Core类中提供两个接口,int gen_chain_word
和int gen_chain_char
分别用于计算单词最多的链和字母最多的链,其他函数属性为private,隐藏在文件中,不能被其他东西调用。其他人在使用的时候只能通过gen_chain_word
和gne_chain_char
这两个接口来使用。
4.计算模块接口的设计与实现过程
计算模块包含一个Core类,其中有两个可供外部调用的接口
int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
类中还有其他八个函数
void newnode(string word);
void addtomap(node newword);
void toforest();
void next1(vector<int> forward, int root);
void next2(vector<int> forward, int root);
int findmostwords(char head, char tail);
int findlongest(char head, char tail);
int listlength(int index);
各函数之间的调用流程
5.UML图
6.计算模块接口部分的性能改进。
(1)改进花费时间
改进计算模块性能所花费的时间:2小时。
(2)改进思路
第一个版本的程序中,无论面对什么情况,都会把有向图中的大大小小的所有链路都分出来,花费了大量的时间。
在不约束首位字母和只约束首字母的情况下,只按照各个根节点来生成最长的单词链。
在约束尾字母的情况下,再加入对尾字母的判断,生成更多的链。
(3)性能分析图
(4)消耗最大的函数
根据分析结果看,消耗最大的函数是 listlength()函数
这个函数的功能是计算单词链字母的长度。
代码如下
int Core::listlength(int index)
{
int sum = 0;
int i = 0;
for (i = 0; i < forest[index].size(); i++)
{
sum += map[forest[index][i]].wordlen;
}
return sum;
}
7.看Design by Contract, Code Contract的内容,描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的
契约式设计,按照某种规定对一些数据等做出约定,如果超出约定,程序将不再运行。
优点
- 加入更多的条件检查,确保函数正常工作
- 强制程序的前条件(pre-condition)、后条件(post-condition)、不变式(invariant)得到保证,从而使程序接口得到进一步的明确。
缺点
- 需要程序语言有断言机制
在编写的Core模块中,一开始对传入的参数进行合法性判断,需要调用该接口的主体保证数据的合法性。
8.计算模块部分单元测试展示
单元测试的代码如下
TEST_METHOD(TestMethod8)//-h b -t t -c
{
Core* core = new Core();
char* result[100];
char* words[] = { "abb","bccccccccccccccccccccc","cccccccccccccccccccccf","ctttt","cbt" };
char* answer[] = { "bccccccccccccccccccccc","ctttt" };
int answerlen = 2;
int resultlen = core->gen_chain_char(words, 5, result, 'b', 't', false);
Assert::AreEqual(resultlen, answerlen);
int i = 0;
for (i = 0; i < answerlen; i++)
{
string stranswer = answer[i];
string strresult = result[i];
Assert::AreEqual(stranswer, strresult);
}
}
这是一个测试约束首字母为'b',约束尾字母为't'的求字母最多的单词链的测试模块,在测试模块中words[]传入所有的单词,result存输出的结果,调用core计算模块中gen_chain_char接口,把返回到result中的结果和预先准备的answer中的答案进行比对。
在测试数据的构造上,主要是构造出有迷惑性的数据比如这一个数据要约束首位字母和字母最多,就构造了一个不符合首尾约束的字母更多的干扰的链,来测试程序能否计算正确。
在Core模块的正确性单元测试中一共设计了12个测试模块,包含了各种参数相结合的正确情况。
单元测试覆盖率
9.计算模块部分异常处理说明
(1)"单词文本隐含单词环"异常
此异常应对当输入的单词能够构成单词环但是并没有输入-r参数的情况。
单元测试样例如下
TEST_METHOD(ErrorTest1)//包含单词环但是没有-r参数
{
Core* core = new Core();
char* result[100];
char* words[] = { "apple","elephant","tea","alex","box","xob","cccccff","football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc" };
char* answer[] = { "football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc","cccccff" };
int answerlen = 3;
try {
int resultlen = core->gen_chain_char(words, 9, result, '\0', 'f', false);
}
catch (exception e) {
Assert::AreEqual("单词文本隐含单词环", e.what());
}
}
其中 football -- llllllllllllllc --- cccccff 能形成单词环,但是 enable_loop 传入 false,应该抛出"单词文本隐含单词环"异常。
(2)"单词包含非法字符"异常
计算模块接口通过char* words[] 来传入全部单词,如果单词字符串中出现不是字母的情况,就应该抛出此异常。
单元测试样例如下
TEST_METHOD(ErrorTest2)//输入的单词中有非法字符,elephant 的字母l被空格代替
{
Core* core = new Core();
char* result[100];
char* words[] = { "apple","e ephant","tea","alex","box","xob","cccccff","football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc" };
char* answer[] = { "football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc","cccccff" };
int answerlen = 3;
try {
int resultlen = core->gen_chain_char(words, 9, result, '\0', 'f', false);
}
catch (exception e) {
Assert::AreEqual("单词包含非法字符", e.what());
}
}
输入的单词中的第二个单词"e ephant"的第二个字符不是字母,应该抛出"单词包含非法字符"异常。
(3)"首尾字母约束不合法"异常
当传入的head 和 tail 参数既不是合法字母,也不是'\0'字符时抛出异常。
单元测试样例如下
TEST_METHOD(ErrorTest3)//首尾字母约束不合法,用 -h *
{
Core* core = new Core();
char* result[100];
char* words[] = { "apple","elephant","tea","alex","box","xob","cccccff","football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc" };
char* answer[] = { "football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc","cccccff" };
int answerlen = 3;
try {
int resultlen = core->gen_chain_char(words, 9, result, '*', '\0', false);
}
catch (exception e) {
Assert::AreEqual("首尾字母约束不合法", e.what());
}
}
在调用gen_chain_char()时,head参数输入为'*',既不是字母也不是'\0',不合法,应当抛出"首尾字母约束不合法"异常。
(4)"有单词为空字符串"异常
当传入的words[]中有空字符串的时候,抛出此异常。
单元测试样例如下
TEST_METHOD(ErrorTest5)//有单词为空字符串 "
{
Core* core = new Core();
char* result[100];
char* words[] = { "apple","","tea","alex","box","xob","cccccff","football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc" };
char* answer[] = { "football","lllllllllllllllllllllllllllllllllllllllllllllllllllllllllc","cccccff" };
int answerlen = 3;
try {
int resultlen = core->gen_chain_char(words, 9, result, '\0', '\0', false);
}
catch (exception e) {
Assert::AreEqual("有单词为空字符串", e.what());
}
}
传入的words[]中的第二个是 "" , 为空字符串,应当抛出"有单词为空字符串"异常。
Core模块抛出异常单元测试情况
10.命令行模块的详细设计过程。
(1)首先通过main函数从命令行中获取参数的个数和具体的参数
int main(int argc, char* argv[])
(2)从第二个参数开始依次读取argv[]中的参数,与"-h","-t","-r","-w","-c"这五个参数进行比较,进行具体处理。
- 如果是"-h"或者"-t"这两个约束首尾字母的参数,则直接读取argv[]中的下一个参数,根据规则,紧跟着的下一个参数应该是字母,如果不是则报错。
- 如果是“-w"或者“-c”这两个参数,则直接读取argv[]中的下一个参数,根据规则,紧跟着的下一个参数应该是单词文本的路径,如果不是则报错。
- 其中,“-w","-c"参数不能同时存在,"-h",”-t“参数可以同时存在,同一个参数不能出现两次,因此增加了五个布尔类型的变量进行是否重复的判断。
以判断”-h“参数为例,部分代码如下
for (i = 1; i < argc; i++)
{
parameter = argv[i];
if (strcmp(parameter, "-h") == 0)
{
if (if_head)
{
cout << "错误:-h参数重复" << endl;
exit(0);
}
if_head = true;
i++;
parameter = argv[i];
if (strlen(parameter) == 1 && isalpha(parameter[0]))
{
head_alpha = parameter[0];
}
else
{
cout << "错误:-h后没有字母" << endl;
exit(0);
}
}
11.命令行模块与计算模块的对接
在main函数读入参数之后,直接进行命令行参数的处理,判断参数都正确之后,根据"-w" 和 "-c" 参数来分别调用Coe模块中的两个接口进行计算
if (if_word)
{
resultlen = core->gen_chain_word(words, wordslen, result, head_alpha, tail_alpha, if_roun);
}
else if (if_char)
{
resultlen = core->gen_chain_char(words, wordslen, result, head_alpha, tail_alpha, if_roun);
}
12.描述结对的过程。
我和结对同学基本上靠网络交流,通过微信来联系,我主要负责程序开发,结对同学主要对程序进行测试,然后把问题反馈给我。
13.结对编程的优点和缺点
优点
- 有人监督,不容易偷懒
- 能在开发过程中思维互补,及时发现问题
- 两个人各有所长,减少了一个人需要学习新知识的时间
缺点
- 对“时空”要求较高,要约定时间地点一起编程。
- 双方总会有分歧,要达成一致难免要一方妥协,略有不爽。
- 代码风格不同时,需要花时间理解。
我的优点和缺点
优点
- 能静下心来多写代码
- 按时完成任务
- 推进任务的完成,比较主动
缺点 - 忘性大,经常写完了忘了上传
- 代码风格有待改进
- 对电脑系统的操作不熟悉
结对同学的优点和缺点
优点
- 做事细心,能够提醒我哪些文件没有上传,能发现细节上的错误。
- 按时完成任务
- 知识渊博,懂很多我不懂的知识和操作。
缺点 - 比较腼腆,不太爱说话。
14.PSP表格 (实际花费时间)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟)
- | - | - | -
Planning | 计划 | 1940 | 1410
·Estimate | ·估计这个任务需要多少时间 | 1940 | 1410
Development | 开发 | 1710 | 1210
·Analysis | ·需求分析(包括学习新技术) | 20 | 180
·Design Spec | ·生成设计文档 | 20 | 20
·Design Review | ·设计复审(和同事审核设计文档) | 20 | 10
·Coding Standard | ·代码规范(为目前的开发制定合适的规范) | 20 | 10
·Design | ·具体设计 | 30 | 30
·Coding | ·具体编码 | 1000 | 600
·Code Review | ·代码复审 | 300 | 180
·Test | ·测试(自我测试,修改代码,提交修改) | 300 | 180
Reporting | 报告 | 230 | 220
·Test Report | ·测试报告 | 200 | 200
·Size Measurement | ·计算工作量 | 10 | 10
·Postmortem & Process Improvement Plan | ·事后总结,并提出过程改进计划 | 20 | 10
| | 合计 | 1960 |1410