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

文章目录

  • 1. 项目地址
  • 2. PSP 估计用时表格
  • 3. 接口设计
    • Information Hiding
    • Interface Design
    • Loose Coupling
  • 4. 接口实现
  • 5. 无警告编译通过截图
  • 6. UML
  • 7. 计算模块接口部分性能改进
    • 无环情况
    • 有环情况
  • 8. Design by Contract/Code Contract
  • 9. 计算模块部分单元测试展示
  • 10. 异常处理部分说明
  • 11. 界面设计
  • 12. 界面模块与计算模块对接互换
    • UI模块的设计
    • 模块对接
    • 功能截图
    • 松耦合模块交换
  • 13. 结对过程
  • 14. 结对编程优缺点
  • 15. PSP实际用时表

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 会给算法带来较大变动,因此我们将接口实现分成三个主要部分,分别为:

  1. -n 功能实现
  2. -w/-c 无环和附加参数组合功能实现
  3. -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_SolverW_solverC_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 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 测试思路类似,不过多赘述。

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实现的图形化界面,最终根据题目要求实现了指令选择以及输入单词、文件导入单词以及导出结果、异常提示以及运行时间显示的功能等。
实现过程分为以下几步:

  1. 功能规划
    根据题目要求,具体地总结出需要实现指令选择以及输入单词、文件导入单词以及导出结果、异常提示以及运行时间显示的功能,并计划将GUI模块分为UI设计与功能函数的实现两阶段去完成。
    UI设计与实现

  2. 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))
      
    1. 功能函数的设计实现与链接
      具体实现与代码解读见下一节
    2. 与计算模块的对接
      具体实现与代码解读见下一节

12. 界面模块与计算模块对接互换

UI模块的设计

  1. 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.translateMainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))self.textEdit.setMarkdown(_translate("MainWindow", "功能参数选择:        ))...
  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")

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

结对项目-最长单词链总结相关推荐

  1. 结对项目-最长单词链博客

    1.Github项目地址 Here ! 2.PSP表格(程序各个模块在开发上预计耗费的时间和实际耗费的时间) PSP2.1 Personal Software Process Stages 预估耗时( ...

  2. 结对项目-最长单词链

    项目 内容 作业所属课程 2019软件工程_罗杰 作业要求链接 结对项目作业 课程目标: 进行结对编程实践 该作业的帮助 实践结对编程,熟悉双人软工开发流程 队友的结对项目总结 链接 1.本次作业gi ...

  3. 结对编程——最长单词链

    项目 内容 这个作业属于哪个课程 2023 年北航软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习软件工程的科学理论知识,在实践中锻炼自我思考能力和团队开发能力 这 ...

  4. 软工第三次作业——结对编程之最长单词链

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

  5. 结对项目-最长英语单词链

    文章目录 结对项目-最长英语单词链 项目信息 PSP 表格 接口设计参考理念 Information Hiding Interface Design Loose Coupling **计算模块接口的设 ...

  6. 软件工程结对项目- 最长英语单词链

    项目 内容 这个作业属于哪个课程 2023年北航敏捷软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习现代化的软件开发方法 这个作业在哪个具体方面帮助我实现目标 对结 ...

  7. 结对项目-最长英语单词链-20373974阮正浩

    项目 内容 这个作业属于哪个课程 软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习软件工程的一般方法并实践 这个作业在哪个具体方面帮助我实现目标 实践结对编程方法, ...

  8. python找最长的单词,Python 找出英文单词列表(list)中最长单词链

    这篇文章主要介绍了Python 找出英文单词列表(list)中最长单词链,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 本文主要 ...

  9. 结对项目——最长英语单词链

    项目 内容 这个作业属于哪个课程 https://bbs.csdn.net/forums/buaa-ase2023 这个作业的要求在哪里 https://bbs.csdn.net/topics/613 ...

最新文章

  1. 【实习招聘】字节跳动智能创作AutoML团队招聘
  2. java自己写一个上下文_5.自己动手写Java Web框架-上下文
  3. 记一次在LAMP中遇到的问题
  4. Wang Xifeng's Little Plot (poj 5024 DFS)
  5. mysql5.7.12 my.ini文件_MySQL5.7缺少my.ini文件的解决方法
  6. .Net5发布在即,当心技术断层!
  7. 浅入浅出数据结构(20)——快速排序
  8. java和c 对接_java和objective-C对接笔记
  9. Jmeter接口测试实战分享,你一定要知道的问题总结!
  10. NYOJ 61:传纸条(一)(三维DP)
  11. Centos 编译安装mysql 5.6.21
  12. 图象关于y轴对称是什么意思_高中数学常考问题1:函数、基本初等函数的图象与性质,真题分析...
  13. iptables的备份
  14. poi ppt 作者属性 修改_POI之PPT文本框生成及样式设置实例
  15. SDN技术的发展应用任重而道远
  16. 硬盘容量的计算方法,这就是为什么实际容量总比官方标示少的原因
  17. 《认识突围:做复杂时代的明白人》读书笔记和自我理解感受
  18. 乐学python视频资源_铁乐学python_day04-作业
  19. Android--- Drawer and Tab Navigation with ViewPager
  20. 基于RTP协议的IP电话QoS监测及提高策略

热门文章

  1. 中国股民掉进罗杰斯们的陷阱(摘录)
  2. vivos7和华为nova7的区别 哪个好
  3. 帕蒂 麦考德《奈飞文化手册》读书笔记
  4. 小白阶段代码的暴力美学
  5. 互联网实验室评CTO李一男离职:百度正在失去创新精神
  6. C++程序设计 姜学锋pdf
  7. 伯努利分布+朴素贝叶斯分类器の概率解释
  8. 第一章 仿支付宝芝麻信用界面制作(需要自定义View的相关知识)
  9. i7 11370H和i7 1165G7 对比哪个好
  10. Nuxt3开发项目模版