【软工】结对编程

【软工】结对编程

本做业属于课程软件工程
做业要求点此c++

第一部分: github地址

源码在githubgit

第二部分:PSP表(包括了实际完成的PSP表)

Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
计划 30 30
· 估计这个任务须要多少时间 30 30
开发 910 1690
· 需求分析 (包括学习新技术) 300 180
· 生成设计文档 30 60
· 设计复审 (和同事审核设计文档) 60 30
· 代码规范 (为目前的开发制定合适的规范) 30 60
· 具体设计 100 200
· 具体编码 430 900
· 代码复审 60 60
· 测试(自我测试,修改代码,提交修改) 200 200
报告 290 260
· 测试报告 130 200
· 计算工做量 30 30
· 过后总结, 并提出过程改进计划 30 30
合计 1430 1980

第三部分:接口设计方法

1.信息隐藏:类的全部属性私有,只有须要被调用的方法共有
2.接口设计:参数处理、文件读入、单词链计算功能独立,互不重叠
3.松耦合:核心能够单独调用,并经过了交换测试github

第四部分:计算模块接口的设计与实现过程

算法设计

对于计算模块,首先要作的就是将读入的单词列表构建为一个图,比较基本的两种思路分别是算法

  • 以单词做为顶点、首尾字母做为边;
  • 以首尾字母做为顶点、单词做为边。

由于咱们须要分别计算最多单词数和最多字母数的最长单词链,采用第二种方法时,对于求最多字母数的最长单词链,能够直接将单词的字母数做为图中边的权重;对于计算最多单词数的最长单词链,能够将各个边的权重都初始化为1。这样就能够将两个函数后续的求解过程统一块儿来,将求解最长单词链的问题转化为求一个有向带权图的最长路径问题,明显要比第一种方法便捷,因此我首先们采用第二种方法来构建一个带权有向图。编程

对图进行初始化之后,第二步须要判断构造的图是有环图仍是无环图,而后分别采起不一样的算法。这里咱们采用的判断方法为进行拓扑排序,若是全部顶点能构成一个拓扑序列,说明图中无环,不然有换。less

对于有向无环图(DAG)的最长路径问题,能够将权值取负,而后就转化为求一个不带负权环的最短路径问题,或者直接用Floyd等算法来求最长路径,可是由于已经进行了拓扑排序,因此最简便的方法仍是直接采用动态规划方法按排序结果求解最长路径。对于以邻接表存储的图,拓扑排序的时间复杂度是O(V+E),求出拓扑顺序后动态规划算法的时间复杂度也为O(V+E),所以算法总的时间复杂度为O(V+E)。函数

对于带环图,求其最长路径属于NP-Hard问题,并无比较高效的算法。咱们采用的方法主要是按照以每一个顶点做为起始点或根据所求的起始顶点,采用深度优先搜索找出全部的路径,而后从中选择最长的路径。工具

算法的独到之处主要在于经过在构建有向图时,经过赋不一样的权值将求解最多单词和最多字母的问题统一块儿来,便于后续的求解,并增长了代码的复用性。使用的拓扑排序策略不只能够判断图中是否有环,更为无环图的求解奠基了基础。性能

计算模块实现

计算模块主要包括一个WordInfo类型和WordGraph,Compute两个类,另外还有gen_chain_word,gen_chain_char两个接口函数,类中的成员和函数的详细信息见下面的类图。单元测试

WordInfo类型记录了单个单词的各项信息,如起始字母、终止字母,权重,在单词列表中的位置等。

WordGraph类将根据输入的单词列表构建一个有向加权图,并对图进行拓扑排序,判断是否有环。

Compute类中含有一个WordGraph成员,根据图的类型采用不一样的算法进行最长单词链的计算。

第五部分:UML图显示计算模块部分各个实体

点击菜单栏“工具”→“获取工具和功能” 获取组件类设计器,而后可用经过对应文件的右键菜单中的“查看类图选项”直接生成类图。

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

在改进计算模块接口部分的性能上大概花费了2天的时间。

改进思路

计算模块在求最长路径时,对于有环图和无环图,首先采用的方法为以字母为顶点,单词为边构造邻接表,而后以每一个顶点做为起始点采用深度优先搜索,找出全部的路径,从中选择符合要求的最长路径的基本算法,确保首先可以实现基本的生成功能,避免出现“过早优化”。性能的改进主要在如下几个方面:

  1. 采用拓扑排序对生成的图进行排序,判断图中是否有环;
  2. 在根据输入单词初始化图时,将首尾字母分别相等(但首字母与尾字母不一样)的多个单词合成一条边,权值取单词中长度最长的一个,简化生成的图,便于后续的拓扑排序(若是排序后发现为有环图,则说明两个顶点之间的多条同向边不能合并,在使用dfs搜索以前再从新初始化图)。
  3. 对于无环图,采用动态规划算法依次求出以每一个顶点为尾节点的最长路径,没必要搜索全部路径;
  4. 对于指定首字母或尾字母的有环图,只搜索全部以该字母开始或结束的路径,而不是搜索全部路径。
  5. 对于较为复杂的有环图,有想过用一些启发式算法来求解,可是这类算法在不少时候求得的都是局部最优解,并不能保证必定能求得全局最优解,与咱们题目的要求不符,于是最终没有采用这类算法。

性能分析

如下是读入一个不带单词环文本时的性能分析图,能够看出除main函数外,花费最多的函数为File_handle类中的deal_file函数,时间主要花费在文本读入过程当中。

如下是读入一个带单词环文本时的性能分析图,能够看出程序中消耗最大的函数为Core模块的dfs函数,主要时间都花费在了路径搜索上,花费了99%以上的时间。

第七部分:契约式设计

优:
容许在编译时检查程序的正确性
极大的保证了程序正确性
缺:
在生产中没法自由地把这些契约disable
对于大量输入输出的函数拖慢运行速度

融入:每一个模块都设计了独立的数据检查

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

部分单元测试代码

对于计算模块接口的测试主要分为两大类,正常状况和异常状况处理,本部分主要介绍正常输入下的单元测试,异常处理将在下一部分介绍。

1.简单测试

TEST_METHOD(TestMethod1)
        {
            char* input[4] = { "END", "OF", "THE", "WORLD" };
            char* result[4] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_word(input, 4, result, 0, 0, false);
            Assert::AreEqual(len, 2);
        }

2.最多单词测试

TEST_METHOD(TestMethod2)
        {
            char* input[11] = { "Algebra", "Apple", "Zoo", "Elephant", "Under", "Fox", "Dog", "Moon", "Leaf", "Trick", "Pseudopseudohypoparathyroidism" };
            char* result[11] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_word(input, 11, result, 0, 0, false);
            Assert::AreEqual(len, 4);
        }

3.最多字母测试

TEST_METHOD(TestMethod3)
        {
            char* input[11] = { "Algebra", "Apple", "Zoo", "Elephant", "Under", "Fox", "Dog", "Moon", "Leaf", "Trick", "Pseudopseudohypoparathyroidism" };
            char* result[11] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_char(input, 11, result, 0, 0, false);
            Assert::AreEqual(len, 2);
        }

4.最多单词带环测试

TEST_METHOD(TestMethod6)
        {
            char* input[5] = { "Algebra", "Applea", "Zooa", "Elephant", "Under" };
            char* result[5] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_word(input, 5, result, 0, 0, true);
            Assert::AreEqual(len, 3);
        }

5.最多字母带环测试

TEST_METHOD(TestMethod7)
        {
            char* input[5] = { "Algebra", "Applea", "Zooa", "Elephant", "Under" };
            char* result[5] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_char(input, 5, result, 0, 0, true);
            Assert::AreEqual(len, 3);
        }

6.最多单词特殊测试

TEST_METHOD(TestMethod10)
        {
            char* input[5] = { "aa", "aaa", "aaaa", "aaaaa", "a" };
            char* result[5] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_word(input, 5, result, 0, 0, true);
            Assert::AreEqual(len, 5);
        }

7.最多字母特殊测试

TEST_METHOD(TestMethod11)
        {
            char* input[5] = { "aa", "aaa", "aaaa", "aaaaa", "a" };
            char* result[5] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_char(input, 5, result, 0, 0, true);
            Assert::AreEqual(len, 5);
        }

8.最多单词指定首字母测试

TEST_METHOD(TestMethod12)
        {
            char* input[11] = { "Algebra", "Apple", "Zoo", "Elephant", "Under", "Fox", "Dog", "Moon", "Leaf", "Trick", "Pseudopseudohypoparathyroidism" };
            char* result[11] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_word(input, 11, result, 'a', 0, false);
            Assert::AreEqual(len, 4);
        }

9.最多字母指定首字母测试

TEST_METHOD(TestMethod13)
        {
            char* input[11] = { "Algebra", "Apple", "Zoo", "Elephant", "Under", "Fox", "Dog", "Moon", "Leaf", "Trick", "Pseudopseudohypoparathyroidism" };
            char* result[11] = { 0 };
            /* 调用Core中封装好的函数 */
            int len = gen_chain_char(input, 11, result, 'a', 0, false);
            Assert::AreEqual(len, 4);
        }

此外还包括指定尾字母测试,指定首尾字母测试

测试覆盖率截图

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

计算模块部分的异常主要包括传入的参数不合法(words指针、result指针为空,参数len为负数,首尾字母约束不合法)、单词中包含环但没有-r参数、输入的单词没法构成单词链等。

1.最多单词测试,包含环但没有-r参数

TEST_METHOD(TestMethod8)
        {
            char* input[5] = { "Algebra", "Applea", "Zooa", "Elephant", "Under" };
            char* result[5] = { 0 };
            /* 调用Core中封装好的函数 */
            
            try {
                int len = gen_chain_char(input, 5, result, 0, 0, false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Error: word text implies word rings");
            }

        }

2.最多字母测试,包含环但没有-r参数

TEST_METHOD(TestMethod9)
        {
            char* input[5] = { "Algebra", "Applea", "Zooa", "Elephant", "Under" };
            char* result[5] = { 0 };
            /* 调用Core中封装好的函数 */
            try {
                int len = gen_chain_char(input, 5, result, 0, 0, false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Error: word text implies word rings");
            }
        }

3.最多单词测试,无单词链

TEST_METHOD(TestMethod4)
        {
            char* input[7] = { "Algebra", "Zoo", "Under", "Dog", "Moon", "Leaf",
                                    "Trick" };
            char* result[7] = { 0 };
            /* 调用Core中封装好的函数 */
            try {
                int len = gen_chain_word(input, 7, result, 0, 0, false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Error: no word chain meets the requirements");
            }
        }

4.最多字母测试,无单词链

TEST_METHOD(TestMethod5)
        {
            char* input[7] = { "Algebra", "Zoo", "Under", "Dog", "Moon", "Leaf",
                                    "Trick" };
            char* result[7] = { 0 };
            /* 调用Core中封装好的函数 */
            try {
                int len = gen_chain_char(input, 7, result, 0, 0, false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Error: no word chain meets the requirements");
            }
        }

5.不存在指定首尾字母的单词链测试

TEST_METHOD(TestMethod17)
        {
            char* input[11] = { "Algebra", "Apple", "Zoo", "Elephant", "Under", "Fox", "Dog", "Moon", "Leaf",
                                    "Trick", "Pseudopseudohypoparathyroidism" };
            char* result[11] = { 0 };
            /* 调用Core中封装好的函数 */
            try {
                int len = gen_chain_char(input, 11, result, 'a', 'm', false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Error: no word chain meets the requirements");
            }
        }

6.参数不合法:words指针为空

TEST_METHOD(TestMethod22)
        {
            char* result[11] = { 0 }; 
            try {
                int len = gen_chain_word(NULL, 11, result, 0, 'm', false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Invalid parameter: 'words' or 'result' is null.");
            }
        }

7.参数不合法:len长度为负

TEST_METHOD(TestMethod25)
        {
            char* input[11] = { "Algebra", "Apple", "Zop", "Elephant", "Under", "Fox", "Dog", "Moon", "Leaf",
                                    "Trick", "Pseudopseudohypoparathyroidism" };
            char* result[11] = { 0 };
            try {
                int len = gen_chain_word(input, -1, result, 0, 'm', false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Invalid parameter: 'len' is less than 0.");
            }
        }

8.参数不合法:首尾字母约束不合法

TEST_METHOD(TestMethod28)
        {
            char* input[11] = { "Algebra", "Apple", "Zop", "Elephant", "Under", "Fox", "Dog", "Moon", "Leaf",
                                    "Trick", "Pseudopseudohypoparathyroidism" };
            char* result[11] = { 0 };
            try {
                int len = gen_chain_char(input, 11, result, 'a', '0', false);
            }
            catch (const char *error_message) {
                Assert::AreEqual(error_message, "Invlid parameter: 'head' or 'tail' is not a letter or 0.");
            }
        }

第十部分:界面模块设计的详细设计

设计

咱们的图形用户界面采用了github上开源的图形界面库imgui。图形界面能够分为如下四部分:

  1. 输入:包括经过Select File按钮导入文本以及文本框输入文本两种输入方式,导入文件后文件的内容会出如今输入框中;
  2. 参数选择:经过参数选择框选择对应的参数,选择的参数发生缺乏、冲突 、错误时会实时给出提示信息;
  3. 运行:当选择的参数无误后会显示Run按钮,用户点击Run按钮后调用核心模块计算;
  4. 输出:点击Run按钮计算完成以后运行结果以及Export按钮显示在按钮下方,点击Export按钮将跳出路径选择窗口,用户选择路径后将结果保存到用户指定的位置。

实现

用imgui开发图形用户界面比较便捷,先从库中选择对应的模版,而后在main函数的对应位置设置组件便可,部分组件的实现过程以下:

//显示提示信息
    ImGui::Begin("Word_chain");
    ImGui::Text("Please input words or select file:");
    //显示文本选择按钮
    ImGui::Button("Select File")

    //参数选择框
    ImGui::Text("Please select parameters:");  
    ImGui::Checkbox("-w", &par_w)

    ImGui::Checkbox("-h", &par_h)
    ImGui::InputText(label_head, head, 2, 0, NULL, NULL)    
    
    //产生计算结果后将结果显示在屏幕上并实现按钮导出功能
    if (par_right && show_result) {
                ImGui::Text("Result:\n%s", answer.c_str());
                if (ImGui::Button("Export")) {
                    if (get_save_file_name(save_file_name)) {
                        outf.open(save_file_name);
                        if (outf) {
                            outf << answer;
                        }
                        outf.close();
                    }
                }
            }

第十一部分:界面模块与计算模块对接

对接过程

当参数选对时才会显示Run按钮,用户点击Run按钮后会进行与计算模块的对接,先对输入框中的单词进行分割,而后调用gen_chain_word、gen_chain_char函数进行计算

计算出结果后会将结果显示在在屏幕上并显示导出按钮。

//用户点击Run按钮后执行if语句中的内容调用计算模块的接口进行计算
            if (par_right && ImGui::Button("Run")) {
                char h, t;
                answer.clear();
                get_words(buf, len, words);
                h = par_h ? head[0] : 0;
                t = par_t ? tail[0] : 0;
                int cnt = 0;
                if (par_w) {
                    cnt = gen_chain_word(pwords, wordnum, result, h, t, par_r);
                }
                else if (par_c) {
                    cnt = gen_chain_char(pwords, wordnum, result, head[0], tail[0], par_r);
                }

                for (int i = 0; i < cnt; i++) {
                    answer += result[i];
                    answer += "\n";
                }
                show_result = true;
            }

           //产生计算结果后将结果显示在屏幕上并实现按钮导出功能
            if (par_right && show_result) {
                ImGui::Text("Result:\n%s", answer.c_str());
                if (ImGui::Button("Export")) {
                    if (get_save_file_name(save_file_name)) {
                        outf.open(save_file_name);
                        if (outf) {
                            outf << answer;
                        }
                        outf.close();
                    }
                }
            }

图形界面截图

第十二部分:结对过程

咱们的结对编程主要按照领航员与驾驶员的模式,两人轮流编程,不断交替,一人编程时另外一人负责监督和检查。

第十三部份:结对编程优缺点

优势:
一、互相鼓励,不容易沮丧:团队工做能增长成员的工做积极性。由于在面对问题的时候,会有人一块儿分担,共同尝试新的策略。
二、互相监督,不容易偷懒:两我的一块儿工做须要互相配合,若是想偷懒去干别的,就会拖延工做进度。
三、互相学习编程技巧:在编程中,相互讨论,能够更快更有效地解决问题,互相请教对方,能够获得能力上的互补。
四、能够培养和训练新人:让资深开发者和新手一块儿工做,可让新人更快上手。
五、每时每刻都处在代码复审的状态,便于发现问题:两人互相监督工做,能够加强代码和产品质量,并有效的减小BUG。
缺点:
一、与合不来的人一块儿编程容易发生争执,不利于团队和谐。
二、经验丰富的老手可能会对新手产生不满的情绪。
三、一山不容二虎,开发者之间可能就某一问题发生分歧,产生矛盾,形成没必要要的内耗。
四、开发人员可能会在工做时交谈一些与工做无关的事,分散注意力,形成效率低下。
五、场地不方便,难于约到共同空闲的时间

我我的的优势:
1.起步早,天天写一点。
2.积极交流。
3.脸皮较厚,强凑了三条优势。

我的缺点:
1.粗心,懒。
2.代码能力差。

个人结对对象优势:
1.代码能力强。
2.熬得住深夜,看得见晨光。
3.求知欲强。
4.帮助同窗,有求必应。

缺点:
1.沉默寡言。

第十四部分:PSP表

合并至第二部分。

附加题

1.GUI部分见博客第9、第十部分。

使用说明(界面见第十部分):
1.可使用select file按钮选择文件,也能够直接在input words文本框输入。
2.提供了参数选择框,参数错误时会给予提示;仅当参数正确时才会弹出run按钮。
3.run的结果直接显示在GUI上,并可使用Export按钮导出。

2.松耦合测试:(16061175 16061156)GUI AND(16061170 16061167) Core

耦合测试直接可调用,可是运行时发现了不一样的运行结果;经查证交换的Core部分算法有问题并告知合做小组同窗修正了。目前双方执行结果一致。

相关文章
相关标签/搜索