BUAA 软工 Week_4 结对作业_最长英语单词链

项目内容
这个作业属于哪个课程 2022 年北航敏捷软件工程
这个作业的要求在哪里 结对编程项目-最长单词链
我在这个课程的目标是 学习软件工程
这个作业在哪个具体方面帮助我实现目标 学会与队友合作,了解结对编程,学习接口相关知识

1. 项目说明

2. 计划

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

 

3. 教材阅读

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

Information Hiding

要求提供一个稳定的接口,限制使用者调用的方法和范围,保护程序的其余部分不受实现的影响,同时也保护这个接口内的内容不被访问。在这次任务中,通过类core对各个函数和功能模块的封装组合,既保证了接口的统一,调用的便捷,也能够实现对这些模块使用的保护,可以提前对输入的参数进行测评、处理,再用核心计算单元进行计算。其中,在stage1中,我们在main函数中直接用inputProcess里的函数实现了对输入参数的分析、处理,再根据参数寻找word计算模块中对应的算法要求。在stage2中,通过使用core的封装,能够做到调用要求的四个函数,并在core中自动调用inputProcess参数处理,并根据分析自动调用word计算模块,或者报告异常情况。

Interface Design

要给调用者一个直接使用的接口,如此次作业中的GUI,命令行界面,通过图形化界面选择参数或者在命令行中输入参数进行接口调用,可以极大的方便调用者,同时,尽量减少接口的输入要求,在方便用户的设计思维下,需要被调用方更加全面的实现各种调用情况的应对,我们对可能存在的各种输入组合以及对应的措施如异常提示,各类功能的调用与结果输出进行了分析和实现。

如:

参数冲突表

表中记录了参数是否可以复合使用。(x 表示两个参数不能同时出现)

参数2 \ 参数1nwmchtr
n x x x x x x x
w x x x x      
m x x x x x x x
c x x x x      
h x   x   x    
t x   x     x  
r x   x       x

异常处理

参数
  • 存在非法参数

    • 未定义参数

      • 不只一个字母

      • 非字母或无字母

  • 参数非法使用

    • 具体参数的非法使用(见具体需求)

    • 冲突

    • 重复

    • 缺失

      • 无参数

      • 只有 -r, -h, -t 但没有功能指令

文件
  • 文件不存在

  • 没有给出文件名

  • 文件非法(不以 .txt 结尾)

  • 存在不只一个文件名

具体需求

-n
  • 功能

    统计该单词文本中共有多少条单词链,包含嵌套单词链

  • 输入

    文件的绝对或相对路径(以 .txt 结尾)。

  • 输出

    命令行输出

    一个数字 n ,代表单词链总数。

    之后的 n 行输出单词链(不同单词用空格分隔)。

  • 异常处理

    单词构成环路

  • 特殊情况

    没有单词链

Loose Coupling

减少各个模块之间的耦合性,能够使得可以几乎将所有的模块独立,并且能够在对模块的实现方法了解很少的基础上实现调用、组合,也可以方便模块正确性测试。

在这次作业中,分为inputProcess,word,core,exception四大板块,其中inputProcess实现了两组输入处理模式,一个是针对阶段一中命令行的输入需求对参数输入进行处理,另一组是针对GUI中能够传入的参数进行处理;word则是计算关键,分为N,M,W_noR,W_R,C_noR,C_R几大功能。异常包包含了各种异常错误类型,core则实现对外统一封装。

附加题小组交换core

本组与周子颖(19373132)、王宇欣(19373349)交换了 dll 模块,并分别进行了测试,测试方式为:

  • 本组单元测试模块 + 对方组的 dll

  • 本组的 GUI + 对方组的 dll

  • 本组的 dll + 对方组测试模块

  • (由于在互换时对方组没有设计 GUI,故无法实现本组的 dll + 对方组 GUI)

在这次的结对编程中,我们保证了core的接口定义,此外要求输入的result是一个提前开了20000个指针类型空间的指针,严格要求inputLen和input所包含的字符串数量相同。

与别组交换core进行测试的时候,也同样要遵循该组对输入参数的规定。例如在此次交换的小组,除了课程组中参数类型规定,该组的契约要求了输入的result要是一个提前开了20000个指针类型空间的指针,输入的char** input只能包含无重复输入的全小写英文单词,输入参数head,tail只能输入小写英文字母。这样就要求我们在调用时,提前将所有的单词串做去重处理,并将不符合要求的字符单词处理了,以及在输入head和tail时保证该参数的正确性。

除了以上的参数要求,互相在交换调用的时候不在关注其实现的过程,并且也只在测试程序中调用接口函数即可,如果出现问题,则可以将该模块单独拎出去进行测试。

在交换模块中,发现对方小组:

  1. 对于无链的情况("ab", "cbc", "dcd", "ede", "faf", "gfg", "hgh")处理出现错误(图一,左图为我们dll,右图为对方dll)

  2. 对于五点全向图测试样例瞬间卡退崩溃

  3. 出现异常情况时,dll没有提示异常信息,也没有处理异常,而是将异常抛出,需要外面调用者自己catch处理(个人认为是一个设计缺陷)(图二)

 

 

 

4. 模块接口设计与实现

-n

算法设计:采用 DFS 的方式实现,每次求出以某一特定字母开头的全部可能单词链。

无环路

数据结构alphatet中每一格存首尾相同的单词list组

 

判断环路

算法设计

采用深度搜索,将首尾相同的单词都视作同一个边,用CycleFind记录每轮递归找到的点,同时找到的点都放入AllFind中,如下如分析,如果在单词深度搜索中没有找到导致环路的点,那么这一轮所有出现过的点都不用再分析了,因为假设再用它为开头找环,如果能够找到环,则在它之前的那一轮循环中也能找到环,这样就出现矛盾,因此用AllFind排除一些重复的点,并且将未知单词个数简化到最多26个点。

 

-w

算法设计

和环路算法数据结构相同,同样视首尾相同的单词为同一条线,换成迪杰斯特拉算法找所有源的最大长度路径,由于无环,首先排除入度不为0的字母,循环遍历只出不进的字母,每一轮找到离该源点最大路径的点,经过一次松弛,记录前项点,其中避开a---a格式的点,保证除了初始点,前项点始终与自己不同,循环所有点后,再进行下一个单源点寻找。在计算最长路径的时候,需要通过前项点寻找路径,同时,假设找到的点为a,如果alphabet中存在a---a格式的点,则需要将路径再添加一次权重,如果长度超过现有最大值则更新。

只有-t时,反转alphabet[colomn][row]colomn\row去寻找

权重为1

-c

算法设计

上述算法中权重为单词长度

-m

算法设计

上述算法中寻找路径时跳过对a---a格式的点的判断

有环路

-w & -c

算法设计

在有环路的情况下,统一采用 DFS 的方式进行检索,每在单词链中添加一个单词即将其标记为 dirty,选取最长的单词链保存。

性能改进

由于 -c 要求选出字符长度最长的单词链,因此在输入时可根据单词长度对其排序,每次从最长单词开始遍历,可以减少对于单词图的遍历次数,获得一定的性能提升。

5. UML 图

 

 

UML、函数流程混杂图(其中绿色为stage1,红色为stage2)

 

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

在测试中-w和-c测试量相同,其中性能测试样例包含四点全向图,多链,固定首尾。分析函数性能,Core::gen_chain_wordCore::gen_chain_char 所封装的 FunctionC_RFunctionC_R 占据了极大的比例。其中 FunctionC_R 更多,是因为该函数需要获取单词的长度作为权重,在查单词长度时导致了这一消耗。

  • 改进思路1:通过加入对点的判断,减少不必要的点的函数递归调用

  • 改进思路2:有参数 -c 时先对单词长度进行排序,再执行算法

  • 改进思路3:引入编译优化

 

 

对于-noR,可以通过不再读入两个以上的同样首尾的单词(a---a格式可以读入两个)来降低其读入的时间开销。

7. Design by Contract, Code Contract

契约式编程是一种设计方法,为传统的抽象数据类型增加了先验条件、后验条件和不变式。通过增加contract实现对函数、方法、接口调用的参数规范,从语法、语义上对调用接口进行了封装保护。比如.NET的代码协议中,contract可以在运行时提供自动测试工具,过滤掉不满足先决条件的测试参数,或在不运行程序时自动检查隐式契约如空指针或越界等情况,或调用程序员的显式契约。

契约式编程对程序语言要求很高,需要有机制来验证契约的成立,可以使用断言机制但是仍有语言不包含这些机制,而且契约的机制并没有统一标准,因此存在语言与语言之间、项目之间契约模块的不同。

契约的思想要求调用者一方根据接口的要求来调用,被调用者则需满足测试要求条件之内的正确性。这保证了双方地位的平等,与以往被调用者需要考虑全所有的情况并给出相应的应对来保证准确性更加安全,可靠,同时也能够使得程序测试或者调用时更加高效,有序。

在这次的结对编程中,我们保证了core的接口定义,参数要求范围内程序运行的正确性,生成了dll模块。我们要求了输入的result要是一个提前开了20000个指针类型空间的指针,严格要求inputLen和input所包含的字符串数量相同。

与别组交换core进行测试的时候,也同样要遵循该组对输入参数的规定。例如在此次交换的小组,除了课程组中原本的参数类型规定,该组的契约要求了输入的result要是一个提前开了20000个指针类型空间的指针,输入的char** input只能包含无重复输入的全小写英文单词,输入参数head,tail只能输入小写英文字母。这样就要求我们在调用时,提前将所有的单词串做去重处理,并将不符合要求的字符单词处理了,以及在输入head和tail时保证该参数的正确性。

8. 单元测试

单元测试覆盖率如下:

 

 

8.1 测试函数说明

init(char** inputw,int inLenw):将测试用例导入;

void check(int refLen,char *refAns[]):将参考结果导入,如果refLen是-1,则默认跳过assert核对环节;

void init(char** inputw,int inLenw) {
input = inputw;
inLen = inLenw;
}
void check(int refLen,char *refAns[]) {
cout << len << endl;
if (true) {
for (int i = 0; i < len; i++) {
const char* tmpRes = result[i];
cout << tmpRes << endl;
}
}
else {
assert(refLen == len);
for (int i = 0; i < len && i < refLen; i++) {
const char* tmpRef = refAns[i];
const char* tmpRes = result[i];
assert(strcmp(tmpRef, tmpRes) == 0);
cout << tmpRes << endl;
}
}
cout << "--------times " << times << " end ----" << endl;
times++;
len = 0;
}

8.2 正确性测试

构造思路

对于所有

  • 单词样例

    • 传入的单词大小写

    • 传入重复单词

  • 无链

    • 传入的 input 数列是否为空

    • 只有一个单词成立是否会被识别为链输出

  • 有链

    • 单链

      • 是否有形如 axxxxa ax 的词组

    • 多链

      • 是否有会被重复使用到的单词如 "ab"之于da ca

  • 有环

W/C

  • -r 带环

  • -h 有参数,以该单词为首,是否存在链

  • -t 有参数,以该单词为首,是否存在链

  • 样例中存在一个以 word 为标准的最长链和一个以 char 为标准的最长链

/*-------M/N-------*/
const int inLenN00 = 0;
const char* inputN00 = NULL;
init((char**)inputN00, inLenN00);

len = Core::gen_chains_all(input, inLen, result);
check(-1,NULL);

const int inLenN0 = 4;
const char* inputN0[inLenN0] = { "aend", "OF", "the", "World" };
const int refLenN0 = 1;
const char* refAnsN0[refLenN0] = { "thea aend" };
init((char** )inputN0, inLenN0);
len = Core::gen_chains_all(input, inLen, result);
check(refLenN0, (char**)refAnsN0);

const int inLenN1 = 10;
const char* inputN1[inLenN1] = { "ab", "bc", "cd", "de", "af", "fg", "gh", "aa", "kk", "lmopq" };
init((char**)inputN2, inLenN2);
len = Core::gen_chains_all(input, inLen, result);
check(-1,NULL);
len = Core::gen_chain_word_unique(input, inLen, result);
check(-1,NULL);


const int inLenM0 = 10;
const char* inputM0[inLenM0] = { "ab", "bc", "cd", "de", "af", "fg", "gh", "aa", "kk", "lmopq" };
const int refLenM0 = 4;
const char* refAnsM0[refLenM0] = {"ab","bc","cd","de"};
init((char**)inputM0, inLenM0);
len = Core::gen_chain_word_unique(input, inLen, result);
check(refLenM0, (char**)refAnsM0);

/*----------W/C_NoR-------*/
const int inLenW1 = 10;
const char* inputW1[inLenW1] = { "AB", "bc", "cd", "de", "af", "fg", "gh", "aa", "kk", "kxxxxxxxxxxxxlmopq" };

const int refLenW11 = 4;
const char* refAnsW11[refLenW11] = { "aa","ab", "bc", "cd" };
len = W(input, inLen, result, 0, 0, true);
check(-1, NULL);

len = W(input, inLen, result, 0, 'd', true);
check(refLenW11, (char**)refAnsW11);

const int refLenW12 = 2;
const char* refAnsW12[refLenW12] = { "kk", "kxxxxxxxxxxxxlmopq" };
len = C(input, inLen, result, 0, 0, false);
check(-1, NULL);

/*----------W/C-----------*/
const int inLenW3 = 3;
const char* inputW3[inLenW3] = { "aaaaaaaaa","aa","aa" };
init((char**)inputW3, inLenW3);
len = Core::gen_chain_word(input, inLen, result, 'b', 0, true);
check(0, NULL);
len = Core::gen_chain_word(input, inLen, result, 'a', 'b', true);
check(-1, NULL);

len = Core::gen_chain_char(input, inLen, result, 'b', 0, true);
check(0, NULL);
len = Core::gen_chain_char(input, inLen, result, 'a', 'b', true);
check(-1, NULL);

const int inLenW2 = 4;
const char* inputW2[inLenW2] = { "aaaa","abbb","bccc","cccca"};
init((char**)inputW2, inLenW2);
len = Core::gen_chain_word(input, inLen, result, 'b', 'c', true);
check(-1, NULL);

len = Core::gen_chain_char(input, inLen, result, 'b', 'c', true);
check(-1, NULL);

8.3 鲁棒性测试

构造思路

对于所有

  • -n, -m, -w_noR, -c_noR

    • 有环

  • -h, -t

    • 参数不是单词字母

  • result长度超过 20000

/*----------M/N-----------*/

const int inLenN3 = 6;
const char* inputN3[inLenN3] = { "BA", "aB","ca","aMMMMmc","cb","bc"};
init((char**)inputN3, inLenN3);

len = Core::gen_chains_all(input, inLen, result);
check(-1,NULL);
len = Core::gen_chain_word_unique(input, inLen, result);
check(-1,NULL);

len = Core::gen_chain_char(input, inLen, result, 'b', 0, false);
check(0, NULL);
len = Core::gen_chain_char(input, inLen, result, 'a', 'b', false);
check(-1, NULL);

/*----------W/C-----------*/
const int inLenW3 = 3;
const char* inputW3[inLenW3] = { "aaaaaaaaa","aa","aa" };
init((char**)inputW3, inLenW3);

len = Core::gen_chain_word(input, inLen, result, '$', 0, true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 0, 0, false);
check(-1, NULL);

len = Core::gen_chain_char(input, inLen, result, '$', 0, true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 0, 0, false);
check(-1, NULL);

const int inLenW0 = 12;
const char* inputW0[inLenW0] = { "XY","YX","XZ","ZX","YZ","ZY","ax","ay","az","za","ya","xa" };
init((char**)inputW0, inLenW0);

len = Core::gen_chain_word(input, inLen, result, 0, 0,false);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 0, 0, false);
check(-1, NULL);
//SET RESULT CONTAIN = 10
len = Core::gen_chain_word(input, inLen, result, 0, 0,true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 0, 0, true);
check(-1, NULL);

8.4 性能测试

构造思路

  • 构造全连接图测试有环检验最长

  • 对于非r构造大数据输入

const int inLenW0 = 20;
const char* inputW0[inLenW0] = { "XY","YX","XZ","ZX","YZ","ZY","ax","ay","az","za","ya","xa","ab","xb","yb","zb","ba","bx","by","bz" };
init((char**)inputW0, inLenW0);

len = Core::gen_chain_word(input, inLen, result,0,0,true);
check(-1,NULL);
len = Core::gen_chain_char(input, inLen, result, 0, 0, true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 'h', 0, true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 0, 't', true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 'y', 0, true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 0, 'y', true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 'x', 'y', true);
check(-1,NULL);
len = Core::gen_chain_word(input, inLen, result, 'x', 't', true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 'h', 'y', true);
check(-1, NULL);
len = Core::gen_chain_word(input, inLen, result, 'a', 0, true);
check(-1, NULL);

len = Core::gen_chain_char(input, inLen, result, 'h', 0, true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 0, 't', true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 'y', 0, true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 0, 'y', true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 'x', 'y', true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 'x', 't', true);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 'h', 'y', true);
check(-1, NULL);
check(-1, NULL);
len = Core::gen_chain_char(input, inLen, result, 'a', 0, true);
check(-1, NULL);

9. 异常处理

计算模块中处理一下几种异常,其余放入 GUI 模块中处理。

  1. -n,-m,输入单词文本带有循环,返回-1

    const int inLenN3 = 6;
    const char* inputN3[inLenN3] = { "BA", "ab","ca","aMMMMmc","cb","bc"};
    init((char**)inputN3, inLenN3);

    len = Core::gen_chains_all(input, inLen, result);
    check(-1,NULL);
    len = Core::gen_chain_word_unique(input, inLen, result);
    check(-1,NULL);

    len = Core::gen_chain_word(input, inLen, result, 'b', 0, false);
    check(0, NULL);
    len = Core::gen_chain_char(input, inLen, result, 'a', 'b', false);
    check(-1, NULL);
  2. -h,-t后输入的字符不是字母,返回-2

    const int inLenW3 = 2;
    const char* inputW3[inLenW3] = { "aAAAaaaaaa","aa" };
    init((char**)inputW3, inLenW3);

    len = Core::gen_chain_word(input, inLen, result, '$', 0, true);
    check(-1, NULL);

    len = Core::gen_chain_char(input, inLen, result, '$', 0, true);
    check(-1, NULL);
  3. 超过20000,返回总长

    const int inLenW0 = 12;
    const char* inputW0[inLenW0] = { "XY","YX","XZ","ZX","YZ","ZY","ax","ay","az","za","ya","xa" };
    init((char**)inputW0, inLenW0);

    /*SET RESULT CONTAIN FROM 20000 TO 10*/
    len = Core::gen_chain_word(input, inLen, result, 0, 0,true);
    check(-1, NULL);
    len = Core::gen_chain_char(input, inLen, result, 0, 0, true);
    check(-1, NULL);

10. GUI 设计

利用 Qt 进行图形化界面设计。

10.1 需求分析

  • 输入

    • 导入单词文本文件

    • 直接在界面上输入单词并提交

  • 参数

    • 用户可选择参数(-n -w -m -c -h -t -r)

  • 输出

    • 将结果输出到界面上

    • 提供导出按钮,将文件保存到指定位置

  • 异常

    • 异常情况返回提示信息

10.2 Qt Design 窗口设计

在 Qt Design 中进行窗口设计

 

10.3 代码生成

利用 Qt 中提供的 uic 工具将上一步中生成的 .ui 文件转化为 .h 文件。

 

10.4 加入功能有关逻辑

输入

界面输入

可在位于界面左上角的文本框中输入内容,点击“清空”按钮,文本框中的内容会被清空。

 

导入文件

点击“导入文本”,可以实现文本导入。

 

参数设置

用户可以勾选需要的参数。特别的,对于 -h-t,用户在勾选后可以在右方输入框内填入首字母 / 尾字母,如图。(若不勾选 -h-t,窗口中的内容视为无效)

 

输出

窗口输出

用户点击“运行“后,可将运行结果输出至下方文本框中。

 

导出文本

点击“导出文本”,弹出保存文件窗口,可以将输出(下方文本框的内容)保存至用户选定的位置(覆盖 / 新建文本文件)。

 

10.5 加入异常处理逻辑

输入异常

  1. 当导入文件不以 .txt 结尾时,在输出框中报出异常,如图。

     

  2. 当导入文件中的单词数量(包括重复)大于 20000 时,会报出异常,如图

     

参数异常

参数冲突

当发生参数冲突时,点击”运行“会在下方文本框中报出相应错误信息,如图:

参数不能同时出现

 

没有功能性参数(-n, -w, -c, -m

 

参数长度不合法

-h-t 中传入参数长度不为 1 时,会在下方输出框中报出该错误。

 

 

参数内容不合法

当参数不是合法的英文字母时,会在下方输出框中报错,如图。

 

运行异常

代码存在环路(但无 -r 参数)

 

10.6 加入其它逻辑

由于运行需要一定时间,在运行时输入文本框如果改变,可能对运行结果的正确性产生一定的影响,因此在点击”运行“后,会先将输入文本框设为只读,并在运行结束后恢复。

11. 模块连接

用 dll 连接图形界面与计算核心。

11.1 生成 dll

用类 Core 封装接口。

  1. 提取接口

    修改源文件中的函数,将其改为适应给定接口的函数:

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

    • int gen_chains_all(char* words[], int len, char* result[])

    • int gen_chain_word_unique(char* words[], int len, char* result[])

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

  2. 建立 Core 类

    建立 Core 类,并将上述接口封装入 Core 类中。

    其中

    • Core.cpp 中给出各接口的实现

      #include "Core.h"

      int Core::gen_chain_word(char** words, int len, char** result, char head, char tail, bool enable_loop) {...}

      int Core::gen_chains_all(char** words, int len, char** result) {...}

      int Core::gen_chain_word_unique(char** words, int len, char** result) {...}

      int Core::gen_chain_char(char** words, int len, char** result, char head, char tail, bool enable_loop) {...}
    • Core.h 中给出类和接口的定义

      #pragma once
      #ifndef CORE_H
      #define CORE_H
      #include "word.h"
      #include "inputProcess.h"
      #include "exceptionPackage.h"
      #endif // CORE_H
      using namespace std;

      extern "C"
      class __declspec(dllexport) Core {

      public:
      //words为输入的单词列表,len为单词列表的长度,result存放单词链,函数返回值为单词链长度。
      //1.单词数量最多的最长单词链:
      //head和tail分别为单词链首字母与尾字母约束(如果传入0,表示没有约束),
      //当enable_loop为true时表示允许输入单词文本中隐含“单词环”
      static int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
      //2.函数返回所有符合定义的单词链,函数返回值为单词链的总数
      static int gen_chains_all(char* words[], int len, char* result[]);
      //3.函数返回首字母不同的,单词数最多的单词链,函数返回值为单词链的长度
      static int gen_chain_word_unique(char* words[], int len, char* result[]);
      //4.计算最多字母数量的最长单词链
      static int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
      };
  3. 生成 dll

11.2 导入 dll 模块与函数

方法

在 GUI 中导入 dll 模块和相关函数,代码如下:

// 定义函数类
typedef int(*functionN)(char* words[], int len, char* result[]);
typedef int(*functionM)(char* words[], int len, char* result[]);
typedef int(*functionW)(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
typedef int(*functionC)(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
// ...

// 导入 dll 模块
int main(int argc, char* argv[]) {
HMODULE hModule;
// 将 dll 模块导入 hModule 中
hModule = LoadLibrary(TEXT("../bin/core.dll")); // 将 dll 放入 bin 目录下
if (hModule == nullptr) { // 若导入失败
cout << GetLastError() << endl; // 打印最近一条错误信息
cout << "dll doesn't exist" << endl; // 输出错误提示
return 0;
}
// 导入 dll 函数
// 根据函数名导入
functionC C = (functionC)GetProcAddress(hModule, "gen_chain_char");
functionW W = (functionW)GetProcAddress(hModule, "gen_chain_word");
functionN N = (functionN)GetProcAddress(hModule, "gen_chains_all");
functionM M = (functionM)GetProcAddress(hModule, "gen_chain_word_unique");
}

常见错误

在实际操作中,出现了部分 bug,记录如下:

动态库生成

  • vs 配置和平台的选择:debug x86 还是 debug x64。(error 173)

  • 生成时是否需要预编译头 pch.h(如果完全从零开始造可以先忽略,如果不是,.h文件的顺序可能需要调整)

  • 生成的名称,如果是函数,尤其是通用的函数,可以通过 extern "C" 来保证函数名就是 dll 生成的名字

动态库使用

  • 有两轮获取指针,一个是 HMODULE hModule = LoadLibrary(_T("core.dll"));, 动态库 dllmain 中定义的一个入口,一个是functionC C = (functionC)GetProcAddress(hModule,"gen_chain_char");,functionC 是参数组的指针类型,这两个其中一个得到了 NULL 的值,都会导致 dll 调用失败

  • HMODULE hModule

    失败的原因可能是是 vs 平台选择错误或者传入地址填写错误,或与 Unicode、LPCSTR、string 等格式类型有关

  • GetProcAddress

    • 返回 NULL:error = 127

    • dll 生成函数名已经出现错误了,不是源文件中的 那个函数名。注:查看 dll 函数名工具:Dependency Walker

11.3 与 GUI 按键连接

main 函数中的 QWidget::connect 函数可以设置按键对应的响应函数,编写方式如下:(只保留框架)

// 运行代码
QWidget::connect(createUi.pushButton, &QPushButton::clicked, [&]{
// 获取输入
words = ...;
result = ...;
wordCount = ;
// 运行代码
if (...) {
length = N(words, wordCount, result);
// 判断返回值的合法性
} else if () {
...
}
// 输出结果
...
}

到此,已实现了 GUI 和计算模块的连接。

12. 结对过程

结对采用了线上与线下结合的方式,截图 & 照片如下:

 

 

 

13. 结对优缺点

先夸我的队友:思路清晰,很好合作,自律负责

优点:在结对编程的时候,能够将题目考虑得更加全面,比如我队友,第一次见面进行结对编程的时候,队友认真的带我捋了一遍所有的功能、异常的可能,经过讨论,可以极大的单人思考时会降低疏漏的点,此外就是编程的时候一写、一审可以提高单人编程准确性,虽然期间会出现两个人同时忽略的bug,但是会减少;也能够在一些逻辑简单的情况下提高编程速度,因为两个人的想法一致的时候,编程速度是由想得最快的那个人的决定的。能够让我学习到一些新的好的习惯,比如队友在遇到不熟悉的用法时,会查一查然后果断开一个小的demo直接测试,而我可能倾向于慢吞吞的上网查,然后还是不确定用法和相关原因,并且队友擅长markdown记录,当分析题目的时候,会有条理的对要求的点进行梳理,排版清晰,覆盖全面的进行总结,而我则更习惯读题读题,拿草稿纸画一画大概有哪些组合,最后真正编程的时候才罗列情况。(PS:DDL的压力也减少了

缺点:结对编程需要两个人有共同的充足的时间,首先有人一起编程会改变之前我们独自编程的习惯,去适应并进入结对编程状态需要时间,此外在结对编程时多出来的互动和讨论可能也会导致编程思路中断,在困难的点上我倾向于自己静下来想一想,也更倾向于自己debug,这个习惯导致我在结对前期的时候根本没法去写一些计算模块,然而我的队友是很好的patner,我的锅...在后期更加适应了两人合作的编程模式后,我们在测试阶段既体验到两人思路的完善,也能提高代码验证的效率。

14. 实际花费时间

独立完成

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划 30 30
· Estimate · 估计这个任务需要多少时间 30 30
Development 开发 900 1380
· Analysis · 需求分析 (包括学习新技术) 90 300
· Design Spec · 生成设计文档 60 90
· Design Review · 设计复审 (和同事审核设计文档) 20 20
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 10
· Design · 具体设计 50 120
· Coding · 具体编码 240 480
· Code Review · 代码复审 60 90
· Test · 测试(自我测试,修改代码,提交修改) 180 360
Reporting 报告 120 240
· Test Report · 测试报告 60 60
· Size Measurement · 计算工作量 30 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 150
  合计 1050 1650
posted @ 2022-04-05 16:24  YSMY  阅读(60)  评论(0编辑  收藏  举报