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

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

作者:李震

团队成员:张启立 李震

0 前言

项目内容
这个作业属于哪个课程2023年北航敏捷软件工程
这个作业的要求在哪里结对项目-最长英语单词链
我在这个课程的目标是初步尝试了解结队编程,体会结队编程的优势
这个作业在哪个具体方面帮助我实现目标让我亲自尝试结队编程,让我感受到结队编程的优势

1 项目地址

  • 教学班级:周四班
  • 项目地址:https://github.com/xiaolizhang77/WordList/tree/master

2 项目计划

PSP表格
PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划10030
· Estimate· 估计这个任务需要多少时间2010
Development开发5001000
· Analysis需求分析 (包括学习新技术)800600
· Design Spec· 生成设计文档300600
· Design Review· 设计复审 (和同事审核设计文档)200100
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)300250
· Design· 具体设计400250
· Coding· 具体编码300600
· Code Review· 代码复审300200
· Test· 测试(自我测试,修改代码,提交修改)600300
Reporting报告300500
· Test Report· 测试报告2060
· Size Measurement· 计算工作量2020
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划10030
合计42604550

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

  1. Information Hiding: 信息隐藏指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。一方面,这可以通过使用访问修饰符(如 public,private 和 protected)来实现。另一方面,我们通过将功能进行细分,分别对其进行封装,这样,我们在设计过程中,只关注如何功能如何调用,而不关注其实现,从而更好地维护代码的封装性和安全性,并避免不必要的耦合。
  2. Interface Design: 良好的接口设计可以提高代码的可读性和可维护性。在设计之前,我们便通过讨论等步骤,事先互相确认了共同设计接口,确保它们清晰地定义了模块之间的交互方式,并遵循 SOLID 原则中的接口隔离原则,同时将其记录到我们的设计文档中,确保我们能按照该接口进行设计实现。
  3. Loose Coupling: 松散耦合是指模块之间的依赖性最小化。在我们的设计过程中,我们互相遵循 SOLID 原则中的依赖反转原则,确保模块之间仅依赖于抽象接口而不是具体实现。

4 计算模块接口的设计与实现过程

第一阶段目标:实现一个命令行的计算最长英语单词链的程序
总体思路:

​ 将输入的单词按照首尾字母进行分类,也就是26*26个类别。通过首尾字母是否有单词,可以构成一个有向图,程序以找到有向图中最长路径为目标。

需求1基本思路:

​ 使用dfs计算每两个字符之间的链有哪些,通过这些链经过的节点,结合单词表进行排列组合,找出所有的链。

​ 基本步骤:创建一个二维数组表示字母之间的边(edge),以及一个结构体数组(word_list)来存储单词。将输入的单词添加到 word_list 中,同时更新 edge 数组。使用 find_chain 函数在 edge 数组中查找所有可能的路径,然后将结果存储在 allChain 变量中。针对每一个路径,使用 print_word_chain 函数打印单词链。在这个过程中,还可以根据需要满足的条件进行筛选。将最终的单词链结果存储在 chain 变量中。

需求2基本思路:

​ 使用dijkstra算法计算每个字符为起始字符时最长链长度,然后找出起始字符最长的一条作为结果。longest_chain使用动态规划计算每个节点作为终点的最长链长度,并逆序构建路径。function2And4使用深度优先搜索(DFS)遍历有向图,从起点开始探索所有可能路径,直到到达终点。在遍历过程中,比较并更新最长路径。最后,将找到的最长路径及其长度分别存储到给定的指针变量中。

需求3基本思路:

​ 和需求2基本一致,只不过需求2使用的有向图边取值为0或者1,而需求3有向图为单词长度。

需求4基本思路:

​ 当有首尾字符规定时,使用dfs计算符合要求的链有哪些,结合单词表找到最长链。

需求5基本思路:

​ 只需要将有向图中对应字符的边设置为0即可。

需求6基本思路:

​ 使用深度优先搜索(DFS)方法遍历 map2d 中的单词,寻找符合条件的最长单词链。使用 searchsearchUseInApi 函数根据指定的条件(如指定的开头字母 para_h,结尾字母 para_t,以及是否排除指定的字母 para_j)调用 dfs 函数,并记录找到的最长单词链。我们提供了一个 API 函数 searchUseInApi,用于在外部调用时传入指定的单词列表、开头字母、结尾字母等条件,并返回最长单词链的结果。

第二阶段

项目封装函数如下:

int gen_chains_all(char *words[], int len, vector<vector<string>> &result);

int gen_chain_word(char *words[], int len, vector<string> &result, char head, char tail, char reject, bool enable_loop);

int gen_chain_char(char *words[], int len, vector<string> &result, char head, char tail, char reject, bool enable_loop);

5 编译无警告

以下是项目编译过程中无警告截图:

在这里插入图片描述

6 UML 图

我们的代码主要采用了面向过程的设计方式,故不便展示代码类的继承实现,这里我们主要展示文件组织形式和核心代码函数调用过程:

  • 文件组织结构:

在这里插入图片描述

  • 函数调用关系:

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

在需求1的计算过程中,消耗最大的函数是 findPathsprint_word。这些函数中包含了递归调用,可能导致大量的计算。为了提高性能,我们尝试了以下策略:

  1. 优化递归:递归可能导致大量的函数调用。尝试将递归转换为迭代,或者使用动态规划或记忆化搜索,以避免重复计算。
  2. 剪枝:在 findPaths 函数中,在递归过程中检查某些条件,以便在不满足条件时提前结束递归。例如,如果找到的路径长度已经超过当前已知最短路径,可以停止继续搜索该路径。
  3. 数据结构优化:使用更高效的数据结构来存储和处理数据。例如,可以考虑使用哈希表、平衡二叉搜索树等数据结构来提高查找、插入和删除操作的性能。

在需求2和3的计算过程中,消耗最大的函数可能是 dfs。这个函数使用递归来查找指定起点和终点之间的最长链。为了提高性能,我们尝试了以下策略:

  1. 优化递归:递归可能导致大量的函数调用。尝试将递归转换为迭代,或者使用动态规划或记忆化搜索,以避免重复计算。
  2. 剪枝:在 dfs 函数中,在递归过程中检查某些条件,以便在不满足条件时提前结束递归。例如,如果找到的路径长度已经超过当前已知最长路径,可以停止继续搜索该路径。

在需求6的计算过程中,最消耗资源的函数是 dfs()search()。为了提高程序性能,我们采取以下策略:

  1. 剪枝:在 dfs() 函数中,我们在搜索过程中进行剪枝,通过剪枝,我们可以减少无效搜索,从而提高程序性能。
  2. 使用动态规划:search() 函数的实现可以通过动态规划方法进行优化。将问题分解成子问题,并存储子问题的解,避免重复计算。对于这个问题,我们为每个字母和单词长度建立一个表格,存储从该字母开始、单词长度为指定长度的最长单词链。这样,在计算过程中,我们可以查表获得子问题的解,而不是重复计算。

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

Design by Contract(契约式设计,缩写为 DbC),一种设计计算机软件的方法。这种方法要求软件设计者为软件组件定义正式的,精确的并且可验证的接口,这样,为传统的抽象数据类型又增加了先验条件、后验条件和不变式。

Code Contract(代码协定),是微软在.NET框架中设计方法,它提供了在 .NET Framework 代码中指定前置条件、后置条件和对象固定的方法。它们的主要优缺点如下:

优点:

  1. 更系统、清楚、简单的设计。契约式设计鼓励程序员思考诸如“例程的先验条件是什么”这样的问题,这样有助于程序员理清概念。
  2. 契约式设计可以帮助开发人员更加清晰地定义代码的期望行为和限制条件,可以提高代码的可读性和可维护性。
  3. 提高可靠性。编写契约可以帮助开发者更好地理解代码,同时契约也有助于测试(契约可以随时关闭或者启用)。
  4. 契约式设计可以帮助开发人员更好地理解和处理代码之间的依赖关系,可以减少代码的耦合度,从而提高代码的重用性和扩展性。

缺点:

  1. 需要时间学习撰写良好契约的思想和技术。目前的主流语言中,只有Eiffel支持契约,而且仅仅支持顺序式程序(sequential program)的契约。
  2. Code Contract需要使用专门的工具来进行契约条件的定义和检查,可能需要额外的学习成本和开发环境支持。
  3. 契约式设计和Code Contract的应用范围受到一定限制,不适用于所有类型的代码和应用场景。

如何融入:

  1. 在讨论需求和设计方案时,我们可以一起讨论并明确代码应该满足的契约条件,包括前置条件、后置条件和不变量等等。
  2. 在设计文档时,我们可以初步撰写代码的契约条件。
  3. 在代码审查和改进过程中,我们可以共同检查和完善契约条件,从而提高代码的可读性和可维护性。

9 测试

文件结构

在这里插入图片描述

​ 在我们的项目中,测试部分的目的是验证程序功能的正确性。具体来说,测试部分的核心是对 libword_list.dll 动态链接库中定义的函数进行测试。这个库提供了一系列用于处理单词链的功能。项目中的测试部分包含以下几个文件:

  1. CMakeLists.txt:定义了如何构建和组织测试部分的 CMake 配置文件。
  2. test_wordlist.cpp:包含了用于测试 libword_list.dll 的主要功能的主程序。
  3. word_list.h:定义了 libword_list.dll 的接口和相关导入导出宏。
  4. readWordFromFile.cpp:实现从文件中读取单词的功能。

下面我们详细分析这几个文件的作用和如何进行测试:

CMakeLists.txt

​ 这个文件定义了如何构建测试目标。它首先设置了测试目标名称为 wordlist_test,然后添加了所有相关的源文件。之后,它创建一个可执行文件目标,并设置其输出目录。接下来,包含了头文件目录,并链接了 libword_list.dll 动态库。最后,它启用了 C++11 标准。

test_wordlist.cpp

​ 这个文件包含了测试的主程序。主要步骤如下:

  1. 输出 “test start…” 提示测试开始。
  2. 调用 readWordsFromFile 函数从 “test.txt” 文件中读取单词,保存在字符串数组 strings 中。
  3. 调用待测试的函数,将结果保存在 result 变量中。
  4. 输出 “test end!” 提示测试结束。
word_list.h

​ 这个文件定义了 libword_list.dll 的接口。它首先根据编译器设置导入导出宏,然后包含了所有需要的头文件和命名空间。接着,它声明了 libword_list.dll 中导出的函数,如 gen_chains_all, gen_chain_word, gen_chain_char

测试架构

我们采用googletest工具对我们的代码进行测试,以下是实现思路:

  1. 在CMakeLists.txt中,包含googletest的头文件和库文件路径。

    bashCopy codeinclude_directories(${CMAKE_SOURCE_DIR}/lib/googletest/include)
    link_directories(${CMAKE_SOURCE_DIR}/lib/googletest/cmake-build-debug/lib)
    
  2. 在CMakeLists.txt中,添加test子目录,这将包含测试代码。

    scssCopy code
    ADD_SUBDIRECTORY(test)
    
  3. test目录中,创建测试源文件test_wordlist.cpp和头文件word_list.h。在test_wordlist.cpp中,编写针对项目功能的测试用例。

  4. 在CMakeLists.txt中,添加测试源文件到一个变量中。

    bashCopy codeset(TEST_SOURCES
        test/test_wordlist.cpp
        test/word_list.h
    )
    
  5. 在CMakeLists.txt中,添加一个可执行的测试目标word_list_test,并将测试源文件作为参数。

    scssCopy code
    add_executable(word_list_test ./test/test_wordlist.cpp)
    
  6. 链接项目中的库以及googletest的库到测试目标word_list_test

    scssCopy codetarget_link_libraries(word_list_test MyLibrary)
    target_link_libraries(word_list_test gtest gtest_main)
    
  7. 编译项目时,将同时编译测试代码。在编译完成后,可以运行word_list_test可执行文件,以运行针对项目功能的测试用例。

测试用例

我们使用两种方式产生测试用例,并且将测试样例分为有环和无环,以下是部分测试样例:

1.手搓无环样例:

"uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuv","uuuuv","uuv","vw","wx","xy","yz","yy","aaaaab","aab","bbbbbbc","cccg","cccd","dg","yz","yy","aaaaab","aab","bbbbbbc","cccg","cccd","dg","yz","yy","aaaaab","aab","bbbbbbc","cccg","cccd","dg","gggggj","hi","ij","jm","mn","opq","opq","opq","opq","opq","op","oo","bb"

2.自动生成无环样例:

"mampikdfgfdazsi", "nyqaffouyi", "ftcmodfvab", "ysadyjbqjn", "ytofdatiic", "ucbbpgldjt", "rpvcjfifda","yzaspfqqhl", "rjzbyezhri", "tcbvwvlcsdfdfdfsdfsdfghjksdfghjklsdfghjklmk", "rmobyrfzad", "cgbuxirxka", "qzkfztenzl", "xnmwvbptte","kwnjsdfsrpwmi", "kchpcaamde", "vvwrkckcyg", "xywwslplun", "xnghndhace", "seeqprvqao", "wtbrjfujid","zftjdfdzcbwva", "xjsbrklhod", "lgmpnndykj", "oselbndknc", "pjsxolpfwi", "karyajsnmi", "tyxrtjsfgr","womlfmmbtu", "qtqajuhmqa", "zmdojgvkbc", "lddwlzpisb", "zwvdqcdjqw", "xbppktxwaw", "wlhjmusfzk","wgktdyjmjg", "jbwmsdfejcjkb", "udksdfoollyxk", "rfipdfuqfj", "ztwqjdqzgm", "udmjcehedc", "tysjpkyczo","vdbntdfrba", "spbcnyibpd", "xqldhshojo", "ketesrwljj", "sjazrwsuah", "qzxaqeqwxp", "kscdthxkpb", "ztmsdfprhtmks", "ynvqpuggfd", "ryynaozysl", "miwhzzvxof", "nblgqayowf", "thqsrqhcqa", "eiiucfhbyd","uovoiyglec", "iuufqbjsdfpzg", "sgpagbnvmk", "qvhumtzvqg", "ydsvqlhmrm", "lgfopmnkma", "qvdemjdlkn","ecrcrbtfia", "zfvdfgyhahth", "nisdfswonbcbf", "pqzygcqhif", "zhkppannvn", "ollnjzjssj", "jpgkdxrtcc","zezjjmfrsdzi", "zskilnqcfi", "niimmanhei", "wucmghusku", "odsoyfdrwa", "pobfgkkmti", "zmnvvgfnvo","mewwesdfylnjg", "yanypvwemb", "ytgysdfypxpcn", "rkquecuruj", "kxigidiwuj", "wquaychqea", "tlclyiknac","yxefklbnys", "ffwiyfrgpb", "xfnvbqpsdftjutcr", "ttwfzntpzl", "nxvszgwlbj", "zonaraujbc", "tihbhnfrbb", "tnjaegslsc", "ruskfsdfubaaq"

3.手搓有环样例:

"uuuuv","vw","wx","xy","yz","yy","aaaaab","aab","bbbbbbc","cccg","cccd","dg","gggggj","hi","ij","jm","mn","opq","op","oo","bb","za","ga","gb","sb","lj","ssl","somethings"

考虑到自动生成无环样例较为困难,python源码放置在项目test文件夹中。

测试覆盖率

以下是核心代码的测试覆盖率,因为编译器会插入一些分支代码防止空指针访问等错误行为,这些分支一执行程序就shut down了,但是gtest在汇编的层面认不出来这些代码是我们的还是编译器加的。因此,func.cpptask1.cpp两个文件的分支覆盖率不是很理想。尽管如此,我们的分支覆盖率平均也是远超90%,代码覆盖率接近100%

在这里插入图片描述

10 异常处理

unknown_parameter 异常类:用于处理存在未知参数的情况。当用户输入的参数中存在一个或多个未知的参数时,该异常会被抛出。

​ 单元测试样例:

# 输入参数
-n input.txt -m -a

# 期望结果
xxx\word_list.exe: unknown option -- m
xxx\word_list.exe: unknown option -- a
There are unknown parameters.

none_parameter 异常类:用于处理没有参数的情况。当用户没有输入任何参数时,该异常会被抛出。

​ 单元测试样例:

# 输入参数(空)

# 期望结果
There are no parameters.

incompatible_parameter 异常类:用于处理不兼容的参数。当用户输入的参数不兼容时,例如同时包括-n-w参数时,该异常会被抛出。

​ 单元测试样例:

# 输入参数
-n input.txt -w input.txt

# 期望结果
The parameters are not compatible.

single_additional_parameter 异常类:用于处理只包含附加参数的情况。当用户只输入一个或多个附加参数,而没有输入功能性参数时,该异常会被抛出。

​ 单元测试样例:

# 输入参数
-h a -t m

# 期望结果
Additional parameters cannot exist alone.

repeat_parameter 异常类:用于处理重复参数的情况。当用户输入了重复的参数时,该异常会被抛出。

​ 单元测试样例:

# 输入参数
-n input.txt -rr

# 期望结果
Duplicate parameters.

format_filename 异常类:用于处理文件名格式不正确的情况。当用户输入的文件名格式不正确时(例如输入文件不是以.txt结尾),该异常会被抛出。

​ 单元测试样例:

# 输入参数
-n input.docx

# 期望结果
The filename format is incorrect.

none_exist_file 异常类:用于处理文件不存在的情况。当用户输入的文件名不存在时,该异常会被抛出。

​ 单元测试样例:

# 输入参数(文件不存在)
-n input.txt

# 期望结果
The file does not exist.

format_parameter_content 异常类:用于处理附加参数内容格式不正确的情况。当用户输入的附加参数内容不符合规定的格式时(如-h参数内容包括多个字母),该异常会被抛出。

​ 单元测试样例:

# 输入参数(文件不存在)
-n input.txt -h ab

# 期望结果
Additional parameter content can only be one letter.

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

​ 我们使用Python语言中的Tkinter库作为基础组件库,并使用Ttkbootstrap库进行美化设计。我们将界面分成几个基础功能区,使用Frame组件,使其看起来简洁突出。我们使用Tkinter中的TextFileDialog功能来获取数据并实现数据的导入,并且使用Toplevel组件来打印输出结果,并将结果保存。整个过程中,我们注重设计的细节和用户体验,确保整个应用程序的流畅性和易用性。

下面是部分代码:

...

dataFrame = ttk.LabelFrame(root, text="Import Data", labelanchor="n")
dataFrame.place(relx=0, rely=0.12, relwidth=1, relheight=0.3)

dataBFrame = ttk.Frame(dataFrame)
dataBFrame.place(relx=0, rely=0, relwidth=0.3, relheight=1)

dataHFrame = ttk.Frame(dataFrame, name="input")

...

dataFFrame = ttk.Frame(dataFrame, name="file")

...

paraFrame = ttk.LabelFrame(root, text="Choose Parameter", labelanchor="n")
paraFrame.place(relx=0, rely=0.45, relwidth=1, relheight=0.25)

eParaFrame = ttk.LabelFrame(paraFrame, text="essential parameter", style="secondary", labelanchor="n")
eParaFrame.place(relx=0.01, rely=0, relwidth=0.485, relheight=0.95)

aParaFrame = ttk.LabelFrame(paraFrame, text="additional parameter", style="secondary", labelanchor="n")
aParaFrame.place(relx=0.505, rely=0, relwidth=0.485, relheight=0.95)

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

我们采用了ctypes库来导入我们的计算模块,为了解决数据的传入传出问题,我们在原先api的基础上对其进行了进一步包装。

  • Python端

    我们基于ctypes.Structure类,定义了两个结构体来接受计算模块的返回值。

    class Ret(Structure):
        _fields_ = [
            ('dataList', c_char_p * 20000),
            ('dataNum', c_int),
            ('dataRes', c_int)
        ]
    
    
    class RetTwo(Structure):
        _fields_ = [
            ('dataList', (c_char_p * 1000) * 20000),
            ('dataNumOne', c_int * 20000),
            ('dataNumTwo', c_int),
            ('dataRes', c_int)
        ]
    

    之后,我们通过ctypes.windll方法导入动态链接库,并调用我们实现的api接口函数。调用结束后,通过win32api库来释放内存,防止内存泄露。

    from ctypes import *
    import win32api
    
    libc = windll.LoadLibrary("xxx")
    func = libc.xxx
    
    ...
    
    win32api.FreeLibrary(libc._handle)
    
  • C++端

    由于C++中vector的问题,我们在原先api的基础上再次进行了包装,创建了api_cpy.cpp文件。(下面是部分内容)

    结构体定义:

    struct cpyRet {
        char *dataList[20000];
        int dataNum;
        int dataRes;
    };
    
    struct cpyRetTwo {
        char *dataList[20000][1000];
        int dataNumOne[20000];
        int dataNumTwo;
        int dataRes;
    };
    

    api函数:

    extern "C" __declspec(dllexport)
    cpyRetTwo *gen_chains_all_cpy(char **words, int len) {
        vector<vector<string>> result;
        auto *retResult = (cpyRetTwo *) malloc(sizeof(cpyRetTwo));
        int size = gen_chains_all(words, len, result);
        retResult->dataNumTwo = result.size();
        retResult->dataRes = size;
    
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < result[i].size(); j++) {
                retResult->dataList[i][j] = (char *) result[i][j].c_str();
            }
            retResult->dataNumOne[i] = result[i].size();
        }
        return retResult;
    }
    
    
    extern "C" __declspec(dllexport)
    cpyRet *
    gen_chain_word_cpy(char **words, int len, char head, char tail, char reject, bool en_loop) {
        vector<string> result;
        auto *retResult = (cpyRet *) malloc(sizeof(cpyRet));
        int size = gen_chain_word(words, len, result, head, tail, reject, en_loop);
        retResult->dataNum = result.size();
        retResult->dataRes = size;
        for (int i = 0; i < size; i++) {
            retResult->dataList[i] = (char *) result[i].c_str();
        }
        return retResult;
    }
    
    extern "C" __declspec(dllexport)
    cpyRet *gen_chain_char_cpy(char **words, int len, char head, char tail, char reject, bool en_loop) {
        vector<string> result;
        auto *retResult = (cpyRet *) malloc(sizeof(cpyRet));
        int size = gen_chain_char(words, len, result, head, tail, reject, en_loop);
        retResult->dataNum = result.size();
        retResult->dataRes = size;
        for (int i = 0; i < size; i++) {
            retResult->dataList[i] = (char *) result[i].c_str();
        }
    
        return retResult;
    }
    

实现界面:在这里插入图片描述

在这里插入图片描述

13 描述结对的过程

  1. 讨论需求和目标:我们首先在微信中讨论了任务的要求和目标,以确保彼此理解任务的重点和细节。
    在这里插入图片描述

  2. 设计和规划:在确定任务和工具后,我们在微信上共同制定一个开发计划和任务分工,以确保我们能够在预定时间内完成任务。我们一起讨论解决问题的方法和技术,并设计相应的程序结构和算法。
    在这里插入图片描述

  3. 开始编码:我们编写代码时,编码的同学输入代码。而另一个同学监督代码的编写并且复核代码,帮助发现潜在的错误并提出改进意见。在编码的过程中,我们一直保持良好的沟通,以确保彼此的理解和协作。
    在这里插入图片描述

  4. 进行测试和调试:当代码编写完毕后,我们进行了测试和调试,以确保程序能够正常工作。我们共同讨论测试方案和测试用例,并一起运行测试程序。

  5. 代码审查和改进:在测试和调试之后,我们共同检查代码,找出可能存在的错误和问题,并提出了改进意见。同时,我们还进行了代码重构和优化,以提高程序的质量和性能。

14 结对编程的优点和缺点

优点:

  1. 增强代码和产品质量,并有效的减少BUG:因为两个人在设计过程中会互相检查彼此的代码,并及时纠正错误。
  2. 加速了开发速度:结对编程可以促进团队成员之间的交流和协作,使开发速度更快。
  3. 降低学习成本。 一边编程,一边共享知识和经验,有效地在实践中进行学习。
  4. 在编程中,相互讨论,可能更快更有效地解决问题。

缺点:

  1. 部分情况下可能会减低开发速度。由于两个人的编程风格、开发速度的不同等因素影响,可能需要花费更多的时间来协调,这可能会导致开发速度降低。
  2. 两个人在一起工作可能会出现工作精力不能集中的情况。程序员可能会交谈一些与工作无关的事情,反而分散注意力,导致效率比单人更为低下。

15 界面模块,测试模块和核心模块的松耦合【附加题】

我们也和其他组进行了模块交换,由于我们两个小组已经规定好了调用接口,故在测试过程中我们在代码方面基本没有问题。

但是,由于我们组采用的是pythonctypes库,在实际调用其他组的核心模块时出现了Module Not Found的报错。经过debug,发现是在windows下缺少部分动态链接库的问题,添加后,发现基本没有问题。下面这是输出的一个例子:

在这里插入图片描述

附合作小组:周奕龙(20373892)林子杰(20373980)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值