OO_U3:JML规格驱动开发与架构演进

李不凡-24371323 2026-05-28 12:26:57

OO_U3:JML规格驱动开发与架构演进

第三单元的学习随着最后一次作业的提交落下了帷幕。本单元以JML为核心,不仅锻炼了我们阅读和实现形式化规格的能力,更在无形中强化了我们对图论算法、性能优化以及面向对象测试的理解。以下是对本单元学习的全面总结。

一、 对JML和规格驱动开发的理解

JML规格驱动开发的核心在于“契约编程”。在实际开发中,JML实现了“做什么(What)”与“怎么做(How)”的分离解耦。

  • 声明式抽象:JML提供的是方法执行前后的状态契约(如requires前置条件,ensures后置条件,assignable副作用范围),它不关心底层的具体数据结构和算法。
  • 安全性保证:严格遵守JML可以极大程度避免因自然语言歧义带来的理解偏差。只要程序的行为满足了规格的约束,其正确性就得到了理论上的保证。
  • 设计与实现解耦:架构师负责制定JML契约,而开发者负责编写代码。开发者在实现时,不仅要逻辑正确,还要在底层将JML中低效的描述(如海量数组遍历)转化为高效的数据结构(如HashMapTreeSet)和算法(如KMP)。

二、 JUnit测试的经验与反思

  1. 测试的维度必须全面:优秀的JUnit测试不能仅仅测试正常流程,必须涵盖正常行为、异常抛出优先级、平局打破规则(Tie-breaking)、边界条件(如ColdStart)以及纯粹性Pure检查
  2. 纯粹性Pure的极限断言:JML中标记为/*@ pure @*/的方法绝对不允许修改任何对象状态。通过在执行查询方法前对用户的关注关系、视频热度、硬币等核心属性进行“快照”,并在执行后进行严格的等价断言,能有效防止底层排序算法误改原集合等隐蔽错误。

三、 三次作业的迭代过程分析

第一次作业:社交图谱的基础搭建

  • 功能聚焦:第一次作业的核心在于构建社区的底层实体与最基本的拓扑关系。系统需要实现 UserVideoNetwork 三个基础类。主要业务指令集中在用户的动态加入(add_user)、关注关系的建立与解除(follow_user/unfollow_user)以及基础行为(upload_video/watch_video)。
  • 架构设计与性能应对:此时的社交网络本质上是一个有向图。虽然 JML 规格中为了描述的严谨性,全部采用了数组(Array)模型进行线性抽象,但在实现底层时,如果直接照搬规格进行遍历,会导致系统陷入大量的 $O(N)$ 甚至 $O(N^2)$ 查找。我在这一阶段将所有线性容器全部替换为 HashMap,降低时间复杂度。

第二次作业:社区生态、硬币经济与文本清理

  • 功能聚焦:第二次作业引入了较为复杂的硬币经济体系和多维度的社交互动。不仅新增了点赞(like_video)、投币(coin_video)、转发(forward_video)和发送评论(send_comment)等指令,还增加了算法考察性质的垃圾评论清理(clean_spam_comments)和最长年龄递减序列查询(queryLongestDecSeq)。
  • 架构设计与性能应对
    1. 文本处理层面:clean_spam_comments 引入了重叠子串的计数逻辑(例如在 "abababa" 中统计 "aba" 需精准得出 3 次)。面对极端测试,普通实现会产生大量的内存拷贝与 $O(N^2)$ 的时间开销,需要优化为双指针新容器重组或手写 KMP 算法以保障线性时间复杂度。
    2. 图论算法层面:queryLongestDecSeq 规格要求寻找一条最长的年龄严格递减路径。这促使我们将无向图转化为有向无环图,利用记忆化搜索(DFS + Cache)将该方法的时间复杂度从指数级降维至线性的 $O(V+E)$。

第三次作业:智能推荐系统与全面重构

  • 功能聚焦:第三次作业实现了系统的智能推荐引擎。新增了视频推荐(recommend_video)、UP主排行推荐(recommend_Nth_up)、最具影响力UP主查询(query_most_influential_up)、用户画像(query_user_profile)以及涉及多层众数统计的全局最强打赏者查询(queryGlobalBestContributor)。
  • 架构设计与性能应对
    1. 契约细节的微调:为了规避浮点数带来的判等与计算误差,官方下发规格将 getHeat() 和相关排行榜的度量参数由旧版的 double 改为了 int,公式系数也全部整数化。
    2. 结构重构解耦:此时 Network 类承载了极度臃肿的图算法与推荐业务。为了遵循“高内聚低耦合”的面向对象设计原则,必须实施职责剥离重构——将纯底层图算法抽离至独立的 GraphAnalyzer(负责双向 BFS 与记忆化 DFS),将推荐业务剥离至 Recommender 类,而 Network 回归至其全局容器与网络构建的本质职责,解耦了复杂的系统层级。

1. 如何发现已有方法/容器在迭代中的变化?

在迭代中,逐字逐行比对新旧JML规格是唯一准则,这一过程除了人工审核也可以借助大模型的力量。通过对比,可以敏锐地发现以下变化:

  • 返回值与公式改变:例如第三次作业中,视频热度getHeat()的返回值为了避免浮点误差,从double改为了int,其计算公式和对应TreeSet的比较器也必须同步修改 。
  • 隐式的副作用增加:例如uploadVideo在后续迭代中不仅要记录视频,其JML增加了对上传者状态的约束,这就要求我们在User类中新增uploadedVideos集合来进行状态维护 。

2. 如何发现程序的性能瓶颈?

JML本质上是数学描述,如果直接“照本宣科”地翻译JML,极易导致CTLE。发现并解决瓶颈的方法包括:

  • 识别高频查询的高复杂度循环:例如cleanSpamComments若采用原数组倒序删除,底层会有大规模的移位拷贝,导致$O(N^2)$的耗时,导致TLE。将其改为双指针正序重组或利用KMP算法可降维至$O(N)$ 。
  • 图论与全局聚合操作的缓存机制:对于“最长递减序列”或“全局最强打赏者”等查询,采用Dirty Flag + 全局缓存的策略,只在addUserfollowUser等改变拓扑结构的指令下清空缓存,可将 $O(V+E)$ 降维至 $O(1)$ 。
  • 职责剥离与架构重构:随着迭代,上帝类Network极易突破Checkstyle的行数限制 。通过剥离出纯算法组件GraphAnalyzer和业务推荐类Recommender,不仅解决了代码行数问题,也让性能瓶颈更容易定位 。

四、 出现的Bug及原因分析

本单元我遇到的最致命的Bug多源于对JML细节的疏忽和底层API机制的不熟悉:

  1. 漏检异常导致NPE:在uploadVideo中,遗漏了对视频分区类型的校验(isValidType)。在遭遇未知分区的非法指令时,直接从Map中取值并调用.add(),引发了NullPointerException
  2. 重复元素删除未净:在removeReceivedVideo中,误用了LinkedListremove(Object)方法,该方法只会删除首个匹配元素。当存在重复转发的同一视频时,无法彻底清空,违背了JML的后置条件,后续改为removeIf才彻底解决 。
  3. 空字符串越界死循环:在处理垃圾评论清理时,遇到关键字为空字符串""的极端构造数据,String.indexOf("", index)会返回字符串长度并导致游标反复累加,陷入无限死循环并触发TLE 。
  4. 异常调用错误:InvalidAge异常本应传入age,却传入user id。

五、 大模型在Unit3中的辅助使用

在规格驱动开发中,大模型是一把“双刃剑”,必须谨慎使用:

  • 优势:大模型非常擅长解析冗长且嵌套深度的JML规格(如queryGlobalBestContributor),能快速提取出其中的“求众数、同分取ID最小”等业务语义 ,并能针对性地生成异常优先级碰撞、ColdStart边界等极为刁钻的JUnit测试用例 。
  • 对架构与效率的忽视风险:在使用大模型时,它有时会过度追求算法的理论最优而忽视工程的边界。例如,它曾建议在recommendVideo中采用直接取全区Top1来优化,但这忽视了极端情况下“某分区为空”可能导致的空集崩溃,因此我中测无法通过,不得不回退重构 。这警示我们,LLM的代码必须经过开发者对其业务边界和JML契约的二次逻辑审查

六、 JML“击鼓传花”游戏感悟与组队编程思考

在第二次研讨课的JML“击鼓传花”游戏中,我获得了极为深刻的体会:

  1. 需求与边界的流失:自然语言是充满歧义的。在将自然语言转化为JML,再由JML转回自然语言的过程中,非常容易丢失诸如“ID必须唯一”、“相同状态下按ID升序排列”等隐性约束。
  2. 理解的偏差:每个人对规格的解读粒度不同。有时规格的编写者预设了某种前提(如图是连通的),但实现者在JML中没有看到相关requires,导致代码在极端用例下崩溃。
  3. 针对未来多人组队编程的建议
    • 接口与规格先行:在写任何一行逻辑代码前,团队必须先统一定义所有类的接口,并给出严格的JML/API规格文档。
    • 测试驱动:组内成员互相交换编写JUnit测试,通过各自理解的规格去测试对方的代码,利用测试样例来暴露信息差。
    • 高频同步机制:建立定期的Code Review会议,特别是针对共享容器的状态变更和边界异常的抛出优先级进行交叉审核,确保全员对“契约”的认知完全统一。
...全文
6 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

305

社区成员

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

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