项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2023北航敏捷软工 |
这个作业的要求在哪里 | 结对项目-最长单词链 |
我在这个课程的目标是 | 学习软件工程理论,在实践中体会并运用软件工程理论,收获团队开发和软件工程实践经验 |
这个作业在哪个具体方面帮助我实现目标 | 学习和实践结对编程的编程方式 |
文章目录
1. 项目地址
- 教学班级:周四班
- 结对成员:@Arthuring(20373091) & @LYuanqiu(20373273)
- 项目地址:word-list
- GUI子项目地址:word-list-GUI
2. PSP 估计用时表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) |
---|---|---|
Planning | 计划 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 60 |
Development | 开发 | 1320 |
· Analysis | · 需求分析 (包括学习新技术) | 60 |
· Design Spec | · 生成设计文档 | 120 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 |
· Design | · 具体设计 | 240 |
· Coding | · 具体编码 | 360 |
· Code Review | · 代码复审 | 180 |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 |
Reporting | 报告 | 210 |
· Test Report | · 测试报告 | 120 |
· Size Measurement | · 计算工作量 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 |
合计 | 1590 |
3. 接口设计
Information Hiding
信息隐藏指的是,在设计模块时,尽可能让一个模块不对外暴露不必要的信息。
在我们的实现中,我们使用了权限控制,使类内部的信息尽量为 private, 从而尽量减少模块之间的耦合。
Interface Design
在面向对象课程中,提到了接口设计的六大原则。在我们的设计中,我们做到了:
- 单一职责原则:我们尽可能让每个类只完成一个功能,比如只完成无环-w,有环-w等等。
- 开闭原则:对代码添加新功能时,尽量不修改原有代码。例如新增异常处理时,单独新增异常处理函数。
- 里氏替换原则:子类只扩展父类功能,而不改变原有功能。
由于程序规模不大,因此我们并没有设计复杂的继承层次架构。
Loose Coupling
松耦合理论,是指模块尽量可以单独处理问题,减少对于其他模块的依赖性。
在我们的实现过程中,我们一开始就定好了后端计算模块和前端模块之间的接口,从而实现了前后端松耦合。
4. 接口实现
**计算模块接口的设计与实现过程。**设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。
我们的计算模块共有3个接口,分别是
int gen_chains_all(char *words[], int len, char *result[])
int gen_chain_word(char *words[], int len, char *result[], char headChar, char tailChar, char rejectChar, bool enable_loop)
int gen_chain_char(char *words[], int len, char *result[], char headChar, char tailChar, char rejectChar, bool enable_loop)
分别对应 -n
,-w
,-c
三个功能参数,函数的参数对应 -h
,-t
,-j
,-r
四个附加参数。
其中,对于功能参数,-w
,-c
之间较为类似,与 -n
,有较大差别。附加参数中,-r
会给算法带来较大变动,因此我们将接口实现分成三个主要部分,分别为:
-n
功能实现-w/-c
无环和附加参数组合功能实现-w/-c -r
有环和附加参数组合功能实现
对于 -n
指令,我们采用暴力搜索,遍历所有的可能,存储至一个 vector<vector<string>>
结果中。
对于 -w/-c
无环情况,根据每个点的入度求图的拓扑序,并按照拓扑序进行动态规划,求最长路径。
我们用inDegree记录每个点的入度,dp
记录动态规划的过程值,pre记录路径,转移方程如下:
//-w
if (i != front && edge[front][i]) {
if (1 + (edge[i][i] > 0) + dp[front] > dp[i]) {
dp[i] = 1 + (edge[i][i] > 0) + dp[front];
pre[i] = front;
}
}
//-c
if (i != front && edge[front][i]) {
if (edge[front][i] + edge[i][i] + dp[front] > dp[i]) {
dp[i] = edge[front][i] + edge[i][i] + dp[front];
pre[i] = front;
}
}
对于 -w/-c -r
,我们首先建立以26个字母为节点,单词为边的有向图,边的权值根据选项的不同设置为 1 或单词长度。然后使用 Tarjan 算法求出有向图中的强连通分量缩点,再使用动态规划求出最长路。
5. 无警告编译通过截图
展示在所在开发环境下编译器编译通过无警告的截图
6. UML
由于程序的规模问题,我们并没有设计复杂层次结构,而是 core
接口直接调用不同的计算任务模块。其中,N_Solver
,W_solver
,C_Solver
分别计算无环情况下的三大主要功能,由于计算单词数最大和字符数最大仅有边权值不同,具体算法可以通用,因此为了增加代码复用性,有环情况的 -w
,-c
及附加参数仅使用了 MaxWordWithRing
类中的同一个算法来完成。GUI 和 CLI 两个前端分别按需调用 core
接口中的函数,实现核心计算模块和其他模块之间的解耦。
7. 计算模块接口部分性能改进
接口性能测试结果如下
无环情况
可以看到,无环情况的主要开销并不在核心计算部分。
有环情况
有环情况使用了一个含有五个点的完全图测试,可以看到主要的时间开销都在 dfs_word
上。
对于有环情况的优化,我们使用了 Tarjan 算法缩点,然后使用缩点后的结果进行拓扑排序,再利用动态规划进行记忆化搜索。
对于带有 -h
,-j
,-t
的特殊情况,我们采用 bfs
先找出能被访问的点,不能被访问的点和边直接从图中删除,尽量减少图的规模。
8. Design by Contract/Code Contract
我认为契约式编程、代码契约有如下优缺点:
- 优点:前置条件和后置条件明确,在多人合作中,他人不用阅读代码细节,就可以知道使用某个模块时需要保证的前提条件,和其产生的影响,代码的解耦性好。
- 缺点:形式化的描述前置条件和后置条件等细节比较麻烦,有时会增加开发的时间成本。
在我们的编程中,core 接口就是一个很好的体现,我们要求传入已经解析好的单词,并在计算后将答案填入 result 对应的空间处。再比如我们要求建图后产生的图将单词去重等。
9. 计算模块部分单元测试展示
对于计算模块的单元测试,我们进行了各种不同参数组合的测试,核心计算模块测试覆盖率达到90%以上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uTSsNYi1-1679143712204)(.\assets\测试覆盖率.png)]
三个主要的功能测试函数如下:
void test_word(char* words[], int len, const char* ans[], int ans_len,
char headChar, char tailChar, char rejectChar, bool enable_loop) {
static char buffer[MAX_NUM][MAX_LENGTH];
memset(buffer, 0, sizeof(buffer));
char* result[MAX_LENGTH];
for (int i = 0; i < ans_len; i++) {
result[i] = buffer[i];
}
int my_len = Core::gen_chain_word(words, len, result, headChar, tailChar, rejectChar, enable_loop);
Assert::AreEqual(ans_len, my_len);
for (int i = 0; i < ans_len; i++) {
if (result != nullptr) {
Assert::AreEqual(strcmp(ans[i], result[i]), 0);
}
}
}
void test_char(char* words[], int len, const char* ans[], int ans_len,
char headChar, char tailChar, char rejectChar, bool enable_loop) {
static char buffer[MAX_NUM][MAX_LENGTH];
memset(buffer, 0, sizeof(buffer));
char* result[MAX_LENGTH];
for (int i = 0; i < ans_len; i++) {
result[i] = buffer[i];
}
int my_len = Core::gen_chain_char(words, len, result, headChar, tailChar, rejectChar, enable_loop);
Assert::AreEqual(ans_len, my_len);
for (int i = 0; i < ans_len; i++) {
if (result != nullptr) {
Assert::AreEqual(strcmp(ans[i], result[i]), 0);
}
}
}
void test_all (char* words[], int len, const char* ans[], int ans_len) {
static char buffer[MAX_NUM][MAX_LENGTH];
memset(buffer, 0, sizeof(buffer));
char* result[MAX_LENGTH];
for (int i = 0; i < ans_len; i++) {
result[i] = buffer[i];
}
int my_len = Core::gen_chains_all(words, len, result);
Assert::AreEqual(ans_len, my_len);
for (int i = 0; i < ans_len; i++) {
if (result != nullptr) {
Assert::AreEqual(strcmp(ans[i], result[i]), 0);
}
}
}
部分测试点如下:
-w
无环情况及参数组合
//W no ring
TEST_METHOD(test_w) {
char* words[] = { "algebra", "apple", "cool", "element", "ur", "fox", "dog", "cat", "leaf", "trick", "paaaam" };
const char* ans[] = { "algebra", "apple", "element", "trick" };
test_word(words, 11, ans, 4, 0, 0, 0, false);
}
TEST_METHOD(test_w_h) {
char* words[] = { "algebra", "apple", "cool", "element", "ur", "fox", "dog", "cat", "leaf", "trick", "pm" };
const char* ans[] = { "cool", "leaf", "fox" };
test_word(words, 11, ans, 3, 'c', 0, 0, false);
}
TEST_METHOD(test_w_t) {
char* words[] = { "algebra", "apple", "cool", "element", "ur", "fox", "dog", "cat", "leaf", "trick", "pm" };
const char* ans[] = { "cool", "leaf", "fox" };
test_word(words, 11, ans, 3, 0, 'x', 0, false);
}
TEST_METHOD(test_w_h_t) {
char* words[] = { "algebra", "apple", "cool", "element", "ur", "fox", "dog", "cat", "leaf", "trick", "pm", "zyx", "xuv" };
const char* ans[] = { "zyx", "xuv" };
test_word(words, 13, ans, 2, 'z', 'v', 0, false);
}
TEST_METHOD(test_w_j) {
char* words[] = { "algebra", "apple", "cool", "element", "ur", "fox", "dog", "cat", "leaf", "trick", "pm", "zyx", "xuv" };
const char* ans[] = { "cool","leaf","fox","xuv" };
test_word(words, 13, ans, 4, 0, 0, 'a', false);
}
-w -r
有环情况及其参数组合
//W ring
TEST_METHOD(test_w_r_r) {
char* words[] = { "element", "heaven", "table", "teach", "talk" };
const char* ans[] = { "table", "element", "teach", "heaven" };
test_word(words, 5, ans, 4, 0, 0, 0, true);
}
TEST_METHOD(test_w_r_h) {
char* words[] = { "element", "heaven", "table", "teach", "talk" };
const char* ans[] = { "element", "teach", "heaven" };
test_word(words, 5, ans, 3, 'e', 0, 0, true);
}
TEST_METHOD(test_w_r_t) {
char* words[] = { "element", "heaven", "table", "teach", "talk" };
const char* ans[] = { "table", "element", "teach" };
test_word(words, 5, ans, 3, 0, 'h', 0, true);
}
TEST_METHOD(test_w_r_h_t) {
char* words[] = { "element", "heaven", "table", "teach", "talk" };
const char* ans[] = { "element", "teach" };
test_word(words, 5, ans, 2, 'e', 'h', 0, true);
}
TEST_METHOD(test_w_r_j) {
char* words[] = { "element", "heaven", "table", "teach", "talk" };
const char* ans[] = {"teach", "heaven" };
test_word(words, 5, ans, 2, 0, 0, 'e', true);
}
选项 -c
,-n
测试思路类似,不过多赘述。
10. 异常处理部分说明
我们先根据不同的可能情况,定义了14种异常情况,并设计了对应的提示语句,随后根据需求从中选择使用。
#define UNKNOWN_OP (-1) // 未知选项,例如 -m
#define CONFLICT_OP (-2) // 参数选项冲突,例如 -n 和 -w
#define NOT_TXT (-4) // 用户输入的最后一个参数不是一个txt文件
#define OPEN_FAILED (-5) // 无法打开用户输入的txt文件或文件不存在
#define NO_WORDS (-6) // txt文件中无单词
#define H_NO_ALPHA (-9) // -h 选项缺少字母
#define T_NO_ALPHA (-10) // -t 选项缺少字母
#define J_NO_ALPHA (-11) // -j 选项缺少字母
#define H_LONG_ALPHA (-12) // -h 选项字母多于1
#define T_LONG_ALPHA (-13) // -t 选项字母多于1
#define J_LONG_ALPHA (-14) // -j 选项字母多于1
#define NO_CHAIN (-17) // 文件中没有单词链
#define HAS_RING (-18) // 在不允许出现环的情况下出现环路
#define LACK_COMMAND (-19) // 缺少功能参数
不同情况的提示语如下:
char* handleException(int error, const char* arg){
static string errMessage;
switch (error) {
case UNKNOWN_OP:
errMessage = "Missing functional parameters, option not found!";
break;
case CONFLICT_OP:
errMessage = "Conflict parameters!";
break;
case NOT_TXT:
errMessage = (string)arg + " not a txt file, or you may missing some arguments!";
break;
case NO_WORDS:
errMessage = "No words in "+(string)arg +"!";
break;
case H_NO_ALPHA:
errMessage = "Parameter '-h' needs a alpha!" ;
break;
case T_NO_ALPHA:
errMessage = "Parameter '-t' needs a alpha!" ;
break;
case J_NO_ALPHA:
errMessage = "Parameter '-j' needs a alpha!" ;
break;
case H_LONG_ALPHA:
errMessage = "Parameter '-h' only needs one alpha!" ;
break;
case T_LONG_ALPHA:
errMessage = "Parameter '-t' only needs one alpha!" ;
break;
case J_LONG_ALPHA:
errMessage = "Parameter '-j' only needs one alpha!" ;
break;
case NO_CHAIN:
errMessage = "There is no chain in the file!";
break;
case HAS_RING:
errMessage = "There are rings in the file!";
break;
case LACK_COMMAND:
errMessage = "Lack functional Command!";
default:
errMessage = "Unexpected error!" ;
break;
}
return &errMessage[0];
}
对于不同的异常情景,我们设计了以下测试点
- 出现未知参数
TEST_METHOD(unkonwn_option) {
init();
char* args[] = { "Wordlist.exe", "-m", "test.txt" };
int ret = main_serve(3, args);
Assert::AreEqual(UNKNOWN_OP, ret);
}
- 输入的功能性参数冲突
TEST_METHOD(conflict_option) {
init();
char* args[] = { "Wordlist.exe", "-n", "-c", "test.txt" };
int ret = main_serve(4, args);
Assert::AreEqual(CONFLICT_OP, ret);
}
- 无法打开或不存在用户输入的文件
TEST_METHOD(no_such_file) {
init();
char* args[] = { "Wordlist.exe", "-n", "no.txt" };
int ret = main_serve(3, args);
Assert::AreEqual(OPEN_FAILED, ret);
}
- 文件中没有单词
TEST_METHOD(no_words_file) {
init();
char* args[] = { "Wordlist.exe", "-n", "no_words.txt" };
ofstream output;
output.open("no_words.txt", ios::out | ios::binary | ios::trunc);
output.close();
int ret = main_serve(3, args);
Assert::AreEqual(NO_WORDS, ret);
}
- 用户的最后一个参数不是txt文件
TEST_METHOD(missing_filename) {
init();
char* args[] = { "Wordlist.exe", "-n" };
int ret = main_serve(2, args);
Assert::AreEqual(NOT_TXT, ret);
}
-h
参数后未指定字母
TEST_METHOD(h_no_alpha) {
init();
char* args[] = { "Wordlist.exe", "-w", "-h", "test.txt" };
int ret = main_serve(4, args);
Assert::AreEqual(H_NO_ALPHA, ret);
}
-t
参数后未指定字母
TEST_METHOD(t_no_alpha) {
init();
char* args[] = { "Wordlist.exe","-w","-t", "test.txt" };
int ret = main_serve(4, args);
Assert::AreEqual(T_NO_ALPHA, ret);
}
-j
参数后未指定字母
TEST_METHOD(j_no_alpha) {
init();
char* args[] = { "Wordlist.exe", "-w", "-j","test.txt" };
int ret = main_serve(4, args);
Assert::AreEqual(J_NO_ALPHA, ret);
}
- 完全没有参数
TEST_METHOD(lack_arguments) {
init();
char* args[] = { "Wordlist.exe" };
int ret = main_serve(1, args);
Assert::AreEqual(NOT_TXT, ret);
}
-h
参数后多于一个字母
TEST_METHOD(h_long_alpha) {
init();
char* args[] = { "Wordlist.exe", "-w", "-h", "ab","test.txt" };
int ret = main_serve(5, args);
Assert::AreEqual(H_LONG_ALPHA, ret);
}
-t
参数后多于一个字母
TEST_METHOD(t_long_alpha) {
init();
char* args[] = { "Wordlist.exe", "-w", "-t", "ab","test.txt" };
int ret = main_serve(5, args);
Assert::AreEqual(T_LONG_ALPHA, ret);
}
-j
参数后多于一个字母
TEST_METHOD(j_long_alpha) {
init();
char* args[] = { "Wordlist.exe", "-w", "-j", "ab","test.txt" };
int ret = main_serve(5, args);
Assert::AreEqual(J_LONG_ALPHA, ret);
}
- 缺少功能性参数
TEST_METHOD(lack_command) {
init();
char* args[] = { "Wordlist.exe", "test.txt" };
int ret = main_serve(2, args);
Assert::AreEqual(LACK_COMMAND, ret);
}
-n
情况下没有单词链(其他功能参数类似,不重复展示)
TEST_METHOD(no_chain_n) {
init();
try {
ofstream output;
output.open("no_chain.txt", ios::out | ios::binary | ios::trunc);
output << "aaa" << endl;
output << "bbb" << endl;
output.close();
char* args[] = { "Wordlist.exe", "-n", "no_chain.txt" };
main_serve(3, args);
}
catch (runtime_error const& e) {
Assert::AreEqual(0, strcmp("There is no chain in the file!", e.what()));
return;
}
Assert::Fail();
}
-n
情况下存在环(其他功能参数类似,不重复展示)
TEST_METHOD(has_ring_n) {
init();
try {
ofstream output;
output.open("has_ring.txt", ios::out | ios::binary | ios::trunc);
output << "aaa" << endl;
output << "aabb" << endl;
output << "bbb" << endl;
output << "bbaa" << endl;
output.close();
char* args[] = { "Wordlist.exe", "-n", "has_ring.txt" };
main_serve(3, args);
}
catch (runtime_error const& e) {
Assert::AreEqual(0, strcmp("There are rings in the file!", e.what()));
return;
}
Assert::Fail();
}
- 存在
-j
的同时存在单词环,-j
排除掉单词后无环
TEST_METHOD(has_ring_c_j) {
init();
try {
ofstream output;
output.open("has_ring.txt", ios::out | ios::binary | ios::trunc);
output << "aaa" << endl;
output << "aabb" << endl;
output << "bbb" << endl;
output << "bbaa" << endl;
output.close();
char* args[] = { "Wordlist.exe", "-c", "-j", "b","has_ring.txt" };
main_serve(5, args);
}
catch (runtime_error const& e) {
Assert::AreEqual(0, strcmp("There are rings in the file!", e.what()));
return;
}
Assert::Fail();
}
11. 界面设计
我们的GUI是用Python的PyQt5实现的图形化界面,最终根据题目要求实现了指令选择以及输入单词、文件导入单词以及导出结果、异常提示以及运行时间显示的功能等。
实现过程分为以下几步:
-
功能规划
根据题目要求,具体地总结出需要实现指令选择以及输入单词、文件导入单词以及导出结果、异常提示以及运行时间显示的功能,并计划将GUI模块分为UI设计与功能函数的实现两阶段去完成。
UI设计与实现 -
UI设计与实现
-
布局
MainWindow
的最基本的布局是一个QWidget:centralwidget
;并用类QVBoxLayout
和类QHBoxLayout
来互相嵌套组合设置大小来排列各个局部QWidget
的布局,以实现能适应窗口大小改变的布局。UI布局代码实现封装至Ui_MainWindow
类中的setupUI
方法中。其中,部分示例如下:self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSpacing(6) self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.addWidget(self.radioButton) self.horizontalLayout.addWidget(self.radioButton_7) self.horizontalLayout.addWidget(self.radioButton_3) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout_4.addLayout(self.verticalLayout) self.textEdit_4 = QtWidgets.QTextEdit(self.centralwidget)
-
各组件的属性设置与文字描述
UI中设置相应提示语文字以及属性设置的代码实现封装至retranslateUi
函数中-
组件主要是需要显示的文字
self.radioButton.setText(_translate("MainWindow", "所有单词链"))
-
输入框以及结果显示框的提示语
self.textEdit_2.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n""<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n""p, li { white-space: pre-wrap; }\n""</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n""<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px;margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p></body></html>"))
-
字体与行距和设置
font = QtGui.QFont() font.setPointSize(7) self.radioButton_2.setFont(font)
- 边框设置
self.textEdit_4.setStyleSheet("QTextEdit{\n""border-color:rgba(0, 0, 0, 0);\n""background-color:rgba(255, 255, 0, 0);\n""\n""}")
- 各组件大小的设计
self.lineEdit_3.setMaximumSize(QtCore.QSize(20, 16777215))
- 功能函数的设计实现与链接
具体实现与代码解读见下一节 - 与计算模块的对接
具体实现与代码解读见下一节
-
12. 界面模块与计算模块对接互换
UI模块的设计
- UI设计
利用QtDesigner
根据功能规划设计好对应的 UI 界面,其中 UI 界面的实现主要分为两部分——布局以及属性设置。UI布局代码实现封装至Ui_MainWindow
类中的setupUI
方法中,UI中设置相应提示语文字以及属性设置的代码实现封装至retranslateUi
函数中,UI效果如图所示:
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(531, 374)
...
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.textEdit.setMarkdown(_translate("MainWindow", "功能参数选择: ))
...
- 功能实现:
我们的功能划分为在GUI前端界面实现输入和展示结果、从文件中导入文字、导出至文件以及命令行命令的分析以及异常处理提醒的功能,并在GUI中调用计算模块运算结果以及判断输入内容是否存在异常。
我们将对应的功能封装至input_word
、input_txt
、saveFile
、dealTxt
函数中来具体实现对应功能并链接到相应 pushButton 中。其中,与 pyqt 相关的主要处理函数包括打开和保存文件相关的实现以及报错提示框。
-
链接函数示例
self.pushButton_2.clicked.connect(lambda :self.input_txt()) self.pushButton_3.clicked.connect(lambda :self.saveFile()) self.pushButton.clicked.connect(lambda :self.input_word())
-
报错示例
box = QMessageBox() box.setWindowTitle("错误") box.setText("功能性参数缺省!!!") box.exec()
-
保存文件示例
def saveFile(self): filename, ok2 = QFileDialog.getSaveFileName(None, "文件保存", "./", "AllFiles(*);;Text Files (*.txt)") try: with open(filename, "w") as f: f.write(self.textEdit_3.toPlainText())
-
打开文件示例
directory1, ok1 = QtWidgets.QFileDialog.getOpenFileName(None, "选取文件夹", "./") # 起始路径 import os filename = directory1 if os.path.exists(filename): try: with open(filename, 'r') as file: # 在此处对文件进行操作,例如读取文件内容 content = file.read() self.textEdit_2.setText(content) except IOError: box = QMessageBox() box.setWindowTitle("错误") box.setText(f"无法打开{filename}") box.exec() else: box = QMessageBox() box.setWindowTitle("错误") box.setText(f"{filename}不存在") box.exec()
模块对接
-
将计算模块的函数打包好,接口最终定义如下:
#ifdef IMPORT_DLL #else #define IMPORT_DLL extern "C" _declspec(dllimport) //指的是允许将其给外部调用 #endif IMPORT_DLL int gen_chains_all(char* words[], int len, char* result[]); IMPORT_DLL int gen_chain_word(char* words[], int len, char* result[], char headChar, char tailChar, char rejectChar, bool enable_loop); IMPORT_DLL int gen_chain_char(char* words[], int len, char* result[], char headChar, char tailChar, char rejectChar, bool enable_loop);
-
在python文件中调用动态链接库的core.dll中的函数:
dll = WinDLL("./core") if self.radioButton.isChecked(): dll.gen_chains_all.restype = ctypes.c_int a = dll.gen_chains_all(words, len(word), ans)
-
由于python与c++中对于变量的定义方式与存储方式是不同的,我们还需要处理一下接口传递的参数,用ctypes的参数来定义传入参数以及处理结果:
dll.gen_chains_all.restype = ctypes.c_int words = (ctypes.c_char_p * 10000)() ans = (ctypes.c_char_p * 10000)() for i in range(len(word)): words[i] = word[i].encode('utf-8')
功能截图
- 参数选择
- 输入文字或者读取文件
- 输出结果并显示运行时间
- 导出结果
- 异常提示
松耦合模块交换
在博客中指明合作小组两位同学的学号,分析两组不同的模块合并之后出现的问题,为何会出现这样的问题,以及是如何根据反馈改进自己模块的。
合作小组:
- 陈正昊 20373379
- 温家昊 20373668
模块合并的问题是我们的GUI原本代码对接他们的core.dll时会报如下错误:
File "E:\GUI\untitled.py", line 278, in input_word
dll = cdll.LoadLibrary(".core_7")
File "C:\Users\Lenovo\AppData\Local\Programs\Python\Python39\lib\ctypes\__init__.py", line 452, in LoadLibrary
return self._dlltype(name)
File "C:\Users\Lenovo\AppData\Local\Programs\Python\Python39\lib\ctypes\__init__.py", line 374, in __init__
self._handle = _dlopen(self._name, mode)
FileNotFoundError: Could not find module '.core' (or one of its dependencies). Try using the full path with constructor syntax.
原因:
-
打包的编译器不同
-
cdll
主要用来加载C语言调用方式(cdecl),windll
主要用来加载WIN32调用方式(stdcall)
解决方式:
- 让对方组用用MSVC编译
- 修改原有的调用方式
dll = cdll.LoadLibrary("./core")
为dll = WinDLL("./core")
13. 结对过程
我们结对编程的过程主要以线下结对为主,腾讯会议作为辅助。
在结对编程过程中,我们首先各自阅读了项目要求,然后交流了各自的理解。随后我们开始整体架构设计,在整体架构设计中,我们线下采用一台电脑,一个键盘进行结对编程。每到一部分任务的结点,我们交换审核和编码角色。大致1小时交换一次。
在完成了整体框架之后,我们将核心计算实现部分分成了几个独立的部分,这时我们采用分别实现,通过腾讯会议连线整合的形式加快进度。最后调试 debug
时,继续采用了线下结对编程的方式。
以下是我们在宿舍结对编程时的照片
14. 结对编程优缺点
结对编程的优点:时间利用效率高,有效防止摸鱼情况。充分发挥两人长处。及时发现编码时由于粗心产生的错误。
结对编程的缺点:有些任务不必要两人同时开发,分别开发可以加快进度。在忙碌的学期中寻找共同时间和地点较为不易。
本次结对的成员为 @LYuanqiu 和 @Arthuring,以下是两人结对的优缺点。
@LYuanqiu | @Arthuring | |
---|---|---|
优点 | 性格很合得来 | 对代码风格要求略严 |
不拖延 | 不拖延 | |
对 GUI 编写比较熟悉 | 比较熟悉测试构造 | |
缺点 | 对 C++ 不太熟 | 不太懂算法,对 C++ 不太熟,不太懂 GUI |
15. PSP实际用时表
在你实现完程序之后,在附录提供的PSP表格记录下你在程序的各个模块上实际花费的时间。
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 30 |
Development | 开发 | 1320 | 1350 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 80 |
· Design Spec | · 生成设计文档 | 120 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
· Design | · 具体设计 | 240 | 180 |
· Coding | · 具体编码 | 360 | 540 |
· Code Review | · 代码复审 | 180 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | 360 |
Reporting | 报告 | 210 | 270 |
· Test Report | · 测试报告 | 120 | 180 |
· Size Measurement | · 计算工作量 | 30 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 80 |
合计 | 1590 | 1650 |