307
社区成员
发帖
与我相关
我的任务
分享JML(Java建模语言)是一种工具,而规格驱动开发是一种以该工具为代表的开发范式。它们的核心思想是:将代码的“设计意图”(规格)和“具体实现”显式地分离开,并以前者为指导进行开发和验证。
我i们可以把 JML 当作“对外可见行为”的合同:前置条件(requires)规定调用方责任;后置条件(ensures)规定实现方交付;assignable 划定可修改的状态边界;pure代表方法无副作用。而“模型字段/不变式”更像数据结构的抽象视图:接口里写的是 users[]/videos[] 、 following[] 这些序列语义,实现里用 Map/List/Set 只是手段,关键是把这些容器映射回规格的抽象序列,并持续满足 invariant。
SDD 的核心流程是“先定可验证的外部行为,再选实现”:先从异常触发条件、状态变化最小集、输出副作用出发规划代码路径;再考虑如何在不破坏JML约束的情况下,通过缓存、索引等降低时间复杂度。
测试设计更偏“规格覆盖”而不是“代码覆盖”:按每个方法的 normal/exceptional behavior 列出分支,用用例一一击中(例如 hw11 的 assertThrows 覆盖异常优先级)。
此外对于方法的副作用检查也很重要,通过“调用前快照 + 调用后对比”来检查方法不会偷偷改不被允许改的状态。
而为了符合工程化的设计,我们可以把快照/对比写成工具函数(VideoSnapshot/UserSnapshot),减少重复;对复杂结构用“抽取可比较的最小视图”(id、计数、集合成员关系)而不是直接比较对象引用;对字符串/数组用专门断言( assertArrayEquals)。
主要是遵循main中接口进行迭代开发,自己的迭代主要集中在对于容器的选取以及对于一些额外状态的维护以提高性能。
在 hw9 中,一开始对于 JML 的了解不够深入,误以为 safe 方法不能改变任何状态(后得知 safe 只是不能改变 JML 中规定了的属性),导致对于互关数量的查询是每次动态计算,且是按照点遍历而不是边遍历,时间复杂度为 O(n²),导致了超时。因此后面对于互关数量的查询改为维护一个互关数量属性,使得时间复杂度降到O(1)。
在 hw10 里,容易出错的点主要在于 queryLongestDecSeq 方法,对于这个方法的视线最好采用 dp 算法,以矩阵的样式实现;如果采用递归方法实现,容易爆栈(虽然结合JML要求好像最多也就递归110层)。
在 hw11 中,主要有以下几个可以提高性能的地方:
WatchedVideo的时间复杂度是O(n),如果维护一个用户画像private final int[] typeCounts = new int[TYPES.length];,则可以做到O(1)。优势:
风险:
确实难免会有bug,在传递过程中,少写或者漏写前置条件和边界。在自然语言描述中,大家一般会省略一些默认的情况,比如对于作业中的投币功能,用自然语言描述可能就会省略掉硬币数不足的情况。而在 JML 转自然语言的时候,我们组的同学一般都是将 JML 翻译了一遍,虽然不是很符合自然语言的“自然性”,但也确实减少了传递过程中的错误与二义性。
统一对需求的理解:
统一对实现方法的理解: