结对项目--计算最长单词链

项目 内容
这个做业属于哪一个课程 2019春季计算机学院软件工程(罗杰)
这个做业的要求在哪里 做业要求
我在这个课程的目标是 完成结对编程
这个做业在哪一个具体方面帮助我实现目标 为团队合做打基础

Github地址

https://github.com/zackertypical/WordChaingit

PSP预估时间

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

接口设计原则

Information Hiding

  • 为了实现良好的封装,须要从两个方面考虑:程序员

    一、将对象的属性和实现细节隐藏起来,不容许外部直接访问。 让使用者只能经过事先预约好的方法来访问数据,从而能够在该方法里加入控制逻辑,限制对属性的不合理访问。github

    二、把方法暴漏出去,让方法来操做或访问这些属性。算法

咱们在设计DFS图的类时,全部数据成员都没法被外部直接访问,例如图的权重数组,邻接表数组,经过外部的接口对图中节点进行权重的修改,进行边的插入的操做,实现了信息的隐藏。编程

在计算最长路径的时候,也仅提供了输出的接口,没法对图的私有成员进行操做,全部计算过程在类里私有函数完成,外部仅访问获得结果的接口。数组

Interface Design

将需求抽象成一个个独立的接口/抽象类,而后被继承或委托/组成的形式来实现或拓展新的具体或更增强大完善的抽象,经过层层封装、继承,最后就会实现运行时多态的特性,从而提升代码的灵活性。app

良好的接口须要有单一职责性和可拓展性。在本次项目中,咱们利用继承与多态的思想,创建了一个图的基类,其余类都实现这个基类提供的方法,例如修改结点权值,插入边等操做,对于结果的读取,实现findAns()函数,但不一样的类该接口的实现方法不同。框架

好比有首尾字母约束的类,实现一样的findAns()接口,不管是怎样的参数组合,最终都要经过这个接口来访问结果。这样下降的模块之间的耦合度,提升了代码复用,提升了模块的单一性。函数

同时Core计算模块也实现了对外的接口,须要传入word还有一些参数,返回result结果,下降了耦合性。

Loose Coupling

软件工程中对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。所以对象的设计应使类和构件之间的耦合最小。

对于不一样的参数类型,咱们构建了继承于基类的子类,例如实现约束首字母的类,须要单独有数组来存单词是否符合首字母约束,而约束尾字母也能够用该类进行计算,只须要在结果输出之后把数组反转便可。

对于首尾字母都有约束的状况也单独有一个子类来完成功能,继承自只有首字母约束的类。

在图内实现dfs,判断是否有环,最终输出结果都是单独实现的函数,每一个方法完成一个功能,下降了函数之间的耦合性。

Core的接口设计和实现过程

一、代码组织:

  • 类的设计

    一、接口

    须要对传入的参数进行解析,实例化Core核心计算类和DFS图,经计算后返回结果

    二、core类

    经过core接口对图进行操做。关键函数以下:

    • void insertChain(char * words[], int len);
      进行单词链的去重操做和排序。
    • void setHeadTail(DFSHeadTailGraph &graph, char tail);
      对图的数组进行操做,指定头尾节点的约束。
    • void insert_weighedEdge(DFSGraph &graph);
      对图的边进行插入操做,赋予边权重。
    • void getresult(char *result[], vector & ans);
      经过图内部函数的计算获得结果。

    三、图类

    对建好的图进行dfs计算出最终的结果链,由core打印结果,自己不存储单词信息,只存储节点的编号。

    关键函数以下:

    ** 私有函数 **
    void findAnsChain();
    调用dfs进行最长链的寻找,把结果保存在私有变量vector ans中。
    int dfs(int index);
    对有环图的dfs。
    int dpDfs(int index);
    对无环图的dfs。

    ** 公有函数 **
    void insertEdge(int i, int j);
    对图进行边的插入。
    void changeVecWeigh(int i, int weight);
    改变图的节点权重。
    const vector & getAnsChain();
    外部访问获得最长链的节点编号数组。
    bool hasCircle();
    外部进行访问,能够获得图是否有环的信息。

    四、Exception类

    对各类异常进行处理,包括参数异常,对图的操做异常等。

    五、命令行输入类

    对输入的单词文本进行处理,实现读入文件,写文件等操做。

二、算法关键

有环的状况要比没有环的复杂度高不少,因此算法第一步要判断是否有环,若是有环,进行普通的深度优先遍历的方法。没有环的话开一个dp数组进行记忆化搜索,性能会提升不少。

对于有首尾字母约束的状况下,没有单独在一个类里面实现,而是经过类的继承来下降模块的耦合性。

UML图

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

性能分析结果:

在小文本数据处理时,并无遇到很大的性能瓶颈,因而咱们利用了大文本进行测试。发如今处理图节点之间连边的函数性能消耗很大,根据VS的性能分析工具,能够看到是在string的处理上进行索引的部分消耗较大,咱们使用的是iterator去访问string的头和尾字母。因而最后改为了用下标访问,速度有所提高。

在单词链有隐环的状况下,dfs耗费的时间的确很大,并无找到改进的方法。

改进以后结果:

Design by Contract, Code Contract

通常认为在模块中检查错误情况而且上报,是模块自己的义务。而在契约体制下,对于契约的检查并不是义务,其实是在履行权利。一个义务,一个权利,差异极大。例如:

if (dest == NULL) { ... }

这就是义务,其要点在于,一旦条件不知足,我方(义务方)必须负责以合适手法处理这尴尬局面,或者返回错误值,或者抛出异常。而:

assert(dest != NULL);

这是检查契约,履行权利。若是条件不知足,那么错误在对方而不在我,我能够马上“撕毁合同”,罢工了事,无需作任何多余动做。这无疑能够大大简化程序库和组件库的开发。

契约所核查的,是“为保证正确性所必须知足的条件”,所以,当契约被破坏时,只代表一件事:软件系统中有bug。其意义是说,某些条件在到达我这里时,必须已经确保为“真”。若是在我这里发现契约没有被遵照,那么代表系统中其余模块没有正确履行本身的义务。

通常来讲,在面向对象技术中,咱们认为“接口”是惟一重要的东西,接口定义了组件,接口肯定了系统,接口是面向对象中咱们惟一须要关心的东西,接口不只是必要的,并且是充分的。然而,契约观念提醒咱们,仅仅有接口还不充分,仅仅经过接口还不足以传达足够的信息,为了正确使用接口,必须考虑契约。

契约式编程的优势:实现面向对象的目标:可靠性、可扩展性和可复用性。

缺点: 若是异常在程序运行过程当中才可以检测出来的话可能致使一些错误。

在本项目中,咱们在计算模块中实现了Core接口,而且定义了传入参数的规范,因此能够采用契约式编程,若是传入的参数不合法,或者传入的不是符合规范的字符,说明调用者没有遵循契约调用参数,能够直接assert。在执行无错误程序期间,不该违反契约条件。

在单元测试当中,咱们所用的也都是断言。

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

1.对图模块的公开类以及公开类里面的公开方法添加单元测试。对于构造函数和公共属性进行单元测试。咱们建立了一个测试图模块的单元测试类进行测试。

思路:对图进行构建,改变节点的权重和边的信息,而后寻找图的最长路,看是否和正确结果相同。

部分代码展现:

TEST_METHOD(TestHeadTailGraph)
{
    DFSHeadTailGraph g(4);
    for (int i = 1; i <= 4; i++)
    {
        g.changeVecWeigh(i, 1);
    }
    g.setHeadSingle(3);
    g.setTailSingle(1);
    g.insertEdge(3, 2);
    g.insertEdge(2, 1);
    Assert::AreEqual(2, g.getEdgeNum());
    vector<int> ans = g.getAnsChain();
    Assert::AreEqual(3, (int)ans.size());
}
TEST_METHOD(LoopGraph)
{
    DFSGraph g(4);
    for (int i = 1; i <= 4; i++)
    {
        g.changeVecWeigh(i, 1);
    }
    g.insertEdge(3, 4);
    g.insertEdge(4, 3);

    Assert::AreEqual(true,g.hasCircle());

}

二、对不一样参数组合的测试

思路:对于全部参数组合,能够进行分析,寻找最长单词链,最长字母链,是否有环,是否有首尾字母的约束,一共有 2*2*4 = 16 状况,分别构造测试数据进行测试。

测试数据的构建:

  • 对于边界条件,好比只输入一个单词,或者没有找到单词链的状况,都须要单独构造测试数据。
  • 全部单词都互相能构成链的状况,好比 “aaaaa aaa aa a”的状况
  • 最长单词链和最长字母链同时存在但结果不一样的状况。
  • 常规测试数据,随机生成。
  • 大文本测试数据。

部分代码展现:

TEST_METHOD(HeadTest_Loop)
{
    char *words[4] = { "cddd","dddc","aac","bad" };
    char *result[4];
    int ans = gen_chain_char(words, 4, result, 'a', 0, true);
    Assert::AreEqual(3, ans);
    string str;
    for (int i = 0; i < ans; i++)
    {
        str.append(result[i]);
    }
    Assert::AreEqual((string) "aaccddddddc", str);
}
TEST_METHOD(TailTest_Loop)
{
    char *words[4] = { "kzz","kdd","ak","ka" };
    char *result[4];
    int ans = gen_chain_char(words, 4, result, 0, 'z', true);
    Assert::AreEqual(3, ans);
    string str;
    for (int i = 0; i < ans; i++)
    {
        str.append(result[i]);
    }
    Assert::AreEqual((string) "kaakkzz", str);
}
TEST_METHOD(HeadTailTest_Loop)
{
    char *words[13] = { "abcd","defg","gkbb","bmmm","mjjj","jooo" ,"bg","gb"};
    char *result[6];
    int ans = gen_chain_word(words, 8, result, 'd', 'j', true);
    Assert::AreEqual(6, ans);
    string str;
    for (int i = 0; i < ans; i++)
    {
        str.append(result[i]);
    }
    Assert::AreEqual((string) "defggkbbbggbbmmmmjjj", str);
}

三、单元测试覆盖率展现

单元测试覆盖率结果以下,覆盖率达到98%。

计算模块异常处理说明

一、图模块的异常种类

在公有方法中,插入边和修改结点权值的函数须要判断是否溢出边界,若是是要抛出异常。

TEST_METHOD(Vertex_insert_edge_outofrange)
{
    try
    {
        DFSGraph g(3);
        g.insertEdge(5, 6);
    }
    catch (exception &e)
    {
        Assert::AreEqual(edge_out_of_range_error, e.what());
    }
}
TEST_METHOD(Vertex_change_weight_outofrange)
{
    try
    {
        DFSGraph g(3);
        g.changeVecWeigh(4, 8);
    }
    catch (exception &e)
    {
        Assert::AreEqual(vertex_out_of_range_error, e.what());
    }
}

二、Core模块输入没法识别的单词

TEST_METHOD(Core_words_unrecognized)
{
    try
    {
        Core core;
        char *words[3] = { "aa123","32432","333" };
        core.insertChain(words, 3);
    }
    catch (exception &e)
    {
        Assert::AreEqual(m_word_error, e.what());
    }
}

三、在core的接口部分,若是出现len超出最大范围,或者head和tail不在指定的字母范围内,则要抛出异常

TEST_METHOD(Interface_check_head_parameter)
{
    try
    {
        checkParameter(10, 'A', 0);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_headchar_error, e.what());
    }
}
TEST_METHOD(Interface_check_tail_parameter)
{
    try
    {
        checkParameter(10, 0, 1);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_tailchar_error, e.what());
    }
}

TEST_METHOD(Interface_check_len_parameter)
{
    try
    {
        checkParameter(1000000, 0, 1);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_len_error, e.what());
    }
}

四、core部分,若是没有选择enable_loop可是单词链中出现隐环,抛出异常

TEST_METHOD(Interface_check_loop)
{
    try
    {
        char *words[2] = { "abb","baa" };
        char *result[2];
        int ans = gen_chain_word(words, 2, result, 0, 0, false);
        Assert::AreEqual(ans, 0);

    }
    catch (exception &e)
    {
        Assert::AreEqual(m_loop_error, e.what());
    }
}

界面模块的详细设计过程

界面模块咱们使用了VS的MFC框架来进行搭建,主要是对用户的输入进行响应,调用咱们Core模块的dll接口来进行结果的输出。

  • 首先须要进行需求分析,用户须要哪些交互的模块,须要输入文本框,选项的按钮,文件名的文本框,最终的确认操做按钮,导出文件按钮,结果展现的文本框等。

  • 接下来给每一个ui进行代码编辑,响应用户的操做。

  • 对接dll接口进行测试。

部分代码展现:

void CWordChainGUIDlg::OnBnClickedOk()
{
        UpdateData(true);
        char *words[MAX];
        int chainlen;
        if (m_inputFile != "")
        {
            bool isread = read_file(m_inputFile, m_inputWords);
            if(!isread)
            {
                throw exception("file not found!");
            }
            chainlen = dealInput(words, m_inputWords);
        }
        else
            chainlen = dealInput(words, m_inputWords);
        char *result[MAX];
        char head = m_headChar.GetAt(0);
        char tail = m_tailChar.GetAt(0);
        if ((head != 0)&&((head <= 96) || (head >= 123)))
            throw exception("head charactor must be lower alphabet");
        if ((tail != 0)&&((tail <= 96) || (tail >= 123)))
            throw exception("tail charactor must be lower alphabet");

        //printf("%s", m_inputWords);
        if (m_isLongestWord)
        {
            m_answer = gen_chain_word(words, chainlen, result, head, tail, m_enableLoop);
        }
        else
        {
            m_answer = gen_chain_char(words, chainlen, result, head, tail, m_enableLoop);
        }
        CString str;
        for (int i = 0; i < m_answer; i++)
        {
            str += result[i];
            str += "\r\n";
            delete[]result[i];
        }
        m_wordAnsChain = str;
        INT_PTR nRes;               
        AnswerDisplayDlg ansDlg;         
        ansDlg.m_ansLength = m_answer;
        ansDlg.m_wordStr = str;
        nRes = ansDlg.DoModal();   

        UpdateData(false);
        for (int i = 0; i < chainlen; i++)
        {
            delete[]words[i];
        }
        if (IDCANCEL == nRes) 
            return;
}

界面模块与计算模块的对接

ui 功能
单词输入框 能够支持输入单词文本,而且对单词文本进行自动分割处理,和文件输入格式相同
首字母输入框 若是没有内容则默认为0,能够支持输入小写字母,若是输入不合理会有错误框弹出提示
尾字母输入框 若是没有内容则默认为0,能够支持输入小写字母,若是输入不合理会有错误框弹出提示
单词链选项 选择最长单词数目或者最长字母数目
是否容许单词链隐环 默认不容许,若是选择则容许
指定输入文件 若是不输入则默认从单词输入框读取,输入文件名则从文件读取,若是找不到文件则会有错误框弹出提示
生成按钮 设置完后生成单词链,会有新窗口弹出
导出文件按钮 能够填写文件名后导出文件

结对编程

优势:

最大的优势是在于两我的之间能够随时的复审和交流,程序各方面的质量取决于一对程序员中各方面水平较高的那一位。这样,程序中的错误就会少得多,程序的初始质量会高不少,这样会省下不少之后修改、测试的时间。

如下摘自博客

(1)在开发层次,结对编程能提供更好的设计质量和代码质量,两人合做能有更强的解决问题的能力。

(2)对开发人员自身来讲,结对工做能带来更多的信心,高质量的产出能带来更高的知足感。

(3)在心理上, 当有另外一我的在你身边和你紧密配合, 作一样一件事情的时候, 你很差意思开小差, 也很差意思糊弄。

(4)在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。由于一我的的知识已经被其余人共享。

缺点:

结对的两我的须要时间磨合,没有尝试过这种模式的人也须要时间去适应。

对于须要研究的项目不适合结对编程。

一些比较简单的测试验证工做,若是须要花较长的时间,结对会形成时间的浪费。

自我评价

优势:执行力较强,态度良好,有合做精神,注意力比较集中,可以较好地统筹规划时间。

缺点:编程能力较弱,对于语言和算法掌握不熟练,花费大量的时间进行学习。

评价队友

优势:可以细心发现bug,态度良好,有合做精神,在合做的过程当中能相互学习、相互磨合。

缺点:执行力较弱。

与其余小组的松耦合测试

  • 本组学号:16021160 15061078

  • 合做小组学号:16061109 16061097

  • 出现的问题:

    一、在测试另外一个小组的dll时,我在文件中写入了中文字符,致使程序没有正常退出,该小组没有对文本的内容进行详细地异常分析,致使程序异常退出。

    二、在该小组测试咱们dll的时候,发现程序中的bug,即对于全部单词都能构成首尾链的状况输出异常,咱们组对本身的bug进行了改进。

    三、对方小组使用类封装的dll,分析编译的时候出现warning提示,接口调用须要类的实例化客户端,因此我当时测试的时候是实例化了该Core类,不能直接调用方法进行测试。

PSP表格实际消耗

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 60 2*60
·Estimate 估计这个任务须要多少时间 60 2*60
Development 开发 57*60 71*60
·Analysis ·需求分析 (包括学习新技术) 8*60 9*60
·Design Spec · 生成设计文档 4*60 2*60
·Design Review · 设计复审 (和同事审核设计文档) 2*60 1*60
·Coding Standard · 代码规范 (为目前的开发制定合适的规范) 1*60 1*60
·Design · 具体设计 5*60 6*60
·Coding · 具体编码 24*60 36*60
·Code Review · 代码复审 8*60 12*60
·Test · 测试(自我测试,修改代码,提交修改) 5*60 5*60
Reporting 报告 5*60 5*60
·Test Report · 测试报告 2*60 3*60
·Size Measurement · 计算工做量 1*60 1*60
·ostmortem & Process Improvement Plan · 过后总结, 并提出过程改进计划 2*60 1*60
合计 63*60 78*60
相关文章
相关标签/搜索