2019软件工程结对做业

软件工程结对编程做业

项目 内容
本次做业所属课程 2019BUAA软件工程
本次做业要求 结对编程做业
我在本课程的目标 熟悉结对编程流程
本次做业的帮助 实践告终对编程的流程,对结对编程的优缺点有了更深的体会
本次做业项目github地址 项目地址

1.本次做业项目github地址

项目地址html

2.开发前PSP表

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

3.接口设计方法

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

我认为这三个方法是类似的,首先不管是面向过程编程中的函数的概念仍是面向对象中的封装的思想都是信息隐藏的原则的体现,这个原则强调代码模块应该采用定义良好的接口来封装(模块的内部结构仅由负责开发的程序员关注),也体现了接口抽象的原则。松耦合原则的也被人称做“高内聚,低耦合”原则,第一次接触这个概念是高老师在计组课上提出CPU这种工程设计模块的时候必定要遵循高内聚低耦合的原则,强调一个代码单元都须要是独立的。在咱们的结对编程中,咱们采用了类将实现过程封装,类的大部分方法和成员变量对外部均不可见的,符合了信息隐藏的原则。同时将整个项目按照功能划分红一个个模块,不一样功能的部分被分红不一样的部分。接口的返回值以及输入参数均是基本类型,生成的dll文件也能够适用于其余程序,因此符合低耦合的标准。程序员

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

计算模块接口的设计与实现过程,设计包括代码如何组织,好比会有几个类,几个函数,他们之间关系如何,关键函数是否须要画出流程图?说明你的算法的关键(没必要列出源代码),以及独到之处。github

计算模块是整个项目的核心,发现这个模块计算的内容有很强的聚合性,所以采用面向对象的方式进行封装,最后抽象出一个实体计算类Solver以及接口类Core,共两个类。Core类的接口每次会实例化一个Solver对象,而后调用max_chain_word函数进行计算。画出一个调用的流程图:算法

算法大体思路:首先拿到问题须要对原问题进行建模,一条单词链相邻两个单词的特征是,前一个单词的末尾等于后一个单词的头,这个能够经过有向图的有向边实现。对于不出现-r参数的状况至关于求图中一些指定点做为起点,一些点做为终点的最大路径问题(采用了一个经典的SPFA算法)。对于出现-r参数的状况归约为求有向有环图中的最大路径问题,属于一个很是经典的NP问题,对于这种状况并无多项式时间的准确算法,所以采用dfs搜索算法。编程

算法的独到之处:采用一图多权值的方法,由于原问题有求最长单词数和字符数两种需求,因此在两个节点边的权值记录了两个,求最大单词数目时使用权值为1,求最长单词数使用权值为目的节点的字符长度。这样对于两种问题的求解方法是基本同样的,只是计算不一样的边权,很好的作到了代码复用。对于输入的单词列表转化为图以后首先采用拓扑排序的方法判断图是否有环,对于-r参数的状况下不必定调用dfs搜索(当判断出图中无环时仍然采用时间复杂度低的SPFA算法)。设计模式

5.UML图

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

VS2017能够下载安装包,支持自动生成类图,参考博客 。最后生成的类图以下:app

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

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

在完成基本程序的运行功能以后,我对程序的性能进行了必定的改进,在这个过程当中是很痛苦的,大概花了两天的时间,其中也走了不少弯路,最后在无环图上优化取得了必定效果,可是有环图改进的幅度很小。

改进的思路:对于无环的状况,尽量采用时间复杂度比较低的算法,权衡再三最后将最终算法改成SPFA算法。对于有环的状况,由于跟许多同窗讨论证明这是一个NP问题,因此能作的改进也是颇有限,主要的思路就是在dfs搜索的时候能剪枝,以及在某些特殊的状况下须要进行特殊处理(好比若是判断出无环,即便带-r参数要调用SPFA函数而不是暴力搜索,以及若是有环的状况下找到一个长度为n的链(n为节点总数)也能够中止搜索获得答案)。对于有环的状况,实际上还能够考虑一些启发式算法好比模拟退火算法等,可是本题目须要输出准确解,启发式算法有必定风险,所以没有采用该方法。

这部分主要有两种状况,一个是没有-r参数的时候调用SPFA方法时的性能,

下面展现一个较小数据集下不带-r的性能分析图:

发现其中耗时最可能是的生成图的函数,说明计算算法性能基本能够。

一个是带有-r参数且图中有环会频繁递归调用DFS函数的状况,这一部分毫无疑问是dfs函数被屡次递归调用。


7.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,又称契约式设计,是面向对象程序设计的一大原则。这个设计原则须要咱们使用三类断言:前提条件,后继状态和不变量。前提条件是指执行某种操做以前指望具有的环境,后继状态是知足前提条件在方法结束退出时系统所拥有的状态。

优势:契约式设计强调了调用者和被调用者地位的平等性,双方都须要履行必定的义务,在传统的C/S设计模式下,被调用者每每指望处理一切可能的异常,对调用者不作约束,每每形成调用方代码质量不好。契约式设计要求调用者调用前准备好正确的参数,被调用者须要保证正确的结果和不变形,保证了双方的代码质量。

缺点:对于程序设计语言有必定要求,DBC须要断言来验证契约是否成立,可是并非全部的程序设计语言都有断言机制。

结对编程做业中,对异常的捕捉都由一个函数进行处理,把类的大部分方法都封装成私有方法,调用的时候会知足隐含前置条件,对于接口的前置进行了约束。模块之间以及函数之间采用了契约的思想,保证了双方在一次函数调用的时候都须要承担必定的责任。

8.计算模块部分单元测试展现

计算模块部分单元测试展现。展现出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试获得的测试覆盖率截图,发表在博客中。要求整体覆盖率到90%以上,不然单元测试部分视做无效。

计算模块单元测试覆盖率达到了98%,截图以下

单元测试分为两大部分,一个是对正常状况下的处理,一个是对异常处理,异常部分的单元测试请参见博客第九部分。正常状况下的测试分为三种,一个是无环无-r参数,一个无环有-r参数,一个是有环有-r参数。对前两种,测试了最大单词数和最大字符数结果相同、结果不一样的状况,同时也测试了不一样的头尾对结果的限制。对于第三种有环的状况,也测试全部无环的状况,同时测试了图中有多个环的状况。除上面的常规测试以外还增长了边界测试,好比最后的结果只有一个单词(不能构成单词链),以及输入长度为0的情形。展现部分单元测试代码以下:

char *test_list1[] = { "abc","cbd","dbbw","csw","zde","opl","wxx" };
char *test_list2[] = {"room", "mazhenya", "apple", "elephant","mahaoxiang","gxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzorange","peanut"};
char *test_list3[] = {"abc","cbd","bba","ddb","yz","uv","wx","vw","xy"};
char *test_list4[] = { "xppppy", "fb","ef","bc","de","cd","zpppb","yppppz" };
char *test_list5[] = { "xppppy", "fb","ef","bc","ft","tb","zpppb","yppppz" };
char *test_list6[] = { "xppppy", "ce","ef","bc","de","cd","zpppb","yppppz" };
char *test_list7[] = { "tppppppppz","zppppppppx","ab","ef","bc","de","cd" };
char *test_list8[] = { "ac","bz","cb","cc" };
char *test_list9[] = { "ac","bc","cb","cc" };//有环
char *test_list10[] = { "cx","xy","bc","zd","yz","ab","de","cpppppppppppppppppppd" };
char *test_list11[] = {"abc", "abc", "xyz"};
TEST_METHOD(TestMethod1)
        {
            
            char *answer1[] = { "abc","cbd","dbbw","wxx" };
            int word_num = 7;
            int answer_num = 4;
            char **results1 = new char*[word_num + 1];
            int res = Core::gen_chain_word(test_list1, word_num, results1, 0, 0, false);
            Assert::IsTrue(judge(array2string(results1,res), array2string(answer1,answer_num)));
            for (int i = 0; i < res; i++)
                delete[] results1[i];
            delete[] results1;
        }

        TEST_METHOD(TestMethod2)
        {
            int word_num = 7;
            int answer_num = 2;
            char **results2 = new char*[word_num + 1];
            char *answer2[] = { "dbbw", "wxx" };
            int res = Core::gen_chain_word(test_list1, 7, results2, 'd', 0, false);
            Assert::IsTrue(judge(array2string(results2, res), array2string(answer2, 2)));
        }

        TEST_METHOD(TestMethod3)
        {
            int word_num = 7;
            int answer_num = 3;
            char **results3 = new char*[word_num + 1];
            char *answer3[] = { "abc","cbd","dbbw"};
            int res = Core::gen_chain_word(test_list1, word_num, results3, 0, 'w', false);
            Assert::IsTrue(judge(array2string(results3, res), array2string(answer3, answer_num)));
        }
        TEST_METHOD(TestMethod4) {
            int word_num = 8;
            int answer_num = 4;
            char **results4 = new char*[word_num + 1];
            char *answer4[] = { "room", "mazhenya", "apple", "elephant" };
            int res = Core::gen_chain_word(test_list2, word_num, results4, 0, 0, false);
            Assert::IsTrue(judge(array2string(results4, res), array2string(answer4, answer_num)));
        }
        TEST_METHOD(TestMethod5) { 
            int word_num = 8;
            int answer_num = 3;
            char **results5 = new char*[word_num + 1];
            char *answer5[] = { "mazhenya", "apple", "elephant" };
            int res = Core::gen_chain_char(test_list2, word_num, results5, 'm', 't', false);
            Assert::IsTrue(judge(array2string(results5, res), array2string(answer5, answer_num)));
        }
        TEST_METHOD(TestMethod6) {// -c 以t结尾
            int word_num = 8;
            int answer_num = 2;
            char **results = new char*[word_num + 1];
            char *answer[] = { "zzzzzzzzzzzzzzzzzzzzzzzzzzzzorange", "elephant" };
            int res = Core::gen_chain_char(test_list2, word_num, results, 0, 't', false);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }
        TEST_METHOD(TestMethod7) {
            int word_num = 8;
            int answer_num = 2;
            char **results = new char*[word_num + 1];
            char *answer[] = { "mahaoxiang","gxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"};
            int res = Core::gen_chain_char(test_list2, word_num, results, 'm', 0, false);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }
        TEST_METHOD(TestMethod8) {
            int word_num = 9;
            int answer_num = 5;
            char **results = new char*[word_num + 1];
            char *answer[] = { "uv","vw","wx","xy","yz" };
            int res = Core::gen_chain_word(test_list3, word_num, results, 0, 0, true);
            //output(results, res);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }

        TEST_METHOD(TestMethod9) {
            int word_num = 9;
            int answer_num = 4;
            char **results = new char*[word_num + 1];
            char *answer[] = { "abc","cbd","ddb","bba" };
            int res = Core::gen_chain_word(test_list3, word_num, results, 0, 'a', true);
            //output(results, res);
            Assert::IsTrue(judge(array2string(results, res), array2string(answer, answer_num)));
        }

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

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

异常1 有环无-r参数 异常发生在输入文件中存在单词环,可是命令设置不支持-r参数,一般发生在忘记设置-r参数的情景下。设计目标是提醒用户检查输入文本或者参数设置是否有误。如下是一个测试循环异常的单元测试用例。

char *test_list3[] = {"abc","cbd","bba","ddb","yz","uv","wx","vw","xy"};
TEST_METHOD(TestMethod10) {
            try {
                int word_num = 9;
                int answer_num = 4;
                char **results = new char*[word_num + 1];
                int res = Core::gen_chain_word(test_list3, word_num, results, 0, 0, false);
                Assert::IsTrue(res == -1);
            }
            catch (const char*  s) {
                Assert::IsTrue(strcmp(s, LOOP_ERROR) == 0);
                cout << s << endl;
            }
    }

异常2 输入的头尾字符不符合题目要求异常发生在用户指定的头尾字符不是0也不是小写字母,目的是提醒用户检查设置的头尾字符限制参数。如下是一个头尾字符异常的单元测试。

char *test_list3[] = {"abc","cbd","bba","ddb","yz","uv","wx","vw","xy"};
try {
                int word_num = 9;
                int answer_num = 4;
                char **results = new char*[word_num + 1];
                int res = Core::gen_chain_char(test_list3, word_num, results, '+', '-', true);
                Assert::IsTrue(res == -1);
            }
            catch (const char*  s) {
                Assert::IsTrue(strcmp(s, TAIL_CHAR_ERROR) == 0);
                //Assert::IsTrue(s == LOOP_ERROR);
                cout << s << endl;
            }

异常3 输入的单词列表中含非法单词 异常发生在用户直接调用接口的时候,没有保证每个单词都是由小写英文单词组成,所以在计算以前也须要进行单词合法性检查。

TEST_METHOD(TestMethod12) {
            char *test_list[] = {"happ1we2", "yuer", "opui8op"};
            try {
                int word_num = 3;
                int answer_num = 0;
                char **results = new char*[word_num + 1];
                int res = Core::gen_chain_word(test_list, word_num, results, 0, 0, false);
            }
            catch (const char* s) {
                Assert::IsTrue(strcmp(s, WORD_ILLEGAL)==0);
                cout << s << endl;
            }
    }

其他还有一些参数错误等异常均在命令行输入处理模块进行处理。

10.界面模块的详细设计过程

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

对于界面模块的设计,采用了MFC程序来实现。整个UI界面包括输入,输出,参数选择三个部分,具体以下图所示。

下面对部分MFC元素的实现做解释说明。

(1)选择文件输入仍是界面输入的CombBox框

comb_choose_input.AddString(_T("选择文件输入(默认)"));
comb_choose_input.AddString(_T("直接输入"));
comb_choose_input.SetCurSel(0);

在初始化时插入选择枝,并设定默认选择为文件输入,在点击运行后的时间经过GetCurSel()方法来获取用户的选择。

(2)参数选择区mode单选框

使用

GetCheckedRadioButton(IDC_max_word, IDC_max_length)

来判断选择枝

(3)文本框(包括输入,输出,head,tail)

采用

GetDlgItem(ID)->GetWindowText(Cstring)

来获取文本框中的文本,须要注意的是,文本内容获取到后类型为Cstring,可用

CT2A(Cstring.GetBuffer())

来转换Cstring为string。

(4)选择(保存)文件的按钮

使用

CString strFile = _T("");
CFileDialog    dlgFile(TRUE, NULL, NULL, OFN_HIDEREADONLY, _T("Describe Files All Files (*.*)|*.*||"), NULL);
if (dlgFile.DoModal())
{
    strFile = dlgFile.GetPathName();
    file_path = CT2A(strFile.GetBuffer());
}

来调用MFC内封装的选择文件UI,若是要保存文件直接打开文件并将内容写入便可。

11.界面模块与计算模块的对接

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

UI模块的运行

在UI模块的运行按钮中绑定事件,点击后,UI模块获取界面上用户输入的信息,转换为相应的参数来调用。

须要注意的是,在运行UI模块时,并不支持经过命令行输入参数进行测试,请直接打开UI模块(MFC_max_word_chain.exe)。若是但愿经过命令行打开加参数的方式进行测试,请使用max_word_chain.exe。

对接方式

UI模块在资源文件中引入Core.lib,在MFC_max_word_chainDlg.cpp这个事件响应文件中,须要调用计算模块时,头文件引入Core.cpp。

处理好用户界面输入的信息后,调用

static int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
static int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);

这两个接口进行运算。运算后将result中获取的结果转换为Cstring返回到用户界面。
最终实现的功能以下:

12.结对的过程

描述结对的过程,提供非摆拍的两人在讨论的结对照片。

咱们两人的结对编程,经历了顺利的开端,略微坎坷的通过和最后圆满的结束。

起初最开始接触结对编程,咱们按照领航员与驾驶员的模式。首先两人一块儿商讨了关于最长链算法的问题,并在有环时如何处理上进行了一段时间的探讨。随后按照驾驶员写代码,领航员检查设计并监督的方式进行,开始效果良好,避免了很多人为bug的产生。

在几回身份对换以后,咱们发现,有时在他人的代码基础之上继续完成本身的代码十分困难。思路不断交替,写代码的效率有了必定的降低。在同伴在本身的代码基础上进行完成时,若完成的结果和本身的思路有些冲突,还会形成一些麻烦。好在项目比较小,咱们两方也都比较认真且有团队精神,在不断的磨合之中,适应了对方的步伐,磨合的也愈来愈好。在最后例如单元测试,错误处理等模块的实现中,咱们的结对编程比较顺利。

整体来讲此次结对编程是一次不错的体验,与他人一块儿交流,互相交换编程思想的经历难能难得。虽然在效率上可能有些问题,但最终项目的完成质量不错就是好的。

13.结对编程反思

看教科书和其它参考书,网站中关于结对编程的章节,例如:
http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html
说明结对编程的优势和缺点。
结对的每个人的优势和缺点在哪里 (要列出至少三个优势和一个缺点)。

结对编程的优势:

1> 提供更好的设计质量,一个不合理的设计通常不会获得两我的的支持,好比在此次结对编程的过程当中,尤为是在性能优化部分,我提出的一些优化方法老是能很快获得pair的反馈(支持或者举出反例),在设计的时候避免不合理的想法可以极大下降试错所带来的代价。

2> 获得更好的代码质量以及减小bug出现的概率,在此次结对编程中在写代码的时候若是没有遵循规范的时候PAIR会及时询问代码含义,督促每一个人遵循良好的代码规范,同时PAIR会指出不少写代码过程当中实现细节的bug。

3> 有利于两个结对编程的人互相交流学习和传递经验。

结对编程的缺点:

1> 两我的一块儿编程的时候会有不少思惟上的碰撞,每一个人的思考方式不同,也比较容易出现对彼此的意见产生疑问没法融合的状况。

2> 有可能出现结对编程逐渐演变成单人工做的情形。

队友的优缺点

优势:学习能力很强,对于以前没有接触过的技术可以快速查找资料并实现;工做能力很强,可以基本按时完成相关的计划;颇有本身的想法,对于项目的每一步都有思考。

缺点:有时候有点懒散,须要不定时提醒进度和计划。

本身的优缺点

优势:由于有班干部的经历,因此沟通交流能力较强;有时间观念,对计划的执行力较强;可以及时与队友沟通交流本身的想法与问题。

缺点:

算法能力有限,到最后也没有找到300s内跑出100个点的有向有环图的最长链问题。

14.PSP表格回填

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

注:有些部分的计时我也不是很肯定究竟算进哪部分比较合理,好比为了想实现300s跑100个有向有环图的点,查找了不少算法,也亲自动手写了一些进行验证,所以这部分一部分放在设计里面,一部分算进测试里面了。

15 模块交换与模块松耦合

由于有两个小组的邀请,因此最后和两个小组进行了模块交换,由于跟第一个小组中出现了问题并进行了修改,因此如下博客展现与第一个小组的交换出现的问题。

合做小组1成员: 16061192 汪慕澜 16061103 赵智源

合做小组2成员: 16021160 庄廓然 15061078 杨帅

问题: 问题主要起源于我对接口的理解有误,在初版程序的时候将类的两个函数public化做为了一个接口,并且这个类是有状态的。可是发现对方的直接声明了一个没有状态的类,声明了类的static方法。以前本身的写法会产生不少warning(warning提示调用接口须要声明一个类的客户端对象),在对面小组的解释后对本身的接口设计进行了重构,对接口又进行了一次封装。

交换效果:

咱们的程序在对方的gui上能够完成运行,效果以下:

对方的程序也能够在咱们的gui上完成运行:

相关文章
相关标签/搜索