第三单元总结
一、JML语言的理论基础
初次接触JML时,我并不太能理解它的意义和实际作用。在初步了解了JML的语法之后,我认为JML只是在用另一种形式对编程者提出要求,甚至感觉JML相比前几次作业单纯的自然语言描述更加“繁琐”。但我经历了三次作业的考验,查阅了相关资料后,似乎才真正get到JML的作用。JML语言是一种精确的形式规范描述语言,能准确表达方法的功能需求,且JML在形式规范的基础上,可以利用自身开发的工具进行高效率的单元测试。准确表达需求、便于进行测试,这是我认为的JML最大的两个用处。JML的语法结构并不复杂,但使用的是规范化的表述方法,能够准确无误地表达出代码设计需求;在规范化的前提下,JML相关的工具链(例如openJML、JMLUnitNG等)能够对代码进行高效的单元化测试,帮助程序员更好地debug。
二、三次作业的架构设计
(一)第九次作业:
首次运用JML完成的作业,难度并不高,主要的难点在于queryNameRank函数和isCircle函数的设计。对于queryNameRank的设计,我是用ArrayList存放Person,在addPerson时根据name的排序,将新的Person插入到相应的位置,这样在调用queryNameRank时,直接用二分查找得到结果;对于isCircle函数,由于测试数据有一定限制,我直接采用了深度优先搜索得到结果。还有一个值得注意的点,Network类中虽然只要求了一个“people”容器,但在设计时我分别使用了ArrayList和HashMap来实现“people”容器,前者用于对name进行排序,后者用于查找,减少算法的复杂度,但要注意两者的同步。以下是第九次作业的架构图:
(二)第十次作业:
本次作业新增了Group类,设计方法可以参照Network,实现难度并不高。在实现getAgeMean、getAgeVar时,我是在addPerson函数中增加了对年龄和、年龄平方和的计算和更新,这样在求均值、求方差时,时间复杂度相对于直接暴力计算从O(n)降到了O(1)。getRelationSum、getValueSum、getConflictSum函数的设计同理。以下为第十次作业的架构图:
(三)第十一次作业:
本次作业在Group类中增加了删除操作,在Network类中增加了几个特殊的函数。Group类中的delPerson容易实现,几乎正好是addPerson的反向操作;Network中的queryMinPath、queryStrongLinked函数的实现,需要用到离散数学的相关知识,也是本次作业最大的难点。而由于测试数据的限制有所改变,isCircle函数原本使用的深度优先搜索算法会让运行时间超标,于是我修改成使用并查集的算法降低时间复杂度,同时也解决了queryBlockSum函数的实现。以下是第十一次作业的架构图:
三、出现的Bug
虽然本单元的作业相对于前两个单元,难度和复杂度都要低一些,但我在三次作业中都出现了不同程度的bug。
1、第九次作业中,由于我对深度优先搜索算法不够熟练,代码设计有误,在中测时没有发现此bug,自己也没有进行足够的测试,在强测中出现运行时错误;
2、第十次作业中,由于我没有仔细阅读JML规格,将getAgeMean、getAgeVar函数简单理解为求平均年龄、求年龄的方差,使用的计算方法和规格中略有差异;而因为int类型除法的特性,我的计算结果和期望的计算结果有细小的差异,也是在强测中才检查出来;
3、第十一次作业中,我主要的bug是queryMinPath、queryStrongLinked两个函数导致的。主要原因是我对离散数学的相关知识掌握的不够熟练,在编写函数时出现了设计上的失误,导致了错误的结果,复杂度上也不过关。
四、心得体会
JML的功能确实很强大,能准确、规范地表达对代码设计的需求。也正是如此,对JML规格的仔细阅读、理解很重要;更重要的是,要能熟练运用JML相关的工具进行单元化的测试,充分享用JML带来的便利。或许正是因为我对JML相关的单元测试不够了解,才导致这三次作业出现了各种bug。