【软件工程】结对编程总结

内容说明

项目 内容
这个作业属于哪个课程 罗杰
这个作业的要求在哪里 结对编程-最长单词链

1.Github地址

Github

2.PSP表格

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

3.Information Hiding, Interface Design, Loose Coupling

In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change).

Written another way, information hiding is the ability to prevent certain aspects of a class or software component from being accessible to its clients, using either programming language features (like private variables) or an explicit exporting policy.
—— wiki

信息隐藏是使用编程语言功能(如私有变量)或显式导出策略来阻止其客户端访问类或软件组件的某些方面的能力。在我们的结对编程中,我们将最终的接口设计为两个函数,而将具体的实现细节封装在独立的模块中,外界不知道内部的运行细节,较好的实现了信息隐藏。

接口设计:按照题中的要求进行设计,没有自行设计的接口。

松耦合:在这次开发中我们将计算模块的实现用Core.dll进行封装。

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

在这里我们首先设计了一个Solver类用于实现对最长单词链的计算,Node类实现存储计算中所需要的各个单词的节点数据。

算法的大致思路:考虑这是一个特殊的有向无环图的最长路径问题(无环情况下),我们可以将所有首先将所有的单词拓扑排序,按排序后的顺序可以使用动态规划的算法实现寻找出最长的路径。而对于有环的情况,我们使用了DFS搜索算法查找出最长路径。

独到之处:无环情况下,按单词首字母将单词分为26类,可能减少了单词数量较多时的排序时间。

5.画出UML图显示计算模块部分各个实体之间的关系

img

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

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

以下为无环,单词数量为10000的运行情况

img

从图中可以看出占用时间最长的是计算出文本中的最长单词链的函数,在这里我们考虑使用动态规划算法来减少计算模块所花的时间。

以下为有环,单词数量为74的运行情况

img

而对于有环的情况,我们这里就实现了一个基础的DFS算法,并采取了一定的剪枝策略,比如记录已经遍历过的点它的最长路径,然后当遍历到这个点的时候,判断一下当前长度+当前点的最长路径长度,如果小于当前发现的最长路径长度,那就不需要继续遍历下去了,因为当前节点所能到达的最长路径仍然小于当前最长路径。用这些剪枝策略,减少了一些递归遍历,一定程度上优化了算法。

7.Design by Contract, Code Contract

契约式设计具有如下的优缺点

  • 优点:能够获得更优秀的设计,组件服务的提供方和使用方各自的义务被表述的更清晰,从而使设计更加系统化、更清楚、更简单;契约可以提高可靠性,可以帮助开发者更好地理解代码并有助于测试;契约式设计可以简化调试工作,便于将错误定位出来。
  • 缺点:设计具有良好契约的程序需要相当的开销:撰写契约需要时间,开发者需要时间学习撰写良好契约的思想和技术,况且,并不是每个软件都需要那么高的质量。

在我们的项目中,我们在gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop)中对传入的参数进行了简单的检查,若不符合约定则通过assert跳出。完成测试后,我们出于性能的考虑,删除了assert语句,使用如下代码进行检查是否符合约定。

if (len <= 0) {
    throw IllegalInterfaceParaException();
}
if (head != 0 && !(head >= 'a' && head <= 'z') && !(head >= 'A' && head <= 'Z')) {
    throw IllegalInterfaceParaException();
}
if (tail != 0 && !(tail >= 'a' && tail <= 'z') && !(tail >= 'A' && tail <= 'Z')) {
    throw IllegalInterfaceParaException();
}

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

在测试计算模块的之前,我们首先测试了一下命令行参数处理模块是否正确:

TEST_METHOD(TestMethod1) {
    // TODO: 在此输入测试代码
    argc = 3;
    argv[1] = "-c";
    argv[2] = "input.txt";
    tag = -1;
    headCh = '\0';
    endCh = '\0';
    isRing = false;
    filename = std::string();
    getopt(argc, argv, tag, headCh, endCh, isRing, filename);
    Assert::AreEqual(1, tag);
    Assert::AreEqual(headCh, '\0');
    Assert::AreEqual(endCh, '\0');
    Assert::IsFalse(isRing);
    Assert::AreEqual(0, filename.compare("input.txt"));
}
TEST_METHOD(TestMethod2) {
    // TODO: 在此输入测试代码
    argc = 3;
    argv[1] = "-w";
    argv[2] = "input.txt";
    tag = -1;
    headCh = '\0';
    endCh = '\0';
    isRing = false;
    filename = std::string();
    getopt(argc, argv, tag, headCh, endCh, isRing, filename);
    Assert::AreEqual(0, tag);
    Assert::AreEqual(headCh, '\0');
    Assert::AreEqual(endCh, '\0');
    Assert::IsFalse(isRing);
    Assert::AreEqual(0, filename.compare("input.txt"));
}

类似的TEST_METHOD我们写了6个,用以测试各项参数读入处理后,tag、headCh、endCh、isRing和filename是否正确。

然后,我们又对计算模块的单词个数最多字符串单词长度最长字符串这两个方法进行了单元测试:

  • 单词个数最多字符串int gen_chain_word();
TEST_METHOD(TestMethod1) {
    // TODO: 在此输入测试代码
    int wordIndex = 0;
    char ** result;
    char headCh = '\0';
    char endCh = '\0';
    bool isRing = false;
    char * wordlist[100];
    wordlist[0] = new char[20] {
        "annzcclv"
    };
    wordlist[1] = new char[20] {
        "klebwukqbui"
    };
    wordlist[2] = new char[20] {
        "qhqkibinpyew"
    };
    wordlist[3] = new char[20] {
        "fkapwouje"
    };
    wordlist[4] = new char[20] {
        "mitecsqa"
    };
    wordlist[5] = new char[20] {
        "mogowquzdsmto"
    };
    wordlist[6] = new char[20] {
        "oxkyhmgemdfpq"
    };
    wordlist[7] = new char[20] {
        "hzvreibfb"
    };
    wordlist[8] = new char[20] {
        "phgxdlmyrw"
    };
    wordlist[9] = new char[20] {
        "kuckfwlghglua"
    };
    wordlist[10] = new char[20] {
        "ucqavnwkqseyy"
    };
    wordlist[11] = new char[20] {
        "quhxkzqxf"
    };
    wordlist[12] = new char[20] {
        "iwoegjfbxhu"
    };
    wordIndex = 13;
    result = new char * [wordIndex];
    int ans = gen_chain_word(wordlist, wordIndex, result, headCh, endCh, isRing);
    Assert::AreEqual(4, ans);
    Assert::IsFalse(std::strcmp(result[0], "mogowquzdsmto"));
    Assert::IsFalse(std::strcmp(result[1], "oxkyhmgemdfpq"));
    Assert::IsFalse(std::strcmp(result[2], "quhxkzqxf"));
    Assert::IsFalse(std::strcmp(result[3], "fkapwouje"));
}
TEST_METHOD(TestMethod3) {
    // TODO: 在此输入测试代码
    int wordIndex = 0;
    char ** result;
    char headCh = '\0';
    char endCh = '\0';
    bool isRing = false;
    char * wordlist[100];
    wordlist[0] = new char[20] {
        "annzcclv"
    };
    wordlist[1] = new char[20] {
        "klebwukqbui"
    };
    wordlist[2] = new char[20] {
        "qhqkibinpyew"
    };
    wordlist[3] = new char[20] {
        "fkapwouje"
    };
    wordlist[4] = new char[20] {
        "mitecsqa"
    };
    wordlist[5] = new char[20] {
        "mogowquzdsmto"
    };
    wordlist[6] = new char[20] {
        "oxkyhmgemdfpq"
    };
    wordlist[7] = new char[20] {
        "hzvreibfb"
    };
    wordlist[8] = new char[20] {
        "phgxdlmyrw"
    };
    wordlist[9] = new char[20] {
        "kuckfwlghglua"
    };
    wordlist[10] = new char[20] {
        "ucqavnwkqseyy"
    };
    wordlist[11] = new char[20] {
        "quhxkzqxf"
    };
    wordlist[12] = new char[20] {
        "iwoegjfbxhu"
    };
    wordIndex = 13;
    result = new char * [wordIndex];
    headCh = 'k';
    int ans = gen_chain_word(wordlist, wordIndex, result, headCh, endCh, isRing);
    Assert::AreEqual(3, ans);
    Assert::IsFalse(std::strcmp(result[0], "klebwukqbui"));
    Assert::IsFalse(std::strcmp(result[1], "iwoegjfbxhu"));
    Assert::IsFalse(std::strcmp(result[2], "ucqavnwkqseyy"));
}
  • 单词长度最长字符串int gen_chain_char();
TEST_METHOD(TestMethod2) {
    // TODO: 在此输入测试代码
    int wordIndex = 0;
    char ** result;
    char headCh = '\0';
    char endCh = '\0';
    bool isRing = false;
    char * wordlist[100];
    wordlist[0] = new char[20] {
        "annzcclv"
    };
    wordlist[1] = new char[20] {
        "klebwukqbui"
    };
    wordlist[2] = new char[20] {
        "qhqkibinpyew"
    };
    wordlist[3] = new char[20] {
        "fkapwouje"
    };
    wordlist[4] = new char[20] {
        "mitecsqa"
    };
    wordlist[5] = new char[20] {
        "mogowquzdsmto"
    };
    wordlist[6] = new char[20] {
        "oxkyhmgemdfpq"
    };
    wordlist[7] = new char[20] {
        "hzvreibfb"
    };
    wordlist[8] = new char[20] {
        "phgxdlmyrw"
    };
    wordlist[9] = new char[20] {
        "kuckfwlghglua"
    };
    wordlist[10] = new char[20] {
        "ucqavnwkqseyy"
    };
    wordlist[11] = new char[20] {
        "quhxkzqxf"
    };
    wordlist[12] = new char[20] {
        "iwoegjfbxhu"
    };
    wordIndex = 13;
    result = new char * [wordIndex];
    int ans = gen_chain_char(wordlist, wordIndex, result, headCh, endCh, isRing);
    Assert::AreEqual(4, ans);
    Assert::IsFalse(std::strcmp(result[0], "mogowquzdsmto"));
    Assert::IsFalse(std::strcmp(result[1], "oxkyhmgemdfpq"));
    Assert::IsFalse(std::strcmp(result[2], "quhxkzqxf"));
    Assert::IsFalse(std::strcmp(result[3], "fkapwouje"));
}
TEST_METHOD(TestMethod9) {
    // TODO: 在此输入测试代码
    int wordIndex = 0;
    char ** result;
    char headCh = '\0';
    char endCh = '\0';
    bool isRing = false;
    char * wordlist[100];
    wordlist[0] = new char[20] {
        "rlqokvxuq"
    };
    wordlist[1] = new char[20] {
        "vvitmqskdyeap"
    };
    wordlist[2] = new char[20] {
        "llkgasgiuzlgx"
    };
    wordlist[3] = new char[20] {
        "cxadwktc"
    };
    wordlist[4] = new char[20] {
        "yinrlisikdjq"
    };
    wordlist[5] = new char[20] {
        "cbrcxzoyigcv"
    };
    wordlist[6] = new char[20] {
        "roeuzja"
    };
    wordlist[7] = new char[20] {
        "pwwbogbwp"
    };
    wordlist[8] = new char[20] {
        "rjztssi"
    };
    wordlist[9] = new char[20] {
        "vypbjouumrc"
    };
    wordlist[10] = new char[20] {
        "vgorbjxqpap"
    };
    wordlist[11] = new char[20] {
        "vrczrlwavkfq"
    };
    wordIndex = 12;
    result = new char * [wordIndex];
    isRing = true;
    int ans = gen_chain_char(wordlist, wordIndex, result, headCh, endCh, isRing);
    Assert::AreEqual(5, ans);
    Assert::IsFalse(std::strcmp(result[0], "vypbjouumrc"));
    Assert::IsFalse(std::strcmp(result[1], "cxadwktc"));
    Assert::IsFalse(std::strcmp(result[2], "cbrcxzoyigcv"));
    Assert::IsFalse(std::strcmp(result[3], "vvitmqskdyeap"));
    Assert::IsFalse(std::strcmp(result[4], "pwwbogbwp"));
}

与测试命令行参数处理模块类似,我们对gen_chain_wordgen_chain_char进行了headCh、endCh和isRing这几个参数的各种情况的测试,充分考虑了各个分支,达到了比较好的测试效果。最终我们的总体覆盖率如下:

img

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

  • WordRingsException:当默认情况下输入出现单词环时抛出
  • IllegalInterfaceParaException:当调用接口时传入不合理的参数时(如len <= 0,head或tail既不为'\0'也不是字母时)
  • IllegalParametersException:命令行参数出现错误时
  • FileNotExitException:输入的文本不存在时

10.命令行模块的详细设计过程

命令行处理我们采用最简朴的字符串比较方法,依次对参数进行分析:

img

对于总体我们处理了不含有“-w”“-c”的异常(因为此时我们不知道该如何选择最长字符串),同时在每一个分支中,我们也都进行了异常处理,如既有“-w”又有“-c”的,或是“-h”“-t”后面没有字母或是不是字母字符等等情况。

命令行处理模块将从控制台读入的信息处理好后,将信息储存在tag,headCh,endCh,isRing和filename中,供getFileInput()方法调用。

11.命令行模块与计算模块的对接

命令行模块引入了定义接口的头文件,直接可以通过调用定义的两个接口实现计算最长单词链的过程。

12.描述结对的过程

总的来说本次结对编程的体验很不错,开始很顺利,过程中虽然遇到了或大或小的麻烦,但我们共同克服,最后写出的项目质量也还不错。

刚开始的时候,我们还不是很适应“驾驶员”、“领航员”的模式,基本就是商量着写代码,但效率也还不错,设计的时候两个人相互补充,也避免了未考虑到某些方面的情况发生。很快我们就将“有向无环图”的情况完成,并进行了测试,性能还不错。不过之后的“有向有环图”的设计就陷入了僵局,我们除了暴力深度搜索想不出什么比较好的方式(事实是我们最后也还是暴力搜索),一度两人都在摸鱼。不过后来,我们决定先将其完成,然后再进行剪枝来优化(毕竟测试数据集也不是很大),很快也将“有向有环图”的情况完成。最后的单元测试和错误处理模块,我们也很顺利地完成。

总体来说这次结对编程让我们体验到了这种新的编程模式,能够和队友在交流碰撞,产生新的想法和点子,互相纠正、互相补充,这种经历十分可贵,于我个人而言也是一次很大的提升。不过,这都是建立在我和我的队友是平日关系密切、很熟悉的前提下,如果在公司中采取这种方式,我就不是很肯定效率会如何了。

最后,奉上结对编程留念图:

img

13.说明结对编程的优点和缺点。结对的每一个人的优点和缺点在哪里

  1. 结对编程的优点和缺点
    • 优点:能够在结对编程的过程中使得代码都至少被两个人检查过,可以提高程序的质量,同时两个人更容易想出好的点子。
    • 缺点:在编程效率上可能会比两个人各自完成自己的部分要低一些。
  2. 结对的每一个人的优点和缺点
成员 优点 缺点
蒋锋 思考问题比较奇葩,能发现一些可能比较好的点子;肯花时间进行项目;善于学习别人的优点 喜欢给项目写入新的BUG
张哲维 有独立思想,适合担任领导者;动手能力强,coding神速;考虑问题比较全面 不能及时发现蒋锋写入的BUG

14.PSP表格回填

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 20
· Estimate · 估计这个任务需要多少时间 30 20
Development 开发 890 890
· Analysis · 需求分析 (包括学习新技术) 100 120
· Design Spec · 生成设计文档 60 30
· Design Review · 设计复审 (和同事审核设计文档) 40 20
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 40
· Design · 具体设计 140 120
· Coding · 具体编码 300 360
· Code Review · 代码复审 80 50
· Test · 测试(自我测试,修改代码,提交修改) 140 150
Reporting 报告 90 100
· Test Report · 测试报告 30 10
· Size Measurement · 计算工作量 30 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 40
合计 1010 1010
posted @ 2019-03-14 23:26  diralpo  阅读(468)  评论(2编辑  收藏  举报