软工结对作业

Posted silhouette-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软工结对作业相关的知识,希望对你有一定的参考价值。

软工结对作业

1. 项目地址

2. PSP表格

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

3. 接口设计方法

3.1. Information Hiding

​ 信息隐藏类似于面向对象中的封装,将内部实现细节与数据隐藏起来,外部只需要关注提供的访问方法与操作。这样一方面保证了内部的数据安全,另一方面可以通过约束保证操作的可靠性。

​ 本次作业中,一方面从文件的角度实现了信息隐藏,计算核心对外只提供了三个接口,对内全通过engine进行调用,所以外部调用时不需要关注内部的实现,实现了信息隐藏。另一方面从程序内部看,任何操作都通过相应的函数进行,并不需要考虑具体实现,也实现了信息隐藏。

3.2. Interface Design

​ 实现了封装之后,接口则需要通过用户需求与功能设计,本次我们采用课程的接口,在后文中有描述。

3.3. Loose Coupling

​ 所谓去耦合,实际上就是提高我们开发的软件功能的普适性,能够适应各种改变。在开发中可以体会到,有时想要通过一些特定方法实现特定功能,以避开一些难题,但其实这个难题正是松耦合所需要的。为了能够去掉模块之间过分的关联,不能依靠其他模块的便利来实现。

4. 计算模块实现

​ 首先是图的结构:每一个单词Word作为图的节点,对两个单词a、b,若a的尾字母与b的首字母相同,则在ab之间连一条a指向b的有向边。

​ 计算模块core实现了如下三个API:

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

​ 其中,words存放输入的单词数组,len为单词数组长度,result用来存放结果单词链,head为规定单词链首字母,tail为规定单词链尾字母,reject为规定单词不允许首字母,以上三个为 \'\\0\' 时表示不做要求,且初始值为 \'\\0\'。enable_loop用来约束是否允许存在环。

​ 我们采用拓扑排序的方式判断有无环,同时求出拓扑序用于后续。

​ 求全部单词链与带环情况采用暴力DFS运算,若不带环即图为DAG,此时采用动态规划快速求解。dp[i]表示以i节点为最后单词的单词链的最长长度,用rec来记录前驱结点。则初始化dp[i]为点权,当word情况下为1,char情况下为单词长度。状态转移方程为 dp[j]=max(dp[j], dp[i] + weight[j]),其中i为j的前驱结点。

​ head、tail、reject的处理方式则使用noUse数组进行标记。为了最好的提高性能,reject在最开始便处理,标记相应单词noUse为true,相当于降低图的度;head则在开始调用函数时处理,减少了DFS的次数或动态规划的开始点;tail则只能在计算过程中处理。

5. 编译结果

6. 计算模块UML图

​ 我们采用面向过程的求解思路,计算模块的函数调用UML图如下:

​ 其中, engine每次调用功能性函数前,会先调用init_words 和 topo_check 进行预准备。

7. 计算模块性能

8. Design by Contract 与 Code Contract

​ Design By Contract也即契约编程,是一种较为形式严格的设计软件方法。通过包含前置条件、后置条件和不变量的合同式的严格的接口规范设计保证程序开发的可靠性。优点是可以较好的保证调用者与被调用者两者的质量。缺点是会造成代码部分冗余与不可读性,以及可能会损失性能。本次作业中,我们对core的三个API的设计与参数进行严格定义,但是并未采取形式化的表述,而是通过更为通俗的表述。并且通过维护文档function design.md 进行沟通。

​ Code Contract是VS提供的一种与语言无关的代码契约插件。能够帮助进行运行时检查、静态检查与文档生成。优点是可以帮助开发人员履行契约,避免了多余精力的耗费。我们编码过程主要在Clion完成,没有使用此查件。

9. 计算模块单元测试

我们使用Google的gtest作为单元测试的模板,分别对计算核心和异常处理进行了测试。

9.1 覆盖率

9.2 计算核心测试与结果

我们给计算核心设计了24个单元测试并全部正确通过

为了方便测试,我将重复的部分提取成函数方便快速设计测试

void test_gen_chain_word(const char* words[], int len, const char* ans[], int ans_len, char head, char tail, char reject, bool enable_loop) 
    char** result = (char**)malloc(10000);
    int out_len = gen_chain_word(words, len, result, head, tail, reject, enable_loop);
    ASSERT_EQ(ans_len, out_len);
    for (int i = 0;i < ans_len;i++) 
        if (result != nullptr) ASSERT_EQ(strcmp(ans[i], result[i]), 0);
    
    free(result);

对应的测试单元

TEST(gen_chain_word, example_w) 
    const char* words[] = "algebra", "apple", "zoo", "elephant", "under", "fox", "dog", "moon", "leaf", "trick", "pseudopseudohypoparathyroidism";
    const char* ans[] = "algebra", "apple", "elephant", "trick";
    test_gen_chain_word(words, 11, ans, 4, 0, 0, 0, false);

9.3 异常测试与结果

我们给所有异常情况设计了14个单元测试,覆盖了所有异常,详细情况见下一章节

10. 计算模块异常处理

本程序设计了如下14种异常

异常情况 具体异常描述 报错输出
没有正确输入参数 检测到-n, -w, -c, -h, -j, -r以外的参数 "this argument is invalid!"
重复输入参数 同一个参数检测到两遍 "arg: " + arg + " repeat!"
-n 与其他参数冲突 检测到-n与其他参数一同使用 "-n can\'t be used with other arguments!"
-w与-c冲突 检测到-c与-w一同使用 "-w can\'t be used with -c!"
-h, -t, -j后续参数错误 检测到-h, -t, -j后续参数不是单个字母 "arg: " + arg + " following argument is wrong!"
-h, -t, -j后续参数缺失 检测到 -h, -t, -j后续没有参数 "arg: " + arg + " missing following argument!"
功能参数缺失 没有检测到-n, -w, -c "need -n or -w or -c!"
没有获取到合法的文件名 没有文件名或者文件名不以.txt结尾 "don\'t get valid filename!"
重复输入文件名 检测到多个文件名 "filename is repeat!"
打开文件失败 该文件不存在或不可读 "opening file fail!"
输入超过10000个词 无环情况下输入超过了10000个词 "words are more than 10000!"
有环情况下输入超过100个词 有环情况下输入超过了100个词 "words are more than 100 when chains has circle!"
输出超过20000行 result结果大于20000 "results are more than 20000 chains!"
在不允许有环的情况下输入有环图 没有检测到-r但是图是有环图 "the chain has circle without -r!"

以以下几组异常处理逻辑与对应的单元测试为例

该段代码包含了 -h, -t, -j后续参数错误-h, -t, -j后续参数缺失重复输入参数这三个异常的抛出过程

if (is_h == false) 
                    is_h = true;
                    int i_next = i + 1;
                    if (i_next == argc) 
                        throw invalid_argument("arg: " + arg + " missing following argument!");
                    
                    string arg_next = argv[i_next];
                    if (arg_next.length() == 1 && isalpha(arg_next[0])) 
                        h_char = tolower(arg_next[0]);
                     else 
                        throw invalid_argument("arg: " + arg + " following argument is wrong!");
                    
                    i++;
                 else 
                    throw invalid_argument("arg: " + arg + " repeat!");
                

在main函数中捕获异常并处理

int main(int argc,char* argv[]) 
    try 
        main_serve(argc, argv);
     catch (invalid_argument const& e) 
        cerr << e.what() << endl;
    
    catch (logic_error const& e) 
        cerr << e.what() << endl;
    
    catch (runtime_error const& e) 
        cerr << e.what() << endl;
    
    return 0;

对应的单元测试

10.1 -h, -t, -j后续参数错误

TEST(main_serve, example_h_wrong) 
    try
        const char* args[] = "Wordlist.exe", "-w", "-h", "aa", "test.txt";
        main_serve(5, args);
     catch(invalid_argument const &e)
        ASSERT_EQ(0, strcmp("arg: -h following argument is wrong!", e.what()));
        return;
    

10.2 -h, -t, -j后续参数缺失

TEST(main_serve, example_h_miss) 
    try
        const char* args[] = "Wordlist.exe", "-w", "-h";
        main_serve(3, args);
     catch(invalid_argument const &e)
        ASSERT_EQ(0, strcmp("arg: -h missing following argument!", e.what()));
        return;
    

10.3 重复输入参数

TEST(main_serve, example_repeat_arg) 
    try
        const char* args[] = "Wordlist.exe", "-n", "-n", "test.txt";
        main_serve(4, args);
     catch(invalid_argument const &e)
        ASSERT_EQ(0, strcmp("arg: -n repeat!", e.what()));
        return;
    

11. 界面模块设计

​ 界面模块采用vue设计开发,所采用的技术栈如下:

  • Vue 3
  • Element - Plus
  • 注册表脚本reg文件
  • XMLHttpRequest

​ 设计风格以简约为主,效果如下:

​ 功能实现:

  • 输入可以通过文件导入,也可以手动输入,且两者可同步使用,导入文件后可自动更新输入框。展示如下:

  • 功能设定选择通过按钮实现,且选择首字母限制后方可进行输入。基本异常情况也会给出错误提示:

  • 结果展示如下:

12. 界面模块与计算模块对接

​ 对接我们采用的vue+cli的方式,熟悉分析选项异常,随后处理好输入输出后,调用程序。调用程序通过html中的a元素的href属性,首先通过reg文件注册url,后通过href调用本地exe文件,达到调用程序的目的。

附加任务- 交换核心:

​ 与我们交换核心的小组学号为20373284、19375201。

​ 运行效果如下:

​ 且我们的API设计完全相同,故较容易实现互换。

13. 结对过程

主要在新主楼F座3楼进行结对编程。

14. 结对编程

14.1. 优点

  • 结对编程可以帮助作出决定,可以综合两个人的知识与思考,做出更全面切实的决定。
  • 保证了代码的可靠性,经过了及时复审,避免了隐藏错误。
  • 在一定程度上避免了独自写代码时的划水。

14.2. 缺点

  • 由于结对编程效率较低,难以维持足够的时间进行结对编程。
  • 两人之间了解不够,有时配合稍有问题。
  • 需要合适的硬件条件,包括场地、设备等。

14.3. 队员优缺点分析

​ 本次结对的队员为庞睿加同学。

队员 优点 缺点
庞睿加 1. 对基本算法熟悉
2. 对Vue前端相对熟练
1. 对于配置环境工作很头疼
2. 高级算法知识相对不够充分,
3. 当陷入难题后工作效率降低
朱彦安 1.学习新工具较快
2.测试较为全面,发现bug与debug相对熟练
1.不懂前端知识
2.比较拖沓

以上是关于软工结对作业的主要内容,如果未能解决你的问题,请参考以下文章

软工实践结对作业-黄紫仪

软工结对编程作业

软工第二次结对作业

软工结对第一次作业

软工网络15个人阅读作业2

第二次软工作业