BUAA_OO_2024 Unit 3 BLOG

邝晓文-22373445 学生 2024-05-16 22:51:18

BUAA_OO_2024 Unit 3 BLOG

任务简介:建立社交关系网络结构,需要完成的任务为实现简单社交关系的模拟和查询,学习目标为入门级 JML 规格理解与代码实现。

测试过程

黑箱测试 & 白箱测试

  • 黑箱测试(Black-box Testing)
    在黑箱测试中,我们不关心程序内部的逻辑,只关注程序输入和对应输出的正确性问题。即将程序视为一个“黑箱”,通过投喂数据,观察其外部表现来测试程序是否符合预期。

  • 白箱测试(White-box Testing)
    与黑箱测试正好相反,白箱测试关注程序的内部逻辑,这就要求测试者需要了解程序的内部结构和代码,并以此为基础构造测试,检测程序中特定路径、分支的正确性。

  • 黑箱测试 or 白箱测试
    两种测试方法各有优劣,黑箱测试易于理解、更接近实际使用场景但可能无法覆盖所有的内部路径,白箱测试能达到高覆盖率、有助于发现和优化性能问题但过度关注细节成本较高
    在实际中,我们应该结合使用两种测试方法,例如在进行单元测试时使用白箱测试,验证代码的内部逻辑;在集成测试时可以结合使用黑箱测试和白箱测试,确保模块间的接口和整体功能的正确性。

单元测试、功能测试、集成测试、压力测试、回归测试

  • 单元测试(Unit Testing)
    单元测试是针对代码中最小的可测试部分(一般为函数/方法)的测试。

  • 功能测试(Functional Testing)
    功能测试关注程序的功能需求是否被满足,验证程序的功能是否按照规定正常工作。

  • 集成测试(Integration Testing)
    集成测试一般在单元测试之后进行,其目的是检查不同模块或服务之间是如何协同工作,即确保各个部分的接口能够正确地交互

  • 压力测试(Stress Testing)
    用于检测程序在极端条件下的性能,比如说高负载、高并发等,这些在常规测试条件下难以出现,压力测试就可以帮助找到程序在极限情况下可能遇到的性能问题。

  • 回归测试(Regression Testing)
    回归测试往往在程序发生更改后进行,确保新增部分不会破坏现有功能,通常包括对之前通过的功能测试的重复。

  • 在测试时,对每个方法进行单元测试、每个功能需求单独验证当然是保证代码正确性的一种方法,但随着代码体量和复杂度的上升,此种方式成本高且难以实现,所以我们可以有针对性地进行测试,例如对具有重要功能或实现复杂易出错的方法进行单元测试,确保每个分支的实现正确性。同时可以应用测试金字塔原则,即大量的单元测试,适量的集成测试,少量的端到端测试,这也可以帮助我们达到较好的测试效果。

数据构造的策略

  • 边界值测试
    构造接近边界条件的数据,如最值、负值等。
  • 随机生成
    可以借助随机数生成器来构造大量不同的测试数据。
  • 针对新增模块
    由于我们是迭代开发,所以在我们可以在每次测试中多测试新增部分,检测其实现的正确性。

架构设计

总体而言按照JML所给出的框架和指导书要求实现,其社交网络、TagMessage等设定也符合我们生活认识,不再展开说明。为提高代码的复用性,新增Counter计数器类,在异常类中使用。此外,由于Network中涉及到很多与图相关的算法,新增Graph类用于处理相关问题。

容器选择

在本次构建中,应格外注意容器的选择,虽说ArrayListHashSet似乎都可以完成存、查、取、删等操作,但是实现形式却千差万别,ArrayList在进行很多操作时都需要进行遍历,有O(n)的复杂度,在进行大数量的数据操作时,很容易超时。所以,对于数据的存储,我们可以选择HashSet等容器提高效率,同时注意到本次作业经常用到id查询,可以利用HashMap建立idobject之间的映射,提高我们的查询效率。
同时,我们可能需要进行堆优化或者实现队列操作,这些都需要选择合适的数据结构,一般而言,这些都可以通过查阅网络资料得到答案,只是需要我们在实现时少一些“理所应当”,多一些考虑即可。

图模型

本单元的关系网涉及到很多图论相关的知识点,我们的Person就是一个个Nodepersons之间的关系可用一条条无向边来表示,通过建立Graph类,我们可以将Network中的相关问题分担到Graph中解决。此外,在本单元作业中,也运用了被多次提及的并查集
Graph的具体属性见下述代码:

public class Graph {
    private HashMap<Integer, Integer> parent; //存放根节点
    private HashMap<Integer, Integer> rank; //实现按秩合并
    private HashMap<Integer, HashSet<Integer>> relatives; //存储邻友关系
    private int blockSum;
    private int tripleSum;
}

Graph中,维护数据的方法主要有四个

add

public void add(int id) {
      if (!parent.containsKey(id)) {
          parent.put(id, id);
          rank.put(id, 0);
          this.blockSum++;
          relatives.put(id,new HashSet<>());
      }
}

在进行ap操作时,我们将person加入到网络中,并将其根节点设为自己、秩设为0

merge

  public int merge(MyPerson person1, MyPerson person2) {
        relatives.get(person1.getId()).add(person2.getId());
        relatives.get(person2.getId()).add(person1.getId());
        int id1 = person1.getId();
        int id2 = person2.getId();

        int fa1 = find(id1);
        int fa2 = find(id2);
        if (fa1 == fa2) {
            return -1;
        }

        int rank1 = rank.get(fa1);
        int rank2 = rank.get(fa2);
        if (rank1 < rank2) {
            parent.put(fa1, fa2);
        }
        else {
            if (rank1 == rank2) {
                rank.put(fa1, rank1 + 1);
            }
            parent.put(fa2, fa1);
        }
        return 0;
    }

在进行ar操作时,同步修改person1person2的亲友关系,同时根据二者的根节点判断是否需要进行并查集的合并操作,在进行合并操作时,我们将深度较小的部分合并到深度较大的部分,即按秩合并,保持整体的平衡性

find

public int find(int id) {   //查找根节点
        int temp = id;
        while (temp != parent.get(temp)) {
            temp = parent.get(temp);
        }

        int now = id;
        while (now != temp) {
            int fa = parent.get(now);
            parent.put(now, temp);
            now = fa;
        }
        return temp;
    }

在执行isCircle查询命令时,我们会用到find操作,通过查询两个节点的根节点是否相同,我们可以知道两个节点是否存在于同个并查集中,在查询过程中,我们实现了路径压缩的优化,其基本思想时将节点的父节点直接设为根节点,将整个查找路径压缩成一条路径,减小树的深度,提高后续操作的效率。

lift

public void lift(MyPerson myPerson1, MyPerson myPerson2) {
        //删除并查集中的关系
        int id1 = myPerson1.getId();
        int id2 = myPerson2.getId();
        relatives.get(myPerson1.getId()).remove(myPerson2.getId());
        relatives.get(myPerson2.getId()).remove(myPerson1.getId());
        HashSet<Integer> visited = new HashSet<>();
        dfs(myPerson1.getId(),visited);
        for (int node:visited) {
            parent.put(node,myPerson1.getId());
            rank.put(node,1);
        }
        if (!visited.contains(myPerson2.getId())) {
            HashSet<Integer> visited2 = new HashSet<>();
            dfs(myPerson2.getId(),visited2);
            for (int node:visited2) {
                parent.put(node,myPerson2.getId());
                rank.put(node,1);
            }
        }
    }

常规并查集一般只存在上面的三个操作,但由于在作业设定中,modifyRelation可能会导致关系的解除,所以我们要增加lift方法在删除关系时对Graph进行维护,由于删除关系后二者可能仍处于同个并查集中,所以删除时需要进行多重判断,相比于前几种操作更为复杂一些,最终实现方法如下,在删除关系时:

  • 首先在亲友网(relatives)中将二者的关系删除
  • 通过深度优先搜索,对与Person1同处一个并查集中的Person进行染色
  • 将与Person1同处于一个并查集中的点的根节点设置为Person1,并将秩设为1
  • 查询Persona2是否被染色,若未被染色,重复Person1的操作,将原并查集分裂为两个新的并查集。

通过上述四个操作,我们就可以对我们建立的图模型进行维护,同时,我们也可以在Graph中实现bfsdfs等方法,查询两个节点间的最小距离、维护tripleSumblockSum,具体实现可见下一节。

性能问题与修复

在最初接触到JML的时候,我把它当成Java语言与自然语言之间的一种临时转换形式,认为我们需要实现的只是将JML逐条翻译,甚至困惑于为什么要用一种比自然语言更不易理解的语言形式来表达需求。但是,这种理解不仅有违JML的初衷,并且如果只是单纯依照JML语句实现方法,在性能上会出现很多问题,所以应有意识地将规格与实现分离

规格与实现分离是软件工程中提出的一个概念,它强调将软件的规格说明与其实现代码分开。对于JML而言,JML提供了明确和形式化的规格,约束方法的行为、减少歧义,便于开发者、测试人员之间的沟通,其在没有实现代码的情况下也可以被审查和测试,甚至可以通过相关工具对JML规格进行静态检查,发现可能的错误,而无需运行程序。这说明规格在一定程度上是独立于实现代码的,实现只需要满足规格中限制,至于具体的实现逻辑与形式则可以自由发挥(即有约束的自由)。

在三次迭代作业中,我们实现了多种查询操作

query block sum

查询社交关系图中连通块的总个数,若在每次查询时都进行一次遍历查找,查询代价较大,故可以通过维护变量blockSum来实现该功能,具体维护操作为

  • 在往关系网中新增Person时执行blockSum++操作
  • 在新增关系时,若Person1和Person2原属于不同的连通块,则blockSum自减
  • 在解除关系时,需要检查Person1和Person2是否仍在一个连通块中,可以通过从Person1出发,对Person1可以“联系”上的人进行“染色”,之后检查Person2是否被染色,若未被染色,则说明由于关系的解除,原连通块分裂为两个连通块,需要将blockSum数量加1。

通过上述维护操作,我们在qbs时只需要将blockSum的值返回即可,提高了查询的性能,同理,通过维护变量,我们可以对qts、qtvs等操作进行优化。

query shortest path

实际上是不加权图中的最短路径问题,可以使用bfs实现,由于查询的起、终点已知,可以用双向bfs进行优化。
双向bfs与bfs类似,只是从两端同时出发,每次依次从起、终点向外扩展一层,并在节点扩展时查询是否被另一方访问过,即查询结束的条件是两方“相会”
tips:依次扩展一层并不是依次扩展一个节点,在我最初实现中按顺序依次从两方队列中取出一个Node进行扩展,得到了错误的最短路径长度,这是因为没有按层扩展,例如与起点距离为2的Node个数可能有多个,就需要将所有Node扩展后再从终点进行扩展,才能保证相会时得到正确的路径长度。

除查询操作外,我们也可以对其他类型的操作进行优化,如

clear notice

Person接收到的NoticeMessage进行接收,一般而言需要遍历判断,若为Notice则进行删除。我们可以在每个Person中置一个脏位receivedNotice,表示在上一次cn后或自初始时是否接收到NoticeMessage,若未接收到,则在cn时直接返回而不遍历,同时在每次cn后将receivedNotice置为false。同理,我们也可以利用脏位进行延迟重建等操作,这也是提升性能的一种方式。

Junit

利用规格信息设计Junit测试

Step1 理解JML规范 获取信息
通过阅读JML规格信息理解其定义的方法行为,注意方法的前置条件、后置条件和不变式。

Step2 生成测试用例
根据JML中的描述,可以根据前置条件等生成相应的用例,用例应包括正常情况和边界情况,若会抛出异常,也应覆盖到异常情况。

Step3 编写Junit测试方法
为测试用例编写Junit测试方法,确保测试覆盖了JML规范中的关键点。使用断言验证代码是否满足要求,例如使用assertEquals对后置条件进行检查,对于不变式也要相应验证对象状态在方法执行前后是否保持不变。

效果分析
Junit一般用于编写和运行可重复的单元测试,允许对代码的各个部分进行隔离测试,从这一点上来说,其用于检测代码实现与规格的一致性是合适的。
针对单个方法编写Junit测试,检测其是否实现了JML规格中定义的所有功能点,同时还可以利用断言等功能检查后置条件和不变式等。通过测试的覆盖率分析,还可以找出未被测试的规格部分,进行进一步检测。
从三次作业的实际运用效果来说,使用Junit测试检验代码实现与规格的一致性的效果也是不错的,按照上述方法构造测试,一般可以发现在代码实现中存在的问题。

学习体会

在本单元中,初步认识了JML语言,正如各处所讲,它的主要用途是为Java代码提供精确的规格描述,或许在本单元推送上第一次读到这句话时,我对它的理解只停留在字面意义,然而经过三次作业的不断疑惑、质疑和理解,我似乎对它有了更多一点的感触。或许在实际应用中,JML的使用场景并不那么广,但是在这个单元中学习到的逻辑思维、单元测试思想却可以有广泛而长久的帮助。面对有时比代码还要长的规格,也能够耐心阅读并理解,并在阅读过程中思考以何种形式实现更为合适。
或许经过OO前两个单元的洗礼,突然的难度断层会让有我们些许不习惯,同时因为JML的某些特质,甚至会存在对JML这一单元的必要性的质疑。但相信这些都在课程组的考量之中,JML的保留也必然有它存在的意义,“那些似乎颠覆了我们熟悉感觉的恰恰就是教育“,希望大家都能在U3中有所收获。

...全文
38 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容概要:本文研究了一种基于遗传算法的新型异构分布式系统任务调度算法,旨在解决异构计算环境中任务分配与调度的复杂优化问题。通过Matlab代码实现该算法,充分利用遗传算法强大的全局搜索能力和鲁棒性,对任务执行时间、资源利用率、系统负载均衡等关键性能指标进行综合优化,有效提升分布式系统的整体运行效率与稳定性。研究详细阐述了算法的整体架构设计、染色体编码策略、适应度函数构造、选择机制以及交叉与变异等遗传操作的实现细节,并通过大量仿真实验验证了所提出算法相较于传统调度方法在收敛速度、解的质量和调度性能方面的显著优越性。; 适合人群:具备一定编程基础和优化算法理论知识,从事分布式计算、高性能计算、云计算资源调度或智能优化算法研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于高性能计算、云计算、边缘计算等异构计算平台中的任务调度优化,提升资源利用效率;②为研究人员提供遗传算法在复杂组合优化问题中应用的完整实现案例,深化对智能优化算法设计原理与仿真实践的理解; 阅读建议:建议读者结合提供的Matlab代码深入研读,重点理解适应度函数的设计逻辑与遗传算子的参数调优策略,并可通过更换不同的任务集和系统模型来测试算法的泛化能力与鲁棒性。
参考李林凤等(2025)一文关于农村劳动力人均受教育年限指标的构建与计算方法,整理了中国31个省份总体、分性别的农村人均受教育年限数据,具体计算方法如下: 农村劳动力人均受教育年限 = (农村未上过学人数 × 1+小学学历人数 × 6+初中学历人数 × 9+高中和中专学历人数 ×12+大专及以上学历人数 × 16)/农村6岁及以上总人口 相关数据:各地区、分性别人均受教育年限数据 一、数据介绍 数据名称:中国各省农村人均受教育年限 数据范围:全国31个省份 时间范围:2006-2024年 样本数量:590条 数据来源:《中国人口和就业统计年鉴》、《中国劳动统计年鉴》 二、数据指标 年份 省份 省份代码 农村人均受教育年限 男性-农村人均受教育年限 女性-农村人均受教育年限 6岁及以上人口 6岁及以上人口_男 6岁及以上人口_女 未上过学人口 未上过学人口_男 未上过学人口_女 小学人口 小学人口_男 小学人口_女 初中人口 初中人口_男 初中人口_女 高中人口 高中人口_男 高中人口_女 大专及以上人口 大专及以上人口_男 大专及以上人口_女 三、参考文献 [1]李林凤,刘杨,杨亦民.种业创新驱动农村产业融合的作用机制与空间分异效应[J].广东财经大学学报,2025,40(6):97-109. [2]徐小阳,李洁,金丽馥.普惠金融对农村教育贫困的纾解效应[J].中国农村经济,2020,(9):41-64.
内容概要:本文围绕基于改进秃鹰算法的微电网群经济优化调度展开研究,提出了一种结合智能优化算法的调度模型,旨在实现微电网群运行过程中的成本最小化与能源利用效率最大化。研究详细阐述了改进秃鹰算法的原理与优化机制,将其应用于含分布式电源、储能系统及多元负荷的微电网群多目标经济调度问题中,充分考虑系统运行约束与能源交互特性,构建了完整的数学模型,并通过Matlab代码实现了算法仿真与结果验证。该资源不仅提供了核心算法代码,还整合了电力系统建模、智能优化、仿真分析等关键技术,形成一套完整的科研复现体系,有助于深入理解先进优化算法在现代能源系统中的实际应用。; 适合人群:具备电力系统基础、优化算法理论及Matlab编程能力的研究生、科研人员和工程技术人员,特别适用于从事微电网、综合能源系统、智能调度等领域研究的专业人士。; 使用场景及目标:①复现并验证改进秃鹰算法在微电网群经济调度中的有效性;②掌握智能优化算法在复杂电力系统调度问题中的建模与求解方法;③依托所提供的Matlab代码开展学术论文复现、科研项目开发或工程方案设计;④拓展应用于储能优化、电动汽车调度、多能协同等关联领域的创新研究。; 阅读建议:建议读者结合文本说明与Matlab代码同步实践,重点关注算法实现细节、模型构建逻辑与参数设置方法,通过仿真实验加深对优化过程的理解,并尝试将该方法迁移至其他类似优化问题中进行对比分析与创新改进。
源码链接: https://pan.quark.cn/s/e75a09b40d94 在计算机系统中,注册表被视为Windows操作系统不可或缺的一部分,它负责存储系统及应用程序的各类配置信息。注册表键、值与数据共同构建了一个层级化的数据库,用于调控软件配置、硬件设备及其他系统层面的设定。对注册表信息进行批量移除是一项需要小心进行的工作,因为不当地移除核心注册表条目可能引发系统运行不正常甚至系统崩溃。 标题"批量移除注册表信息"指的是借助特定工具或手段,一次性清除多个注册表记录。此过程通常包含搜索与特定关键词相联系的注册表条目,并依据用户设定的标准执行删除。批量移除能够节省时间,但伴随的风险也随之提升,因此需要对操作有透彻的认识和丰富的实践经验。 描述中提及的"免费下载"或许是指提供了一款名为RegistryWorkshop的应用程序,这是一款专注于注册表管理与编辑的软件。RegistryWorkshop使用户能够便捷地搜寻、调整和移除注册表条目,并且特别突出了其批量处理能力。借助"Ctrl+F"进行搜寻,用户可以输入关键词,随后软件将展示所有符合的注册表条目,用户可从中选择移除。除此之外,用户亦能设定移除条件,例如依据注册表条目的建立时间、体积或其他特征进行筛选和移除。 RegistryWorkshop作为一个功能强大的注册表编辑工具,具备以下特性: 1. **搜寻与替换**:用户可通过输入关键词搜寻注册表,找到相关项目后,可选择替换或移除它们。 2. **批量操作**:让用户能够一次性选取多个注册表条目进行移除,或实施其他批量操作,如重命名、复制、剪切和粘贴等。 3. **备份与复原**:在实施任何改动前,RegistryWorkshop会提示用户建立备份...

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧