https://github.com/kilotron/Wordlist-Pair-git
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 |
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。数据结构
计算模块主要在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的对外接口实现其功能。
为了清晰地显示类之间的关系,图中省略了类的成员。
在未指定开头结尾字母时,程序进行了屡次重复搜索,改进的思路是利用已有的LongestPathFrom
函数避免重复搜索,改进后程序减小了50%的执行时间。
下图是未指定开头和结尾字母,按字母数计算单词链长度,存在单词环,总共有60个单词的状况下前28s的性能分析图。
改进性能用了大约半个小时。
契约式设计(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_loop
为false
则抛出异常,若是一切正常则将结果放到result
数组中并返回result的长度,这是后置条件。测试时也是根据这些条件来测试的。
有关异常的测试在下一部分给出。
这部分测试计算的两个接口,构造测试数据的思路是根据是否指定开头结尾字母,计算单词链长度的方式,是否存在单词环,以及边界状况分别讨论。下面给出部分代码。
代码中,TEST_INIT
、GEN_CHAIN_WORD
、GEN_CHAIN_CHAR
、ASSERT_RESULT_LEN
、ASSERT_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); }
这部分根据输入参数的各类不一样组合构造测试用例,检测程序是否正确解析。下面给出其中一个用例。
// 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); }
分两个模块分别测试,下图是参数解析和文件读取部分的测试覆盖率,总覆盖率为93%。
下图是计算模块的覆盖率,总覆盖率为91%。
异常类型 | 异常说明 |
---|---|
输入文件异常 | 文件名非法或者文件不存在 |
输入参数异常 | 未输入参数,-h或-t选项后跟的不是单个字母,包含未定义的选项 |
单词环异常 | 存在单词环但未给出-r选项 |
// 文件错误 异常测试 TEST_METHOD(TestMethod8) { Preprocess pre; Assert::ExpectException<std::exception>([&] { pre.readfile("you asshole"); }); }
场景:文件不存在。
// 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) { } }
场景:未输入参数。
// 有环但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选项。
界面使用Qt实现的,风格上采用了默认的样式,没有进行调整。
有一个菜单栏,支持打开文件和查看帮助的操做。左边是输入输出界面,能够直接在Input
对应的框里编辑,右边是计算的选项,用QRadioButton
和QCheckBox
实现-w -c -h -t -r
五个选项。
右下方有三个按钮,Extract Words
把输入框中的单词提取出来,而后分行显示在Input
框中。Find
根据上方参数的设定状况查找单词链,结果显示在Output
框中,Export
把Output
框中的文本导出到文件。
界面以下图。
布局以下图。采用手工编码的方式实现,使用了一些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);
合做小组的学号:
16061011
16061152
把咱们小组记为A,合做小组记为B。下面分别说明Core B + GUI A和Core A + GUI B两种状况。一开始两种状况都不能正常运行,通过讨论咱们发现使用Core.dll的方式不一致,A组使用动态加载的方式,B组采用的是静态加载的方式,解决方法是统一加载方式,对于两种加载方式咱们都进行了尝试。
这个组合采用动态加载方式。咱们为B组的Core模块添加了def文件,将源代码从新编译后获得新的Core.dll。而后从新运行程序,可以正常使用。
这个组合采用静态加载方式。如图,未修改前看不到B组的GUI。咱们将A组的Core模块与B组的GUI放在同一个解决方案中,在Core的两个接口的函数的声明前添加__declspec(dllexport)
修饰。未修改B组GUI源码前发现其中有几个Core A没有定义的异常,因而咱们将异常种类统一后从新编译。
在测试过程当中发现GUI B未提供单词去重的功能,Core A抛出了异常,GUI B显示了异常信息,整个程序没有崩溃。因而咱们在Core A中实现了单词去重并从新编译。最后的运行截图以下。
因为前期找不到合适的时间,为了保证进度,咱们一开始采用的是先分工,再一块儿复审代码的形式。周末有共同的空闲时间时,咱们再一块儿讨论和编写测试用例。整体来讲比较顺利。上图是咱们再新主楼结对编程的图片。
优势:
缺点:
对队友的评价
优势:
缺点:
队友对个人评价
优势:
缺点: