结对项目-最长英语单词链
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2023年北航敏捷软件工程 |
这个作业的要求在哪里 | 结对项目-最长英语单词链 |
我在这个课程的目标是 | 学习软件工程的一般方法并实践 |
这个作业在哪个具体方面帮助我实现目标 | 实践结对编程方法,锻炼工程能力 |
软件工程结对编程作业
- 教学班级:周四下午班
- 项目成员:20373447 朱承烜 20373974 阮正浩
- 项目地址:https://gitee.com/mamba0824/buaapaireprogramming/
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 30 | |
· Estimate | · 估计这个任务需要多少时间 | 20 | |
Development | 开发 | 2880 | |
· Analysis | · 需求分析 (包括学习新技术) | 300 | |
· Design Spec | · 生成设计文档 | 60 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 180 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | |
· Design | · 具体设计 | 120 | |
· Coding | · 具体编码 | 2160 | |
· Code Review | · 代码复审 | 300 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 400 | |
Reporting | 报告 | 120 | |
· Test Report | · 测试报告 | 120 | |
· Size Measurement | · 计算工作量 | 10 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | |
合计 | 3120 |
接口设计与实现
设计理念
-
Infromation Hiding(信息隐藏)
:信息隐藏是将敏感的或外部无需访问的信息封装在自身内部,使得外部不可见此类信息。在我们的设计中,我们对单词节点Node进行封装,外部只能通过给出的方法对单词节点中的属性(单词内容,长度,首尾字母,该节点的出度入度等)进行修改和查询操作。 -
Interface Design(接口设计)
:接口设计的好坏决定了模块之间通信效率和效果。良好的接口设计应该可以让某一模块通过简单调用其它模块的接口,高效地接收到自己需要的结果,而不关心实现的细节。在我们的设计中,对于基本任务实现代码写在interface.cpp
文件中,其中实现了三个外部调用的功能函数接口,分别是:计算最多单词数量的单词链接口:int gen_chain_word(char* words[], int len, char* result[], int head, int tail, int banned,int enable_loop)
,计算字母最多的单词链接口int gen_chains_all(char* words[], int len, char* result[])
,以及计算单词文本中可以构成多少个单词链接口int gen_chain_char(char* words[], int len, char* result[], int head, int tail, int reject, int enable_loop)
。每个接口中的参数可以划分成两部分,分别是传入的请求相关参数和返回的结果相关参数。相关性强的参数往往放在一起,比如所有单词的列表
char* words[]
和单词的个数len
,以及附加参数的相关信息:head
,tail
,reject
等,这样的信息往往前后连续放置。返回值通常表示函数运行成功或异常。 -
loose coupling(松耦合)
:松耦合指减少不同模块之间的相关性,对于一个模块的修改不必担心破坏其他关联模块的结构。我认为多人共同开发一个项目时,应该尽量实现模块间的松耦合,这样一个模块变动,另一个模块就不会变动,在我们的设计中,我们首先实现一些功能单一的简单函数,其它具有复杂功能的函数是通过调用这些具有原子性的函数实现的,如Node节点里的get,set函数,之后对Node图的dfs
等相关操作建立在调用这些简单函数的基础上实现,基于我们良好的信息隐藏和接口设计,我们代码中各模块的耦合度也得到降低。
计算模块接口的设计和实现
具体解释:
EXPORT_DLL int gen_chain_word(char* words[], int len, char* result[], int head, int tail, int banned,int enable_loop);
EXPORT_DLL int gen_chains_all(char* words[], int len, char* result[]);
EXPORT_DLL int gen_chain_char(char* words[], int len, char* result[], int head, int tail, int reject, int enable_loop);
计算模块对外开放上述三个函数作为接口,分别对应三个主任务。
选择以单词为节点建图,节点存储邻接数组,两层循环复杂度 O ( V 2 ) O(V^2) O(V2)。
若为-n,由于要输出所有单词链,直接dfs即可。
若为-w,由于仅要求最大链,考虑优化方法。对于无环情况,我们发现可以利用DAG图的拓扑性质进行dp解决问题。首先进行拓扑排序检测环路同时得到拓扑序(复杂度 O ( V + E ) O(V+E) O(V+E)),令dp[i]代表以节点i为结尾的最大单词链长度并初始化为1,按照拓扑序进行遍历更新dp[i]。
具体的方法是对于节点i,依次考虑接在其后的所有相邻节点,相邻节点取j时,若dp[j] < dp[i] + 1,则更新dp[j]为dp[i] + 1, 并记录dp[j]的前置节点为i,否则不进行操作,遍历完成后取dp表的最大值并根据前置节点恢复整条单词链即可。这种方法之所以能成立是由于拓扑序保证了处于遍历序列后面的节点不可能到达前面的节点,所以也不会造成更新。
考虑附加参数,-t容易处理,若节点i尾字母不合要求,考虑最大值时不计算dp[i]即可;-j和-h需要对图进行一定的重构,-j可以遍历所有节点将首字母不合要求的排除(代码实现上是用disabled标记),-h稍微复杂一点,有-h时唯一影响答案的情况是非指定首字符的单词作为链的头部,所以只需要再通过一次拓扑排序的过程将首字母不合要求且入度为0的节点排除即可。
对于-r的有环图,上述优化失效,目前还未找到正确的线性方法,直接类似-n使用dfs暴力搜索,同时对附加参数进行处理,这部分抽象至gen_chain_loop方法进行处理,比较常规便不赘述细节。
UML图展示:
展示在所在开发环境下编译器编译通过无警告的截图
计算模块接口部分的性能改进
一开始我们基于-n的暴力dfs方法对-w,-c的情况进行了简单实现,初步验证了正确性,但是对每个节点进行dfs的开销巨大难以接受。
于是我们希望利用剪枝或dp方法进行优化,并发现DAG图的拓扑序具有良好性质,能够保证dp的简单性和正确性,通过前文提到的具体算法,我们成功地将无环情况的求解复杂度降到了线性,小于建图的 O ( V 2 ) O(V^2) O(V2)复杂度,这种情况下消耗最大的函数为:
Node *create_graph(char *words[], int len) {
Node *nodes = (Node *) malloc((len + 5) * sizeof(Node));
if (nodes == nullptr) return nullptr;
int idx = 0;
for (int i = 1; i <= len; i++) {
char *word = words[i];
auto tmp = new Node(word, i - 1);
nodes[idx++] = *tmp;
}
for (int i = 0; i < len; i++) {
int h = nodes[i].get_head();
int t = nodes[i].get_tail();
for (int j = 0; j < len; j++) {
if (j != i) {
if (t == nodes[j].get_head()) {
nodes[j].ind_inc();
nodes[i].add(j);
}
}
}
}
return nodes;
}
-n或有环情况下,dfs函数成为消耗最大的函数:
void dfs(Node node, string str, int cnt, Node* nodes, vector<string> &ans) { //-n
int size = node.get_oud();
for (int i = 0;i < size ;i++) {
int to = node.get_to(i);
if (!nodes[to].is_used()) {
nodes[to].set_used(true);
dfs(nodes[to], str + " " + nodes[to].get_body(), cnt + 1, nodes, ans);
nodes[to].set_used(false);
}
}
if (cnt > 1) {
ans.push_back(str);
// cout << str << endl;
}
}
void dfs_r(Node node, int cnt, Node *nodes, vector<int> &tmp, vector<int> &ans, int &maxlen, int type, int tail) { //-r
int size = node.get_oud();
tmp.push_back(node.get_id());
if (type == 1) {
cnt += 1;
} else if (type == 2) {
cnt += node.get_len();
}
for (int i = 0; i < size; i++) {
int to = node.get_to(i);
if (!nodes[to].is_used() && !nodes[to].is_disabled()) {
nodes[to].set_used(true);
dfs_r(nodes[to], cnt, nodes, tmp, ans, maxlen, type, tail);
nodes[to].set_used(false);
}
}
if (cnt > maxlen && (tail == 0 || node.get_tail() == tail)) {
maxlen = cnt;
ans = tmp;
}
tmp.pop_back();
}
这部分我们做了一定力所能及的优化,如dfs过程中动态构建string便于输出单词链等。
改进计算模块性能上所花费的时间大致有3-4小时。
Design by Contract/Code Contract
关于契约式设计,其强调三个基本概念:前置条件、后置条件、不变式。契约式设计要求模块在运行(调用)前满足前置条件,在运行之后结果满足后置条件,并且运行的结果中满足不变式所要求某些变量的不变。
这一思想与OO课中所提到的规格划设计具有异曲同工之妙,其优点是:与防御式编程相比契约式编程可以有效减少模块中因为开发人员事先未规定好模块规格造成的对过多特殊情况的判断的代码冗余,因此,契约式编程对模块之间耦合的规定和要求更加明确,同时可以消除一些模块之间的兼容性问题,不用去处理不满足先验条件的情况。其缺点是:契约式编程需要一种机制来验证契约的成立与否,因此撰写、检查和实现的过程中往往需要结合语言本身的特性。
本次作业中,在调用核心的单词链解析相关函数前,调用者必须保证已预先处理单词,检查是否有环,去除重复单词,将单词转换为小写等;而在调用结束后,需要返回计算结果的长度,并提供结果对应的单词链以便进行输出;此处的不变条件则为传入的字符串,字符串是不可变的,单词链中使用的必须是未修改,未拆分的单词。但其实本次作业中我运行用契约式编程的地方并不多,大多还是写一些冗余代码来预防出错,可能是由于时间较紧而没有在模块规格上预先考虑太多。
计算模块部分单元测试展示
使用CLion+GoogleTest编写单元测试,分别对输入、计算、输出模块进行测试,主要检验各接口函数。
构造数据的思路:首先测试函数在正常输入下功能的正确性,然后构造对应不同异常的测试数据检验鲁棒性,再结合分支覆盖率探查未测试到的分支,对测试数据进行补充。
以参数解析部分的测试为例:
TEST(inputTest, parse_para_1) { //基本功能
char *path = nullptr;
int len = 10;
char *argv[15] = {" ", "-w", "input0.txt", "-h", "a", "-t", "b", "-j", "c", "-r"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, 0);
EXPECT_EQ(cal_type, 2);
EXPECT_EQ(head, 'a');
EXPECT_EQ(tail, 'b');
EXPECT_EQ(banned, 'c');
EXPECT_EQ(loop, 1);
std::cout << path << std::endl;
}
//各种异常情况
TEST(inputTest, parse_para_2) {
char *path = nullptr;![在这里插入图片描述](https://img-blog.csdnimg.cn/1aa0109c609847a8b14b4cecd7b0e29d.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/09d417400cdc4458b9e34bf68553e248.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/01d6b18a67c24d4c8d7ff19e57607f12.png#pic_center)
int len = 1;
char *argv[10] = {""};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -1);
}
TEST(inputTest, parse_para_3) {
char *path = nullptr;
int len = 2;
char *argv[10] = {"", "-n"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -7);
}
TEST(inputTest, parse_para_4) {
char *path = nullptr;
int len = 5;
char *argv[10] = {"", "-n", "input.txt", "-w", "input.txt"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -9);
... 本部分共26个单元测试,以达到高覆盖率
整体覆盖率截图:
如图,在所测试的core,input,output模块以及参数解析para.cpp中均达到了较高的覆盖率。
计算模块部分异常处理说明
我们总共支持了十六种异常,以下是针对每一种异常做的单元测试。
参数中不存在文件路径
当参数中不含有文件路径时,会反馈"Missing file path" 。
单元测试代码如下:
TEST(inputTest, parse_para_3) {
char *path = nullptr;
int len = 2;
char *argv[10] = {"", "-n"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -7);
}
参数中未指定任务
在一个参数序列中没有指定任务,会反馈"Missing working parameters"。
单元测试代码如下:
TEST(inputTest, parse_para_24) {
char *path = nullptr;
int len = 3;
char *argv[10] = {"", "-h", "i"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -1);
}
参数之间存在冲突
对于参数 -n,并不支持和其他参数共同使用,此时会反馈"-n Can only be used alone".
单元测试代码如下:
TEST(inputTest, parse_para_4) {
char *path = nullptr;
int len = 5;
char *argv[10] = {"", "-n", "input.txt", "-w", "input.txt"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -9);
}
参数指定了多个任务
参数 -n 、-w 、-c 分别代表需要执行的三个任务。在一个参数序列中只允许存在一个任务,违反则会反馈"Repetition of assigned tasks"。
单元测试代码如下:
TEST(inputTest, parse_para_5) {
char *path = nullptr;
int len = 5;
char *argv[10] = {"", "-c", "input.txt", "-w", "input.txt"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -2);
}
重复指定首字母
当出现多次使用 -h 参数时,会反馈"Repeat the specified parameter -h"。
单元测试代码如下:
TEST(inputTest, parse_para_6) {
char *path = nullptr;
int len = 7;
char *argv[10] = {"", "-c", "input.txt", "-h", "a", "-h", "a"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -3);
}
重复指定尾字母
当出现多次使用 -t 参数时,会反馈"Repeat the specified parameter -t"。
单元测试代码如下:
TEST(inputTest, parse_para_7) {
char *path = nullptr;
int len = 7;
char *argv[10] = {"", "-c", "input.txt", "-t", "a", "-t", "a"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -3);
}
重复指定不能出现的字母
当出现多次使用 -j 参数时,会反馈"Repeat the specified parameter -j"。
单元测试代码如下:
TEST(inputTest, parse_para_8) {
char *path = nullptr;
int len = 7;
char *argv[10] = {"", "-c", "input.txt", "-j", "a", "-j", "a"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -3);
}
重复指定允许有环参数
当出现多次使用 -r 参数时,会反馈"Repeat the specified parameter -r"。
单元测试代码如下:
TEST(inputTest, parse_para_9) {
char *path = nullptr;
int len = 5;
char *argv[10] = {"", "-c", "input.txt", "-r", "-r"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -3);
}
参数格式不合法
当命令中存在既不是任务参数,又不是附加参数后的合法字母的字符串时,会反馈"Illegal parameter format" 。
单元测试代码如下:
TEST(inputTest, parse_para_10) {
char *path = nullptr;
int len = 4;
char *argv[10] = {"", "-c", "input.txt", "aa"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -4);
}
指定首字母不合法
当指定 -h 参数时,如果后面的字符并不是大小写字符,会反馈"Lettering information is not legal" 错误。
单元测试代码如下:
TEST(inputTest, parse_para_12) {
char *path = nullptr;
int len = 5;
char *argv[10] = {"", "-c", "input.txt", "-h", "0"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -6);
}
指定尾字母不合法
当指定 -t 参数时,如果后面的字符并不是大小写字符,会反馈"Lettering information is not legal" 错误。
单元测试代码如下:
TEST(inputTest, parse_para_13) {
char *path = nullptr;
int len = 5;
char *argv[10] = {"", "-c", "input.txt", "-t", "0"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -6);
}
指定不能出现的字母不合法
当指定 -j 参数时,如果后面的字符并不是大小写字符,会反馈"Lettering information is not legal" 错误。
单元测试代码如下:
TEST(inputTest, parse_para_14) {
char *path = nullptr;
int len = 5;
char *argv[10] = {"", "-c", "input.txt", "-j", "aa"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -6);
}
文件名不合法
当文件后缀不是 .txt 时,会反馈"File path is not legal"
单元测试代码如下:
TEST(inputTest, parse_para_11) {
char *path = nullptr;
int len = 3;
char *argv[10] = {"", "-c", "input.txp"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -5);
}
附加参数后无字母信息
当-h,-t, -j参数之后没有写单词时,会反馈"Missing letter information"
单元测试代码如下:
TEST(inputTest, parse_para_15) {
char *path = nullptr;
int len = 4;
char *argv[10] = {"", "-c", "input.txt", "-h"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -8);
}
TEST(inputTest, parse_para_16) {
char *path = nullptr;
int len = 4;
char *argv[10] = {"", "-c", "input.txt", "-t"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -8);
}
TEST(inputTest, parse_para_17) {
char *path = nullptr;
int len = 4;
char *argv[10] = {"", "-c", "input.txt", "-j"};
int cal_type = 0, head = 0, tail = 0, banned = 0, loop = 0;
int r = parse_para(len, argv, cal_type, head, tail, banned, loop, &path);
EXPECT_EQ(r, -8);
}
单词中存在隐藏环
当传入的单词表中存在隐藏环且未指定 -r 参数时,会反馈"Find Loop,but !enable_loop!"。
单元测试代码如下:
TEST(coreTest, LOOP_CHECK)
{
char* words[4] = { "gg", "abc", "cde", "ea"};
char* result[10];
int r = gen_chains_all(words, 4, result);
EXPECT_EQ(r, -10);
r = gen_chain_char(words, 4, result, 'a', 'z', 0, 0);
EXPECT_EQ(r, -10);
r = gen_chain_word(words, 4, result, 'a', 'z', 0, 0);
EXPECT_EQ(r, -10);
}
-h与-j参数附带的字母相同
当同时使用-h与-j参数时,若这两个参数后的附加字母相同,会反馈"-h -j has the same char"
单元测试代码如下:
TEST(coreTest, HJ_CHECK)
{
char* words[2] = { "abc", "cde"};
char* result[10];
int r = gen_chain_char(words, 2, result, 'a', 0, 'a', 0);
EXPECT_EQ(r, -11);
r = gen_chain_word(words, 2, result, 'a', 0, 'a', 0);
EXPECT_EQ(r, -11);
}
界面模块的详细设计过程
界面设计采用Python
中的ttkbootstrap
库实现
主界面:
在左部文本框内进行所需要解析的单词文本的输入,或者通过Import File
按钮从本地导入文件,导入文件时会自动清空文本框内的已有内容,导入的文件内容也会显示在文本框中,对于文本框中输入的文本也可以通过Save File
按钮保存到本地。
在右边界面进行参数选择,其中-n
,-w
,-c
参数为三选一且必选项,通过RadioButton
组件实现,-h
,-t
,-j
,-n
参数为可选项通过checkButton
组件实现,对于前三个参数必须选择后其后面的输入框中输入一个字母。在正确选择和填写参数后可以点击submit
按钮提交调用解析函数开始解析。
若未选择必选参数或者选择前三个附加参数后未填写字母或-h
与-j
参数后的字母相同,则在submit是会有对应的报错信息提示用户。
结果展示界面:
计算结束后答案将反馈到文本框中,用户可点击Save File
自行下载使用。
若找不到答案则返回“Can’t find ans”
关闭此界面后将返回单词输入界面。
界面模块与计算模块的对接
界面模块与计算模块的对接主要通过下面的函数:
char* call_by_cmd(int len, char* cmd)
参数cmd
是一个命令行命令的字符串,len
是这个字符串的长度。
该函数的实现在solve这个模块中的gui_cmd.cpp
文件中,首先从cmd
参数中提取出任务和文件路径,之后该函数内部会调用solve
函数,并将计算的答案重定向输入到solution.txt
文件中,若有异常则会将异常信息通过返回值的方式进行反馈。若不存在异常则反馈空串。异常信息在计算过程中会保存在error.log
文件中,在返回之前会读取这个文件将异常结果返回。
界面模块中将通过python
导入dll的方式导入libsolver.dll
模块,调用其中的call_by_cmd
接口,从相应文件中读取异常信息或计算结果信息之后展示到界面中。
结对过程描述
我们结对采用线上线下相结合的模式,线下集中开发,线上调式优化。
我们的编程水平比较接近, 对编程语言的掌握也都不算太好,此外我们都是第一次用C++写多文件的项目。因此,初期我们主要是一起讨论算法,研究如何实现第一阶段基本的功能,同时各自进行C++的回顾与学习如何利用CMake运行C++项目。算法和整体架构敲定后我们开始结对编程,主要我来敲代码,我的队友进行代码复审,有不懂的地方或者不对劲的地方另一人都可以随时提出来。编写代码有时会遇到一些想不到的bug,比如指针相关问题,这时我们会一起搜索如何解决这个问题。我们两人在结对编程过程中都积极交流,遇到一直解决不了的问题也会相互帮助相互鼓励,所以我们的合作也十分愉快。
结对过程记录
结对编程的优缺点
优点:
- 结对编程时代码的实施复审过程可以有效减少单人编程时对特殊情况考虑不周而造成意想不到的bug的情况,减少代码修改和寻找bug的时间;
- 软件架构设计与算法思路经过两个人的思考,合理性可以有效提高,整个方案也能更加清晰,减少个人编程闭门造成导致后期重构的可能性;
- 是一个相互学习,能力互补的过程。既可以在结对过程中学习对方思考问题的方式和角度,同时也锻炼了我的表达能力,在结对编程中我们两人认为自己对方案或算法的概括和表达能力都得到有效锻炼。
缺点:
主要是两人时间的协调问题。结对编程的这两星期,自己每个周末都有考试,队友在有空闲时间时却需要等待我,导致我们大多只能利用周一到周五的课余时间进行编程,课余时间很少的情况下,需要牺牲很多休息时间,
**我的优点:**善于交流,有责任感,喜欢做计划,因此我制定了我们的开发计划,对任务进度能较好进行把控,此外能按时完成自己的任务。
**我的缺点:**编程能力与工程能力较弱。
**队友的优点:**编程能力较强,逻辑思维能力强,提供了核心算法的思路。啃较硬的骨头,也能按时完成自己的任务。
**队友的缺点:**代码工程化的能力较弱
PSP 表格 —— 实际
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 30 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 15 |
Development | 开发 | 2880 | 3120 |
· Analysis | · 需求分析 (包括学习新技术) | 300 | 360 |
· Design Spec | · 生成设计文档 | 60 | 90 |
· Design Review | · 设计复审 (和同事审核设计文档) | 180 | 120 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 25 |
· Design | · 具体设计 | 120 | 120 |
· Coding | · 具体编码 | 2160 | 1200 |
· Code Review | · 代码复审 | 300 | 400 |
· Test | · 测试(自我测试,修改代码,提交修改) | 400 | 420 |
Reporting | 报告 | 120 | 180 |
· Test Report | · 测试报告 | 120 | 120 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 120 |
合计 | 3120 | 3155 |