项目 内容
这个作业属于哪个课程 2023北航敏捷软工
这个作业的要求在哪里 结对项目-最长单词链
我在这个课程的目标是 学习软件工程理论,在实践中体会并运用软件工程理论,收获团队开发和软件工程实践经验
这个作业在哪个具体方面帮助我实现目标 学习和实践结对编程的编程方式

项目地址

  • 教学班级:周四班
  • 结对成员:@Arthuring(20373091) & @LYuanqiu(20373273)
  • 项目地址:https://github.com/LYuanqiu/2023SE_word-list
  • GUI子项目地址:https://github.com/LYuanqiu/wordlist_GUI

PSP开发时间估计与实际记录

在开始实现程序之前,在下述 PSP 表格记录下你估计将在程序的 各个模块的开发上耗费的时间

PSP 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

设计

看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。

Information Hiding:信息隐藏在面对对象的设计中十分重要,要求每个类保管其内部的数 据,并对外提供相应的接口,这样数据更加安全、解耦的更加完全。在本次作业中,我们将数据封装起来,为每个功能型参数构造一个单独的类来储存相关属性以及运算结果的方法,只对外提供构建类和解决方法的的接口,满足Information Hiding的设计要求。

Interface Design:接口设计对于日常开发的规范十分重要,好的接口设计可以言简意赅地表述清楚其作用,并实现相关内容。在涉及到多人合作的部分时,接口设计对于不同部分的合并也起着至关重要的作用。尤其是在本次作业中还有对于松耦合的测试要求,即需要跟其他组进行交换,我们的接口设计在最一开始就确定好了。当时还没有确定好具体的算法,接口设计的原则是根据实现这一功能所需的信息而确定。最终确定如下接口:

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)
//计算最多单词数量的最长单词链,其中前三个参数已经在上文进行了说明,`head`和`tail`分别为单词链首字母与尾字母约束(如果传入0,表示没有约束),`reject`为单词链中单词不允许出现的首字母约束,当`enable_loop`为`true`时表示允许输入单词文本中隐含“单词环”int gen_chain_char(char* words[], int len, char* result[], char head, char tail, char reject, bool enable_loop)
//计算最多字母数量的最长单词链,参数意义同`gen_chain_word`

Loose Coupling:松耦合可以使一个类与另外一个类隔开,它们之间只是通过消息来联系,我们根据不同的功能将整个程序划分为了不同的模块分别实现。最终,我们的CLI和GUI主程序与Core都是松耦合的,分别负责命令行指令、图形化界面以及对数据进行运算的功能。

计算模块的设计与实现

计算模块接口的设计与实现过程。 设计包括代码如何组织,比如会有几个类,几个函数,他们之间关系如何,关键函数是否需要画出流程图?说明你的算法的关键(不必列出源代码),以及独到之处。

接口设计

计算模块的接口设计见下:

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);
代码组织

在Core类中进行读入的单词数组的预处理,如判断是否有环、去除重复数组、生成边权图等,同时对于-j指令,在Core类中进行首字母去除。同时我们对每一种功能型参数设计一个类,N_Solver,W_Solver,C_Solver,对于成环的情况,我们也设计了一个类MaxWordWithRing。在Core类中对单词进行预处理后,根据命令行指令创建相应的类,调用类内的计算函数,返回一个vector<string>结果,并在Core类中处理存储至char **result中。

算法

  1. 对于-n指令,我们采用暴力搜索,遍历所有的可能,存储至一个vector<vector<string>>结果中。

  2. -w:
    根据每个点的入度求图的拓扑序,并按照拓扑序进行动态规划,求最长路径。
    我们用inDegree记录每个点的入度,dp记录动态规划的过程值,pre记录路径,转移方程如下:

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

    对于指定-h的情况,我们修改了求inDegree的方法,并在求取路径的过程中只访问与与指定字母相连的其他点,并将指定的首字母的入度置为0。
    对于指定-t的情况,我们在DP结束后,只判断合法字母的对应点的DP值是否符合要求;当没有指定尾字母时,我们取所有点中DP值最大的点。

  3. -c:运算过程同-w,但转移方程改变:

    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;}
    }
    
  4. 对于 -w/-c -r,我们首先建立以26个字母为节点,单词为边的有向图,边的权值根据选项的不同设置为

编译无警告

展示在所在开发环境下编译器编译通过无警告的截图

UML展示实体关系

阅读有关 UML 的内容,画出 UML 图显示计算模块部分各个实体之间的关系(画一个图即可)。

性能改进

计算模块接口部分的性能改进。 记录在改进计算模块性能上所花费的时间,并展示你程序中消耗最大的函数,陈述你的性能改进策略。

接口性能测试结果如下

无环情况


可以看到,无环情况的主要开销并不在核心计算部分。

有环情况


有环情况使用了一个含有五个点的完全图测试,可以看到主要的时间开销都在 dfs_word 上。
对于有环情况的优化,我们使用了 Tarjan 算法缩点,然后使用缩点后的结果进行拓扑排序,再利用动态规划进行记忆化搜索。
对于带有 -h-j-t 的特殊情况,我们采用 bfs 先找出能被访问的点,不能被访问的点和边直接从图中删除,尽量减少图的规模。

Design by Contract,Code Contract

阅读 Design by Contract,Code Contract 的内容,并描述这些做法的优缺点,说明你是如何把它们融入结对作业中的。

Design by Contract,即契约式设计。其中用到的涉及到规则约束和编译检查相关的部分被称为Code Contract
每个类或者实体方法都需要满足某种前置条件,后置条件和不变式以及其他的状态约束条件,这对于代码的状态有了一个很好的描述:

  • 期望所有调用它的客户模块都保证一定的进入条件:这就是函数的先验条件—客户的义务和供应 商的权利,这样它就不用去处理不满足先验条件的情况。
  • 保证退出时给出特定的属性:这就是函数的后验条件—供应商的义务,显然也是客户的权利。
  • 在进入时假定,并在退出时保持一些特定的属性:不变条件。

在调用某方法的前后都需要进行检查,且检查的任务分别归属于调用者与被调用者,在调用结束后还需要检验不变条件。在本次作业中,我们的core类在调用不同的单词链运算类前,需要由调用者预先处理单词,例如检查是否有环,去除重复单词,将单词转换为小写,将单词串转化成图等;而在调用结束后,需要返回计算结果的长度,并提供结果对应的单词链以便进行输出;此处的不变条件则为传入的字符串,字符串是不可变的,单词链中使用的必须是未修改,未拆分的单词。

单元测试

计算模块部分单元测试展示。展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路。并将单元测试得到的测试覆盖率截图,发表在博客中。要求总体覆盖率到 90% 以上,否则单元测试部分视作无效。

对于计算模块的单元测试,我们进行了各种不同参数组合的测试,核心计算模块测试覆盖率达到90%以上。

三个主要的功能测试函数如下:

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 ringTEST_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 测试思路类似,不过多赘述。

异常处理

计算模块部分异常处理说明。计算模块部分异常处理说明。 在博客中详细介绍每种异常的设计目标。每种异常都要选择一个单元测试样例发布在博客中,并指明错误对应的场景。

我们先根据不同的可能情况,定义了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();}

界面模块的设计

在博客中详细介绍界面模块是如何设计的,并写一些必要的代码说明解释实现过程。

我们的GUI是用Python的PyQt5实现的图形化界面,最终根据题目要求实现了指令选择以及输入单词、文件导入单词以及导出结果、异常提示以及运行时间显示的功能等。
实现过程分为以下几步:

  1. 功能规划
    根据题目要求,具体地总结出需要实现指令选择以及输入单词、文件导入单词以及导出结果、异常提示以及运行时间显示的功能,并计划将GUI模块分为UI设计与功能函数的实现两阶段去完成。
  2. UI设计与实现
  • 布局
    MainWindow的最基本的布局是一个QWidgetcentralwidget;并用类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))
      
  1. 功能函数的设计实现与链接
    具体实现与代码解读见下一节
  2. 与计算模块的对接
    具体实现与代码解读见下一节

界面模块与计算模块的对接

详细地描述 UI 模块的设计与两个模块的对接,并在博客中截图实现的功能。

UI模块的设计

  1. UI设计
    利用QtDesigner根据功能规划设计好对应的UI界面,其中UI界面的实现主要分为两部分——布局以及属性设置。UI布局代码实现封装至Ui_MainWindow类中的setupUI方法中,UI中设置相应提示语文字以及属性设置的代码实现封装至retranslateUi函数中,UI效果如图所示:
    def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(531, 374)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)self.verticalLayout_2.setObjectName("verticalLayout_2")self.verticalLayout_5 = QtWidgets.QVBoxLayout()self.verticalLayout_5.setObjectName("verticalLayout_5")self.verticalLayout_4 = QtWidgets.QVBoxLayout()self.verticalLayout_4.setObjectName("verticalLayout_4")self.textEdit = QtWidgets.QTextEdit(self.centralwidget)self.textEdit.setEnabled(True)self.textEdit.setMinimumSize(QtCore.QSize(0, 32))self.textEdit.setMaximumSize(QtCore.QSize(16777215, 32))self.textEdit.setMouseTracking(True)self.textEdit.setAcceptDrops(True)self.textEdit.setStyleSheet("QTextEdit{\n"
"border-color:rgba(0, 0, 0, 0);\n"
"background-color:rgba(255, 255, 0, 0);\n"
"\n"
"}")self.textEdit.setUndoRedoEnabled(False)self.textEdit.setReadOnly(True)self.textEdit.setOverwriteMode(False)self.textEdit.setAcceptRichText(False)self.textEdit.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)self.textEdit.setObjectName("textEdit")self.verticalLayout_4.addWidget(self.textEdit)self.verticalLayout = QtWidgets.QVBoxLayout()self.verticalLayout.setObjectName("verticalLayout")self.horizontalLayout = QtWidgets.QHBoxLayout()self.horizontalLayout.setSpacing(6)self.horizontalLayout.setObjectName("horizontalLayout")self.radioButton = QtWidgets.QRadioButton(self.centralwidget)self.radioButton.setObjectName("radioButton")self.horizontalLayout.addWidget(self.radioButton)self.radioButton_7 = QtWidgets.QRadioButton(self.centralwidget)self.radioButton_7.setObjectName("radioButton_7")self.horizontalLayout.addWidget(self.radioButton_7)self.radioButton_3 = QtWidgets.QRadioButton(self.centralwidget)self.radioButton_3.setObjectName("radioButton_3")self.horizontalLayout.addWidget(self.radioButton_3)self.verticalLayout.addLayout(self.horizontalLayout)self.verticalLayout_4.addLayout(self.verticalLayout)self.line_3 = QtWidgets.QFrame(self.centralwidget)self.line_3.setFrameShape(QtWidgets.QFrame.HLine)self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken)self.line_3.setObjectName("line_3")self.verticalLayout_4.addWidget(self.line_3)self.textEdit_4 = QtWidgets.QTextEdit(self.centralwidget)self.textEdit_4.setEnabled(True)self.textEdit_4.setMaximumSize(QtCore.QSize(16777215, 32))self.textEdit_4.setAcceptDrops(True)self.textEdit_4.setStyleSheet("QTextEdit{\n"
"border-color:rgba(0, 0, 0, 0);\n"
"background-color:rgba(255, 255, 0, 0);\n"
"\n"
"}")self.textEdit_4.setUndoRedoEnabled(False)self.textEdit_4.setReadOnly(True)self.textEdit_4.setAcceptRichText(True)self.textEdit_4.setTextInteractionFlags(QtCore.Qt.NoTextInteraction)self.textEdit_4.setObjectName("textEdit_4")self.verticalLayout_4.addWidget(self.textEdit_4)self.line = QtWidgets.QFrame(self.centralwidget)self.line.setFrameShape(QtWidgets.QFrame.VLine)self.line.setFrameShadow(QtWidgets.QFrame.Sunken)self.line.setObjectName("line")self.verticalLayout_4.addWidget(self.line)self.horizontalLayout_5 = QtWidgets.QHBoxLayout()self.horizontalLayout_5.setObjectName("horizontalLayout_5")self.checkBox = QtWidgets.QCheckBox(self.centralwidget)self.checkBox.setMaximumSize(QtCore.QSize(140, 16777215))font = QtGui.QFont()font.setPointSize(7)self.checkBox.setFont(font)self.checkBox.setObjectName("checkBox")self.horizontalLayout_5.addWidget(self.checkBox)self.radioButton_5 = QtWidgets.QCheckBox(self.centralwidget)self.radioButton_5.setMaximumSize(QtCore.QSize(140, 40))font = QtGui.QFont()font.setPointSize(7)self.radioButton_5.setFont(font)self.radioButton_5.setObjectName("radioButton_5")self.horizontalLayout_5.addWidget(self.radioButton_5)self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit.setMaximumSize(QtCore.QSize(20, 16777215))font = QtGui.QFont()font.setPointSize(7)self.lineEdit.setFont(font)self.lineEdit.setObjectName("lineEdit")self.horizontalLayout_5.addWidget(self.lineEdit)self.radioButton_6 = QtWidgets.QCheckBox(self.centralwidget)self.radioButton_6.setMaximumSize(QtCore.QSize(140, 16777215))font = QtGui.QFont()font.setPointSize(7)self.radioButton_6.setFont(font)self.radioButton_6.setObjectName("radioButton_6")self.horizontalLayout_5.addWidget(self.radioButton_6)self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit_2.setMaximumSize(QtCore.QSize(20, 16777215))font = QtGui.QFont()font.setPointSize(7)self.lineEdit_2.setFont(font)self.lineEdit_2.setObjectName("lineEdit_2")self.horizontalLayout_5.addWidget(self.lineEdit_2)self.radioButton_2 = QtWidgets.QCheckBox(self.centralwidget)self.radioButton_2.setMaximumSize(QtCore.QSize(140, 16777215))font = QtGui.QFont()font.setPointSize(7)self.radioButton_2.setFont(font)self.radioButton_2.setObjectName("radioButton_2")self.horizontalLayout_5.addWidget(self.radioButton_2)self.lineEdit_3 = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit_3.setMaximumSize(QtCore.QSize(20, 16777215))font = QtGui.QFont()font.setPointSize(7)self.lineEdit_3.setFont(font)self.lineEdit_3.setObjectName("lineEdit_3")self.horizontalLayout_5.addWidget(self.lineEdit_3)self.verticalLayout_4.addLayout(self.horizontalLayout_5)self.verticalLayout_5.addLayout(self.verticalLayout_4)self.line_2 = QtWidgets.QFrame(self.centralwidget)self.line_2.setStyleSheet("background-colcor:rgb(255,255,255);")self.line_2.setFrameShape(QtWidgets.QFrame.VLine)self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)self.line_2.setObjectName("line_2")self.verticalLayout_5.addWidget(self.line_2)self.verticalLayout_2.addLayout(self.verticalLayout_5)self.verticalLayout_3 = QtWidgets.QVBoxLayout()self.verticalLayout_3.setObjectName("verticalLayout_3")self.textEdit_2 = QtWidgets.QTextEdit(self.centralwidget)self.textEdit_2.setInputMethodHints(QtCore.Qt.ImhHiddenText)self.textEdit_2.setObjectName("textEdit_2")self.verticalLayout_3.addWidget(self.textEdit_2)self.horizontalLayout_2 = QtWidgets.QHBoxLayout()self.horizontalLayout_2.setObjectName("horizontalLayout_2")self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)self.pushButton_2.setObjectName("pushButton_2")self.horizontalLayout_2.addWidget(self.pushButton_2)self.verticalLayout_3.addLayout(self.horizontalLayout_2)self.textEdit_3 = QtWidgets.QTextEdit(self.centralwidget)self.textEdit_3.setReadOnly(True)self.textEdit_3.setObjectName("textEdit_3")self.verticalLayout_3.addWidget(self.textEdit_3)self.verticalLayout_2.addLayout(self.verticalLayout_3)self.horizontalLayout_3 = QtWidgets.QHBoxLayout()self.horizontalLayout_3.setObjectName("horizontalLayout_3")self.pushButton = QtWidgets.QPushButton(self.centralwidget)self.pushButton.setObjectName("pushButton")self.horizontalLayout_3.addWidget(self.pushButton)self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)self.pushButton_3.setObjectName("pushButton_3")self.horizontalLayout_3.addWidget(self.pushButton_3)self.verticalLayout_2.addLayout(self.horizontalLayout_3)MainWindow.setCentralWidget(self.centralwidget)self.menubar = QtWidgets.QMenuBar(MainWindow)self.menubar.setGeometry(QtCore.QRect(0, 0, 531, 22))self.menubar.setObjectName("menubar")MainWindow.setMenuBar(self.menubar)self.statusbar = QtWidgets.QStatusBar(MainWindow)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)MainWindow.setTabOrder(self.radioButton_3, self.radioButton_7)MainWindow.setTabOrder(self.radioButton_7, self.pushButton_2)
    def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))self.textEdit.setMarkdown(_translate("MainWindow", "功能参数选择:\n"
"\n"
""))self.textEdit.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=\" margin-top:6px; margin-bottom:6px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">功能参数选择:</p></body></html>"))self.radioButton.setText(_translate("MainWindow", "所有单词链"))self.radioButton_7.setText(_translate("MainWindow", "最多字母数"))self.radioButton_3.setText(_translate("MainWindow", "最多单词数"))self.textEdit_4.setMarkdown(_translate("MainWindow", "附加型参数选择:\n"
"\n"
""))self.textEdit_4.setPlaceholderText(_translate("MainWindow", "附加型参数"))self.checkBox.setText(_translate("MainWindow", "是否允许单词环"))self.radioButton_5.setText(_translate("MainWindow", "禁止的单词首字母"))self.radioButton_6.setText(_translate("MainWindow", "指定单词链首字母"))self.radioButton_2.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>"))self.textEdit_2.setPlaceholderText(_translate("MainWindow", "输入想要分析的文本..."))self.pushButton_2.setText(_translate("MainWindow", "从文件中导入文本"))self.pushButton_2.clicked.connect(lambda :self.input_txt())self.textEdit_3.setPlaceholderText(_translate("MainWindow", "分析所得长度以及对应单词链..."))self.pushButton.setText(_translate("MainWindow", "开始分析"))self.pushButton_3.setText(_translate("MainWindow", "导出结果"))self.pushButton_3.clicked.connect(lambda :self.saveFile())self.pushButton.clicked.connect(lambda :self.input_word())
  1. 功能实现:
    我们的功能划分为在GUI前端界面实现输入和展示结果、从文件中导入文字、导出至文件以及命令行命令的分析以及异常处理提醒的功能,并在GUI中调用计算模块运算结果以及判断输入内容是否存在异常。
    我们将对应的功能封装至input_wordinput_txtsaveFiledealTxt函数中来具体实现对应功能并链接到相应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 osfilename = directory1if 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()
      

模块对接

  1. 将计算模块的函数打包好,接口最终定义如下:

    #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);
    
  2. 在python文件中调用动态链接库的core.dll中的函数:

    dll = WinDLL("./core")
    if self.radioButton.isChecked():dll.gen_chains_all.restype = ctypes.c_inta = dll.gen_chains_all(words, len(word), ans)
    
  3. 由于python与c++中对于变量的定义方式与存储方式是不同的,我们还需要处理一下接口传递的参数,用ctypes的参数来定义传入参数以及处理结果:

    dll.gen_chains_all.restype = ctypes.c_intwords = (ctypes.c_char_p * 10000)()
    ans = (ctypes.c_char_p * 10000)()for i in range(len(word)):words[i] = word[i].encode('utf-8')
    

功能截图

  1. 参数选择
  2. 输入文字或者读取文件


  3. 输出结果并显示运行时间

  4. 导出结果



  5. 异常提示

松耦合模块交换

在博客中指明合作小组两位同学的学号,分析两组不同的模块合并之后出现的问题,为何会出现这样的问题,以及是如何根据反馈改进自己模块的。

合作小组:

  • 陈正昊 20373379

  • 温家昊 20373668
    模块合并的问题是我们的GUI原本代码对接他们的core.dll时会报如下错误:

      File "E:\GUI\untitled.py", line 278, in input_worddll = cdll.LoadLibrary(".core_7")File "C:\Users\Lenovo\AppData\Local\Programs\Python\Python39\lib\ctypes\__init__.py", line 452, in LoadLibraryreturn 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")

结对过程

提供两人在讨论的结对图像资料(比如 Live Share 的截图)。关于如何远程进行结对参见作业最后的注意事项。


我们的结对过程如下:

  1. 3.7日一起仔细阅读本次结对作业题目并对团队任务进行初步规划与分工
  2. 3.8~3.10日一起编程整理功能、梳理代码逻辑、设计接口完成CLI模块
  3. 3.11~3.12日分别实现不同的功能性函数的计算模块
  4. 3.13~3.14分共编程,根据一同设计好的接口分别实现GUI模块以及计算模块单元测试并统计测试覆盖率
  5. 3.15日一起编程实现异常处理
  6. 3.16日一起编程实现对异常的单元测试
  7. 3.17日一起编程实现模块交换,与另一个组进行模块互换实现对接。

结对编程的优缺点

看教科书和其它参考书,网站中关于结对编程的章节,例如:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html ,说明结对编程的优点和缺点。同时描述结对的每一个人的优点和缺点在哪里(要列出至少三个优点和一个缺点)。

结对编程的优点:

  • 两个人优缺点互补的话可以提高效率和准确率
  • 减少很多不必要的低级bug
  • 及时讨论可以提高对困难问题的解决效率
    结对编程的缺点:
  • 两个人合作需要一定的磨合时间,前期合作可能效率并不高

龚悦同学的优点:

  • 逻辑严谨
  • 思维敏捷
  • 能在多方面找参考资料,解决问题的能力强

龚悦同学的缺点:

  • 对C++不很了解

吕元秋同学的优点:

  • 心态好,鼓励同学
  • 幽默有趣,编程不无聊,适当的调动气氛可以提高效率
  • 积极同交换组的队员沟通

吕元秋同学的缺点:

  • 对C++不了解,不是很能熬夜,晚上讨论的时候很容易困QAQ

软工第三次作业——结对编程之最长单词链相关推荐

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

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

  2. 软工第三次作业-结对编程

    结对项目-最长英语单词链 哈哈,这次记住了,来,初始化! 项目 内容 这个作业属于哪个课程 2023年北航敏捷软件工程社区 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习软 ...

  3. 罗杰软工第三次作业——结对编程

    BUAA-2023软件工程结对编程博客作业 项目 这个作业属于哪个课程 2023北航敏捷软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习并实践软件工程开发的方法论. ...

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

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

  5. ASE第三次作业——结对编程

    ASE第三次作业--结对编程 成员:张贺 杨涛 石恩升 github地址:https://github.com/ThomasMrY/ASE-project-MSRA 题目简介: 此次编程的题目是--统 ...

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

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

  7. 第三次作业-结对编程

    Github项目地址:https://github.com/WHYNOTEN/WordCount.git 合作同学作业地址:https://www.cnblogs.com/Mchandu/p/1065 ...

  8. 软件工程第三次作业 结对编程

    结对编程 小组成员: 201521123055 林一心 博客地址 201521123046 张杭镖 博客地址 项目地址 一.分析改进现有代码 1.单元测试: 2.覆盖率: 3.确定当前设计的一个弱点, ...

  9. 第三次作业——结对编程

    一.地址 GITH地址:https://github.com/haveadate/WordCount.git 结对伙伴的作业地址:https://www.cnblogs.com/haveadate/p ...

最新文章

  1. socket recv 服务端阻塞 python_网络编程(基于socket编程)
  2. 基于MD5的增强型摘要算法
  3. case when完成不同条件的显示
  4. C++学习笔记第二天:几个知识点
  5. sp寄存器和bp寄存器_为什么早期x86处理器寄存器很少?
  6. php的数据模型包括,数据库中模型的分类有哪些
  7. 【mysql问题】can't connect to mysql server on 'localhost' (10060)
  8. 绒毛动物探测器:通过TensorFlow.js中的迁移学习识别浏览器中的自定义对象
  9. C++11 继承构造函数
  10. 二分法猜数——C/C++
  11. android 手势截图,小米8手机如何截图/长截屏/手势截屏?小米8四种截图方法
  12. 思科ccnp现在出来工作有多少钱
  13. 项目做PC端页面采用rem适配的使用步骤
  14. 电池SOC仿真系列-基于GA-BP神经网络的电池SOC估算方法
  15. 整车控制器(VCU,vehicle Controller Unit)
  16. 计算机图形学:DDA(数值微分)画线法
  17. Flutter 与 RN对比
  18. 中国剩余定理证明及代码实现
  19. Android开发之WebDav
  20. JavaWeb实现裁剪图片上传完整代码

热门文章

  1. 【高等数学】多元函数微分法及其应用1
  2. 【安卓中的缓存策略系列】安卓缓存策略之综合应用ImageLoader实现照片墙的效果
  3. 30个题型+代码(冲刺2023蓝桥杯)(中)
  4. kb4524157安装失败_微软称Win10安装KB3081440可解决更新KB3081424失败问题
  5. i7 13700h参数 酷睿i713700h性能怎么样 相当于什么水平
  6. 谷歌云请更正这张卡片的信息_如何在Android的Google键盘上改进自动更正
  7. Springboot + i18n国际化
  8. 全面了解 Grid 布局
  9. 双11开场10秒即爆单,360亿方云如何助力电商企业高效协同
  10. Android实战简单新闻主界面设计