【软工】结对编程

【软工】结对编程

本作业属于课程软件工程
作业要求点此
源码在github

第一部分: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.松耦合:核心可以单独调用,并通过了交换测试

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

算法设计

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

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

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

对图进行初始化以后,第二步需要判断构造的图是有环图还是无环图,然后分别采取不同的算法。这里我们采用的判断方法为进行拓扑排序,如果所有顶点能构成一个拓扑序列,说明图中无环,否则有换。

对于有向无环图(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中封装好的函数 */
            int len = gen_chain_word(input, 5, result, 0, 0, false);
            Assert::AreEqual(len, -1);
        }

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

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

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

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

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

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

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

TEST_METHOD(TestMethod17)
        {
            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' , 'm', false);
            Assert::AreEqual(len, 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();
                    }
                }
            }

图形界面截图

第十一部分:结对过程

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

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

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

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

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

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

缺点:
1.沉默寡言。

附加题

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

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

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

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

猜你喜欢

转载自www.cnblogs.com/wxmwy/p/10521900.html