309
社区成员
发帖
与我相关
我的任务
分享JML(Java Modeling Language)作为一种行为规格描述语言,其核心价值在于将"做什么"与"怎么做"分离。在本次作业中,官方提供了完整的 NetworkInterface、UserInterface、VideoInterface 接口及其 JML 规格,我们只需要根据规格实现代码,而不需要猜测方法的预期行为。
JML 的几个关键 clause 的理解:
requires(前置条件):定义了方法调用的合法前提。例如 requires rank > 0,意味着调用方必须保证 rank 为正整数,否则方法行为未定义。这实际上是一种契约——调用方满足前置条件,实现方保证后置条件。ensures(后置条件):定义了方法执行后必须满足的状态。它是正确性验证的核心依据,也是编写单元测试断言的直接来源。assignable(可修改范围):明确限定了方法可以修改的变量/容器集合,这是一种帧条件(frame condition),防止实现者产生意外的副作用。pure**:标注方法为纯查询方法,不产生任何副作用。对于 pure 方法,调用前后的系统状态必须完全一致。signals(异常条件):精确定义了在什么条件下抛出什么异常,这对异常处理的正确性至关重要。本次作业对 safe 方法做了拓展约束:
这意味着实现 safe 方法时,每一步操作都必须在 JML 中有依据,不能"顺手"做一些额外的事情。
| 维度 | 传统开发 | 规格驱动开发 |
|---|---|---|
| 需求来源 | 口头/文档描述,容易产生歧义 | JML 精确描述,无歧义 |
| 正确性标准 | 模糊的"能跑就行" | ensures 和 signals 的精确满足 |
| 测试依据 | 根据理解自行构造 | 直接从 JML 提取断言条件 |
| 边界处理 | 容易遗漏 | signals 明确了所有边界和异常 |
| 多人协作 | 容易出现理解不一致 | 规格即契约,统一所有人认知 |
本次作业要求为 recommend_Nth_up 方法编写 JUnit 单元测试,需要对 JML 的全部内容进行检查,包括 requires、ensures、pure、assignable 等。
测试用例设计原则:
strictEquals 或 getUsers() 检查系统状态是否改变。控制变量法构造同分场景:
computeUpScore 的计算公式,识别所有影响变量(getInterest、getInfluence)利用辅助方法:
strictEquals() 方法可以快速比较两个 User 对象在方法调用前后是否一致getUsers() 方法返回全体用户的浅拷贝,可以遍历检查全局状态| JML Clause | 测试内容 | 示例 |
|---|---|---|
requires | 前置条件不满足时抛异常 | userId 不存在 → UserIdNotFoundException |
ensures | 返回值正确性 | 返回的 up 主 ID 应为第 rank 名 |
signals | 各种异常分支 | rank ≤ 0 → InvalidRankException |
pure | 调用前后状态不变 | 调用前后所有 User 对象 strictEquals 为 true |
assignable | 未修改不该修改的变量 | 调用前后 videos 容器不变 |
| 作业 | 核心内容 | 新增类/属性 | 新增方法 |
|---|---|---|---|
| hw9(第一次) | 基础社交网络 | User, Network, Video | addUser, followUser, watchVideo 等基础操作 |
| hw10(第二次) | 视频交互系统 | 新增 coins, medals, contributors, comments | likeVideo, coinVideo, forwardVideo, sendComment 等 |
| hw11(第三次) | 智能推荐系统 | 新增 typeCounts, videos 列表 | recommendVideo, recommendNthUp, queryMostInfluentialUp, queryUserProfile, queryGlobalBestContributor |
对比接口文件:每次作业都会更新官方包中的 Interface 文件,通过对比新旧版本的接口定义,可以发现:
ensures 条件的增减)关注属性变更:本次作业中 User 类新增了 typeCounts(各分区观看数)和 videos(发布的视频列表)。这意味着:
watchVideo 方法需要同步更新 typeCountsuploadVideo 方法需要同步更新 videosassignable clause 中发现注意返回值类型变化:如 getHeat 从浮点改为整数,这直接影响 computeVideoScore 的实现和溢出处理。
分析时间复杂度:
queryMutualFollowingSum:暴力遍历是 O(n²),用户量大时成为瓶颈queryLongestDecSeq:使用记忆化 DFS 优化到 O(V+E)recommendNthUp:需要遍历所有用户并排序,O(n log n)容器选择:
HashMap 用于 O(1) 查找(如 containsUser、containsVideo)ArrayList 用于有序列表(如 contributors、contributions)ArrayList.remove(Object) 是 O(n) 操作防溢出处理:
computeVideoScore 中 getHeat() * getInterest() 可能溢出,需先转为 long 再相乘computeUpScore 中累加多个乘积,同样需要 long 运算bestId 初始值设为 -1 而非 Integer.MAX_VALUE。JML 要求同贡献值时返回 ID 最小的贡献者,比较条件 contributorId < bestId 中,如果 bestId 初始为 -1,则永远不会被替换。bestId 初始值改为 Integer.MAX_VALUE。recommendNthUp 抛出了 ColdStartVideoException,但评测系统认为不应抛出此异常。signals clause 时不够仔细,recommendNthUp 的异常列表只有 UserIdNotFoundException、InvalidRankException、NoVideoUploadedException、ColdStartUserException,不包含 ColdStartVideoException。coinVideo 抛出 InsufficientCoinsException。addUserCoins 为用户充值,导致用户余额为 0。addUserCoins 操作。likeVideo 的 signals clause 包含 EqualUserIdException,即当 userId == video.uploaderId 时应抛出异常,但实现时遗漏了这个检查。if (userId == video.getUploaderId()) throw new EqualUserIdException(userId);。int * int 仍为 int,结果溢出后才赋给 long 变量,为时已晚。(long) video.getHeat() * user.getInterest(...) 先将其中一个操作数转为 long。requires → 异常检查、ensures → 返回值/状态更新 的映射。signals clause,大模型可以系统地生成所有异常分支的检查代码,减少遗漏。ColdStartVideoException),这反而会导致与规格不符。基本流程:
signals clause 生成异常测试ensures 生成正确性断言pure/assignable 生成状态一致性检查在击鼓传花游戏中,JML 规格在多人之间传递时容易暴露以下问题:
在传递过程中,最容易出现的问题:
为了在团队编程中减少信息差,可以采取以下措施:
以代码规格为唯一权威来源:
建立 Code Review 机制:
制定命名和实现规范:
编写共享的测试用例库:
使用版本控制进行变更通知:
本次单元的学习让我深刻体会到规格化开发的力量: