307
社区成员
发帖
与我相关
我的任务
分享第三单元主要围绕 JML 规格和规格驱动开发展开。我最大的感受是:这单元表面上是在写代码,实际上是在训练我们“把需求说清楚”的能力。有了 JML 规格,很多东西都被写成了比较严格的“契约”,一开始我觉得 JML 只是换一种方式描述题目,但真正做作业时我也发现,读懂 JML 并不代表一定能写出高质量代码,因为还要考虑数据结构、时间复杂度和后续迭代带来的影响。
我最大的体会是:JML 更像是“合同”。只要按照规格实现,功能结果一般不会偏太远;但如果只机械翻译 JML,很容易写出能过小数据、但强测超时的代码。比如在查询互关关系、统计视频信息、处理用户和评论关系时,如果每次都暴力遍历所有对象,逻辑上可能没错,但性能会很差。
我一开始主要是写一些简单样例,验证方法能不能正常返回。后来发现这样不够,因为很多 bug 出现在边界情况,比如用户不存在、列表为空、重复添加、删除后再查询、异常抛出顺序等。因此后面我会更有意识地把测试分成几类:正常功能测试、异常测试、边界测试和简单的压力测试。虽然 JUnit 不能保证完全没 bug,但至少能帮助我在每次修改代码后快速检查有没有把原来正确的功能改坏。
第一次作业主要是基础社交网络,包括用户、关注关系、视频、最短路径、互关统计等内容。这个阶段的重点是把基本容器搭起来,例如用 HashMap 根据 id 快速找用户和视频,用集合维护关注关系。
第二次作业加入了更多视频互动,比如点赞、投币、转发、评论、清理垃圾评论、查询热门视频等。这个阶段我明显感觉到,单纯把功能写出来不难,难的是每一次互动都会影响后面的统计结果,所以很多状态必须同步维护。
第三次作业进一步加入推荐相关内容,开始涉及用户兴趣、UP 主影响力、推荐视频和推荐 UP 主等逻辑。这个时候就不能只看新增方法本身,而要反过来想:这个新方法依赖哪些旧状态?旧方法是不是也要跟着维护新的容器?
比如推荐功能需要知道用户看过哪些类型的视频,那么 watchVideo 就不能只是记录“看过”,还要维护对应类型的观看次数。再比如推荐 UP 主需要计算 UP 主影响力,那么上传视频时也要维护上传者自己的视频集合。
我发现已有方法或容器变化的方法,主要有三个:先对比新旧接口,看新增方法需要什么数据;再顺着 JML 的 ensures 反推哪些旧方法会影响新结果;最后用测试和强测结果检查有没有遗漏。
我遇到的主要问题是异常处理和复杂度问题。刚开始实现关注关系时,我只考虑了功能正确,没有仔细检查多个异常条件同时出现时的优先级,结果在一些边界数据下抛出的异常和规格要求不一致。后来通过补充测试才发现这个问题。另外,第九次作业中的 queryMutualFollowingSum 也是我印象最深的 bug。一开始我的思路很直接:查询时遍历所有用户,再判断互关关系。逻辑上确实能算出结果,但复杂度太高,数据一大就容易 CPU_TIME_LIMIT_EXCEED。后来我改成在关注和取关时实时维护互关总数,查询时直接返回结果,才解决了超时问题。
这次作业加入了点赞、投币、评论等互动功能后,最大的 bug 来自状态维护不完整。有一次我在删除评论时只更新了评论集合,却忘记同步更新相关统计信息,导致后续查询热门视频时结果出现偏差。还有一些涉及容器遍历和删除的操作,如果直接在遍历过程中修改集合,就容易出现逻辑错误。这个阶段让我意识到,只要一个状态会被多个功能依赖,就必须保证所有相关操作都同步维护。
问题主要出现在推荐功能上。由于新增了用户兴趣、视频类型统计、UP 主影响力等数据,我一开始只在新增方法里维护这些信息,却遗漏了部分旧方法。例如用户观看视频后会更新兴趣统计,但删除视频或者其他相关操作时没有同步更新,导致推荐结果和实际状态不一致。除此之外,还有一些容易忽略的小问题,比如队列取元素时 queue.poll() 可能返回 null,如果直接拆箱就可能出现空指针风险。这些问题虽然不大,但往往比较隐蔽,需要依靠测试和强测才能发现。
我遇到的 bug 基本可以分成三类:第一类是异常处理和边界条件问题;第二类是复杂度设计不合理导致的性能问题;第三类是迭代开发过程中状态维护不一致的问题。其中最后一类最容易出现,因为新增功能往往会依赖旧数据结构,如果只关注新代码而忽略旧逻辑,就很容易埋下隐患。
大模型最大的帮助是能把复杂的 JML 翻译成比较好懂的自然语言,也能帮我快速生成一些基础 JUnit 测试思路。比如某个方法有哪些异常、哪些边界情况要测,它能比较快地列出来。
但是大模型也有很明显的问题:它容易写出“看起来正确但很慢”的代码。因为它经常会直接按照 JML 的逻辑写多层循环,而不会主动考虑缓存、容器设计和复杂度优化。所以我觉得大模型适合用来辅助理解规格、整理测试点和检查遗漏,但真正的架构设计和性能优化还是要自己把关。
游戏让我感觉挺真实的。一个需求在多人之间传递时,哪怕一开始只是很小的边界没有说清楚,传到后面也可能完全变味。
比如自然语言里说的是“统计满足某个条件的元素”,但写成 JML 时可能漏掉空数组、重复元素、顺序保持或者异常情况。后面的人如果只看这个不完整的 JML,就会按照错误的理解继续实现,最后测试的人甚至可能觉得代码没问题,因为他看到的需求本身已经变了。
这个游戏让我意识到,很多 bug 其实不是代码能力问题,而是需求沟通问题。每个人都以为自己理解了,但理解的可能不是同一个东西。
第三单元让我真正体会到,写代码不只是把功能堆出来,还要把需求理解清楚、把边界处理完整、把性能提前考虑进去。
JML 让我学会用更精确的方式描述需求,JUnit 让我学会用测试保护代码,三次迭代也让我明白了容器设计和复杂度分析的重要性。
这一单元最重要的收获不是某一个具体方法怎么写,而是意识到:规格是沟通的基础,测试是代码的保险,性能是强测不会放过的底线。