软件工程结对项目博客做业

1. GitHub项目地址

https://github.com/kilotron/Wordlist-Pair-git

2. PSP表格

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

3. 利用Information Hiding, Interface Design, Loose Coupling方法设计接口

Information hiding is part of the foundation of both structured design and object-oriented design. In structured design, the notion of “black boxes” comes from information hiding. In object-oriented design, it gives rise to the concepts of encapsulation and modularity and it is associated with the concept of abstraction. (Code Complete 2nd)程序员

信息隐藏在结构化设计和面向对象设计中都很重要,与封装、模块化、抽象的概念紧密相关。github

Loose coupling means designing so that you hold connections among different parts of a program to a minimum. Use the principles of good abstractions in class interfaces, encapsulation, and information hiding to design classes with as few interconnections as possible. Minimal connectedness minimizes work during integration, testing, and maintenance. (Code Complete 2nd)算法

Loose coupling是减小程序各部分的联系,在设计接口时,须要将各模块相互之间的联系降到最低。编程

在设计接口时,咱们尽可能放宽函数的前置条件,同时让函数名简洁直观,尽可能准确界定函数功能。例若有这样两个函数:数组

class WordGraph {
public:
    bool IsCyclic();
    Path * LongestPathBetween(char head, char tail, bool isWeighted);
}

对于第二个函数,调用时指定开头字母和结尾字母,以及计算单词链长度的方法(是否以单词长度做为权重),函数返回单词链。这就作到了Loose coupling,调用者关心的参数和结果比较明确,不须要依赖其余的函数或模块。对于第一个函数,它的功能是判断是否存在单词环,调用者不需关心内部实现(怎么计算是否存在环的),这就作到了Information hiding。数据结构

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

计算模块主要在WordGraph这个类中,同时还有与WordGraph相关的Edge、Node和Path类,前两者组成WordGraph内部数据结构,Path用于表示计算结果。app

WordGraph对外提供5个接口:ssh

  • WordGraph(char *words[], int len)用给定的单词列表构造WordGraph对象
  • bool IsCyclic()判断是否存在单词环
  • Path *LongestPathFrom(char start, bool isWeighted)计算给定开头字母的最长单词链
  • Path *LongestPathBetween(char start, char end, bool isWeighted)计算给定开头和结尾字母的最长单词链
  • Path *LongestPathTo(char end, bool isWeighted)计算给定结尾字母的最长单词链
  • Path *LongestPath(bool isWeighted)计算未给定开头结尾字母的最长单词链

参数isWeighted是计算单词链长度的方式,true为使用单词字母数做为权重,false则反之。模块化

在此基础之上,定义核心模块的对外接口:

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);

上面两个函数调用WordGraph的对外接口实现其功能。

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

为了清晰地显示类之间的关系,图中省略了类的成员。

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

在未指定开头结尾字母时,程序进行了屡次重复搜索,改进的思路是利用已有的LongestPathFrom函数避免重复搜索,改进后程序减小了50%的执行时间。

下图是未指定开头和结尾字母,按字母数计算单词链长度,存在单词环,总共有60个单词的状况下前28s的性能分析图。

改进性能用了大约半个小时。

7. 看Design by Contract, Code Contract的内容

契约式设计(Design by Contract)是设计软件的一种方法,它用形式化可验证的方法来定义前置条件、后置条件和不变式。从一个被调用模块的角度来说,contract包括expect, guarantee, maintain三个部分的内容,也就是在进入模块前,指望一些条件是成立的,模块执行结束后,保证一些条件是成立的,而且保持类的某些属性是一致的。其优势在于模块功能边界清晰,全责分明,出现问题时容易定位是谁出现问题,不过要提早制定contract须要时间,制定一个合适的contract也须要必定的经验和技巧。

Code Contracts for .NET是一个工具,能够执行前置条件、后置条件和不变式的静态或运行时检查,而且能够生产文档,对自动化验证DbC颇有帮助。

在此次咱们实现的计算模块中的这个接口就体现了DbC:int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);,例如words的长度和len保持一致,result数组须要在调用函数前分配好足够的空间,head和tail只能是0或者字母,这些是前置条件;若是存在单词环但enable_loopfalse则抛出异常,若是一切正常则将结果放到result数组中并返回result的长度,这是后置条件。测试时也是根据这些条件来测试的。

8.单元测试展现

有关异常的测试在下一部分给出。

8.1 计算模块测试

这部分测试计算的两个接口,构造测试数据的思路是根据是否指定开头结尾字母,计算单词链长度的方式,是否存在单词环,以及边界状况分别讨论。下面给出部分代码。

代码中,TEST_INITGEN_CHAIN_WORDGEN_CHAIN_CHARASSERT_RESULT_LENASSERT_RESULT是为了简化测试代码定义的宏。

  • TEST_INIT:声明测试须要使用的变量,准备单词列表
  • GEN_CHAIN_CHAR/GEN_CHAIN_WORD:计算模块对外接口的简化版
  • ASSERT_RESULT_LEN:单词链中单词的个数
  • ASSERT_RESULT(word, i): 单词链第i个单词应该是word
// 字母最多,不指定开头结尾
TEST_METHOD(TestMethod7)
{
    TEST_INIT(22,
        "Absorb", "bailout", "basic", "cat", "team",
        "calm", "cad", "hedonism", "dam", "moon",
        "damn", "dame", "happen", "earn", "nap",
        "each", "equip", "pack", "hasp", "hack",
        "hot", "AGoddamnSuperLongWordEndsWithT");
    GEN_CHAIN_CHAR(0, 0, false);
    ASSERT_RESULT_LEN(5);
    ASSERT_RESULT("agoddamnsuperlongwordendswitht", 0);
    ASSERT_RESULT("team", 1);
    ASSERT_RESULT("moon", 2);
    ASSERT_RESULT("nap", 3);
    ASSERT_RESULT("pack", 4);
}

// 单词数最多, 不指定开头和结尾
TEST_METHOD(TestMethod3)
{
    TEST_INIT(22,
        "Absorb", "bailout", "basic", "cat", "team",
        "calm", "cad", "hedonism", "dam", "moon",
        "damn", "dame", "happen", "earn", "nap",
        "each", "equip", "pack", "hasp", "hack",
        "hot", "AGoddamnSuperLongWordEndsWithT");
    GEN_CHAIN_WORD(0, 0, false);
    ASSERT_RESULT_LEN(10);
    ASSERT_RESULT("absorb", 0);
    ASSERT_RESULT("basic", 1);
    ASSERT_RESULT("cad", 2);
    ASSERT_RESULT("dame", 3);
    ASSERT_RESULT("each", 4);
    ASSERT_RESULT("hot", 5);
    ASSERT_RESULT("team", 6);
    ASSERT_RESULT("moon", 7);
    ASSERT_RESULT("nap", 8);
    ASSERT_RESULT("pack", 9);
}

8.2 输入参数测试

这部分根据输入参数的各类不一样组合构造测试用例,检测程序是否正确解析。下面给出其中一个用例。

// pre.command 指定head
TEST_METHOD(TestMethod5) {
    Preprocess pre;
    int argc = 5;
    char ** argv = new char *[argc];
    for (int i = 0; i < argc; i++) {
        argv[i] = new char[10];
    }
    strcpy(argv[0], "Wordlist.exe");
    strcpy(argv[1], "-w");
    strcpy(argv[2], "-h");
    strcpy(argv[3], "A");
    strcpy(argv[4], "wordlist.txt");

    pre.command(argc, argv);
    Assert::IsTrue(pre.kind == W);
    Assert::AreEqual("wordlist.txt", pre.filename);
    Assert::AreEqual('a', pre.head);
}

8.3 测试覆盖率

分两个模块分别测试,下图是参数解析和文件读取部分的测试覆盖率,总覆盖率为93%。

下图是计算模块的覆盖率,总覆盖率为91%。

9. 异常处理说明

异常类型 异常说明
输入文件异常 文件名非法或者文件不存在
输入参数异常 未输入参数,-h或-t选项后跟的不是单个字母,包含未定义的选项
单词环异常 存在单词环但未给出-r选项

9.1 输入文件异常

// 文件错误 异常测试
TEST_METHOD(TestMethod8)
{
    Preprocess pre;
    Assert::ExpectException<std::exception>([&]
    {
        pre.readfile("you asshole");
    });
}

场景:文件不存在。

9.2 输入参数异常

// pre.command 异常测试
TEST_METHOD(TestMethod6) {
    try {
        Preprocess pre;
        int argc = 1;
        char ** argv = new char *[argc];
        argv[0] = new char[10];
        strcpy(argv[0], "Wordlist.exe");
        pre.command(argc, argv);
        Assert::Fail();
    }
    catch (std::exception e) {

    }
}

场景:未输入参数。

9.3 单词环异常

// 有环但enable_loop是false,应该抛出异常
TEST_METHOD(TestMethod1)
{
    TEST_INIT(7, "gag", "fag", "glitz", "zaf", "jof", "fij", "lkkj");
    Assert::ExpectException<std::exception>([&]
    {
        GEN_CHAIN_WORD(0, 0, false);
    });
}

场景:给出的单词列表中存在环,但未给出-r选项。

10. 界面模块的详细设计过程

界面使用Qt实现的,风格上采用了默认的样式,没有进行调整。

有一个菜单栏,支持打开文件和查看帮助的操做。左边是输入输出界面,能够直接在Input对应的框里编辑,右边是计算的选项,用QRadioButtonQCheckBox实现-w -c -h -t -r五个选项。

右下方有三个按钮,Extract Words把输入框中的单词提取出来,而后分行显示在Input框中。Find根据上方参数的设定状况查找单词链,结果显示在Output框中,ExportOutput框中的文本导出到文件。

界面以下图。

布局以下图。采用手工编码的方式实现,使用了一些QHBoxLayout和QVBoxLayout。

布局的代码以下:

leftLayout = new QVBoxLayout;
rightLayout = new QVBoxLayout;
topRightLayout = new QHBoxLayout;
bottomRightLayout = new QHBoxLayout;
mainLayout = new QHBoxLayout;

// 左边的输入输出框
leftLayout->addWidget(inputLabel);
leftLayout->addWidget(inputTextEdit);
leftLayout->addWidget(outputLabel);
leftLayout->addWidget(outputTextEdit);

// 右上角的单词数最多和字母数最多单选框
topRightLayout->addWidget(maxLabel);
topRightLayout->addWidget(wordRadioButton);
topRightLayout->addWidget(charRadioButton);

// 右下角查找和导出按钮
bottomRightLayout->addWidget(findButton);
bottomRightLayout->addWidget(exportButton);

// 右边布局
rightLayout->addLayout(topRightLayout);
rightLayout->addLayout(headLayout);
rightLayout->addLayout(tailLayout);
rightLayout->addWidget(loopCheckBox);
rightLayout->addWidget(extractButton);
rightLayout->addLayout(bottomRightLayout);

mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);

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

合做小组的学号:

16061011

16061152

把咱们小组记为A,合做小组记为B。下面分别说明Core B + GUI A和Core A + GUI B两种状况。一开始两种状况都不能正常运行,通过讨论咱们发现使用Core.dll的方式不一致,A组使用动态加载的方式,B组采用的是静态加载的方式,解决方法是统一加载方式,对于两种加载方式咱们都进行了尝试。

11.1 Core B + GUI A

这个组合采用动态加载方式。咱们为B组的Core模块添加了def文件,将源代码从新编译后获得新的Core.dll。而后从新运行程序,可以正常使用。

11.2 Core A + GUI B

这个组合采用静态加载方式。如图,未修改前看不到B组的GUI。咱们将A组的Core模块与B组的GUI放在同一个解决方案中,在Core的两个接口的函数的声明前添加__declspec(dllexport)修饰。未修改B组GUI源码前发现其中有几个Core A没有定义的异常,因而咱们将异常种类统一后从新编译。

在测试过程当中发现GUI B未提供单词去重的功能,Core A抛出了异常,GUI B显示了异常信息,整个程序没有崩溃。因而咱们在Core A中实现了单词去重并从新编译。最后的运行截图以下。

12.结对过程

因为前期找不到合适的时间,为了保证进度,咱们一开始采用的是先分工,再一块儿复审代码的形式。周末有共同的空闲时间时,咱们再一块儿讨论和编写测试用例。整体来讲比较顺利。上图是咱们再新主楼结对编程的图片。

13. 结对编程

11.1 结对编程的优缺点

优势:

  • 两人能够随时交流,不断复审代码,所以代码的质量比较高。
  • 当有另外一我的在身边紧密配合, 作一样一件事情的时候, 不容易开小差,所以会比平时更加认真 。
  • 两人互换角色适合在高强度编程时保持代码的质量。

缺点:

  • 程序员的代码、工做方式、技术水平都变得公开和透明,一些人可能不太习惯。
  • 对时间要求较高,当两我的找不到共同的时间时,结对编程实施起来就比较困难。
  • 两我的须要深刻的了解和合做,不然不一致的代码习惯会影响结对编程过程。

11.2 结对中每一个人的优缺点

对队友的评价

优势:

  • 积极热情,有责任心
  • 具备批判性思惟
  • 注重细节

缺点:

  • 编码风格不太一致

队友对个人评价

优势:

  • 能力强,推动了程序的核心算法部分,对我帮助很大
  • 工做积极,老是提早完成相应内容
  • 时间安排合理,使得任务顺利完成

缺点:

  • 说话声音稍微有点轻,但不影响交流ε=ε=ε=(~ ̄▽ ̄)~

14. PSP表格见2

相关文章
相关标签/搜索