结对编程博客
项目 | 内容 |
---|---|
本次作业所属课程 | 2019BUAA软件工程 |
本次作业要求 | 结对编程作业 |
我在本课程的目标 | 熟悉结对编程流程 |
本次作业的帮助 | 帮助我提升了对项目时间的认识 |
本次作业项目 | Github项目地址 |
1.Github项目地址
2.估计开发时间及实际开发时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 5 |
Development | 开发 | 1300 | 1500 |
· Analysis | · 需求分析 (包括学习新技术) | 280 | 300 |
· Design Spec | · 生成设计文档 | 20 | 0 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 0 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 120 | 180 |
· Coding | · 具体编码 | 460 | 600 |
· Code Review | · 代码复审 | 60 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | 450 |
Reporting | 报告 | 120 | 180 |
· Test Report | · 测试报告 | 50 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 140 |
合计 | 1450 | 1700 |
3.结对编程中的接口设计方法介绍
- 最初我们把所有代码都写在了同一个文件里,也没有建立类。然后到后面测试环节时,我们发现这样的代码很难进行分类的单元测试。然后我们对代码进行了重构,这样代码不仅更方便测试了,也让代码可读性更高,更易于后面阶段的封装。
- 在对计算模块进行封装时,我们发现gui和命令行都需要对获得的单词进行拆分,因此我就将计算单元中的单词切割也进行了封装。这样命令行和gui都只需要调用dll就能进行大部分操作。
- 关于高内聚低耦合,我们将单词链转化成图,将图计算方面的函数都归入一个类中。而想要求单词链并不需要知道图的算法是如何运行的,只需要调用计算接口即可。
- 三个Core.dll 中的接口:
extern "C" CoreAPI int build_map(char* words[], char* text); \\对单词进行拆分
extern "C" CoreAPI int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);\\计算最长单词单词链
extern "C" CoreAPI int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);\\计算最长字母单词链
4.计算模块接口的设计与实现过程。
有3个类:
Core
,FindChain
,Init
。18个函数。Init
处理原始数据,命令行参数处理、单词切分等任务。FindChain
对Init
的数据进行深加工,抽象出边和图,进行相关的计算。Core
将两个类结合在一起,进行必要的调度。判断是否有环是通过是否存在拓扑序进行判断的,无环的最长路是用一个简单的spfa
,在处理有环的最长路是,为了提高效率使用邻接链表,并使用了一个剪枝:如果当前最长路已经将一个点的所有出边都覆盖到,那么这个点的最长路一定不会更优。
5.UML
6.计算模块接口部分的性能改进
在计算部分进行性能改进时,我们基本上就在对有环的情况进行优化,我们为了提高效率使用邻接链表,使用了一个剪枝:如果当前最长路已经将一个点的所有出边都覆盖到,那么这个点的最长路一定不会更优。
我们性能分析后发现主要性能还是消耗在dfs的递归上,因此主要优化目标还是对dfs进行剪枝。
7.Code Contrac的优缺点
- 优点 :能极大可能的避免异常崩溃的发生,让编写出来的代码更加安全可靠。同时也是一次对代码的复审。
- 缺点:很麻烦,增加代码量。
- 在结对编程中,由于是两个人一个看,一个写,这样的话代码质量本身就比较高了,就可以让这个写入Assert的过程变成人工检查。这样既保证了安全性,又加快了编码效率。
8.计算模块部分单元测试展示。
该部分代码测试的是GetWordChain_NoRing函数,即没环的情况。
TEST_METHOD(TestMethod8)
{
char* words[10] = { "abb","bc", "cd", "de", "drrrrr","rrrrr" };
char* result[105];
FindChain findchain;
findchain.BuildMap(words, 6, 1, 0, 0);
int len = findchain.GetWordChain_NoRing(result);
Assert::AreEqual(len, 5);
for (int i = 0; i < 3; i++)
{
int slen = strlen(result[i]);
Assert::IsTrue(result[i][slen - 1] == result[i + 1][0]);
}
}
以上为当自环在这个链的最后时的测试,用来检查自环是否被计算上。
TEST_METHOD(TestMethod7)
{
char* words[10] = { "aa","cc","bb", "dd", "ee", "rr" };
char* result[105];
FindChain findchain;
findchain.BuildMap(words, 6, 1, 'c', 0);
int len = findchain.GetWordChain_NoRing(result);
Assert::AreEqual(len, 1);
}
以上为对每个字母都是自环的情况进行考虑,虽然没有形成链但是一个单词也要输出链。
该部分代码测试的是GetWordChain_Ring函数,即有环的情况。
TEST_METHOD(TestMethod1)
{
char* words[10] = { "ab","bc","ca", "cd", "de", "dee" };
char* result[105];
FindChain findchain;
findchain.BuildMap(words, 6, 1, 0, 0);
int len = findchain.GetWordChain_Ring(result);
Assert::AreEqual(len, 5);
for (int i = 0; i < 4; i++)
{
int slen = strlen(result[i]);
Assert::IsTrue(result[i][slen - 1] == result[i + 1][0]);
}
}
测试覆盖率
9. 计算模块部分异常处理说明
- 不能识别的参数
TEST_METHOD(InitError5) { Core core; char* argv[10] = { "program", "w" }; try { Init* init = core.init_word(2, argv); Assert::Fail(); } catch (const char* msg) { Assert::AreEqual("Incorrect command line parameters!", msg); } }
对应可能忘记加上
-
等等场景。测试时保证一定会抛出异常,否则会fail
,并且异常信息正确。
- 同时输入了
-w
和-c
指令TEST_METHOD(InitError2) { Core core; char* argv[10] = { "program", "-w","a.txt","-c","a.txt" }; try { Init* init = core.init_word(5, argv); Assert::Fail(); } catch (const char* msg) { Assert::AreEqual("Command line arguments include both -w and -c", msg); } }
测试时同时输入w和c参数,保证一定会抛出异常,且异常信息正确。
- 没有输入
-w
和-c
指令TEST_METHOD(InitError3) { Core core; char* argv[10] = { "program" }; try { Init* init = core.init_word(1, argv); Assert::Fail(); } catch (const char* msg) { Assert::AreEqual("Command line parameters do not contain - w or -c", msg); } }
测试时没有输入w和c参数,保证一定会抛出异常,且异常信息正确。
-h
或-t
指令后参数格式错误TEST_METHOD(InitError4) { Core core; char* argv[10] = { "program", "-w", "a.txt", "-h", "0" }; try { Init* init = core.init_word(5, argv); Assert::Fail(); } catch (const char* msg) { Assert::AreEqual("The parameter should be a letter", msg); } }
测试时
-h
后面的格式错误,保证一定会抛出异常,且异常信息正确。
- 存在环路且没有
-r
指令TEST_METHOD(LoopError1) { HINSTANCE CoreDLL = LoadLibrary("Core.dll"); p_gen_chain_word gen_chain_word = (p_gen_chain_word)GetProcAddress(CoreDLL, "gen_chain_word"); Core core; char* words[10] = { "ab","bc","cd", "da", "abb", "bcc" }; char* result[105]; try { int len = gen_chain_word(words, 6, result, 0, 0, 0); Assert::Fail(); } catch (const char* msg) { Assert::AreEqual("There is a ring in the word list", msg); } }
测试时不输入
-r
,且单词中存在环路,保证一定会抛出异常,且异常信息正确。
- 缺失参数
TEST_METHOD(InitError1) { Core core; char* argv[10] = { "program", "-w" }; try { Init* init = core.init_word(2, argv); Assert::Fail(); } catch (const char* msg) { Assert::AreEqual("Missing parameters", msg); } }
测试时
-w
后未接文件路径,导致缺失参数,保证一定会抛出异常,且异常信息正确。
- 找不到文件
TEST_METHOD(BuildError1) { Core core; char text[1000]; char* argv[10] = { "program","-w","../WordlistProject/zzy.txt" }; Init* init = core.init_word(3, argv); try { core.read_file(init, text); Assert::Fail(); } catch (const char* msg) { Assert::AreEqual("File not found", msg); } }
构造一个不存在的文件,保证一定会抛出异常,且异常信息正确。
10.界面模块详细设计过程
界面模块的第一步是选择一个舒适的
gui
库,我们去github
上选择star
数最高的imgui
。简单的练习后可以上手了,但是发现缺少文件操作的函数,于是去issues
找到了需要的函数。ShowMainMenuBar(core, init); OpenButtonMonitor(core, init, inputText); SaveButtonMonitor(core, init, outputText); ImGui::Columns(3, "mixed"); ImGui::Text("Input"); ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags_AllowTabInput; ImGui::InputTextMultiline("##Input", inputText, IM_ARRAYSIZE(inputText), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 32), inputFlags); //ImGui::Separator(); ImGui::NextColumn(); ImGui::Text("Output"); ImGuiInputTextFlags outputFlags = ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_ReadOnly; ImGui::InputTextMultiline("##output", outputText, IM_ARRAYSIZE(outputText), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 32), outputFlags); ImGui::NextColumn(); FlagCheckBox(core, init, inputText, outputText);
主界面将划分为3列,分别为输入、输出和相关功能的按钮
static ImGuiFs::Dialog openDlg; const char* myPath = openDlg.chooseFileDialog(OpenButtonPressed); OpenButtonPressed = false; if (strlen(openDlg.getChosenPath()) > 0) { ImGui::Text("Open file: \"%s\"", openDlg.getChosenPath()); } else { ImGui::Text("Open file: \"%s\"", ""); } if (strlen(myPath) > 0) { FILE* fp = fopen(myPath, "r"); int i = 0; while ((text[i] = fgetc(fp)) != EOF && i < MAX_BYTES - 1) i++; text[i] = '\0'; fclose(fp); }
在菜单按钮上实现文件打开与导出,根据打开的文件路径,将文件内容读入到输入框中。导出功能类似,将输出框中的内容到处到所选的文件。
11.界面模块(GUI或命令行模块)与计算模块的对接
我们将计算模块打包成3个接口,然后GUI模块只需要对这三个接口进行调用即可。
extern "C" CoreAPI int build_map(char* words[], char* text); \\对单词进行拆分
extern "C" CoreAPI int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);\\计算最长单词单词链
extern "C" CoreAPI int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);\\计算最长字母单词链
计算功能
直接从文本框输入后计算,右边是可选选项。
文件功能
从导入文件处输入后计算。
与王冰小组耦合
我们与王冰小组进行了gui的耦合。
- 我们的gui和他们的计算模块耦合,没什么需要修改的地方,只需要调用他们计算模块的两个计算链的函数。
- 我们的计算模块和他们gui耦合,也没什么问题,直接调用接口就能计算。
- 这是与他们gui耦合的截图
- 王冰学号:16061155 谢静芬学号:16061093
12.描述结对的过程
我们坐到一个电脑前,坐在一个凳子上挤在一起,一个看一个写。
13. 结对编程与队友优缺点
结对编程确实能够提高代码的质量,但是有些事情还是分工做比较合适。因此在选择编程方法时我认为要因地制宜。另结对编程确实得找个大点的地方,寝室里挤着不是很舒服。
优点 | 缺点 | |
---|---|---|
张朝阳 | (1)对搜索算法提出了很多改进,思维活跃。(2)代码能力强。(3) 熟悉STL库 | 喜欢摸鱼 |
余宸狄 | (1)思维严谨,谋定后动(2)熟悉多种算法,更好地提出思路(3)喜欢使用基本类型,回归c本质 | 不够细心,是个瞎子 |
14.实际花费时间
见第2条