项目 内容
这个作业属于哪个课程 2022 年北航敏捷软件工程
这个作业的要求在哪里 结对编程项目-最长单词链
我在这个课程的目标是 学习软件工程相关知识,提高自己的代码能力与团队协作能力。
这个作业在哪个具体方面帮助我实现目标 通过两个人的团队作业提高自己的协作能力与表达水平,在实践中锻炼需求分析、测试与文档编写能力。

1. 项目说明

  • 教学班级:周五班
  • 项目地址:https://github.com/ericaaaaaaaa/BUAA_SE_homework.git

2. 计划

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

3. 教材阅读

Information Hiding

Information hiding 也称作信息隐藏,是将计算机程序中可能发生变化的部分与其它部分隔离开,从而让程序在该部分被修改时不会被大幅修改。信息隐藏通常的实现方法是通过提供接口。本次作业完成中我们在第二阶段中将各个参数对应的方法封装到 Core 类中,再提供四种接口,实现信息隐藏。

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

Interface Design

Interface design 主要面向用户,强调给用户一个方便使用的接口。本次作业中我们设计了 GUI 图形界面,方便用户更直观的使用程序。此外,同上文中 Information Hiding 所述,本次作业抽象出 Core 类的接口方便用户调用。

Loose Coupling

Loose coupling,又称松耦合,是紧耦合的对立面。实现松耦合可以降低组件之间的关联性,将其相互影响降到最低。可实现松耦合的对象包括类、接口、数据和服务等。在本次结对编程中,我们将 GUI 设计与核心计算模块 Core 分离,将 Core 生成动态链接库 dll,并用 GUI 直接调用 dll 完成功能的运行,实现了运算与展示部分的松耦合。此外,我们的单元测试模块也通过调用 dll 实现测试与运算的松耦合。

附加题

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

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

在互换 dll 时用了不少时间统一接口设计,但在互换后也依然存在部分不统一的地方(如对方组输入给 dll 的单词保证为小写,本组则在 dll 中处理大小写等)

互换测试中发现对方组的 bug 有:

  • Core 中未解决单词链中单词不允许相同的情况

    • 输入单词:aaaa, aaaa, aaaaa

    • 参数:-w, -r

    • 输出:

  • Core 无法处理部分合法输入,异常退出

    • 输入:"ab", "cbc", "dcd", "ede", "faf", "gfg", "hgh"

    • 参数:-m

    • 输出:异常退出(exit code -529697949)

4. 模块接口设计与实现

-n

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

无环路

alphatet中每一格存首尾相同的单词组

判断环路

算法设计

采用深度搜索,将首尾相同的单词都视作同一个边,用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 图如下:其中,图上方 Word 是用来保存单词(长度 & 内容),WordListCore 为主要计算模块,图中右侧 UiForm, QWidget, Form 是 GUI 实现中用到的类,图中下方的 Exception 是异常处理时需要用到的结构。

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

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

  • 改进思路1:通过加入对点的判断,减少不必要的点的函数递归调用
  • 改进思路2:有参数 -c 时先对单词长度进行排序,再执行算法
  • 改进思路2:引入编译优化

7. Design by Contract, Code Contract

Design by Contract

Design by contract,又称契约式设计,是软件工程中常用的方法,要求设计者为软件提供正式、精确且可验证的接口。

本次设计中我们在实现之前先分析了需求,并写出了设计文档,结合老师博客中提供的接口,定义数据结构和函数等。在写代码时参照设计文档完成设计,实现了契约式设计。

Code Contract

Code contract 是微软采用的一种设计方法,称为程式码合约(代码协定),要求在代码中指定前置条件,后置对象和对象固定的方法。

  • 前置条件是输入方法或属性时必须满足的要求
  • 后置方法描述在方法或属性代码退出时的预期
  • 对象固定描述处于良好状态的类的预期状态

在制定设计文档时,我们设想了功能模块的运行流程,从而将任务分解,由不同的函数分别完成。如在接受输入时:

  • initAlphabet

    • 初始化单词表
    • 前置条件:无
    • 后置方法:得到初始化后的单词表
    • 异常处理:无
  • analyzeParam
    • 分析参数
    • 前置条件:初始化后的单词表
    • 后置方法:各参数变量填入相应内容
    • 异常处理:参数不合法(缺失,重复,冲突,……)
  • readWordFromFile
    • 从文件中读入单词
    • 前置条件:初始化后的单词表、合法的参数
    • 后置方法:填充好单词的单词表
    • 异常处理:单词数量超出限制

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-----------*/
const int inLenW3 = 2;
const char* inputW3[inLenW3] = { "aaaaaaaaa","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

    1. 参数不是单词字母
  • 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 = 2;
const char* inputW3[inLenW3] = { "aaaaaaaaa","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 性能测试

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

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,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,输入单词文本带有循环

    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后输入的字符不是字母

    const int inLenW3 = 2;
    const char* inputW3[inLenW3] = { "aaaaaaaaa","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 = 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. 结对优缺点

结对编程优缺点

优点

  1. 一起编码便于更好的发现 bug(很多时候在写的过程中就发现了错误)
  2. 可以相互学习,相互鼓励

缺点

  1. 需要找到两个人都空闲的时间,可工作的时间缩短
  2. 代码风格可能存在不一致的情况

每个人的优缺点

姓名 优点 缺点
王雯清 有过 Qt 编写的经验、善于给函数命名、总结排版 不熟悉 visual studio
彭琳芝 沟通能力强、有责任心、善于查找资料,解决疑难问题 不熟悉 visual studio

14. 实际花费时间

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

结对编程项目-最长英语单词链相关推荐

  1. [2022软工第三次作业]结对编程项目——最长英语单词链

    项目 内容 本作业所属课程 2022年北航敏捷软件工程教学实践 本作业要求 结对编程项目-最长英语单词链 个人课程目标 学习到软件工程的方法论,了解整个过程,并进行亲自实践 本作业在哪个具体方面帮助我 ...

  2. 结对编程项目——最长英语单词链

    目录 结对编程项目--最长英语单词链 1. 项目地址 2. PSP 表格记录花费的时间 3. UML 图 4. 计算模块接口的设计与实现过程 5. 参考资料中 Information Hiding.I ...

  3. 北航2022软件工程第三次作业——结对编程(最长英语单词链)

    软件工程第三次结对编程作业 项目 内容 这个作业属于哪个课程 北京航空航天大学2022春季软件工程(罗杰 任健) 这个作业的要求在哪里 结对编程项目-最长英语单词链 我在这个课程的目标是 学习软件工程 ...

  4. 结对项目-最长英语单词链

    文章目录 结对项目-最长英语单词链 项目信息 PSP 表格 接口设计参考理念 Information Hiding Interface Design Loose Coupling **计算模块接口的设 ...

  5. 软件工程结对项目- 最长英语单词链

    项目 内容 这个作业属于哪个课程 2023年北航敏捷软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习现代化的软件开发方法 这个作业在哪个具体方面帮助我实现目标 对结 ...

  6. 结对项目-最长英语单词链-20373974阮正浩

    项目 内容 这个作业属于哪个课程 软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习软件工程的一般方法并实践 这个作业在哪个具体方面帮助我实现目标 实践结对编程方法, ...

  7. 结对项目——最长英语单词链

    项目 内容 这个作业属于哪个课程 https://bbs.csdn.net/forums/buaa-ase2023 这个作业的要求在哪里 https://bbs.csdn.net/topics/613 ...

  8. 「软工结对编程」:最长英语单词链

    项目 内容 这个作业属于哪个课程 2023年北航敏捷软件工程社区 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习有关软件开发的方法论,熟悉基本的软件开发流程,通过" ...

  9. 2023软工第三次作业-最长英语单词链

    结对项目-最长英语单词链 项目 内容 这个作业属于哪个课程 2023北航软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 帮助我初步建立软件工程敏捷开发的整体流程和概念, ...

最新文章

  1. Lua截取utf-8编码的中英文混合字符串
  2. Eclipse安装spring tool suite(4.9.0版本)
  3. Nacos系列:Nacos的三种部署模式
  4. 《社交网站界面设计(原书第2版)》——2.10 自我反省式的出错信息
  5. XamarinAndroid组件教程RecylerView适配器设置动画
  6. 阿里巴巴Java开发规约插件p3c
  7. 每一个程序员都应该知道的高并发处理技巧、创业公司如何解决高并发问题、互联网高并发问题解决思路、caoz大神多年经验总结分享...
  8. 生成树(STP)学习笔记
  9. Centos7下安装netstat
  10. 如何以 JAVA call 一個現有的 dll 檔?
  11. eigen 列拼接_R语言-强大的矩阵运算
  12. Python中的异常处理try、exception、raise
  13. append从一个添加到另一_麻城一总投资1.5亿项目开工建设,另一项目预计9月底开工,还有一个年产值14亿的5G项目快速推进中...
  14. 终于注册csdn博客了!
  15. Properties 类的详细使用(十三)
  16. Zookeeper的事务--Transaction
  17. IO流的详解,彻底了解IO流
  18. configure: error: no acceptable C compiler found in $PATH 问题解决
  19. Mysql可视化软件-Navicat和SQLyog
  20. pandas 如何删掉第一行_pandas删除指定行详解

热门文章

  1. python自学笔记
  2. mysql和虚拟主机区别_mysql和虚拟主机的区别是什么
  3. android复读机功能,点滴复读机最新版-点滴复读机app下载v3.0.1 安卓版-腾牛安卓网...
  4. python 中画球体_python – 有效地绘制许多球体
  5. 资本资产定价模型简介-多因子寻找Alpha统计套利
  6. 中国人民大学 计算机应用技术,中国人民大学计算机应用技术考研经验-人大信息学院考研辅导班...
  7. workbench焊接实例_[转载]Workbench的焊接模拟过程(高斯移动热源)
  8. 2023 上海(深圳)国际导热散热材料及设备展览会
  9. 《涨知识啦24》---JBS or MPS?
  10. 我的世界java额外参数_我的世界超平坦自定义部分参数