结对项目-最长英语单词链
文章目录
- 结对项目-最长英语单词链
- 项目信息
- PSP 表格
- 接口设计参考理念
- Information Hiding
- Interface Design
- Loose Coupling
- **计算模块接口的设计与实现过程**
- 问题分析
- 接口设计
- 编译通过截图
- UML 图
- 计算模块接口部分的性能改进
- Design by Contract 和 Code Contract 的理解与应用
- 计算模块部分单元测试展示
- 构建思路
- 测试样例
- 代码覆盖率截图
- 计算模块部分异常处理说明
- 命令行参数异常
- 1、参数过少
- 2、-h(-t、-j类似)参数后缺少字母参数
- 3、不是所给的七个参数
- 4、多个文件参数
- 5、-n不支持与其他参数组合使用
- 6、参数不兼容
- 7、不存在功能型参数
- 8、附加型参数不允许重复使用
- 9、未提供文件参数
- 10、非法 -option
- 11、-h 和 -j 存在逻辑错误
- 文件异常
- 1、文件不存在
- 2、文件无法打开
- 3 、无法写solution.txt文件
- 运行时异常
- 1、文件中不存在单词
- 2、存在单词环
- 3、超过20000个单词链
- 界面模块的详细设计过程
- 界面模块与计算模块的对接**界面模块与计算模块的对接**
- 接口互换
- 与 core 模块的对接
- 与命令行模块的对接
- 结对过程
- 结对总结
- 优点
- 缺点
- 团队分析
结对项目-最长英语单词链
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2023 年北航敏捷软件工程 |
这个作业的要求在哪里 | 结对项目-最长英语单词链 |
我在这个课程的目标是 | 学习软件工程相关知识,提高自己的代码能力与团队协作能力 |
这个作业在哪个具体方面帮助我实现目标 | 结对协作能力 |
项目信息
- 教学班级:周四下午班
- 小组成员:卓乐 19376092,刘畅 20372004
- 项目地址:https://github.com/zhuole1025/Longest_English_Words_Chain
PSP 表格
预计耗时与实际耗时的 PSP 表格如下:
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 60 |
Development | 开发 | 2250 | 2310 |
· Analysis | · 需求分析 (包括学习新技术) | 120 | 120 |
· Design Spec | · 生成设计文档 | 120 | 120 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
· Design | · 具体设计 | 180 | 240 |
· Coding | · 具体编码 | 900 | 900 |
· Code Review | · 代码复审 | 300 | 240 |
· Test | · 测试(自我测试,修改代码,提交修改) | 540 | 600 |
Reporting | 报告 | 270 | 270 |
· Test Report | · 测试报告 | 180 | 180 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 2580 | 2640 |
接口设计参考理念
看教科书和其它资料中关于 Information Hiding,Interface Design,Loose Coupling 的章节,说明你们在结对编程中是如何利用这些方法对接口进行设计的。
Information Hiding
Information Hiding 是指对一些重要信息以及不希望别人使用或修改的属性进行包装,通过提供接口实现对内部属性的访问与修改,从而将内部实现与外部调用分离,使用者只需要调用对应的外部接口而不需要去关心内部具体的实现方式。在我们的实现中,我们对核心算法的图和单词节点分别建立了 Graph 类和 Word 类进行封装;通过封装 Solver 类来实例化一个 Graph 然后调用具体算法解决问题并对结果进行后处理。
Interface Design
Interface Design 遵守面向对象设计的六大设计原则:单一职责原则、开闭原则、里氏替换原则、迪米特法则、接口分离原则、依赖倒置原则。我们在定义接口时保证每一个参数都有明确的定义,不会让调用者产生歧义,并且每一个接口仅负责一个任务,避免承担多种职责。
Loose Coupling
Loose Coupling 是指模块之间的耦合程度较低,其关键在于双方约定数据交换的协议(即接口)后,可以任意修改内部实现,如进行性能优化,而不影响数据接口或交互部分。因此,在实际中,我们尽量让底层函数的功能单一,并将模块之间信息传递的部分设计的简洁且统一,减少模块之间的相互依赖。
计算模块接口的设计与实现过程
问题分析
本问题的核心在于找到首尾字母相连的单词序列,我们可以将每个字母当成一个顶点,从而建立一个 26 个顶点的有向图。其中,每个单词表示为从首字母到尾字母的一条边。在没有 -r
参数的情况下,该图是一个有向无环图。
接口设计
我们的计算模块 core.dll
设计了以下三个接口:
CORE_EXPOSE
int gen_chains_all(const char* words[], int len, char* result[]);CORE_EXPOSE
int gen_chain_word(const char* words[], int len, char* result[], char head, char tail, char skip, bool enable_loop);CORE_EXPOSE
int gen_chain_char(const char* words[], int len, char* result[], char head, char tail, char skip, bool enable_loop);
我们接口的约定如下:
words
:传入读入的文件内容,数组中每个元素都是一个单词的头指针,单词均为小写且以 ‘\0’ 结尾len
:传入单词个数results
:保存返回的结果,需要由调用者为其申请 128MB 的空间并将首地址赋给results[0]
。返回结果格式为每一个元素保存一行输出的首地址,以 ‘\0’ 结尾;若返回结果大于 128MB,则返回结果为空head
:传入单词链首字母,若指定则必须传入其小写字母,若不指定首字母,则传入 ‘\0’tail
:传入单词链尾字母,若指定则必须传入其小写字母,若不指定尾字母,则传入 ‘\0’skip
:传入单词链不能出现的首字母,若指定则必须传入其小写字母,若不指定则传入 ‘\0’enable_loop
:是否允许单词链中存在循环- 返回值:返回值大于零则表示结果有效
gen_chains_all
返回值表示所有符合定义单词链的数量gen_chain_word
返回值表示最多单词数量的最长单词链的单词数量gen_chain_char
返回值表示最多字母数量的最长单词链的单词数量
在这三个接口中,实例化了我们封装的 Solver
类,并调用对应的方法进行计算。Solver
的方法接口如下:
int get_all(char* results[]);int get_max_word(char* results[], char head, char tail, char skip, bool loop);int get_max_char(char* results[], char head, char tail, char skip, bool loop);
其中各接口的参数定义与 core
中均相同。而在这些接口中则对输入进行预处理(如环路检测),调用 Graph
类中的具体算法进行计算,并对最终输出结果进行后处理(将 vector<string>
转换为 char* []
)。
Graph
类中存储了输入文件构建的图的各种信息,并实现了具体算法,如自环查询、最长链路查询等;Word
类封装了边的信息,存储了单词对应边的各种信息,如首尾字母、边权、访问信息等。
编译通过截图
UML 图
计算模块接口部分的性能改进
我们使用 VS 自带的性能分析工具对程序进行分析。以 -w -r
这一情况进行为例,程序运行的 profile 如下。可以看出 dfs 计算占用了主要时间,因此我们针对这一部分进行优化,对递归的情况进行剪枝,对于不满足条件的情况提前结束递归,从而提升程序性能。
Design by Contract 和 Code Contract 的理解与应用
Design by Contract 是一种设计计算机软件的方法,这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。 这种方法的名字里用到的“契约”是一种比喻,因为它和商业契约的情况有点类似。
- 好处:可以比较方便的进行形式化验证,同时保证代码的准确性。
- 不足:编写约束和不变式的成本非常高,有时甚至编写约束的时间会长于代码。
在我们的代码实现中,通过约束前置条件、后置条件和不变量来实现接口的准确性。比如要求单词在函数内不能改变,调用者为结果序列分配空间等等。但是考虑到结对编程的时间成本,我们并没有使用形式化的语言进行严格描述。
Code Contract 是由微软开发的用于实现契约式设计的插件,用于对 .NET 编程提供运行时检查、静态检查和文档生成。本次作业中,由于我们主要采用 C++ 结合 CMake进行开发,因此未采用 Code Contract 插件。
计算模块部分单元测试展示
本部分我们了使用 VS 的单元测试模块,主要对 core
模块的三个接口以及 main
函数进行了测试,下文将重点对计算模块部分的单元测试进行说明。
构建思路
- 测试覆盖全:构造测试样例时需要尽可能完整的覆盖各种参数及其不同组合,如
-w -h -t -j -r
这样的复杂情况也需要考虑。 - 重点测试边界情况:构造测试样例时需考虑特殊的边界情况进行测试,如输出的单词链是否包含至少两个单词、输出的单词链是否包含重复单词等等。
测试样例
以 gen_chains_all()
的单元测试为例,该方法测试测试返回合法单词链的总数是否正确,然后对所有合法单词链进行排序,并依次对比每个单词链是否正确。
// unit test for get_all function in graph.h case 1
TEST_METHOD(gen_chains_all_test1)
{const char* words[] = { "abc", "cde", "efg", "ghi" };int len = 4;vector<char*> results(500, 0);int expected_ans = 6;vector<string> expected_words = { "abc cde", "cde efg", "efg ghi", "abc cde efg", "cde efg ghi", "abc cde efg ghi" };int ans = gen_chains_all(words, len, results.data());Assert::AreEqual(ans, expected_ans);vector<string> pred_words;for (int i = 0; i < ans; i++) {pred_words.push_back(results[i]);}sort(pred_words.begin(), pred_words.end());sort(expected_words.begin(), expected_words.end());for (int i = 0; i < expected_ans; i++) {Assert::AreEqual(pred_words[i], expected_words[i]);}
}
代码覆盖率截图
最终我们构造了 16 组单元测试,代码覆盖率如下,接近 100%。
计算模块部分异常处理说明
在本次任务中,我们主要设计了三个方面的异常:命令行参数异常、文件异常以及运行时异常。在说明异常处理之前,首先声明一些我们支持的行为:
不区分参数的大小写(文件名除外)
允许任意一个功能型参数(
-n
,-w
,-c
)以及附加型参数-r
重复出现参数的顺序不做限制,但要求附加型参数(除
-r
外)的后一个参数必须是字母(不区分大小写),异常处理一律按是否为字母判断、
命令行参数异常
异常处理测试的主体函数:
void test_arg_exception(int argc, char* argv[], const char * expected_info) {try{test_args(argc, argv, 0, 0, 0, 0, false, "");}catch (const std::exception& e){Assert::AreEqual(0,strcmp(expected_info, e.what()));}Assert::Fail;}
1、参数过少
"Usage: " << argv[0] << "[-option]+ <filename>"
TEST_METHOD(test_arg_exc1) {vector<char*> argv = { "Wordlist.exe" };string expected_info = "Usage: ";expected_info += "Wordlist.exe";expected_info += "[-option]+ <filename>";test_arg_exception(argv.size(), argv.data(), expected_info.c_str());}
2、-h(-t、-j类似)参数后缺少字母参数
(不是字母 或者 不是单个字母)
"Usage -h needs a letter following"
TEST_METHOD(test_arg_exc2) {vector<char*> argv = { "Wordlist.exe" , "-w", "-h", "test"};char* info = "Usage -h needs a letter following";test_arg_exception(4, argv.data(), info);argv = { "Wordlist.exe" , "-w","-t", "-r","test" };info = "Usage -t needs a letter following";test_arg_exception(5, argv.data(), info);argv = { "Wordlist.exe" , "-w", "-j", "test" };info = "Usage -j needs a letter following";test_arg_exception(4, argv.data(), info);}
3、不是所给的七个参数
"arg not in the option"
TEST_METHOD(test_arg_exc3) {vector<char*> argv = { "Wordlist.exe" , "-c", "-m", "test" };char* info = "arg not in the option";test_arg_exception(4, argv.data(), info);}
4、多个文件参数
"process one file at a time!"
TEST_METHOD(test_arg_exc4) {vector<char*> argv = { "Wordlist.exe" , "-w", "test", "-r","test"};char* info = "process one file at a time!";test_arg_exception(5, argv.data(), info);argv = { "Wordlist.exe", "-w", "tets", "test" };test_arg_exception(4, argv.data(), info);}
5、-n不支持与其他参数组合使用
"we don't support option -n used with other options."
TEST_METHOD(test_arg_exc5) {vector<char*> argv = { "Wordlist.exe" , "-n", "-r", "test" };char* info = "we don't support option -n used with other options.";test_arg_exception(4, argv.data(), info);argv = { "Wordlist.exe", "-n", "-w", "test"};test_arg_exception(4, argv.data(), info);}
6、参数不兼容
"option -w, -n, -c should not be used together"
TEST_METHOD(test_arg_exc6) {vector<char*> argv = { "Wordlist.exe" , "-w", "-c", "test" };char* info = "option -w, -n, -c should not be used together";test_arg_exception(4, argv.data(), info);}
7、不存在功能型参数
"you should use one of -w, -n, -c"
TEST_METHOD(test_arg_exc7) {vector<char*> argv = { "Wordlist.exe" , "-r", "-j", "a", "test"};char* info = "you should use one of -w, -n, -c";test_arg_exception(5, argv.data(), info);}
8、附加型参数不允许重复使用
"-h, -t, -j should be used no more than twice!"
TEST_METHOD(test_arg_exc8) {vector<char*> argv = { "Wordlist.exe" , "-w", "-J", "m", "-j", "m", "test"};char* info = "-h, -t, -j should be used no more than twice!";test_arg_exception(7, argv.data(), info);argv = { "Wordlist.exe" , "-w", "-h", "m", "-h", "m", "test" };test_arg_exception(7, argv.data(), info);argv = { "Wordlist.exe" , "-w", "-t", "m", "-t", "m", "test" };test_arg_exception(7, argv.data(), info);}
9、未提供文件参数
"no file given!"
TEST_METHOD(test_arg_exc9) {vector<char*> argv = { "Wordlist.exe" , "-w", "-t", "t" };char* info = "no file given!";test_arg_exception(4, argv.data(), info);}
10、非法 -option
"'-', a specific option should be followed."
TEST_METHOD(test_arg_exc10) {vector<char*> argv = { "Wordlist.exe" , "-w", "-", "test" };char* info = "'-', a specific option should be followed.";test_arg_exception(4, argv.data(), info);argv = { "Wordlist.exe" , "-w", "-jh", "test" };test_arg_exception(4, argv.data(), info);}
11、-h 和 -j 存在逻辑错误
"the letters of -h and -j shouldn't be the same."
TEST_METHOD(test_arg_exc11) {vector<char*> argv = { "Wordlist.exe" , "-w", "-h", "a", "-j", "a", "test"};char* info = "the letters of -h and -j shouldn't be the same.";test_arg_exception(7, argv.data(), info);}
文件异常
1、文件不存在
"File<" + filename + "> does not exist!"
TEST_METHOD(test_openfile_exception) {try{open_file("tests.txt");}catch (const std::exception& e){Assert::AreEqual(0, strcmp("File<tests.txt> does not exist!", e.what()));return;}Assert::Fail;}
2、文件无法打开
"Fail to open file<" + filename + ">"
TEST_METHOD(test_openfile_exception2) {ofstream file_open("../test.txt");try{open_file("../test.txt");}catch (const std::exception& e){Assert::AreEqual(0, strcmp("Fail to open file<../test.txt>", e.what()));return;}file_open.close();Assert::Fail;}
3 、无法写solution.txt文件
"Error: " + outfile + " cannot open!"
TEST_METHOD(test_openfile_exception2) {ofstream file_open("solutionn.txt");try{open_file("solution.txt");}catch (const std::exception& e){Assert::AreEqual(0, strcmp("Error: solution.txt cannot open!", e.what()));return;}file_open.close();Assert::Fail;}
运行时异常
1、文件中不存在单词
"There is no word in your input!"
ifstream file("tests.txt");
try
{wordList = extract_words(file);file.close();
}
catch (const exception& e)
{Assert::AreEqual(0, strcmp("There is no word in your input!", e.what()));file.close();return -1;
}
2、存在单词环
"Error: There is a circle in the graph."
TEST_METHOD(gen_chain_word_test5){const char* words[] = {"algebra", "apple", "zoo", "elephant", "under", "fox", "panz", "medium", "dog", "moon", "leaf", "trick", "knee"};int len = 13;vector<char*> results(500, 0);// -wint ans = gen_chain_word(words, len, results.data(), 0, 0, 0, false);Assert::AreEqual(-1, ans);// -w -h eans = gen_chain_word(words, len, results.data(), 'e', 0, 0, false);Assert::AreEqual(-1, ans);// -w -t dans = gen_chain_word(words, len, results.data(), 0, 'd', 0, false);Assert::AreEqual(-1, ans);}
3、超过20000个单词链
返回值为-2
"Error: Too many results."
界面模块的详细设计过程
界面实现效果:
使用 python 中的 pyqt5 库,实现了单词链计算程序的 GUI 界面。支持以下功能:
两种导入单词文本的方式
导入单词文本文件
点击 Browse 按钮,选择本地文件(仅支持打开 txt 文件)
# browse local files and dirs def open_file_dialog(self):options = QFileDialog.Options()options |= QFileDialog.DontUseNativeDialogfile_name, _ = QFileDialog.getOpenFileName(self, "Open File", "", "Text Files (*.txt)", options=options)if file_name:with open(file_name, 'r') as file:self.text_edit.setText(file.read())self.file_path_edit.setText(file_name)
直接在界面输入单词
第一个文本框里输入单词,
Clear Input Content
按钮支持清空文本内容self.input_clear_button.clicked.connect(self.text_edit.clear)
点击
Submit
按钮提交为
-n -w -c -h -t -r -j
参数功能提供交互按钮Select Region
部分提供 -n, -w,-c 的单选,从界面上避免了功能型参数的不兼容。h
、t
、j
、r
按钮代表附加型参数,只有h
、t
、j
被选择的情况下右侧的文本框才会显示出来,在内部判断输入是否为单个字母if self.option['t'] == 1:content = self.line_input_t.text()if len(content) != 1:self.throw_error_msg("option '-t' needs a single letter.")returnelse:tail = ord(content[0])
参数的异常情况处理
附加型参数
h
、t
、j
后的文本框输入必须是单个字母if self.option['t'] == 1:content = self.line_input_t.text()if len(content) != 1:self.throw_error_msg("option '-t' needs a single letter.")returnelse:tail = ord(content[0])
ALL
按钮不支持与附加型参数联合使用if self.radio_btn_n.isChecked() and self.option['h'] + self.option['t'] + self.option['j'] + self.option['r'] > 0:self.throw_error_msg("option '-n' should not be used with other options.")
ALL
、WORD
、CHAR
按钮均为被选择self.throw_error_msg("you need to choose one of the upper options:ALL, WORD, CHAR.")
输出结果并提供“导出”按钮
设计了一个只读的文本框,用于显示结果。
Export
按钮支持将结果保存到指定的位置def on_export_btn_click(self):filename, _ = QFileDialog.getSaveFileName(self, 'Save File', os.path.expanduser("~/Desktop"),"Text Files (*.txt)")if filename:with open(filename, 'w') as f:f.write(self.output_text.toPlainText())
显示每次操作命令的运行计时
在运行后,界面会增加一个 QLabel 显示运行时间
run_time = end_time - start_time self.time_label.setText(f"program running time: {run_time:.6f} seconds.\n")
界面模块与计算模块的对接界面模块与计算模块的对接
首先是动态链接库的构建,在 core.h
中定义三个接口:
#ifdef CORE_DLL
#define CORE_EXPOSE extern "C" __declspec(dllexport)
#else
#define CORE_EXPOSE extern "C" __declspec(dllimport)
#endifCORE_EXPOSE
int gen_chains_all(const char* words[], int len, char* result[]);
CORE_EXPOSE
int gen_chain_word(const char* words[], int len, char* result[], char head, char tail, char skip, bool enable_loop);
CORE_EXPOSE
int gen_chain_char(const char* words[], int len, char* result[], char head, char tail, char skip, bool enable_loop);
CMakeLists.txt 中加入命令:add_library(core SHARED src/core.cpp src/core.h src/solver.h src/graph.h src/word.h )
包括所有需要的文件,并设置输出路径set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)
然后在 gui 界面中调用生成的动态链接库 core.dll
:my_dll_ = CDLL(‘core.dll’)
在传递参数时,需要将 python 对象转换为 c++ 类数据类型,主要是使用 ctypes
库
my_dll_ = CDLL(path) # 调用core.dll
func = my_dll_.gen_chain_word # 设置接口函数c_words = [c_char_p(w.encode()) for w in words]
c_arr = (c_char_p * len(words))(*c_words) # const char* words[]c_len = c_int(len_)
c_head = c_char(head)
c_tail = c_char(tail)
c_skip = c_char(skip)
c_enable_loop = c_bool(enable_loop)
result_ptr = (c_char_p * num_rows)() # char* result[]ans = func(c_arr, c_len, result_ptr, c_head, c_tail, c_skip, c_enable_loop) # 调用接口
GUI界面可执行文件的生成指令:pyinstaller --onefile --distpath E:\a_class\2023_ase\pairprogramming\Longest_English_Words_Chain\guibin .\wordchains.py
接口互换
合作的小组成员:王永瑶 19374223、吴湛宇19374006
由于编程时两组均按照课程组提供的接口进行设计,因此在和他们小组进行对接时,并没有出现任何对接问题。
与 core 模块的对接
与命令行模块的对接
操作:
结果:
结对过程
本次结对编程大部分采用线上沟通的方式(腾讯会议),同时也有在线下对关键部分进行了讨论。
在结对过程中,我们先对任务进行了讨论与规划,然后根据两人技术栈进行任务分配,比如一人主要负责核心算法实现和单元测试,另一人主要负责命令行、GUI等部分。然后,在两人开始编程前,要先对各模块的接口进行明确定义,从而减少不必要的重构与修改。通常,我们每完成一个小的阶段性任务后就会一起讨论一下当前进度,是否遇到什么问题,以及规划下一个小阶段的任务。同时,如果有人遇到或发现了 bug,我们两个人也会快速反馈并帮助对方解决,极大减少了后期测试修复的难度。
结对总结
优点
- 两个人编程可以互相发现对方代码的 bug,填补对方的思维漏洞。
- 两个人共同完成一项任务,可以相互监督并防止对方摸鱼。
- 对于可以并行的任务,如算法实现与gui开发,结对可以极大提升编程效率。
缺点
- 如果将任务分解的过碎,反而引起工作效率的下降。
- 如果对对方任务完全不了解,沟通起来效率可能会较低
团队分析
优点 | 缺点 | |
---|---|---|
卓乐 | 对算法较为熟悉;学习新技能较快;熟悉面向对象思想 | 对 C++ 特性及 CMake 不是很熟悉 |
刘畅 | 熟悉 C++ 语言;熟练使用 VS 各种功能;熟悉 python 开发 GUI | 对具体算法不是很熟悉 |
结对项目-最长英语单词链相关推荐
- 软件工程结对项目- 最长英语单词链
项目 内容 这个作业属于哪个课程 2023年北航敏捷软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习现代化的软件开发方法 这个作业在哪个具体方面帮助我实现目标 对结 ...
- 结对项目-最长英语单词链-20373974阮正浩
项目 内容 这个作业属于哪个课程 软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习软件工程的一般方法并实践 这个作业在哪个具体方面帮助我实现目标 实践结对编程方法, ...
- 结对项目——最长英语单词链
项目 内容 这个作业属于哪个课程 https://bbs.csdn.net/forums/buaa-ase2023 这个作业的要求在哪里 https://bbs.csdn.net/topics/613 ...
- [2022软工第三次作业]结对编程项目——最长英语单词链
项目 内容 本作业所属课程 2022年北航敏捷软件工程教学实践 本作业要求 结对编程项目-最长英语单词链 个人课程目标 学习到软件工程的方法论,了解整个过程,并进行亲自实践 本作业在哪个具体方面帮助我 ...
- 结对编程项目——最长英语单词链
目录 结对编程项目--最长英语单词链 1. 项目地址 2. PSP 表格记录花费的时间 3. UML 图 4. 计算模块接口的设计与实现过程 5. 参考资料中 Information Hiding.I ...
- 结对编程项目-最长英语单词链
项目 内容 这个作业属于哪个课程 2022 年北航敏捷软件工程 这个作业的要求在哪里 结对编程项目-最长单词链 我在这个课程的目标是 学习软件工程相关知识,提高自己的代码能力与团队协作能力. 这个作业 ...
- 「软工结对编程」:最长英语单词链
项目 内容 这个作业属于哪个课程 2023年北航敏捷软件工程社区 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 学习有关软件开发的方法论,熟悉基本的软件开发流程,通过" ...
- 2023软工第三次作业-最长英语单词链
结对项目-最长英语单词链 项目 内容 这个作业属于哪个课程 2023北航软件工程 这个作业的要求在哪里 结对项目-最长英语单词链 我在这个课程的目标是 帮助我初步建立软件工程敏捷开发的整体流程和概念, ...
- 北航2022软件工程第三次作业——结对编程(最长英语单词链)
软件工程第三次结对编程作业 项目 内容 这个作业属于哪个课程 北京航空航天大学2022春季软件工程(罗杰 任健) 这个作业的要求在哪里 结对编程项目-最长英语单词链 我在这个课程的目标是 学习软件工程 ...
最新文章
- Centos7上yum安装mongodb4-2
- Atitit.auto complete 自动完成控件的实现总结
- java判断括号是否闭合_【python每日一练】有效括号
- C语言90道试题资料
- Spring 自定义注解,配置简单日志注解
- OpenVINO 中的BFYX解释
- Intellij idea智能提示设置
- HashMap的工作原理--重点----数据结构示意图的理解
- 拓端tecdat|用TensorFlow实现MNIST
- 关于几种图片格式的压缩
- EVO Evaluation of SLAM 4 --- ORB-SLAM3 编译和利用数据集运行
- VC删除IE缓存、COOKIE及浏览记录
- async/await 记录
- 何文江先生出任创意信息集团总经理
- Ubuntu订阅电信物联网平台
- python shp文件_对python 读取线的shp文件实例详解
- 理解深度学习中的卷积
- 24 Three.js的环境光源THREE.AmbientLight
- 一个可以扩容C盘的第三方免费软件
- java设计功能怎么实现代码_Java中的门面设计模式及如何用代码实现
热门文章
- 语法糖(Syntactic sugar)/ 语法盐(syntactic salt)
- 举报两年论文终撤稿!123页PDF实名举报985大学导师学术造假后续来了...
- 骨传导耳机是什么,骨传导耳机对耳朵有什么好处吗
- cesium 学习笔记(三) 在地图上放置3D建筑模型
- 减速机的漏油原因及快速维修方法
- 国内影响已扩散,Wannacry蠕虫勒索软件及修复方案
- 可视化接口管理平台 YApi,让你轻松搞定 API 的管理问题
- mysql图形查询操作 点找面及面找点 Polygon获取中心点坐标 空间地理位置计算
- 005--Keil使用--出现integer conversion resulted in truncation
- pod install 时遇到 Automatically assigning platform `iOS` with version `11.0` on target XXX......