305
社区成员
发帖
与我相关
我的任务
分享第三单元的学习随着最后一次作业的提交落下了帷幕。本单元以JML为核心,不仅锻炼了我们阅读和实现形式化规格的能力,更在无形中强化了我们对图论算法、性能优化以及面向对象测试的理解。以下是对本单元学习的全面总结。
JML规格驱动开发的核心在于“契约编程”。在实际开发中,JML实现了“做什么(What)”与“怎么做(How)”的分离解耦。
requires前置条件,ensures后置条件,assignable副作用范围),它不关心底层的具体数据结构和算法。HashMap、TreeSet)和算法(如KMP)。/*@ pure @*/的方法绝对不允许修改任何对象状态。通过在执行查询方法前对用户的关注关系、视频热度、硬币等核心属性进行“快照”,并在执行后进行严格的等价断言,能有效防止底层排序算法误改原集合等隐蔽错误。User、Video 和 Network 三个基础类。主要业务指令集中在用户的动态加入(add_user)、关注关系的建立与解除(follow_user/unfollow_user)以及基础行为(upload_video/watch_video)。Array)模型进行线性抽象,但在实现底层时,如果直接照搬规格进行遍历,会导致系统陷入大量的 $O(N)$ 甚至 $O(N^2)$ 查找。我在这一阶段将所有线性容器全部替换为 HashMap,降低时间复杂度。like_video)、投币(coin_video)、转发(forward_video)和发送评论(send_comment)等指令,还增加了算法考察性质的垃圾评论清理(clean_spam_comments)和最长年龄递减序列查询(queryLongestDecSeq)。clean_spam_comments 引入了重叠子串的计数逻辑(例如在 "abababa" 中统计 "aba" 需精准得出 3 次)。面对极端测试,普通实现会产生大量的内存拷贝与 $O(N^2)$ 的时间开销,需要优化为双指针新容器重组或手写 KMP 算法以保障线性时间复杂度。queryLongestDecSeq 规格要求寻找一条最长的年龄严格递减路径。这促使我们将无向图转化为有向无环图,利用记忆化搜索(DFS + Cache)将该方法的时间复杂度从指数级降维至线性的 $O(V+E)$。recommend_video)、UP主排行推荐(recommend_Nth_up)、最具影响力UP主查询(query_most_influential_up)、用户画像(query_user_profile)以及涉及多层众数统计的全局最强打赏者查询(queryGlobalBestContributor)。getHeat() 和相关排行榜的度量参数由旧版的 double 改为了 int,公式系数也全部整数化。Network 类承载了极度臃肿的图算法与推荐业务。为了遵循“高内聚低耦合”的面向对象设计原则,必须实施职责剥离重构——将纯底层图算法抽离至独立的 GraphAnalyzer(负责双向 BFS 与记忆化 DFS),将推荐业务剥离至 Recommender 类,而 Network 回归至其全局容器与网络构建的本质职责,解耦了复杂的系统层级。在迭代中,逐字逐行比对新旧JML规格是唯一准则,这一过程除了人工审核也可以借助大模型的力量。通过对比,可以敏锐地发现以下变化:
getHeat()的返回值为了避免浮点误差,从double改为了int,其计算公式和对应TreeSet的比较器也必须同步修改 。uploadVideo在后续迭代中不仅要记录视频,其JML增加了对上传者状态的约束,这就要求我们在User类中新增uploadedVideos集合来进行状态维护 。JML本质上是数学描述,如果直接“照本宣科”地翻译JML,极易导致CTLE。发现并解决瓶颈的方法包括:
cleanSpamComments若采用原数组倒序删除,底层会有大规模的移位拷贝,导致$O(N^2)$的耗时,导致TLE。将其改为双指针正序重组或利用KMP算法可降维至$O(N)$ 。addUser、followUser等改变拓扑结构的指令下清空缓存,可将 $O(V+E)$ 降维至 $O(1)$ 。Network极易突破Checkstyle的行数限制 。通过剥离出纯算法组件GraphAnalyzer和业务推荐类Recommender,不仅解决了代码行数问题,也让性能瓶颈更容易定位 。本单元我遇到的最致命的Bug多源于对JML细节的疏忽和底层API机制的不熟悉:
uploadVideo中,遗漏了对视频分区类型的校验(isValidType)。在遭遇未知分区的非法指令时,直接从Map中取值并调用.add(),引发了NullPointerException 。removeReceivedVideo中,误用了LinkedList的remove(Object)方法,该方法只会删除首个匹配元素。当存在重复转发的同一视频时,无法彻底清空,违背了JML的后置条件,后续改为removeIf才彻底解决 。""的极端构造数据,String.indexOf("", index)会返回字符串长度并导致游标反复累加,陷入无限死循环并触发TLE 。在规格驱动开发中,大模型是一把“双刃剑”,必须谨慎使用:
queryGlobalBestContributor),能快速提取出其中的“求众数、同分取ID最小”等业务语义 ,并能针对性地生成异常优先级碰撞、ColdStart边界等极为刁钻的JUnit测试用例 。recommendVideo中采用直接取全区Top1来优化,但这忽视了极端情况下“某分区为空”可能导致的空集崩溃,因此我中测无法通过,不得不回退重构 。这警示我们,LLM的代码必须经过开发者对其业务边界和JML契约的二次逻辑审查。在第二次研讨课的JML“击鼓传花”游戏中,我获得了极为深刻的体会:
requires,导致代码在极端用例下崩溃。