结对项目-最长单词链总结

|项目|内容
|:--|:--|:--|:--|:--
|这个作业属于哪个课程|https://edu.cnblogs.com/campus/buaa/BUAA_SE_2019_LJ
|这个作业的要求在哪里|https://edu.cnblogs.com/campus/buaa/BUAA_SE_2019_LJ/homework/2638
|我在这个课程的目标是|熟悉结对编程流程,进行一次结对开发
|这个作业在哪个具体方面帮助我实现目标|进行结对编程实践,了解了结对编程的优缺点

GitHub项目地址

最长单词链项目

开始实现前的PSP表

|PSP 2.1|Personal Software Process Stages|预估耗时(分钟)|实际耗时(分钟)
|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|Planning|计划|1830|
|· Estimate| · 估计这个任务需要多少时间|1830|
|Development| 开发|1620|
|· Analysis| · 需求分析 (包括学习新技术)|200|
|· Design Spec| · 生成设计文档|60|
|· Design Review|· 设计复审 (和同事审核设计文档)|30|
|· Coding Standard|· 代码规范 (为目前的开发制定合适的规范)|30|
|· Design|· 具体设计|200|
|· Coding|· 具体编码|600|
|· Code Review| · 代码复审|200|
|· Test|· 测试(自我测试,修改代码,提交修改) |300|
|Reporting|报告|210|
|· Test Report| · 测试报告|150|
|· Size Measurement| · 计算工作量|30|
|· Postmortem & Process Improvement Plan|· 事后总结, 并提出过程改进计划|30|
||合计|1830|

接口设计方法

看教科书和其它资料中关于Information Hiding, Interface Design, Loose Coupling的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的

Information Hiding(信息隐藏)原则,是David Parnas在1972年最早提出信息隐藏的观点。他在其论文中指出:代码模块应该采用定义良好的接口来封装,这些模块的内部结构应该是程序员的私有财产,外部是不可见的。是指程序的具体实现在外部是不可见的,只暴露出一些接口。
Interface Design,良好的接口设计需要遵循单一职责原则,开放-封闭原则,里氏替换原则等,在本次作业中,Core类只暴露出2个接口。
Loose Coupling,松耦合,一个模块对另一个模块的调用较少。
在本次作业设计中,Core模块仅暴露出2个接口gen_chain_word()和gen_chain_char(),用户对于内部的具体实现是不知道的。

计算模块接口的设计与实现过程

设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处

计算模块包含一个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);

Core类中的其他函数:

    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);

各函数之间的调用流程图如下:

算法大致思路:
根据单词链的定义,一个单词的最后一个字母等于另一个单词的第一个字母,故想到用有向图来实现。

UML图

阅读有关UML的内容:https://en.wikipedia.org/wiki/Unified_Modeling_Language。 画出UML图显示计算模块部分各个实体之间的关系(画一个图即可)。

计算模块接口部分的性能改进

记录在改进计算模块性能上所花费的时间,描述你改进的思路,并展示一张性能分析图(由VS 2015/2017的性能分析工具自动生成),并展示你程序中消耗最大的函数

花费时间:
改进计算模块性能所花费的时间:2小时。

改进思路:
第一个版本的程序中,无论面对什么情况,都会把有向图中的大大小小的所有链路都分出来,花费了大量的时间。在不约束首位字母和只约束首字母的情况下,只按照各个根节点来生成最长的单词链。在约束尾字母的情况下,再加入对尾字母的判断,生成更多的链。

性能分析图如下:

从性能分析图来看,消耗最大的函数是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;

}

看Design by Contract, Code Contract的内容

看Design by Contract, Code Contract的内容:
http://en.wikipedia.org/wiki/Design_by_contract
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
描述这些做法的优缺点, 说明你是如何把它们融入结对作业中的

Design by Contract(契约编程),Design by Contract使用了三类断言:后继条件(post-conditions),前提条件(pre-conditions),以及不变量(invariants)。
契约编程好处在于程序员只需按照之前制定好的契约来对自己的代码负责,但是契约式编程非常的繁琐,这也就是缺点。
在我们本次的作业中,并没有严格满足契约式编程。

计算模块部分单元测试展示

展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到90%以上,否则单元测试部分视作无效。

对Core模块进行了多个测试,以下举出2例

测试示例一代码如下:

TEST_METHOD(TestMethod5)//-h a -t t -w

{

    Core* core = new Core();

    char* result[100];

    char* words[] = { "abbbb","bcccc","cdddd","deee","ct" };

    char* answer[] = { "abbbb","bcccc","ct"};

    int answerlen = 3;

    int resultlen = core->gen_chain_word(words, 5, result, 'a', '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);

    }

}

这是一个约束单词链首字母为a,末尾字母为t,单词数量最多的测试,调用Core模块中的gen_chain_word接口。这是一个测试程序能否正确跑对的测试。

测试示例二代码如下:

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的测试,但不同的是,要求输出字母最多的单词链,在所给的输入数字中存在一个干扰项,即“bccccccccccccccccccccc”,"cbt"。来检验程序是否能够正确执行。

在Core模块的单元测试中,总共构造了12个测试。得到的测试覆盖率如下:

计算模块部分异常处理说明

在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。

(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模块抛出异常单元测试情况

界面模块(如果没有实现GUI,则可以描述命令行模块)的详细设计过程

在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。

(1)首先通过main函数从命令行中获取参数的个数和具体的参数

    int main(int argc, char* argv[])

从第二个参数开始依次读取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);

	    }

}

界面模块(GUI或命令行模块)与计算模块的对接

详细地描述UI模块的设计与两个模块的对接,并在博客中截图实现的功能。

在main函数读入参数之后,直接进行命令行参数的处理,判断参数都正确之后,根据"-w" 和 "-c" 参数来分别调用Core模块中的两个接口进行计算

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);

}

描述结对的过程

看教科书和其它参考书,网站中关于结对编程的章节

例如:
http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html
说明结对编程的优点和缺点。
结对的每一个人的优点和缺点在哪里 (要列出至少三个优点和一个缺点)。

  • 结对编程的优点和缺点

    • 优点:
      • 两个程序员互相帮助,得到能力上的互补。
      • 能有效的增强代码质量,BUG也随之减少。
      • 遇到难题时能相互讨论,快速的解决问题。
    • 缺点:
      • 每个人的习惯都不同。如果结对的两个人编程习惯上有较大差异,会产生不可预料的麻烦。
      • 当两个人的能力有较大差异时,能力强者可能会对能力较弱者产生矛盾。
  • 个人优缺点

    • 优点:
      • 个人完成项目的压力会大大减少。
      • 可以学习对方的长处。
      • 项目进展推动较快。
    • 缺点:
      • 可能刚开始时会有点难以适应这种模式。

完成程序后的PSP表

|PSP 2.1|Personal Software Process Stages|预估耗时(分钟)|实际耗时(分钟)
|:--|:--|:--|:--|:--|:--|:--|:--|:--|
|Planning|计划||
|· Estimate| · 估计这个任务需要多少时间|1830|1430
|Development| 开发||
|· Analysis| · 需求分析 (包括学习新技术)|200|180
|· Design Spec| · 生成设计文档|60|20
|· Design Review|· 设计复审 (和同事审核设计文档)|30|10
|· Coding Standard|· 代码规范 (为目前的开发制定合适的规范)|30|10
|· Design|· 具体设计|200|30
|· Coding|· 具体编码|600|600
|· Code Review| · 代码复审|200|180
|· Test|· 测试(自我测试,修改代码,提交修改) |300|220
|Reporting|报告||
|· Test Report| · 测试报告|150|200
|· Size Measurement| · 计算工作量|30|10
|· Postmortem & Process Improvement Plan|· 事后总结, 并提出过程改进计划|30|10
||合计|1830|1430

posted on 2019-03-14 21:11  fanzk99  阅读(353)  评论(1编辑  收藏  举报

导航