Featured image of post 北航OO U3 JML与社交网络

北航OO U3 JML与社交网络

你来出题 我来解题

关于架构

本单元的核心任务是根据接口提供的需求,写出其具体实现。

三次迭代的递进关系并不很强,更像是不同模块的各自实现。

第一次迭代明确了Network作为中枢,并引入了核心对象person与tag

第二次迭代引入account模块与article系统

第三次迭代则引入了综合性很强的message系统,其中涉及到了tag与article的相关内容

由于接口的需求很明确,整个图模型的架构相对确定,我们的核心内容主要只是完成方法与辅助方法。

关于测试

本单元对测试的要求较高,中测的核心问题主要出现在Junit测试中。

但为了通过中测所进行的测试,往往是最简单的单元测试。
因此哪怕已经经历了一整个单元,我对许多其他测试的具体内容仍然是完全陌生的。

单元测试

针对软件中的最小可测试单元进行的测试,确保每个单元能正常工作。

特点:

  • 粒度最小:聚焦单个单元,测试范围独立。
  • 白盒测试为主:需了解代码内部结构,设计测试用例覆盖不同分支、边界条件等。

功能测试

基于软件需求规格说明书,验证功能是否符合用户预期,不关注内部实现细节

特点:

  • 黑盒测试为主:从用户视角出发,仅关注输入与输出。
  • 粒度较大:对整体进行测试,系统性强,只能鉴定问题的存在性,而很难做好定位

集成测试

多个单元或模块组合起来,测试它们之间的接口和交互是否正常。

特点:

  • 关注接口与协作:测试重点往往是模块间的依赖关系和数据流动。
  • 渐增式测试:一般采用自顶向下、自底向上或混合方式逐步集成模块,便于定位模块间交互出现异常的位置。

压力测试

通过模拟高负载、高并发场景,测试系统在极限条件下的性能、稳定性和容错能力。

特点:

  • 破坏性测试:常常故意施加超过正常范围的压力。
  • 关注性能指标:如CPU time,Running time,内存等指标。

回归测试

在发生如代码修改、功能新增等变化后,重新测试已验证过的功能,确保变更未引入新缺陷或破坏原有功能。

特点:

  • 重复性测试:可重复执行历史测试用例来检验是否破坏原有功能。

关于大模型

本次作业我使用大模型的地方并不是很多,但第六次上机实验给了我很大的启发。

上机实验中的三个阶段分别是:

  • 直接生成
  • 给出相关知识与解释
  • 给出具体的示例

在根据这三个步骤实现之后,大模型的表现确实有很大进步。首先给出任务目标,然后再明确期望格式和核心目的,采用的是采用目标层约束层示例层的三层结构,减少模型理解偏差。

在实际使用过程中,我还有一些其他的使用体验,例如动态反馈。其实这个是我使用大模型时的常态,究其根本原因,可能便是对自己要解决什么问题都不清楚。于是在大模型的输出中进一步寻找问题,寻求解释,从而一层一层地解决问题。虽然最后一般也有不错的结果,但是还是相对耗时。
可能由自己直面问题,将核心目的拆解出来再借助大模型,会有更好的效果。

关于性能

规格与实现分离。规格是题干,实现是题解题干限定了你的结果,但是做题的过程由你的实现所决定。一道选择题可能大家都能写出正确答案,但是不同人的思考模式与做题技巧是不同的。如果你的实现能够满足规格的所有要求,那么这个实现便是正确的,但未必是优秀的。

在本次作业中,优秀的评断主要体现在TIME。因此我们一般要考虑构建一些数据结构,试图以空间换时间。

并查集

在query_circle中,我采用了并查集的方法,结合了路径压缩与按秩合并

并查集的核心思想就是为一个连通分支选出一个代表结点

路径压缩:

1
2
3
4
5
private int find(int id) {
        if (parent.get(id) != id) {
            parent.put(id, find(parent.get(id))); // 压缩
        } return parent.get(id);
    }

按秩合并:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
private void union(int id1, int id2) {
        int root1 = find(id1);
        int root2 = find(id2);
        if (root1 != root2) { // 按秩合并
            if (rank.get(root1) < rank.get(root2)) {
                parent.put(root1, root2);
            } else if (rank.get(root1) > rank.get(root2)) {
                parent.put(root2, root1);
            } else {
                parent.put(root2, root1);
                rank.put(root1, rank.get(root1) + 1);
            }
        }
    }

但是并查集便于增添,不便于删改。当某段关系被解除,路径压缩后的并查集不能再判断出这个人是否会脱离该连通分支。因此此时须考虑重新构建并查集。

增量化设计

这一优化对我的影响相对较深,至少让我首次意识到size的维护居然能避免一次大遍历。

增量化设计的核心思想就是在变化时对数值做维护,在查询时直接得到当前结果。或者说这是一种在线算法,求值不从头开始,而是由上一状态+变化过程而得来。

query_tag_value_sumquery_triple_sumquery_tag_age_var中,我主要使用了这一思想。具体实现上的易错点就不必在这里赘述了。

缓存与脏位

缓存类似于增量化设计,但缓存的内容不一定是正确的

某些值虽然我知道在某个过程中发生了改变,但是维护起来相对复杂,甚至效率堪比重新计算。倘若在每次改变的时候都进行维护,虽然精准,但会造成效率的损失。

例如均值是方便增量化维护的,而方差就不方便

因此可以选择设置dirty位,仅在需要查询时再进行缓存数据的更新。

我在query_best_acquaintancequery_best_contributorquery_tag_age_var以及并查集的重建中主要使用了这一思想。

MyContainer

主要解决在有序列表中按值删除对象中的遍历问题。

Linkedlist固然好,但是它只是能很快的删除某个结点内部不存在值与结点的映射。因此只需仿照Linkedlist构建一个类,但增加一个属性HashMap用来由值索引到结点。(因为一个值可能对应多个节点,所以可以考虑HashMap<Integer,HashSet<...>>

在hw10强测中因未采用这一数据结构而发生了CTLE。

关于Junit

我在本单元的研讨课上较多地讲述了如何由规格构建测试的方法论。在此将主要内容进行重述。

自动化测试基本方法

  1. @RunWith(Parameterized.class)
    切换到 Parameterized 运行器,让测试类依据多组参数生成多个Test对象,多次执行同一个测试方法,避免了手动编写大量重复的测试代码。
    @Parameterized.Parameters
    定义一个返回测试数据集合的静态方法,返回的集合包含testNum个小集合,每个小集合包含生成Test对象的参数。

  2. 多次@Test
    手动构建测试样例,覆盖能够触发 JML 规格中所有可能行为的输入数据和对象状态。

基于规格的数据生成

  1. 数据限制
    • requires明确了方法的某种输入约束(如参数范围、对象状态)。
    • invariant是类级别的约束,必须确保测试数据满足这些基本约束。
  2. 多场景构造
    • 正常场景:构造满足前置条件的典型输入,触发 JML ensures 条款中的正常行为。
    • 边界场景:构造边缘输入,如空集合边界值单一元素等。
    • 异常场景:构造违反前置条件的输入,触发 JML signals 条款中的异常。
    • 复杂场景:构造具有复杂关系或大规模数据的场景,测试方法在高负载下的正确性。

基于规格的断言生成

invariant -> 基本数据要求,可断言执行方法后是否满足

constraint -> 数据变化要求,可保存old信息进行断言

assignable -> 对没被assign的变量做pure断言

signal -> 异常捕捉方法或assertThrow

ensures -> 方法核心效果的断言

pure -> 保存old信息(或备份信息)进行断言

关于心得

本单元整体给我的感觉是比较舒适的。除了hw10的实验预习题中那颗树的JML让我感到深刻畏惧,其他的JML读起来都让人很明白自己需要做什么。哪怕是看起来最复杂的sendMessage,很多行JML也只是对应一个小方法的调用。而有些言简意赅的JML,背后却需要十分复杂的实现。

有了颜色标注之后,阅读JML的枯燥与混乱已经基本消失殆尽。

但是JML的书写对我来说仍然是极为困难的课题,哪怕强如助教也会在官方包出现小的失误。但JML作为根本要旨,要保证绝对的严格与严谨,书写JML需要有对方法极高的概括力,还有对细节的全面把握。不过学完了这个单元,我们终于可以说自己走出了了解JML的第一步。

comments powered by Disqus
Easy Life and Easy Learning
使用 Hugo 构建
主题 StackJimmy 设计