标签:绑定 地方 文字 open 大脑 代码风格 算法与数据结构 十分 结构
这一部分已经在最新版的作业要求中被取消,因此不再赘述
首先是传统艺能:对拍器
即使在JML的大环境中,对拍器仍然相当好用。本次作业的评测系统可以说是十分好写,打好jar包后直接用shell编写若干重定向,并用diff命令即可快速高效的实现对比,困难的部分是数据生成器
数据生成器的好坏,直接决定了bug的覆盖性,也直接决定了性能(运行时间)的分析。制造一个好的数据生成器的过程,本身也是测试驱动开发的一个过程,这代表着某种策略随机出来的所有样例,所编写的程序都能有效应对。
总的来说,对拍器的强大之处在于自动化和随机性。不同于Junit手动编写样例,对拍器一次完成终身受益。而随机性的引入可以在概率意义上将分支覆盖率提升到100%-x(x是一个无穷小量)
其次是新晋帮手:Junit
Junit与对拍器的出发点并不同,并不是比对整个工程的输入与输出,黑盒测试,而是对每一个方法进行测试
使用Junit的一个最好的利益,就是可以通过少数几组样例迅速的检查分支覆盖率。分支覆盖率与边界条件一起,是单线程程序正确性bug的最大出处。分支覆盖率搞定之后,再搞定边界数据,即可大致说明程序没有正确性bug
最后是课程要求使用的JML相关的测试
OpenJML工具体验很差,因为,他对于JML的语法十分死板。一个最明显的例子就是,JML中利用数组来表示的数据,实现的时候也必须使用静态数组才能通过相关测试
好在,课程组及时调整了任务,改为针对某个方法进行体验与测试。
为了完成课程目标,我做出了代码如下:
//@ ensures \result == a * b;
public static int mult(int a,int b) {
return a * b;
}
这个代码被JML工具检测出了溢出int的错误
总体来说,我认为JML及其Solver的主要任务是,解决上文提到的另一个问题:边界条件。
终于结束了JML相关测试的编写。接下来谈一谈架构设计与作业代间的重构
首先,是容器VS数组的问题。从第二次开始,为了复杂度不爆表,题目要求任意时刻存在于容器中的总点数均有一个上限。考虑到静态数组封装方便,使用方便,还计算迅速,我利用操作系统内存管理的观点对nodeId进行了离散化,具体做法如下:
这样一套机制,类似对拍器,一经建立,无需重构。预见到了可能的改变上限,使用参数化的思想,将各种上限等等写为参数,方便需求更改
第一次作业
第二次作业
第三次作业
总体来说,这几次作业,我没有经历重构。不过,仍存在设计上的缺陷。为了省事,我没有采用extend来构造新类,而是直接复制粘贴源代码本身。这样做的好处是,可以直接访问“子类”的东西(checkstyle禁止protect变量,这很迷),而不用构造大量的getxxx方法。坏处显而易见,就是造就了一个400行左右的SubwaySystem类。事实上,如果做一些精简与风格优化,大概可以做到300+行,不过时间有限,能力不足,没有做工程性的优化。
与其讨论bug,不如讨论互测心得,原因有二:
第一次作业
第二次作业
第三次作业
这次作业就有一些难度了。主要矛盾还是在换乘相关的计算上
课后看来,标程采用的是没有什么优化(除了一个类似cache的缓存)的dij,屋里有拆点dij,有标程dij,也有分层Floyd。
之所以采用离散化+Floyd,是因为者可以在扩展性并不差的前提下,很简洁的完成任务。算上各种空行和定义,以及为了checkstyle一行80字符做出的换行,我总代码量也不到500行,实际工作量很少。有些dalao使用拆点+cache+dij获得了很高的效率,在题目限定数据规模下,大概比我平均快一倍不到。组内有人因为神秘原因,比我的裸Floyd暴力算法还要慢3倍
这次互测我抓到了很奇葩的bug。在某一种策略的生成器制造的符合要求的样例条件下,有一个同学的代码出bug率为100%(拍一组爆一组,连爆十几组后我把它沉默了),可能是强测对于边界条件卡的不够严格。问题出在静态数组的边界,想来这也是一个离散化的同仁,不过可能是细节考虑不周,出现了bug
除了bug,我还遇到了一位神人的代码。我现在也不知道这个神人究竟是真神还是反话。他的神举动如下:
不管怎么看,他都有过度封装的嫌疑,甚至不是嫌疑。毕竟一个包里只放一个文件和另一个包,这样的举动很奇葩。
不过,这么复杂的工程能在1周内搞到没bug,也是很强。我接手一个1700+的工程很难在1-2天内完善并测试。
最后,他源代码在statistic检查后,总共有1706行代码。1706这个数具有很特别的意义,所以我甚至怀疑过这位dalao是故意的。
不过,不管这位同学是否有意如此,我都不太能理解如此复杂的包结构。他能做的大概有:算更多的指标(比如满意度,票价,换乘这些都算指标),运用更多的算法\策略(比如dij,Floyd,spfa。。。),不过在我的架构下,想要完成这些也不困难,甚至代码量还会少些。可读性的话,反正我读了半天是不知道他怎么回事儿,可能也有先入为主的因素吧。总之,我的代码风格不好,几乎没有封装类。他的代码风格在这一方面做的确实比我好,不过一个小工程需要如此大动干戈吗?这一点我保留怀疑态度。
有关互测和架构相关的体会前文已经论述,不再赘述,这里谈一谈我对JML的理解与看法,并结合实际课程环境谈一谈我的感想
首先,JML作为一种相对严格的规范(相比于javadoc或者设计文档),他能做出的正确性判断要精确的多。正如指导书所说,只要你确保满足了JML的需求,那么你的程序一定是正确的。在真实开发中,我推测JML类似规范的编写要比开发困难得多(从课程组助教组精英云集但仍反复修改JML可见一斑)。想要写出完美的JML规范可以说是很困难的。
在JML的加持下,如果仅以完成作业为目标,那么本单元毫无疑问就是数据结构巩固与提高+图算法初探。事实上,在作业过程中实际编写代码的时候,我主要思索的也并不是JML或者什么规范,工程,而是算法与数据结构。毕竟理想不能当面包吃。规范没搞透可以日后再来,三次无效作业的风险还是挺大的。
然而,正如今年的改革一样,去年的JSF据说是自己写规格,互测抓规格,因此把规范设计的课程变成了语文,基本语法与细节,博弈论,社会关系等等,今年电梯改革很有成效,因此JML单元不应堕落成数据结构补课。因此,在课程空闲时间,穿插着还是要看一些规范设计与架构的知识。从我自身来看,就java这门语言来说,面对1000行左右的工程已经不是十分头疼,相比于学期初有了一定的提升。然而,架构设计也一直是我的弱点。每次都提交default package下面三个孤零零的类,我都有点对不起同屋的大哥们。
但是,具体问题具体分析。这几周的OO在客观上没有了开学时的霸气(因为神奇的OS更加神奇了),因此投入时间基本上也只剩1-2天。面对短暂的时间与较小的代码量(100-200-400),真的没有经历,甚至没有必要去苦心孤诣钻研架构与设计。有些同学提出过将架构与设计作为评判标准以鼓励钻研这一部分内容,对于他的目的与动机,我完全支持。但他说的方法我认为一不一定有效二不一定实际。
从被赋予学分的那一刻起,面向对象设计与构造这九个字就和成绩,分数死死地绑在了一起,也就和利益绑在了一起。谁都不是神,面对与利益绑定的东西,真的很难理想主义的去实践,体验。事实上,今年OO改革之所以成功,我认为很大一部分不在于课程目标或者讲授形式(这些甚至都没有变),而在于助教给出了一种新的思路,将利益引向了相对正确的方向。我认为,面对与利益绑定的作业,真的很难全身心的投入、学习那些有用但是对于完成作业意义不大的事物。这就好比OS课程,在github往届代码的引导下,我自己真的做不到完全独立开发。
说完了一些关于实际问题的感想,我最后谈一谈我对于JML的一些心得。也算是一个首尾呼应
我认为最重要(我的理解不一定正确)的一点,是设计与实现分离,而不是确保正确性。至少在课程中,我还并没有遇见过非JML不可的正确性判断方法,至少黑盒测试(对拍)和Junit的双重火力网可以在实际资源允许的限度和bug的容忍限度下很出色的完成要求(大概)。然而,作为面向对象初学者,我对于设计与实现分离的理解并不好。
而JML这一单元,可以说是“强迫”引入这一工具,毕竟我认为完整看完并理解甚至复演了整个Apprunner的同学应该会相当少(我比较摸,我连完整看完都没能做到)。可以说,在实际工程中,我认为我会遇到很多这样的问题(接手一个完成了一半的工程),试想,如果没有JML这类设计规范,只给出指导书(需求)和Apprunner的源代码,那完成这几次作业将会多么痛苦。可以说,没有设计实现分离的思路,就没有我只完成两个类及其算法的从容与淡定,更没有本单元就是个数据结构和图算法作业的感想。能把一个不算小的工程(千级别),退化成一个图算法作业,设计实现分离的威力可见一斑。
写完博客回头阅读的时候,发现自己有些话可能说的不是那么符合自己的能力与身份,可能欠妥,不过,我也不想成为为了分数而圆滑的人。希望大家不要在意那些感到不舒服的字眼,在此提前致歉。
我不喜欢贴图,因为过大的图片很影响阅读体验,而且,很多图片表达的内容大同小异,无非是完成课程的要求,所以我尽量使用文字进行描述
最后老惯例,作为一门6系核心专业课和一门对未来有影响的课,真心祝愿OO课程越来越好
标签:绑定 地方 文字 open 大脑 代码风格 算法与数据结构 十分 结构
原文地址:https://www.cnblogs.com/shhh2000/p/10901696.html