结对编程项目——最长英语单词链
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2023 年北航软件工程 |
这个作业的要求在哪里 | 结对项目-最长英语单词链 |
我在这个课程的目标是 | 掌握工程化的软件开发流程,在实践中锻炼自我思考能力和团队开发能力 |
这个作业在哪个具体方面帮助我实现目标 | 通过两人合作完成结对编程项目,锻炼合作编码和时间统筹能力 |
一、项目信息
- 教学班级:周四班
- 项目地址:https://gitee.com/realssd/se-pair
成员:19376309-鲁文澔 20373862-杜维康
二、PSP 表格——预计时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 30 |
Development | 开发 | 2360 |
· Analysis | · 需求分析 (包括学习新技术) | 450 |
· Design Spec | · 生成设计文档 | 120 |
· Design Review | · 设计复审 (和同事审核设计文档) | 120 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 |
· Design | · 具体设计 | 120 |
· Coding | · 具体编码 | 720 |
· Code Review | · 代码复审 | 200 |
· Test | · 测试(自我测试,修改代码,提交修改) | 600 |
Reporting | 报告 | 130 |
· Test Report | · 测试报告 | 60 |
· Size Measurement | · 计算工作量 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 40 |
合计 | 2520 |
三、接口设计
Information Hiding(信息隐藏)
信息隐藏原则,与计算机领域的透明化思想类似,用户只需要知道他们感兴趣的部分的内容,在本次作业中,实际使用的用户,只对输入、可选项、输出感兴趣,而对接用户则只对定义的三个接口感兴趣。而在更深的层次中是什么样子的,用户不感兴趣,也不需要知道。
所以在我们的作业中,核心部分完全透明化,只提供三个接口对外交互。
Interface Design(接口设计)
接口是一种约定,(遵守)实现这个接口,即代表着他们具有相同的行为方式。随着程序规模的增长,接口所承担着的对象定义约束也会越来越多,所以接口设计是一个很重要的工作。如何尽可能使接口之间正交,在维护可拓展性的情况下保证接口的简洁。
在本次作业中,我们通过对业务进行抽象,为一个图模型来定义接口,将功能通用化了。
Loose Coupling(松耦合)
松耦合是指模块间的耦合程度低,耦合分为数据耦合和行为耦合。我们尽量减少了模块间的行为耦合。并通过标准库中的类型进行数据耦合。在我们的结对项目中,我们把模块主要分为了四大类,分别为element
、exception
、include
、tool
,这样的松耦合大大提高了项目的代码质量。
结合松耦合的设计,我们的core.dll文件可以由他人的cli和gu程序调用。而我们的cli程序可以使用他人的dll来进行计算。
四、计算模块接口的设计与实现过程
4.1 计算模块的接口设计
计算模块的接口我们使用的是作业要求文档中给出的接口,其由CLI程序直接调用,
int gen_chains_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);
但是由于我们的GUI界面采用了Python作为开发语言,其并不能catch到C++抛出的异常,所以我们在这三个接口外添加了一层数据聚合与异常处理层,其由方法
char *callByCmd(char *cmd, int len);
构成,主要作用为解析Python传入的参数、对上述三个接口返回的结果进行字符串序列化、对抛出的异常进行捕获和字符串序列化后返回到Python程序中。
4.2 计算模块的接口实现
计算模块的开发我们采用了由内而外的开发轨迹,即先对内部功能进行开发,完成后逐步集成和封装,最后实现由交互应用调用的接口。
我们的核心部分思路是将单词集合抽象为一个以字母为节点,单词为边的有向图,通过对图的遍历来实现需求,其主要由三个类构成:
- Word类:单词的封装,输入单词字符串,提供获取小写后字符串、开头结尾字符、长度等基本信息等方法,以及对单词编号、设置两端节点的方法。
- CharNode类:字母节点的封装,提供绑定单词作为出入边、获取度以及对单词进行长度排序等功能。
- WordGraph类:单词集合图的封装,提供了图的建立、寻找最长单词链、寻找所有单词链等方法。
而在上述计算核心的外层,还有一些工具函数包括
- readWordList方法:从文件中读取字符串,并进行单词的划分
- copyVector:复制vector容器
- dfs:寻找最长链使用的dfs函数
- dfsGetAll:寻找所有路径使用的dfs函数
- getConnected:分支划分函数
- checkCircle:检查成环性
在此的外层则是三个接口,对输入数据构造为满足核心部分要求的数据,并将返回数据构造为接口定义的数据。
最外层则是抛出的异常进行捕获和字符串序列化层,定义的三个接口和该层的callByCmd
函数都对外进行了暴露,该层还有一些参数校验与处理的方法。
五、编译通过无警告截图
- MacOS
- Windows
六、UML图
七、计算模块部分的性能改进
在最初的版本,对于多分枝的树桩单词集合数据,我们的程序在-w
、-c
以及-n
上很容易卡死。
通过分析,我们初步得出结论,-n
卡死很正常,因为确实有很多数据(测试数据为一个树,一共有约
25
!
25!
25!个单词链)
但是对于-w
和-c
两种情况来说不应该卡死。针对这两个参数我们对CPU时间进行了分析
通过性能分析得知,dfs耗时最长,而且其递归了很多次,按照原有的思路,应该是遍历所有的单词链,找到最长的一个。
但是相同节点对之间的单词链实际上并不需要全部遍历,只需要满足以下规则即可
- 相同节点对之间走最长的一个单词,对于最长字符链(
-c
)来说最长单词可以比其他单词长度更长,对于最长单词链(-w
)来说长度相同,选择并不影响,考虑开发效率,我们决定统一使用最长单词 - 对于自环单词,其能增加长度,且不改变状态,在有
-r
的情况下应加尽加
所以我们对CharNode
节点的插入Word
规则进行了修改,在插入时进行降序排序,同时将自环单词放在最前面。
在寻找最长单词链的dfs过程中,当遇到第一个非自环单词,这一节点对的路径遍确定了。
八、关于Design by Contract / Code Contract的思考
Design by Contract (DbC) 和 Code Contract (CC) 是一种软件开发方法,旨在帮助开发者通过规范化代码的行为来提高代码的可靠性和可维护性。
DbC 是由 Bertrand Meyer 在 1988 年提出的,它通过在代码中添加前置条件、后置条件和类不变量的方式来定义代码的行为。前置条件是指必须满足的条件才能调用代码,后置条件是指代码执行后应该保证的条件,类不变量是指类的某些属性在执行代码后应该保持不变。通过这些规范,可以确保代码执行的正确性,从而提高代码的可靠性和可维护性。
CC 则是微软在 .NET Framework 4.0 中引入的一种实现 DbC 的方法。CC 可以让开发者在代码中使用断言来定义前置条件、后置条件和类不变量。与 DbC 相比,CC 提供了更加灵活的语法,并可以在运行时对代码进行验证,从而提供了更加可靠的保障。
其实在大二下学期的OO课程中,我们就感受过契约或者说规格编程的体验,在设计完规格后开始编码,可以大幅度降低编程时的心智负担,能做到每一个部分都能有合适的验证准则,同时在编码时,也能注意副作用的红线。
优点:
- 通过明确定义代码的行为,可以提高代码的可靠性和可维护性。
- 可以帮助开发者更好地理解代码的作用和行为。
- 可以在代码中添加更加严格的约束,从而减少代码中的错误和漏洞。
- 可以在代码调用时提供更加详细的错误信息,从而更快地发现和修复错误。
缺点:
- 需要编写更多的代码来定义前置条件、后置条件和类不变量,从而增加了代码的复杂度。
- 如果后续有所改动,变更上会比较笨拙。
- 如果定义不当,会导致代码更加脆弱和不易维护。
- 在运行时进行验证会带来一定的性能开销。
在结对编程中,我们通过以下方式融入DbC 与 CC :
- 共同制定并严格遵守代码契约:在编写代码前,我们共同制定程序的前置条件、后置条件和不变量,并将其嵌入到代码中。在编写和调试代码时,遵守代码契约,以确保代码的正确性和可靠性。
- 使用契约语法和工具:在结对编程过程中,使用了代码契约相关的语法和工具,例如 Code Contracts 工具,通过在代码中使用 Contract 类和 Contract 属性来嵌入契约条件,然后使用静态分析和运行时检查来验证代码的正确性。在结对编程中,我们共同使用这些工具来制定和遵守代码契约,以减少代码错误和增加代码可靠性。
九、单元测试
本次编程单元测试的设计思路和普通白盒测试的思路一样,面对代码结构设计尽可能覆盖分支的测试样例。
分为以下测试组:
- TestArgsValidate:参数检查功能测试
- TestReadWordList:读入划分单词测试
- TestInterface:暴露接口逻辑测试
- TestWord:Word类测试
- TestCharNode:节点类测试
- TestWordGraph:单词图类测试
例如,测试成环检测的测试,区分了a->a
,a->b->c->a
,a->a+a->b->c->a
,d->a->b->c->a
几种类型的样例
TEST(TestWordGraph, TestLoopCheck) {
auto nodeVector = new vector<CharNode *>();
auto aNode = new CharNode('a');
nodeVector->push_back(aNode);
auto aaWord = new Word("aa", 2);
sort(nodeVector->begin(), nodeVector->end(), degreeCmp);
aNode->insertInWord(aaWord);
aNode->insertOutWord(aaWord);
bool hasCircle = checkCircle(nodeVector);
EXPECT_EQ(hasCircle, false);
auto bNode = new CharNode('b');
auto cNode = new CharNode('c');
nodeVector->push_back(bNode);
nodeVector->push_back(cNode);
auto abWord = new Word("ab", 2);
auto bcWord = new Word("bc", 2);
bNode->insertInWord(abWord);
aNode->insertOutWord(abWord);
bNode->insertOutWord(bcWord);
cNode->insertInWord(bcWord);
sort(nodeVector->begin(), nodeVector->end(), degreeCmp);
hasCircle = checkCircle(nodeVector);
EXPECT_EQ(hasCircle, false);
auto caWord = new Word("ca", 2);
cNode->insertOutWord(caWord);
aNode->insertInWord(caWord);
sort(nodeVector->begin(), nodeVector->end(), degreeCmp);
hasCircle = checkCircle(nodeVector);
EXPECT_EQ(hasCircle, true);
auto dNode = new CharNode('d');
auto dbWord = new Word("db", 2);
dNode->insertOutWord(dbWord);
bNode->insertInWord(dbWord);
sort(nodeVector->begin(), nodeVector->end(), degreeCmp);
hasCircle = checkCircle(nodeVector);
EXPECT_EQ(hasCircle, true);
}
以及对于连通分量划分的样例,同样也是构造一些例子检测计算结果
TEST(TestWordGraph, TestGetConnected) {
auto a = new CharNode('a');
auto b = new CharNode('b');
auto c = new CharNode('c');
connect(a, b, "ab");
connect(b, c, "bc");
auto d = new CharNode('d');
auto e = new CharNode('e');
auto f = new CharNode('f');
auto g = new CharNode('g');
connect(d, e, "de");
connect(e, f, "ef");
connect(f, d, "fd");
connect(f, g, "fg");
auto z = new CharNode('z');
auto nodeMap = new map<char, CharNode *>();
nodeMap->insert(map < char, CharNode * > ::value_type('a', a));
nodeMap->insert(map < char, CharNode * > ::value_type('b', b));
nodeMap->insert(map < char, CharNode * > ::value_type('c', c));
nodeMap->insert(map < char, CharNode * > ::value_type('d', d));
nodeMap->insert(map < char, CharNode * > ::value_type('e', e));
nodeMap->insert(map < char, CharNode * > ::value_type('f', f));
nodeMap->insert(map < char, CharNode * > ::value_type('g', g));
nodeMap->insert(map < char, CharNode * > ::value_type('z', z));
auto connects = getConnected(nodeMap);
EXPECT_EQ(connects->size(), 3);
for (auto &conn: *connects) {
if (std::find(conn->begin(), conn->end(), a) != conn->end()) {
EXPECT_NE(std::find(conn->begin(), conn->end(), b), conn->end());
EXPECT_NE(std::find(conn->begin(), conn->end(), c), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), d), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), e), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), f), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), g), conn->end());
} else if (std::find(conn->begin(), conn->end(), d) != conn->end()) {
EXPECT_NE(std::find(conn->begin(), conn->end(), e), conn->end());
EXPECT_NE(std::find(conn->begin(), conn->end(), f), conn->end());
EXPECT_NE(std::find(conn->begin(), conn->end(), g), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), b), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), c), conn->end());
} else {
EXPECT_NE(std::find(conn->begin(), conn->end(), z), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), b), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), c), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), e), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), f), conn->end());
EXPECT_EQ(std::find(conn->begin(), conn->end(), g), conn->end());
}
}
}
还有验证参数有效性的测试样例,针对冲突、缺失、非法等情况进行了构造
TEST(TestArgsTools, TestArgsValidation) {
// 测试冲突
int case1Argc = 3;
const char *case1Argv[] = {"-c", "-w", "test_input"};
bool getError = false;
try {
argsValidate(case1Argc, (char **) case1Argv);
} catch (Exception& e) {
getError = true;
cout << e.getExceptionMessage() << endl;
EXPECT_EQ(e.getExceptionCode(), BAD_PARAMETER);
}
EXPECT_TRUE(getError);
int case2Argc = 3;
const char *case2Argv[] = {"-n", "-w", "test_input"};
getError = false;
try {
argsValidate(case2Argc, (char **) case2Argv);
} catch (Exception& e) {
getError = true;
cout << e.getExceptionMessage() << endl;
EXPECT_EQ(e.getExceptionCode(), BAD_PARAMETER);
}
EXPECT_TRUE(getError);
// 测试需要成对出现的
int case3Argc = 3;
const char *case3Argv[] = {"-h", "a", "test_input"};
getError = false;
try {
argsValidate(case3Argc, (char **) case3Argv);
} catch (Exception& e) {
getError = true;
cout << e.getExceptionMessage() << endl;
EXPECT_EQ(e.getExceptionCode(), BAD_PARAMETER);
}
EXPECT_TRUE(getError);
// 测试不带后字符的
int case4Argc = 2;
const char *case4Argv[] = {"-h", "test_input"};
getError = false;
try {
argsValidate(case4Argc, (char **) case4Argv);
} catch (Exception& e) {
getError = true;
cout << e.getExceptionMessage() << endl;
EXPECT_EQ(e.getExceptionCode(), BAD_PARAMETER);
}
EXPECT_TRUE(getError);
// 测试非法参数的
int case5Argc = 2;
const char *case5Argv[] = {"-q", "test_input"};
getError = false;
try {
argsValidate(case5Argc, (char **) case5Argv);
} catch (Exception& e) {
getError = true;
cout << e.getExceptionMessage() << endl;
EXPECT_EQ(e.getExceptionCode(), BAD_PARAMETER);
}
EXPECT_TRUE(getError);
// 测试对的
int case6Argc = 6;
const char *case6Argv[] = {"-w", "-h", "b", "-j", "l", "test_input"};
getError = false;
try {
argsValidate(case6Argc, (char **) case6Argv);
} catch (Exception& e) {
getError = true;
cout << e.getExceptionMessage() << endl;
EXPECT_EQ(e.getExceptionCode(), BAD_PARAMETER);
}
EXPECT_FALSE(getError);
int case7Argc = 6;
const char *case7Argv[] = {"-*", "test_input"};
getError = false;
try {
argsValidate(case7Argc, (char **) case7Argv);
} catch (Exception& e) {
getError = true;
cout << e.getExceptionMessage() << endl;
EXPECT_EQ(e.getExceptionCode(), BAD_PARAMETER);
}
EXPECT_TRUE(getError);
}
最终单测的覆盖率整体达到95%左右,最低覆盖率被测文件达到了94%
十、异常处理说明
本次编程异常定义如下,对应单测位于定义下方
#define INVALID_PARAMETER_EXCEPTION (-1) // 内部调用参数异常
TEST(WordTest,InvalidParameterTest) {
cout << "Start Test Invalid Parameter Exception" << endl;
int errorCode = 0;
Word* word = NULL;
try {
word = new Word("",0);
} catch (Exception& e) {
errorCode = e.getExceptionCode();
cout << e.getExceptionMessage() << endl;
}
EXPECT_EQ(errorCode,INVALID_PARAMETER_EXCEPTION);
errorCode = 0;
word = NULL;
try {
word = new Word("",-2);
} catch (Exception& e) {
errorCode = e.getExceptionCode();
cout << e.getExceptionMessage() << endl;
}
EXPECT_EQ(errorCode,INVALID_PARAMETER_EXCEPTION);
cout << "End Test Invalid Parameter Exception" << endl;
delete word;
}
#define OBJECT_DATA_CORRUPTED (-2) // 数据损坏异常
TEST(WordTest,ObjectDataCorruptedTest) {
cout << "Start Test Object Data Corrupted Exception" << endl;
int errorCode = 0;
Word* word = new Word("Banana",6);
::memset(word,0,sizeof(Word));
try {
word->getLastChar();
} catch (Exception& e) {
errorCode = e.getExceptionCode();
cout << e.what() << endl;
}
EXPECT_EQ(errorCode,OBJECT_DATA_CORRUPTED);
try {
word->getFirstChar();
} catch (Exception& e) {
errorCode = e.getExceptionCode();
cout << e.getExceptionMessage() << endl;
}
EXPECT_EQ(errorCode,OBJECT_DATA_CORRUPTED);
cout << "End Test Object Data Corrupted Exception" << endl;
}
#define FILE_OPEN_FAILED (-3) //文件打开失败异常
try {
set<string>* wordList = readWordList("");
} catch (Exception& e) {
EXPECT_EQ(e.getExceptionCode(),FILE_OPEN_FAILED);
cout << e.getExceptionMessage() << endl;
}
#define HAS_CIRCLE_WITHOUT_PERM (-4) //未允许的成环异常
// 成环异常位于下方单测
#define BAD_PARAMETER (-5) // 外部调用参数不合法
string testCase = "Element\n"
"Heaven\n"
"Table\n"
"Teach\n"
"Talk";
fstream fs;
fs.open("test_input", ios::out);
fs.write(testCase.c_str(), testCase.length());
fs.close();
int case1Argc = 3;
int case2Argc = 2;
int case3Argc = 3;
int case4Argc = 2;
int case5Argc = 2;
int case6Argc = 6;
int case9Argc = 5;
const char *case1Argv[] = {"-c", "-r", "test_input"};
const char *case2Argv[] = {"-n", "test_input"};
const char *case3Argv[] = {"-h", "a", "test_input"};
const char *case4Argv[] = {"-h", "test_input"};
const char *case5Argv[] = {"-q", "test_input"};
const char *case6Argv[] = {"-w", "-h", "b", "-j", "l", "test_input"};
const char *case9Argv[] = {"-w", "-r", "-t","t","test_input"};
try {
parseArgs(case1Argc, (char **) case1Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
try {
parseArgs(case2Argc, (char **) case2Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
try {
parseArgs(case3Argc, (char **) case3Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
try {
parseArgs(case4Argc, (char **) case4Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
try {
parseArgs(case5Argc, (char **) case5Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
try {
parseArgs(case6Argc, (char **) case6Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
try {
parseArgs(case9Argc, (char **) case9Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
#define NO_VALID_WORDLIST_DETECTED (-6) // 无满足条件结果
fs.open("test_input", ios::out);
int case8Argc = 2;
const char *case8Argv[] = {"-n", "test_input"};
fs.close();
try {
parseArgs(case8Argc, (char **) case8Argv);
} catch (Exception& e) {
cout << e.getExceptionMessage() << endl;
}
#define TOO_MANY_WORDLIST_DETECTED (-7) // 答案过大(高于20000条)
// 答案过大样例文件过大,难以放入单元测试,放入黑盒进行
#define DUPLICATED_WORD (-8) // 存在重复单词
string testCase10 = "aba\n"
"ABA";
fs.open("test_input", ios::out);
fs.write(testCase10.c_str(), testCase10.length());
fs.close();
try {
cmd = "-n test_input";
callByCmd((char*)cmd.c_str(),cmd.length());
} catch (Exception& e) {
cout << e.what() << endl;
}
对于列出的异常,我们并不会进行处理,而是将错误信息返回给用户。
在与exe对接中,我们采用的是throw + try…catch的方法来对异常进行捕获。
在捕获到异常后,在终端中将异常的what方法的返回值打印出来。
GUI的逻辑也与之类似,但是传递异常的方式为由callByCmd做了一次封装,如果存在异常,该方法返回的字符串就是异常信息的字符串。
十一、界面模块的详细设计
我们采用的Python的 PyQt5
实现的界面模块
实现界面
界面上方是我们的参数选择栏,其中分为两种选择框
一种是单选框,分别是所有单词链、最多单词数和最多字母数,他们三个分别代表着参数-n
、-w
、-c
,这三个参数之间不能兼容,所以只能同时选择一种参数
另一种是多选框,分别是指定首字母、指定尾字母、不允许首字母和允许隐含单词环,他们四个分别代表着参数-r
、-h
、-t
、-j
,这四个参数之间可以兼容,所以可以同时选择多个参数,其参数对应的字母通过下拉框选择,也可以不指定字母,那么结果和没有选择这个参数相同
界面中间主体是两个文本框,一个是基于 textEdit 的手动输入框,一个是基于 textBrowser 的结果输出框。当输入和输出过长时,会自动生成滚动栏以查看结果,具体如上图所示
对于最下方的三个按键,其中计算单词链是在选择参数、输入单词后进行计算,并将计算结果或者异常报错输出到右边的结果输出框里
而从文件导入单词和保存结果这两个按键,是通过python调出文件选择框,来实现文件的导入和结果的导出。基于编码前设计,导入和保存都只能通过 txt 文件实现,而不可通过其他文件实现。这样的设计避免了一系列的异常发生
11.1界面模块与计算模块的对接
界面模块的对接主要通过函数 callByCmd():
char *callByCmd(char *cmd, int len)
该函数会将输入的字符串命令进行拆分,并调用对应的 API 模块,最后将结果或异常信息通过返回值的方式返回。其可能调用的 API 如下
int gen_chains_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)
在通过 Pyqt5 实现的界面模块中,我们通过 python 导入 DLL 的方式,导入 libCore.dll 文件后调用其中的 callByCmd 接口,读取返回值对应的结果或者异常信息,并将其显示在界面模块的结果输出栏上。
通过以上方法,我们实现了界面模块与计算模块的对接
11.2与别组同学的对接
为了验证模块的松耦合,我们与20373861-李治圻,19375035-孙靖懿组同学交换了dll进行测试
交换过程中在异常处理上出现了问题,我们都使用了自定义异常来进行处理,这导致了我们无法捕获对方抛出的异常。为此我们决定修改自定义异常继承std::exception
类,在抛出时使用自定义异常,在接收时使用父类异常。在对接时,由于文件名不一样,还需要对文件名进行修改。
另一组的同学使用他们的CLI和GUI程序成功调用我们的dll计算,包括计算正确与异常捕获都成功了。我们由于GUI依赖于独有的接口,所以我们仅使用CLI程序调用他们的DLL,也成功实现了计算和异常捕获
十三、结对过程
我们的结对使用线下进行,我们开发分为两部分,Core部分和GUI部分。Core部分鲁文澔同学作为程序员,杜维康同学作为领航员,GUI部分则相反。每次结对前需要先梳理这次要写些什么,进行实现讨论后再开始编码。
线下结对照片:
十四、结对总结
结对优缺点分析
优点:
- 提高代码质量:结对编程可以减少缺陷和错误,提高代码的质量。
- 促进学习和交流:结对编程可以促进开发者之间的交流和学习,提高技术能力。
- 提高生产率:结对编程可以减少错误和重复工作,提高生产效率。
缺点:
- 需要额外的时间和精力:结对编程需要两个开发者一起合作,因此需要额外的时间和精力。
- 可能导致意见分歧:两个开发者在结对编程过程中可能会出现意见分歧,导致进度缓慢。
- 难以实现:结对编程需要两个开发者具有一定的技术能力和经验,因此难以实现。
团队优缺点分析
鲁文澔 | 杜维康 | |
---|---|---|
优点 | 能肝,会C++,有一些先进工具的使用经验 | 会用PyQT,在进行黑盒测试的时候认真细致,擅长发现问题 |
缺点 | 对于前端非常头疼,而且缺少一台Windows电脑和Windows平台下的编译经验 | 对 C++ 不太熟悉,git的使用不是很熟练 |
十五、PSP 表格——实际时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | |
---|---|---|---|
Planning | 计划 | 30 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 60 |
Development | 开发 | 2360 | 2230 |
· Analysis | · 需求分析 (包括学习新技术) | 450 | 240 |
· Design Spec | · 生成设计文档 | 120 | 90 |
· Design Review | · 设计复审 (和同事审核设计文档) | 120 | 100 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 60 |
· Design | · 具体设计 | 120 | 120 |
· Coding | · 具体编码 | 720 | 1080 |
· Code Review | · 代码复审 | 200 | 180 |
· Test | · 测试(自我测试,修改代码,提交修改) | 600 | 360 |
Reporting | 报告 | 130 | 140 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 30 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 40 | 60 |
2520 | 2430 |