结对编程项目-最短单词链

项目内容
这个作业属于哪个课程2023 年北航软件工程
这个作业的要求在哪里结对项目-最长英语单词链
我在这个课程的目标是学习软件工程的科学理论知识,在实践中锻炼自我思考能力和团队开发能力
这个作业在哪个具体方面帮助我实现目标通过两人合作完成一个小项目,锻炼思考、合作和时间管理能力

1.项目说明

  • 教学班级:周四上午班
  • 项目地址:https://github.com/SE-cjj-wxz/wordlist-app

2.计划

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划120240
· Estimate· 估计这个任务需要多少时间120240
Development开发15901530
· Analysis· 需求分析 (包括学习新技术)60180
· Design Spec· 生成设计文档60120
· Design Review· 设计复审 (和同事审核设计文档)12060
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)3030
· Design· 具体设计240120
· Coding· 具体编码600600
· Code Review· 代码复审180180
· Test· 测试(自我测试,修改代码,提交修改)300240
Reporting报告510570
· Test Report· 测试报告180180
· Size Measurement· 计算工作量3030
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划300360
合计22202340

3.教材阅读

3.1 Information Hiding

信息隐藏是软件开发过程中的设计法则之一,即将每个程序的成分隐蔽或封装在一个单一的设计模块中,定义每一个模块时尽可能少地显露其内部的处理,该方法对于提高软件的可修改性、可测试性和可移植性都有重要的作用。

针对本项目的设计,我们将方法封装到 core.cpp 中,只对外提供三种接口,隐藏了具体的实现细节,同时对图和点的方法分别进行封装,外部只能通过给定的方法进行访问。

int countChains(char** words, int length, char* result[]);
int getLongestWordChain(char** words, int length, char* result[], char head, char tail, char ban, bool allow_circle);
int getLongestCharChain(char** words, int length, char* result[], char head, char tail, char ban, bool allow_circle);

3.2Interface Design

接口设计主要面向用户需求,在CLI方面,上文中我们抽象出的三种接口方便用户调用,并设计了合理的参数来完成数据的传入和结果的返回;而在GUI方面,我们直接通过限定用户选项范围的方式避免用户的错误输入行为(比如功能型参数只有 -n、-w、-c三个选项,保证了该类型参数不会缺失)

在这里插入图片描述

3.3Loose Coupling

在软件工程中,松耦合(loose coupling)是指模块或组件之间的互相依赖关系尽可能地减少。松耦合的设计可以使软件更具有可维护性、可扩展性和可重用性。

在本项目中,我们将用户界面模块(GUI)和计算核心模块(core)分开实现,其中GUI通过调用Core生成的动态链接库dll来完成运算,因而实现了界面模块和计算模块的松耦合。

4.计算模块接口设计与实现

4.1 建图与数据结构

我们的建图方式是每个字母作为一个结点,每个单词作为一条有向边,从单词首字母对应的结点连接到单词尾字母对应的结点,对于自环需要特殊处理。此外,为了方便处理,我们为强连通分量单独建类。

具体来说,我们实现了以下数据结构来存储图:

  • 结点类:每个字母对应一个结点,记录这个结点的出边、自环、入度
  • 边类:每个单词对应一条边,记录单词字符串,边权,边的终点。
  • 图类:记录图上的每个结点和强连通分量
  • 强连通分量:存储属于这个强连通分量的结点,存储强连通分量任意两点之间的最短距离

4.2 计算模块接口

能够构成所有单词链的数目

计算模块对应接口为:countChains

根据输入的单词列表建图,判断是否成环。若成环,则抛出异常。否则在图上进行拓扑排序,拓扑排序的同时记录答案。

在这里插入图片描述

计算最多字母数量的单词链

计算模块对应的接口为: getLongestCharChain

根据输入的单词列表建图,边权为单词长度,判断是否成环。

若不成环: 对图中的结点进行拓扑排序,按照拓扑序进行 DP 转移,DP 转移方程为:
v a l u e [ v ] = m a x ( v a l u e [ v ] , v a l u e [ u ] + e . v a l u e ) , e : u → v value[v] = max(value[v], value[u] + e.value), \quad e:u \rightarrow v value[v]=max(value[v],value[u]+e.value),e:uv
每当结点 v 出队时,更新自环的贡献:
v a l u e [ v ] ← v a l u e [ v ] + c i r c l e [ v ] value[v] \leftarrow value[v] + circle[v] value[v]value[v]+circle[v]
若存在环: 先求出图的强连通分量,同时也得到了强连通分量之间的拓扑关系。然后对每个强连通分量内部使用 DFS 算法求解出任意两个点之间的距离。对于自环,都在 DFS 时加入答案,在拓扑序 DP 的阶段只考虑强连通分量之间的边。

附加型参数:

  • -h:对于不能作为首字母的字母结点,其初始值为 -1,在 DP 转移的过程中,如果 value < 0 则不能向外转移。对于能作为首字母的结点,其初始值为0。
  • -t:最后计算最大值的时候只统计能作为尾字母的结点的值。
  • -j:删除不能出现的首字母对应的结点的所有自环和出边。

在这里插入图片描述

计算最多单词数量的单词链

计算模块对应的接口为: getLongestWordChain

与计算最多字母数量的单词链的过程基本一致,唯一的区别是建图时将边权都赋值为1。

5.编译无警告

编译无警告的控制台输出如下:

在这里插入图片描述

6.UML图

UML图绘制如下:

在这里插入图片描述

7.计算模块接口部分的性能改进

7.1性能测试

我们使用了 Google 开源的性能分析工具 gproftools 来分析程序的性能,部分截图如下:

在这里插入图片描述

上面文本一共六列,分别是:

  • 分析样本数量(不包含其他函数调用)
  • 分析样本百分比(不包含其他函数调用)
  • 目前为止的分析样本百分比(不包含其他函数调用)
  • 分析样本数量(包含其他函数调用)
  • 分析样本百分比(包含其他函数调用)
  • 函数名

7.2性能改进

以性能测试中的程序为基准,将其某一组数据运行时间作为 baseline 。

由性能分析结果可知,最耗时的函数是 SCC::dfs ,考虑通过并行的方式提高运行效率,我们采用 OpenMP 并行计算以强连通分量每个点为起点的 DFS 。该函数中使用 vector<string> 记录遍历的路径,字符串拷贝耗时较大,用边类的指针代替字符串。

改进后性能对比:

  • baseline:35.2 s
  • OpenMP:16.7 s
  • OpenMP + pointer:11.4 s

最终加速比约为 3.1

8.Design by Contract,Code Contract

8.1Design by Contract

Design by Contract(契约式设计)是一种软件开发方法,强调使用形式化的契约(contracts)来改善软件质量和可靠性。

优点:

  1. 开发过程有着明确的约定,方便理解沟通。
  2. 契约式设计还提供了强大的测试工具,可以自动验证组件之间的约束条件,减少测试工作量。
  3. 提高代码的可维护性。
  4. 增加代码的重用性。

缺点:

  1. 增加开发成本。
  2. 限制程序的灵活性,在程序中做出的任何修改都不能违法约束条件。

在本项目中,我们在开始编码前先写了需求分析文档和设计文档,之后再设计开发过程中严格按照设计文档中规定的规范进行编码,实现了契约式设计。

8.2Code Contract

Code Contract是一种在.NET代码中定义先决条件、后置条件和对象不变量的技术。它由Microsoft在.NET Framework 4.0中引入,旨在提高代码质量和可靠性。

其中先决条件定义了方法或函数的输入参数必须满足的要求,后置条件定义了方法或函数的输出结果必须满足的要求,对象不变量定义了一个对象的状态必须满足的条件。

优点:

  1. 更好的代码维护性
  2. 提高代码的质量和可靠性

缺点:

  1. 学习Core Contract需要新的语法和概念,消耗时间精力
  2. Code Contract可能会增加代码的运行时开销

在本项目中,我们对函数进行了先决条件、后置条件和对象不变量的限制,例如:

  • lexerArgs()
    • 先决条件:无
    • 后置条件:得到 opt2val
    • 对象不变量:无
  • readWords()
    • 先决条件:无
    • 后置条件:wordslength必须大于0,否则按照文件为空报错。
    • 对象不变量:输出的words中字母全部为小写

9.计算模块部分单元测试

测试点针对情况
0、14-n基本测试
1、17-w基本测试
2-c基本测试
3、8-w -h复合测试
4-w -t复合测试
5-w -j复合测试
6、9-w -r复合测试
10-c -h复合测试
11-c -r复合测试
12-c -h -r 复合测试
13-w -h -r复合测试
16-w -h -j复合测试
18-c -t -j复合测试

countChains 接口的单元测试示例:

TEST(testCase, test14) {
	char* words[] = {
		"algebra",
		"apple",
		"exe",
		"elephant"
	};
	int ret = 8, len = 4;
	char** result = (char**)malloc(sizeof(char*) * 100);
	int ans = countChains(words, len, result);
	EXPECT_EQ(ans,ret);
}

getLongestWordChain 接口的单元测试示例:

TEST(testCase, test8) {
	char* words[] = {
		"algebra",
		"apple",
		"zoo",
		"elephant",
		"under",
		"fox",
		"dog",
		"moon",
		"leaf",
		"trick",
		"pseudopseudohypoparathyroidism"
	};
	int ret = 2, len = 11;
	char head = 'l', tail = 0, ban = 0; 
	bool circ = false;
	char** result = (char**)malloc(sizeof(char*) * 100);
	int ans = getLongestWordChain(words, len, result, head, tail, ban, circ);
	EXPECT_EQ(ans,ret);

getLongestCharChain 接口的单元测试示例:

TEST(testCase, test18) {
	char* words[] = {
		"algebra",
		"apple",
		"zoo",
		"elephant",
		"under",
		"fox",
		"dog",
		"moon",
		"leaf",
		"trick",
		"kkd"
	};
	int ret = 5, len = 11;
	char head = 0, tail = 'd', ban = 'l'; 
	bool circ = true;
	char** result = (char**)malloc(sizeof(char*) * 100);
	int ans = getLongestCharChain(words, len, result, head, tail, ban, circ);
	EXPECT_EQ(ans,ret);
}

计算模块单元测试覆盖率:

在这里插入图片描述

10.计算模块部分异常处理说明

针对整个项目,我们设计了共14种异常,列表如下:

  1. logic_error(”duplicate option ”);
TEST(testCase, test19) {
	int argc = 4; 
	char* argv[] = {
		"-n",
		"-n",
		"input.txt"
	};
	string str = "duplicate option: -n";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”option that should have argument does not have argument”);
TEST(testCase, test20) {
	int argc = 4;
	char* argv[] = {
		"-n",
		"input.txt",
		"-h"
	};
	string str = "option that should have argument does not have argument: -h";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”option has illegal argument (not a single letter)”);
TEST(testCase, test21) {
	int argc = 5;
	char* argv[] = {
		"-h",
		"an",
		"-n",
		"input.txt"
	};
	string str = "option has illegal argument: -h has an";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”illegal option”);
TEST(testCase, test22) {
	int argc = 5;
	char* argv[] = {
		"-b",
		"a",
		"-n",
		"input.txt"
	};
	string str = "illegal option: -b";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”duplicate fileName”);
TEST(testCase, test23) {
	int argc = 4;
	char* argv[] = {
		"-n",
		"input.txt",
		"input2.txt"
	};
	string str = "duplicate fileName: input2.txt";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”missing functional parameters (-n -w -c)”);
TEST(testCase, test24) {
	int argc = 3;
	char* argv[] = {
		"-r",
		"input.txt",
	};
	string str = "missing functional parameters (-n -w -c)";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”functional parameters are not compatible”);
TEST(testCase, test25) {
	int argc = 4;
	char* argv[] = {
		"-n",
		"-w",
		"input.txt",
	};
	string str = "functional parameters are not compatible";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”file does not exist”);
TEST(testCase, test26) {
	int argc = 3;
	char* argv[] = {
		"-n",
		"input2.txt",
	};
	string str = "file does not exist";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”the file content is empty”);
TEST(testCase, test27) {
	char* words[] = {

	};
	int len = 0;
	char** result = (char**)malloc(sizeof(char*) * 100);
	string str = "the file content is empty";
	try {
		int ans = countChains(words, len, result);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	} 
}
  1. logic_error(”n can only be used alone”);
TEST(testCase, test28) {
	int argc = 4;
	char* argv[] = {
		"-n",
		"-r",
		"input.txt",
	};
	string str = "-n can only be used alone";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”there is no matching result”);
TEST(testCase, test30) {
	char* words[] = {
		"ab",
		"ss"	
	};
	int len = 2;
	char** result = (char**)malloc(sizeof(char*) * 100);
	string str = "there is no matching result"; 
	try {
		int ans = countChains(words, len, result);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	} 
}
  1. logic_error(”-h and -j cannot have the same value”);
TEST(testCase, test31) {
	int argc = 8;
	char* argv[] = {
		"-w",
		"-h",
		"a",
		"-j",
		"a",
		"-r"
		"input.txt",
	};
	string str = "-h and -j cannot have the same value";
	try {
		WordList(argc, argv);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	}
}
  1. logic_error(”there is a circle in the chain without -r”)
TEST(testCase, test29) {
	char* words[] = {
		"word",
		"draw"	
	};
	int len = 2;
	char** result = (char**)malloc(sizeof(char*) * 100);
	string str = "there is a circle in the chain without -r"; 
	try {
		int ans = countChains(words, len, result);
	} catch(exception& e) {
		string err = e.what(); 
		EXPECT_EQ(err, str);
	} 
}
  1. logic_error(”the result is too long”);

其中前12种异常均在CLI和GUI外层处理,真正交给 core 处理的异常只有最后的13和14两类,对于指针数组result的长度上限为20000的异常,我们程序会在超出上限时报错并保证返回值正确。(但是由于当输入满足单词个数的限制时必然不可能使result数组长度溢出,因此我们并没有构造针对的测试样例,但仍然支持该异常的检测)。

11.界面模块的详细设计过程

本项目我们使用了 基于c++的QT 进行图形化界面设计与实现。

11.1功能需求分析

  1. 支持导入文本文件
  2. 支持输入文本
  3. 功能型参数必须选一个(采用下拉选项,强制限定三选一)
  4. 辅助型参数选了必须要有值(下拉选项:无 + 26个字母,同样强制限定值合法)
  5. 是否选择成环(使用checkBox实现)
  6. 支持保存结果到文件(solution.txt)
  7. 显示每次操作命令的运行计时(计时功能)

11.2异常处理

选定参数后点击开始计算,之后判断是否有如下异常:

  1. -n和其他任何参数一块选
  2. 在不选-r的情况下成环
  3. 没有满足条件的结果需要

11.3界面实现

使用 Qt designer 进行界面设计与实现,同时添加合理的布局限制,保证当窗口大小变化时界面布局依然合理美观。

在这里插入图片描述

11.4功能实现

下面结合几个具体的功能解释具体的逻辑实现思路(总体思路是使用lambda表达式进行逻辑书写):

  1. 导入文本
connect(ui->uploadFile, &QPushButton::clicked, this, [=](){
        QString fileName = QFileDialog::getOpenFileName(this,
            NULL, NULL, "Text files (*.txt);; *.*");
        if (fileName.isEmpty()) {
            return;
        }
        QFile myFile(fileName);
        if (!myFile.open(QFile::ReadOnly | QFile::Text)) {
            QMessageBox::warning(this, "文件打开失败", "你选择的文件不存在或者拒绝访问");
            return;
        }
        QTextStream in(&myFile);
        QString str = in.readAll();
        ui->inputText->setPlainText(str);
    });

点击右上角”上传文件“即可选取单词文本:

在这里插入图片描述

  1. 保存结果
connect(ui->saveFile, &QPushButton::clicked, this, [=](){
        QString fileName = QFileDialog::getSaveFileName(this,
            NULL,  "solution.txt", "Text files (*.txt)");
        if (fileName.isEmpty()) {
            return ;
        }
        QFile myFile(fileName);
        if (!myFile.open(QIODevice::WriteOnly)) {
            QMessageBox::warning(this, "文件保存失败", "你选择的文件拒绝访问");
            return;
        }
        QTextStream stream(&myFile);
        stream << ui->outputText->toPlainText();
        myFile.close();
    });    

点击”导出结果“即可将结果报错到指定位置的文件中:

在这里插入图片描述

  1. 异常提示

以 -n -r 为例,界面会提示用户 -n 不能和其他选项一块选择:

在这里插入图片描述

其他异常的提示形式与之类似,不再赘述。

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

界面模块通过调用计算模块的动态链接 core.dll 实现相应功能,其中对于GUI中导入 dll 的方法,我们直接通过在 CMakelist中加入如下代码的方式实现:

find_library(CORE_LIB NAME core.dll PATHS ../bin/)
target_link_libraries(GUI ${CORE_LIB})

之后在 gui.cpp 中调用相应的 core.h 中定义的接口即可完成功能对接,对接效果如下,以 -w -r 为例:

其中输入文本为:

cecw cecwcwe cecwcwevwev rv gn rvb vd vt jt rukj mtnu gp yuo ytp tyip gkth ndry mugrlyipp oruk orpu
olkyur ppouthjdm tu pr olug ppouthjdml kmy ui ppouthjdmp yk yuto ppouthjdmty kyu ptpo yutp kr dyjy
ieytuyt ety uety uetyj eyj tyj ykj srh tes fcv etaw tm mtnua

在这里插入图片描述

可以看到结果正常输出,并且用时 28.11s

13.附加题——与其他组互换模块

我们与另一组互换了模块,互换的最大问题是双方的开发环境不同,我们组是 window 环境,而另一组则是 window + Mac 环境。

另一组成员:

  • 强生:20373249
  • 申浩佳:20373776

dev-combine 分支地址:https://github.com/SE-cjj-wxz/wordlist-app/tree/dev-combine。不包含另一组同学的源代码,仅在 dev-combine/bin文件夹下有两个组合好的程序:分别为本方的GUI和对方的Core.dll

13.1本组core与另一组GUI

由于开发环境的不同,本组的 core.dll 并不能被另一组的GUI直接调用,因而我们选择将本组的源码直接发给对方,在对方的环境下链接生成 core.dll,最终结合运行成功,效果图如下:

在这里插入图片描述

13.2本组GUI与另一组core

对方将 core.dll 发给本组,我们进行替换之后可以成功运行GUI,效果图如下:

在这里插入图片描述

13.3本组core与另一组单元测试

除了进行计算模块和用户测试模块的互换外,我们使用对方的单元测试模块测试了本组的计算模块,测试效果如下:

在这里插入图片描述

14.结对过程

结对编程采用线下的方式,地点通常在老主楼四楼的休息区。

在这里插入图片描述

15.结对编程的优缺点

15.1优点

  1. 提高代码质量:结对编程中两个人共同编写和审查一段代码,有利于及时地发现和修复错误。
  2. 知识分享:在结对编程中两个人可以相互学习和交流经验。
  3. 提高团队合作能力:结对编程要求两个人相互协作,可以锻炼两个人的沟通和合作能力。

15.2缺点

  1. 更高的成本:结对编程对两个人的时间消耗都很大,导致成本开销增加。
  2. 更强的时间管理要求:结对编程对于领航员的要求很高,需要其综合考虑多种因素从而合理地安排时间,因而增加了管理成本。

15.3队员优缺点分析

姓名优点缺点
陈俊杰领导、沟通能力较强,善于进行需求分析和功能设计,熟练掌握Qt的设计与编写不熟悉C++语言
王雪竹熟悉C++语言,算法基础较好,能够快速编码实现需求不习惯合作编程

16.实际花费时间

已记录在第二部分的计划表中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ChatGPT结对编程是一种将两个程序员合作编写代码的技术。这种技术可以提高编写代码的效率和质量,同时也可以提高两位程序员的技能水平。以下是ChatGPT结对编程的步骤: 1. 首先,找到一个合适的编程伙伴。最好是一个有一定编程经验的人,但如果你是新手,也可以与另一个新手合作。 2. 确定你们编写的代码项目。你们可以选择一个共同感兴趣的项目或者一个有挑战性的项目。确保你们都对项目有一定的理解。 3. 确定你们的角色。一个人可以担任主要代码编写者,另一个人可以担任代码审核者。这样可以确保代码的质量。 4. 确定编程环境。你们可以使用一个共同的编程环境,如Visual Studio Code或者Atom。也可以使用在线编程环境,如CodePen或JSFiddle。 5. 开始编写代码。一个人负责编写代码,另一个人负责审核代码。在编写代码的过程中,你们可以随时通过聊天工具进行交流和讨论。 6. 定期进行代码审核。定期进行代码审核可以确保代码的质量。你们可以定期的分享代码,并相互审核对方的代码。 7. 完成项目并进行总结。完成项目后,你们可以总结你们的经验和教训,并提出改进建议。这将有助于你们以后更好的编写代码。 总之,ChatGPT结对编程是一种非常有用的技术,可以提高编写代码的效率和质量。通过合作编写代码,你们可以相互学习,相互支持,以及增强你们的编程技能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值