结对项目-最长单词链博客

一、Github项目地址

二、PSP表格(程序各个模块在开发上预计耗费的时间和实际耗费的时间)

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

三、关于Information Hiding, Interface Design, Loose Coupling等方法的接口设计

Information Hiding

  • 其实题目给出的三种方法或者说是原则都是和封装有关的,在个人理解里封装是技术,而好比信息隐藏实则是目的。虽然咱们在实际的代码中能够说是没有用到面向对象的格式来编写,但其实用到的仍然仍是面向对象的思想。好比说计算核心Core的内部功能是不展现出来的,经过做业规定的两个接口来使用户和代码交换各自须要的东西。相似的在用户输入模块、读取文件模块等咱们都设计了与计算模块Core相似的接口,每一个不一样独立的功能都封装成了函数,保证信息的隐藏功能。

Interface Design

  • 我查阅了一些资料,找到的大多数的接口设计貌似都是很陌生并且与如今所学格格不入。但仍是从其中学习到了一些不管设计什么接口都应该最起码遵循的原则,好比命名必须规范优雅,保证接口要作的事情是比较单一的事情(单一性),良好的可扩展性和可移植性,而在实际编程中咱们也是这样作的。

Loose Coupling

  • 这个词刚刚看上去我甚至都不知道是什么意思,在维基百科上才大概了解了一些:

In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Subareas include the coupling of classes, interfaces, data, and services.[1] Loose coupling is the opposite of tight coupling.git

——引用自维基百科程序员

  • 可是仍然觉的对这个概念很不清晰,继续往下看并按照中文翻译查了一些资料,大概知道了其仍是指一个组件与另外一个组件具备直接联系的程度。仍然仍是封装与非封装的意思。在这里举几个咱们程序中的封装接口的例子:
void InputHandler(int argc, char* argv[], bool &enable_loop, int &word_or_char, char &head, char &tail, string &Filename);
void ReadFile(string FileName, vector<string> &words);
void DFS_Length(Graph G, int v, vector<string> words, char tail);

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

4.1 问题分析

之前作过相似的题,输入的全部单词可否所有首尾相连造成链。因为单词首尾相连有多种链接方式,故基本的数据结构为图。
建图有两种方式,一种是以单词为节点,若是单词间正好能够首尾链接,则添加一条边,该边即为链接的字母。另外一种建图方式是以字母为节点,以单词为边,出现一个单词,即把首字母节点向尾字母节点添加一条边,边的值即为该单词。
对于这道题目而言,因为单词须要输出,加之对第二种建图方式掌握并不熟练,所以选择的是第一种建图方式。
模型确立后,问题就能够简化成“求图中的最长链”,即最长路径问题,显然问题是多源最长路径问题。github

4.2 数据结构与算法

数据结构为图,存储方式为邻接矩阵,理由是能更契合floyd算法。
对于无环状况,因为为多源最长路径问题,联想到最短路径问题,能够肯定为floyd算法。
而对于有环状况,因为出现了正值环,floyd算法再也不适用。在找不到更有解决方法的状况下,只能适用DFS深度优先搜索求解。算法

4.3 模块组织

ReadFile: 读取文件的模块,将文件中的单词提取进入容器vector中。
Graph: 图的定义。
InputHandler:处理输入的模块,读取命令行并处理参数。
FindLongestWordList: 计算模块,内含计算接口。计算出单词中的最长链。编程

4.4 算法关键

首先须要判断有无环,对于没有-r参数的输入来讲,若是有环须要报错。这里也是用到DFS的染色算法。每一个点有三种状态:未遍历过,遍历过,当前序列正在遍历。若是一次DFS中一个点与正在遍历中的点相连了,说明DFS回到了以前的点,即图中有环。
另外一问题是因为无环状况最多可有10000个单词,而floyd算法时间复杂度为O(n^3),暴力的计算显然是不行的。考虑到对于无环的状况,有以下特性:对于单词element和elephant,因为无环,这两个单词最多只有一个会出如今链中。(不然会出现element, t..., ..., ....e, elephant / element,这样必定是有环的),而若是要知足字母最多,显然这时候须要选择elephant加入链中。所以咱们能够对于全部首尾字母相同的单词,保留首尾字母组合中,最长的一个单词。这样的操做以后,最多的单词数目为351,即便是时间复杂度O(n^3)的算法也能很快得出结果。另外能够计算得,最长链的长度最大为51。数组

五、UML图

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

  • 首先是无环状况,其性能最大阻碍是10000个单词大样本状况下,floyd算法时间复杂度太高致使的。可是在4.4有介绍过,咱们能够经过无环单词链的特性来削减样本数量,削减后单词数量少,即便时间复杂度高也能很快跑出结果。所以性能方面上没有太大问题。

  • 其次是有环状况,因为DFS算法仍属于暴力递归搜索,并不算很好的算法,其性能也着实较差。可是咱们也想不到更好的解决算法,因此并无改进。

七、关于Design by Contract, Code Contract的优缺点以及结对做业中的体现

  • 契约式编程对于软件工程是一个极大的理论改革,对于C/S模式形成了极大的影响和冲击。对于C/S模式,咱们看待两个模块的地位是不平等的,咱们每每要求server很是强大,能够处理一切可能的异常,而对client漠不关心,形成了client代码的低劣。而在DbC中,使用者和被调用者地位平等,双方必须彼此履行义务,才能够行驶权利。调用者必须提供正确的参数,被调用者必须保证正确的结果和调用者要求的不变性。双方都有必须履行的义务,也有使用的权利,这样就保证了双方代码的质量,提升了软件工程的效率和质量。缺点是对于程序语言有必定的要求,契约式编程须要一种机制来验证契约的成立与否。而断言显然是最好的选择,可是并非全部的程序语言都有断言机制。那么强行使用语言进行模仿就势必形成代码的冗余和不可读性的提升。好比.NET4.0之前就没有assert的概念,在4.0后全面引入了契约式编程的概念,使得契约式编程的可用性大大提升了。此外,契约式编程并未被标准化,所以项目之间的定义和修改各不同,给代码形成很大混乱,这正是不多在实际中看到契约式编程应用的缘由。在咱们的代码中,对于模块间使用了契约的思想,保证双方地位的平等。调用者的传入参数必须是正确的,不然责任不在被调用者,而在传入者。

——优缺点引用自维基百科数据结构

八、计算模块部分单元测试展现

  • 在单元测试部分咱们对程序中除输出部分外(因为输出部分只是一个简单的输出到文件)其余因此部分或函数进行的全面的单元测试,如图共25个,单元测试的所有代码也已上传至Github。下面我将拿出部分单元测试代码具体介绍,并在这部分的最后附上单元测试的测试服几率截图。
TEST_METHOD(TestMethod3)
        {
            // TODO: normal_test3
            char* words[101] = { "element", "heaven", "table", "teach", "talk"};
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }

            int l = gen_chain_word(words, 5, answer, 0, 0, true);
            Assert::AreEqual(l, 4);
            Assert::AreEqual("table", answer[0]);
            Assert::AreEqual("element", answer[1]);
            Assert::AreEqual("teach", answer[2]);
            Assert::AreEqual("heaven", answer[3]);
            for (int i = 0; i < 101; i++)
            {
                free(answer[i]);
            }
        }
  • 上面的单元测试代码是测试计算核心中的gen_chain_word接口函数,因为单元测试须要我手动加入words,因此这里的单元测试数据比较小,就是构造一个有环有链的单词文本,而且是在输入‘-r’的状况下,从而获得一个正确的单词链。
TEST_METHOD(TestMethod6)
        {
            // TODO: normal_test6
            char* words[101] = { "apple", "banane", "cane", "a", "papa", "erase" };
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }

            int l = gen_chain_char(words, 6, answer, 'a', 'e', false);
            Assert::AreEqual(l, 3);
            Assert::AreEqual("a", answer[0]);
            Assert::AreEqual("apple", answer[1]);
            Assert::AreEqual("erase", answer[2]);
            for (int i = 0; i < 101; i++)
            {
                free(answer[i]);
            }
        }
  • 上面的单元测试代码是测试计算核心中的gen_chain_char接口函数,这里构造了一个没有环的文本数据,并且其最多单词链和最长单词链不一样,并固定了首尾字母。
TEST_METHOD(TestMethod2)
        {
            // 正确_2
            int argc = 6;
            char* argv[101] = { "Wordlist.exe", "-r", "-h", "a", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);

            Assert::AreEqual(enable_loop, true);
            Assert::AreEqual(word_or_char, 2);
            Assert::AreEqual(head, 'a');
            Assert::AreEqual(tail, char(0));
            Assert::AreEqual(Filename, (string)"test_1.txt");
        }
  • 上面的单元测试代码是测试接收命令行输入函数InputHandler,这里没有什么太多好说的, 就是把命令行输入参数的全部正确组合所有测试一遍便可(参数输入顺序能够改变)。

单元测试覆盖率截图(因为C++没有找到直接测试单元测试覆盖率的插件,这里用的方法是将单元测试代码移至main函数中用OpenCppCoverage插件获得的覆盖率,部分异常测试没有放进来,因此覆盖率没有达到100%)app

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

  • 在异常处理模块咱们一共自定义了8种类型的异常,接下来我将会结合每种异常的单元测试说明每种异常的设计目标以及错误对应的场景(单元测试的构造方法就是保证此函数能够捕捉到异常且捕捉的是与当前错误相对应的异常,不然单元测试不经过)。

1. 错误的参数组合(其中包括出现多个相同命令好比‘-r’、‘-r’,‘-h’和‘-c’同时出现,‘-h’和‘-c’都没有,即不指定求何种单词链)

TEST_METHOD(TestMethod3)
        {
            // 错误_1
            int argc = 5;
            char* argv[101] = { "Wordlist.exe", "-r", "-r", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            try {
                InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);
                Assert::IsTrue(false);
            }
            catch (myexception1& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是‘-r’出现了两次,错误的参数组合。函数

2. 指定单词链首尾不合法(好比‘-h’、‘1’或者‘-t’、‘ag’)

TEST_METHOD(TestMethod7)
        {
            // 错误_5
            int argc = 6;
            char* argv[101] = { "Wordlist.exe", "-r", "-h", "1", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            try {
                InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);
                Assert::IsTrue(false);
            }
            catch (myexception2& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是‘-h’指定首字母为‘1’,明显是错误的。oop

3. 输入的参数不是指定的那几个参数,不符合规定(如输入‘-b’)

TEST_METHOD(TestMethod9)
        {
            // 错误_7
            int argc = 5;
            char* argv[101] = { "Wordlist.exe", "-b", "-r", "-c", "test_1.txt" };
            char head;
            char tail;
            bool enable_loop;
            int word_or_char = 0;
            string Filename;
            try {
                InputHandler(argc, argv, enable_loop, word_or_char, head, tail, Filename);
                Assert::IsTrue(false);
            }
            catch (myexception3& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是输入参数‘-b’显然是不符合规定的。

4. 文件不存在的状况

TEST_METHOD(TestMethod2)
        {
            // 错误
            vector <string> words;
            try {
                ReadFile("normal_test3.txt", words); // 不存在的文件
                Assert::IsTrue(false);
            }
            catch (myexception4& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是测试了一个在此路径下不存在的文件。

5. 读取的文件中有单词长度超过600

TEST_METHOD(TestMethod2)
        {
            // 错误
            vector <string> words;
            try {
                ReadFile("long_word_test.txt", words);
                Assert::IsTrue(false);
            }
            catch (myexception4& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是文件中存在长度超过600的单词。

6. 读取的文件中无环的超过10000个单词,有环的超过100个单词

TEST_METHOD(TestMethod2)
        {
            // 错误
            vector <string> words;
            try {
                ReadFile("more_words_test.txt", words); 
                Assert::IsTrue(false);
            }
            catch (myexception4& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是所测试文件中单词数超过了10000。

7. 读取文件中有单词环且参数没有输入‘-r’

TEST_METHOD(TestMethod10)
        {
            // wrong_test2
            char* words[101] = { "alement", "oeaven", "tabla", "teaco", "talk" };
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }


            try {
                int l = gen_chain_char(words, 5, answer, 0, 'n', false);
                Assert::IsTrue(false);
            }
            catch (myexception7& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是传入单词能够造成环,且用户没有传入参数‘-r’。

8. 读取文件中没法造成最少两个单词的单词链

TEST_METHOD(TestMethod11)
        {
            // wrong_test3
            char* words[101] = { "alement", "oeaven", "tabla", "teaco", "talk" };
            char* answer[101];
            for (int i = 0; i < 101; i++)
            {
                answer[i] = (char*)malloc(sizeof(char) * 601);
            }


            try {
                int l = gen_chain_word(words, 5, answer, 'b', 'n', true);
                Assert::IsTrue(false);
            }
            catch (myexception8& e) {
                Assert::IsTrue(true);
            }
            catch (...) {
                Assert::IsTrue(false);
            }
        }

这个单元测试是规定了首尾字母后,单词链中没有用户所要求的单词链。

十、界面模块的详细设计过程(GUI)

  • 在界面模块这方面咱们没有实现GUI,而是完成了最基本的命令行模块。其实若是是命令行模块的话就很是简单了,根据在命令行输入的内容及长度存入char* argv[]以及int argc中,而后再传给InputHandler函数中对传入的参数进行分析处理,主要是识别错误的参数输入(第9部分已经详细介绍)以及将正确的参数组合中的信息存下来,好比说head和tail是否有限定,单词文本是否容许有环以及要求的单词链是要单词最多仍是单词总长度最长。因为实现很简单,这里没必要再贴上代码赘述。

十一、界面模块与计算模块的对接(GUI)

  • 命令行模块与两个计算核心模块的对接其实也很简单。咱们从命令行读入的各种参数若是是正确无误的,那么咱们能够相对应地肯定传入两个计算模块的head、tail、enable_loop以及执行哪一个计算模块的判断变量。即肯定规范的单词链首字母尾字母,若是没有规定则传入0,是否容许有环的变量。若是不容许,则须要判断传入单词文本是否能够造成环,若是造成环则报告异常。下面是简单是一张命令行输入截图:

十二、结对之过程

  • 因为与队友为舍友,结对时相对简单不少,只须要到对铺和队友一块儿结对编程就好了。咱们的水平差很少, 编程能力和数据结构算法的掌握都不算太好。初期时咱们主要是一块儿讨论算法,如何实现基本的功能,数据结构应该用什么。敲定一个算法以后就开始分头找资料,最后再汇总资料,交给他来敲代码或者我来在一些地方进行修改。编写时常常会遇到一些意料不到的bug,最后必须一块儿搜索如何解决。可是两我的在一块儿编写代码时,有一我的来随时审视代码,有不懂的地方或者不对劲的地方另外一人均可以随时提出来。所以虽然结对编程效率没有提升, 可是效果会比两个单人编写来的更好。
    总的来讲此次题目难度仍是没有那么爆炸,因此咱们之间的合做也比较愉快。至于提意见的艺术是根本用不上的,毕竟是舍友也不会产生矛盾。下面是咱们在初期时讨论算法的图片:

1三、结对编程的优缺点及评价

结对编程优缺点

  • 下面是一些结对编程的优势:程序员互相帮助,互相教对方,能够获得能力上的互补。可让编程环境有效地贯彻Design。加强代码和产品质量,并有效的减小BUG。下降学习成本。一边编程,一边共享知识和经验,有效地在实践中进行学习。在编程中,相互讨论,可能更快更有效地解决问题。固然,结队编程也会有一些很差的地方:对于有不一样习惯的编程人员,能够在起工做会产生麻烦,甚至矛盾。有时候,程序员们会对一个问题互不相让(代码风格可能会是引起技术人员口水战的地方),争吵不休,反而产生重大内耗。两我的在一块儿工做可能会出现工做精力不能集中的状况。程序员可能会交谈一些与工做无关的事情,反而分散注意力,致使效率比单人更为低下。

评价(队友陈致远)

  • 优势:
    • 认真负责,轮流编程时的任务完成准时并且质量很高
    • 有探索精神,有遇到不管软件问题仍是算法问题必定要探个究竟
    • 考虑全面,程序不管正确状况方面仍是报错方面都考虑的很细致
  • 缺点:
    • 咱们项目经验都比较少,有些地方都不是很驾轻就熟
相关文章
相关标签/搜索