[北航软工]第一次结对做业-最长单词链

BUAA软件工程 第一次结对做业

项目 内容
这个做业属于哪一个课程? 北航软工
这个做业的要求在哪里? 第一次结对做业
我在这个课程的目标是? 学习高效严谨的软件工程开发过程,创建团队意识
这个做业在哪一个具体方面帮助我实现目标 熟悉并了解软件工程的基本知识,创建兴趣

1. 项目的Github地址

binggge/longestWordChain html

项目有两个分支,master分支是命令行形式,UI分支是带GUI的程序。c++

2. 14. PEP表格

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

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

信息隐藏

信息隐藏是指在设计和肯定模块时,使得一个模块内包含的特定信息(过程或数据),对于不须要这些信息的其余模块来讲,是不可访问的。 在咱们的设计过程当中,模块除了被调用的两个接口或者说成员函数之外,其余的成员都是private属性的,它如何实现算法,如何存储数据,过程当中某个变量的值如何,都与外部无关,从外部不可访问和修改,保证编程的安全性。git

接口设计

对调用者和使用者而言,不须要知道函数内部的实现,只须要掌握接口的信息。接口设计主要是做用和反作用的明确性,若是接口应该一开始就设计的比较成熟,这样咱们修改实现接口的程序时,调用接口的程序就不用修改。咱们设计的两个接口,约定了函数的具体做用即以不一样的方式寻找最长单词链,约定了传入参数的具体意义,而且规定了只能修改char * result变量做为结果的输出,其余的参数应该不改变。程序员

松耦合

松耦合和接口设计是相辅相成的。只有在规定好接口规范之后,才能实现进一步的解耦合。咱们只规定接口的做用,而不规定接口的内容。这样不一样组件之间能够轻易地作到互相独立。github

附加题:换GUI体现松耦合

咱们在完成代码编写后,与其余组同窗互换了GUI和DLL,另外一个组是周二的白世豪(16061167),宋卓洋(16061170)组。因为生成dll的教程相似,并无出现什么问题,对于无异常状况能够正常运行,以下图所示:算法

工做目录

其中Core.dll是咱们生成的dll,DLL1.dll是另一个组的dll,通过测试,加载两个dll时GUI的行为一致。 otherGUI.PNG mycore.PNG 微信图片_20190314142743.png编程

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

整个程序的逻辑大体以下图所示:设计模式

graph LR A[命令行启动] -->B(分析参数) B --> C{参数正确} C --> I{单词个数} C --> J{字母个数} B --> F{参数错误} F --> G{报错} I --> H{参数是否容许有圈?} H -->|YES| D[Result one] H -->|NO| E[Result two] J -->K{参数是否容许有圈} K -->|YES| M[Result three] K -->|NO| N[Result four]

由于GUI程序是不会涉及到普通的参数正确性的(只有关于指定头尾的字母是否正确),因此 出于解耦合的须要,咱们并无将处理参数的程序集成在Core类之中,而是独立出来,在main函数中判断参数的正确性。Core类中确保接受到的是正确的单词组char *words[]和其余合适的参数。咱们暴露在外的只有两个接口。安全

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

其余的部分以private成员的形式,供内部使用。微信

而后每一个接口中对单词的处理也须要分红两种。

\\没有-r选项,检测有环时直接报错,无环时以宽度优先搜索的形式(不会陷入死循环)找到最长单词链;
\\有-r选项,不须要检测是否有环,直接以深度优先搜索的形式搜索最长单词链;
\\伪代码以下
if(!enable_loop)
{
    roundtest();//有环时抛出错误
    createMap();//创建bfs的地图
    BFS();//宽度优先搜索
    getBFSResult();//处于节省时间和空间的考虑,上一步宽度优先搜索并无保存路径,而是得出了最长的长度,而后根据长度倒推出路径。
}
else
{
    createMap();//创建dfs的地图
    DFS();//深度优先搜素
    getDFSResult();//推导最终路径,缘由同上
}

基础的函数大体就是这些,两种类型chain_wordchain_char的各有一套。而后咱们还引入了get_tails()就是在扫描边的时候,排除不可能的首尾选项。大体原理就是当不成环的时候,出度为0的点才多是尾字母,不然不多是最长链。这样咱们能够剪枝,减小搜索时的复杂度。

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

使用了自动化工具生成类图与调用关系,参考工具Github,生成的调用关系以下: 类图.png 手画类图以下,咱们的类关系至关简单。

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

总体上说,咱们采起了BFS解决没有-r的状况,使用DFS解决有-r的状况。

首先是没有-r的部分。因为算法在设计之初就已经考虑到了一些优化,咱们重复执行同一条指令50次,取得更好的采样效果。如图所示,整个算法的耗时最长是在建图与删重边。程序在输入8000个单词时运算时间在1s之内。所以没有作进一步改进,这里介绍一下完成的优化。 耗时分析BFS.PNG BFS-1.PNG 咱们主要完成了如下几个优化:

  • 建图时有重边的只保留一条。这样能够避免BFS重复搜索节点。
  • BFS节点记忆化。在访问BFS节点后记录当前距离,在下一次另外一条边访问时判断,若是小于当前记忆的距离则剪枝。
  • 减小起始搜索节点。考虑到没有环,所以不考虑自环时起始节点的入度为0,末尾节点的出度为0。在没有指定开始/结束字符时,经过这种方法减小可能的起始节点,下降搜索次数。

在含有-r的部分,考虑到这是一个NP问题,并且要求的是准确解,不是近似解,咱们采用回溯法进行深度搜索。程序输入90个单词,摘自Wikipedia的一个随机网页,运行时间3分04秒。 DFS.PNG DFS-2.PNG 能够发现,程序在DFS上耗费了大量的时间。咱们对于这种模式依然作了一些优化,咱们同BFS同样减小了可能的起点,由于起点的出度>=入度,终点的入度>=出度,也有必定效果。

此外,咱们还在总体上对程序进行了优化。考虑处处理器支持AVX指令集,咱们在编译选项中选择了支持生成AVX2指令的编译选项,取得了不错的效果,对于一组较复杂的测试用例,运行时间从2分58秒减小到2分03秒,提高接近1/3。

7.看Design by Contract, Code Contract的内容:描述这些作法的优缺点, 说明你是如何把它们融入结对做业中的

契约式设计要求软件设计者为软件组件定义正式的,精确的而且可验证的接口,接受一个合规的输入,并保证合规的输出。根据百科的定义来看,这种设计模式重在精确二字。

  • 优势
    • 减小了防护式编程的一部分工做量
    • 提升了程序的可维护性
    • 使程序员的之间的合做变得有序
  • 缺点
    • 制定契约,维护契约,使程序知足契约的代价(时间和人力)比较大
    • 必须强制要求整个项目内的代码都符合契约,不然部分的无效影响整个工程的质量

本次结对做业中,首要契约是做业要求中规定的接口,这个下降了沟通的一部分任务。实际编程中咱们制定的契约主要是双方完成的函数方法部分对于类成员的行为,以及对于异常状况的处理。在如此契约之下,咱们才完成了从Core类到GUI的低耦合。

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

单元测试总体覆盖率91%。未覆盖到的部分主要是抛出异常的if语句。 Coverage.PNG

计算模块暴露的接口共有两个,分别为

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

咱们对其进行了单元测试。测试的流程是测试程序加载一组测试数据,放入char * words[]中,与一些参数一块儿传入函数,获得返回的result后加载标答并逐个比较,以下面的代码片断所示:

TEST_METHOD(TestMethod1)
    {
        Core * core = new Core();
        // do sth to update words
        core->gen_chain_word(words1, len, result1, 0, 0, false);
        // get true value into realAnswer
        Assert::AreEqual(51, length_of_result1);
        for (int i = 0; i < length_of_result1; i++) {
            Assert::AreEqual(strcmp(result1[i], realAnswer[i]), 0);
        }
    }

对于正常数据,咱们使用随机生成+对拍的方式检验。 对于异常数据,咱们构造了如下几个特殊状况:

  • words 为空,模拟文件异常或不存在
  • words 有环,可是没有-r
  • tail 或 head 指定的单词链找不到
  • 没有单词链

其中对于有环的状况,咱们还分了如下几种状况:

  • 有自环
  • 有多个自环
  • 有多条边组成的环

如下为单元测试展现,因为太长默认收起。

注意,请依次运行每一个单元测试,不要一块儿运行,防止出错。

正确数据测试 -w
TEST_METHOD(TestMethod1)
    {
        Core * core = new Core();
        int len = readFile1("../WordChainUnitTesr/words1.txt");
        core->gen_chain_word(words1, len, result1, 0, 0, false);
        int len2 = readFile1("../WordChainUnitTesr/solution1.txt");
        Assert::AreEqual(51, len2);
        for (int i = 0; i < len2; i++) {
            Assert::AreEqual(strcmp(result1[i], words1[i]), 0);
        }
    }
其中 words1.txt, solution1.txt能够在Github上找到。
相关文章
相关标签/搜索