窝窝第三单元总结
惯例会有剑三沙雕图
这是想要调戏天策的一周
JML
理论基础
出现意义
记得在CO
课上高老板提到过,咱们设计系统必须强调层次。每一个单元作好本身的事情,管理这些单元的上一级作好管理的事情,逐层管理,层次清晰,便于梳理。这就要求咱们在设计的时候,给各个单元进行明确的分工,即明确它会接收的输入范围,以及输入输出之间的对应关系。在CO
的时候咱们的设计采用的是引脚、接口定义输入输出,经过天然语言描述输入输出之间的关系,这样显然有失严谨性。JML
是一个规范的描述单元模块功能的语言,它没有天然语言的二义性,从而更加准确。java
JML
语法
本人以为语法这个东西实在不必死记硬背,用的时候查手册就行了。点击就送语法手册(嘻~node
规格化设计与契约式编程
本单元工做
这一单元中,咱们的工做主要是根据给定的JML
规格描述,实现相应的接口。我认为,这是属于已经规格设计完成,契约已经订好以后的一个履行契约的过程,而不是契约的制定。(其实前两个单元能作到规格化设计,能够极大程度上避免出BUG
)算法
为何要契约式编程
这里,不谈那些神必的理论问题,姑且谈谈以前看别人代码的一些感想。编程
乱是最大的感觉,通常状况下代码的混乱程度和BUG
数量是正相关的。常常一个类写到一半就忘了本身这个类是要作什么的,和别的类应该怎么交互。数组
在OOP
中,对类的功能设计是基本,若是写着写着就把类的功能给弄混了,整个工程基本上就是一坨混沌。数据结构
契约式编程等于说是在想好各个类的功能以后就明确的把这个功能给肯定下来,立一个契约。接下来要作的就是按照先前立下的契约将模块各个击破。架构
固然若是自己的规格设计就有问题,那应该是救不了了。并发
做业梳理
第一次JML
做业
结构图
此次做业中,因为结构很简单,因此,个人设计很是的朴素,就实现了官方要求的两个类。经过三hash的方法管理路径和节点。eclipse
代码统计分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Main.main(String[]) | 1 | 1 | 1 |
SillyPath.SillyPath(int[]) | 1 | 2 | 2 |
SillyPath.compareTo(Path) | 3 | 4 | 4 |
SillyPath.containsNode(int) | 3 | 2 | 3 |
SillyPath.equals(Object) | 6 | 2 | 6 |
SillyPath.getDistinctNodeCount() | 1 | 1 | 1 |
SillyPath.getNode(int) | 1 | 1 | 1 |
SillyPath.hashCode() | 1 | 1 | 1 |
SillyPath.isValid() | 1 | 1 | 1 |
SillyPath.iterator() | 1 | 1 | 1 |
SillyPath.size() | 1 | 1 | 1 |
SillyPath.toString() | 1 | 1 | 1 |
SillyPathContainer.SillyPathContainer() | 1 | 1 | 1 |
SillyPathContainer.addPath(Path) | 3 | 2 | 4 |
SillyPathContainer.containsPath(Path) | 1 | 1 | 1 |
SillyPathContainer.containsPathId(int) | 1 | 1 | 1 |
SillyPathContainer.getDistinctNodeCount() | 1 | 1 | 1 |
SillyPathContainer.getPathById(int) | 2 | 1 | 2 |
SillyPathContainer.getPathId(Path) | 4 | 1 | 4 |
SillyPathContainer.nodesCntDecrease(Path) | 1 | 3 | 3 |
SillyPathContainer.nodesCntIncrease(Path) | 1 | 3 | 3 |
SillyPathContainer.removePath(Path) | 1 | 1 | 1 |
SillyPathContainer.removePathById(int) | 2 | 1 | 2 |
SillyPathContainer.size() | 1 | 1 | 1 |
Class | OCavg | WMC | |
Main | 1 | 1 | |
SillyPath | 1.91 | 21 | |
SillyPathContainer | 1.92 | 23 | |
Package | v(G)avg | v(G)tot | |
1.96 | 47 | ||
Module | v(G)avg | v(G)tot | |
Project9 | 1.96 | 47 | |
Project | v(G)avg | v(G)tot | |
project | 1.96 | 47 |
因为此次对功能的需求比较简单,因此没怎么设计架构,但复杂度也很低。ide
BUG
我方
本次因为功能需求很简单,因此没有测出BUG
互测屋
一个互测屋的代码基本上同样,没有找出BUG
第二次JML
做业
结构图
众所周知OO
是一门代码重构课程(暴论
本次做业中,为了防止出现TLE
的状况,选择了放弃架构的美观,经过面向数据的编程,追求规定数据范围内的更快速度。
架构上,SillyPath
类照搬第九次做业。FloydGraph
则经过直接在第九次做业的代码的基础上添加新功能代码来实现,因此架构很丑陋。
为了防止本身使用迪杰斯特拉算法实现cache
的时候出现错误,本次做业选择用实现起来较为简单的Floyd
算法规避算法难度。同时,因为本次做业数据量较小,加上我使用静态数组实现Floyd
,因此其复杂度高的缺陷并无暴露,反而其常数小、实现简单的优点在这次做业中表现出色。
代码统计分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
graph.BfsGraph.BfsGraph() | 1 | 1 | 2 |
graph.BfsGraph.addPath(Path) | 2 | 3 | 4 |
graph.BfsGraph.allocVirtualNode() | 1 | 1 | 2 |
graph.BfsGraph.containsEdge(int,int) | 2 | 2 | 3 |
graph.BfsGraph.containsNode(int) | 1 | 1 | 1 |
graph.BfsGraph.containsPath(Path) | 1 | 1 | 1 |
graph.BfsGraph.containsPathId(int) | 1 | 1 | 1 |
graph.BfsGraph.getDistinctNodeCount() | 1 | 1 | 1 |
graph.BfsGraph.getPathById(int) | 2 | 1 | 2 |
graph.BfsGraph.getPathId(Path) | 4 | 1 | 4 |
graph.BfsGraph.getShortestPathLength(int,int) | 3 | 1 | 3 |
graph.BfsGraph.initVisited() | 1 | 1 | 2 |
graph.BfsGraph.isConnected(int,int) | 3 | 1 | 3 |
graph.BfsGraph.nodesCntDecrease(Path) | 1 | 3 | 3 |
graph.BfsGraph.nodesCntIncrease(Path) | 1 | 3 | 3 |
graph.BfsGraph.nodesMapDecrease(Path) | 1 | 4 | 4 |
graph.BfsGraph.nodesMapIncrease(Path) | 3 | 2 | 3 |
graph.BfsGraph.pathDecreaseUpdate(Path) | 1 | 1 | 1 |
graph.BfsGraph.pathIncreaseUpdate(Path) | 1 | 1 | 1 |
graph.BfsGraph.removePath(Path) | 1 | 1 | 1 |
graph.BfsGraph.removePathById(int) | 2 | 1 | 2 |
graph.BfsGraph.resetMatrix() | 1 | 4 | 9 |
graph.BfsGraph.size() | 1 | 1 | 1 |
graph.BfsGraph.updateDistance() | 1 | 2 | 2 |
graph.BfsGraph.updateOneNodeShortestDistance(int) | 4 | 3 | 4 |
graph.FloydGraph.FloydGraph() | 1 | 1 | 1 |
graph.FloydGraph.addPath(Path) | 3 | 2 | 4 |
graph.FloydGraph.allocVirtualNode() | 1 | 1 | 2 |
graph.FloydGraph.containsEdge(int,int) | 3 | 2 | 4 |
graph.FloydGraph.containsNode(int) | 1 | 1 | 1 |
graph.FloydGraph.containsPath(Path) | 1 | 1 | 1 |
graph.FloydGraph.containsPathId(int) | 1 | 1 | 1 |
graph.FloydGraph.getDistinctNodeCount() | 1 | 1 | 1 |
graph.FloydGraph.getPathById(int) | 2 | 1 | 2 |
graph.FloydGraph.getPathId(Path) | 4 | 1 | 4 |
graph.FloydGraph.getShortestPathLength(int,int) | 4 | 1 | 4 |
graph.FloydGraph.isConnected(int,int) | 4 | 1 | 4 |
graph.FloydGraph.nodesCntDecrease(Path) | 1 | 3 | 3 |
graph.FloydGraph.nodesCntIncrease(Path) | 1 | 3 | 3 |
graph.FloydGraph.nodesMapDecrease(Path) | 1 | 4 | 4 |
graph.FloydGraph.nodesMapIncrease(Path) | 3 | 2 | 3 |
graph.FloydGraph.pathDecreaseUpdate(Path) | 1 | 1 | 1 |
graph.FloydGraph.pathIncreaseUpdate(Path) | 1 | 1 | 1 |
graph.FloydGraph.removePath(Path) | 1 | 1 | 1 |
graph.FloydGraph.removePathById(int) | 2 | 1 | 2 |
graph.FloydGraph.resetMatrix() | 1 | 3 | 6 |
graph.FloydGraph.size() | 1 | 1 | 1 |
graph.FloydGraph.updateDistance() | 7 | 6 | 10 |
graph.Main.main(String[]) | 1 | 1 | 1 |
graph.SillyPath.SillyPath(int[]) | 1 | 2 | 2 |
graph.SillyPath.compareTo(Path) | 3 | 4 | 4 |
graph.SillyPath.containsNode(int) | 3 | 2 | 3 |
graph.SillyPath.equals(Object) | 6 | 2 | 6 |
graph.SillyPath.getDistinctNodeCount() | 1 | 1 | 1 |
graph.SillyPath.getNode(int) | 1 | 1 | 1 |
graph.SillyPath.hashCode() | 1 | 1 | 1 |
graph.SillyPath.isValid() | 1 | 1 | 1 |
graph.SillyPath.iterator() | 1 | 1 | 1 |
graph.SillyPath.size() | 1 | 1 | 1 |
graph.SillyPath.toString() | 1 | 1 | 1 |
Class | OCavg | WMC | |
graph.BfsGraph | 2.36 | 59 | |
graph.FloydGraph | 2.65 | 61 | |
graph.Main | 1 | 1 | |
graph.SillyPath | 1.91 | 21 | |
Package | v(G)avg | v(G)tot | |
graph | 2.5 | 150 | |
Module | v(G)avg | v(G)tot | |
Project10 | 2.5 | 150 | |
Project | v(G)avg | v(G)tot | |
project | 2.5 | 150 |
其中BfsGraph
是我实现的一个测试版本,写的比较丑,故复杂度很高。
由上可见,Floyd
在实现复杂度上也是比较低的。即便是我这种两次做业直接写一块的屎架构,仍然能将复杂度控制在可接受的范围内。
BUG
我方
因为Floyd
实现很是简单,本次做业成功规避了算法实现上的错误。同时因为面向数据的编程,个人程序运行效率较高,没有出现TLE
的状况。
互测屋
本次互测你们水平相近,代码功能要求不复杂,且时间比较宽松,故而你们都和平度过。
第三次JML
做业
结构图
本次做业我采用的是分层Floyd
的算法。因为此次须要计算的最短路径种类较多,每一个各写一个Floyd
过于麻烦,因此我实现了一个计算类FloydHelper
用于封装Floyd
算法。这次做业因为处于各类ddl
的并发期,我没能很好地去设计架构,更多的是直接一股脑地塞进了一个SillyRailwaySystem
类中,使得这个类的长度几近500行,这是设计上的失败。
代码统计分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
railway.FloydHelper.FloydHelper(int,int) | 1 | 1 | 1 |
railway.FloydHelper.calculate(int[][]) | 5 | 6 | 8 |
railway.Main.main(String[]) | 1 | 1 | 1 |
railway.SillyPath.SillyPath(int[]) | 1 | 2 | 2 |
railway.SillyPath.compareTo(Path) | 3 | 4 | 4 |
railway.SillyPath.containsNode(int) | 3 | 2 | 3 |
railway.SillyPath.equals(Object) | 6 | 2 | 6 |
railway.SillyPath.getDistinctNodeCount() | 1 | 1 | 1 |
railway.SillyPath.getNode(int) | 1 | 1 | 1 |
railway.SillyPath.getUnpleasantValue(int) | 2 | 1 | 2 |
railway.SillyPath.hashCode() | 1 | 1 | 1 |
railway.SillyPath.isValid() | 1 | 1 | 1 |
railway.SillyPath.iterator() | 1 | 1 | 1 |
railway.SillyPath.size() | 1 | 1 | 1 |
railway.SillyPath.toString() | 1 | 1 | 1 |
railway.SillyRailwaySystem.SillyRailwaySystem() | 1 | 2 | 2 |
railway.SillyRailwaySystem.addPath(Path) | 4 | 2 | 4 |
railway.SillyRailwaySystem.addPathTwoMapInformation(Path,int) | 1 | 3 | 3 |
railway.SillyRailwaySystem.allocVirtualNode() | 1 | 1 | 2 |
railway.SillyRailwaySystem.containsEdge(int,int) | 3 | 2 | 4 |
railway.SillyRailwaySystem.containsNode(int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.containsPath(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.containsPathId(int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.getConnectedBlockCount() | 1 | 1 | 1 |
railway.SillyRailwaySystem.getDistinctNodeCount() | 1 | 1 | 1 |
railway.SillyRailwaySystem.getGlabolMap(int[][],HashMap<Integer, int[][]>) | 1 | 6 | 6 |
railway.SillyRailwaySystem.getLeastTicketPrice(int,int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getLeastTransferCount(int,int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getLeastUnpleasantValue(int,int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getPathById(int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getPathId(Path) | 4 | 1 | 4 |
railway.SillyRailwaySystem.getShortestPathLength(int,int) | 4 | 1 | 4 |
railway.SillyRailwaySystem.getUnpleasantValue(Path,int,int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.isConnected(int,int) | 4 | 1 | 4 |
railway.SillyRailwaySystem.nodesCntDecrease(Path) | 1 | 3 | 3 |
railway.SillyRailwaySystem.nodesCntIncrease(Path) | 1 | 3 | 3 |
railway.SillyRailwaySystem.nodesMapDecrease(Path) | 1 | 4 | 4 |
railway.SillyRailwaySystem.nodesMapIncrease(Path) | 3 | 2 | 3 |
railway.SillyRailwaySystem.pathDecreaseUpdate(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.pathIncreaseUpdate(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.removePath(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.removePathById(int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.removePathTwoMapInformationById(int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.resetMatrix() | 1 | 3 | 6 |
railway.SillyRailwaySystem.search(int,int) | 2 | 2 | 4 |
railway.SillyRailwaySystem.size() | 1 | 1 | 1 |
railway.SillyRailwaySystem.upDateConnectBlock() | 4 | 1 | 6 |
railway.SillyRailwaySystem.upDatePriceMap() | 1 | 1 | 7 |
railway.SillyRailwaySystem.upDateUnpleasantMap() | 1 | 1 | 7 |
railway.SillyRailwaySystem.updateDistance() | 7 | 6 | 10 |
railway.SillyRailwaySystem.updateTransferMap() | 1 | 5 | 5 |
Class | OCavg | WMC | |
railway.FloydHelper | 4.5 | 9 | |
railway.Main | 1 | 1 | |
railway.SillyPath | 1.92 | 23 | |
railway.SillyRailwaySystem | 3.03 | 109 | |
Package | v(G)avg | v(G)tot | |
railway | 2.86 | 146 | |
Module | v(G)avg | v(G)tot | |
Project11 | 2.86 | 146 | |
Project | v(G)avg | v(G)tot | |
project | 2.86 | 146 |
如图可见,本次个人SillyRailwaySystem
类因为是直接由上一次的FloydGraph
强行塞进新功能获得的,因此复杂度极高,层次极差。
BUG
我方
因为Floyd
隐式存在的正确性优点,因此此次我在正确性上面仍然没有出错。同时因为此次的图的节点数比第二次做业还要少,因此Floyd
的常数小的优点更加明显,因此此次做业也没有出现TLE
的状况。
互测屋
咱们组的Lancer
在各类对拍中比其余人慢许多,我意识到他的算法可能有问题,可是因为时间缘由,此次并无去构造一个强样例卡他的TLE
,可是咱们组确实有人作到了,只能说,我技不如人吧。
OpenJML
初体验
整体感受,体验较差,首先,资料太少,官方ReadMe
写得比较屎。
其次,各类神秘的版本不兼容问题。
再者,idea
上没有相应的、成熟的插件。
而且各类语法要求很神秘,课程组给的JML
没办法直接运行上去。
体验
本着体验的态度,我装了个eclipse
来运行OpenJML
插件。
初始代码以下
public class Mult { //@ ensures \result == a * b; public static int mult(int a,int b) { return a * b; } public static void main(String[] args) { System.out.println(mult(2,7)); } }
静态检查结果以下
能够看到,咱们的代码出问题了。
问题在于 a == 1061067 && b == 1121128
的时候出现了算术溢出的状况。
修改规格后,代码以下
public class Mult { //@ requires ( a < 10 && a > -10 && b < 10 && b > -10 ); //@ ensures \result == a * b; public static int mult(int a,int b) { return a * b; } public static void main(String[] args) { System.out.println(mult(2,7)); } }
静态检查结果
限制了数据范围后,可以经过测试。
小结
OpenJML
这个工具自己很是有意义,用逻辑验证来验证正确性,是很是可靠的。
可是,这个工具在推广上尚有不足。使用过于繁琐,兼容性差,文档很迷。
做为一个用户,我是不太倾向于使用这么一个软件的。
JMLUnit
初体验
因为课程组给的规格没法直接使用,本着体验工具的态度,我本身写了个很是简单的程序用以体验。
package Miao; public class Main { //@ requires true; //@ ensures \result == a * b; public static int mult(int a,int b) { return a * b; } public static void main(String[] args) { mult(2,7); } }
生成的样例以下
能够看出,JMLUnit
的样例主要针对各类边界条件的检查。实际上,在通常的开发中,边界条件由于其容易出错,会被格外重视,反而不容易出错。
可以意识到的BUG
都不会成为BUG
,咱们的测试更多地应该放在各类分支的遍历上。
评测机一些神必问题
这个单元中个人对拍器出现了神必问题。在往被测程序的进程中写入时会卡死,我认为多是缓冲区写满的问题,但照理来讲,被测程序也一直在读取,不该该出现这个问题。最后我没法解决。
因而我直接改为命令行重定向输入,从文本输入。问题得以解决。
单元感想
正面
- 规格化设计和契约式编程是很是好的一个教学重点。
OO
不该该只教怎么设计各个对象的分工,也应该教导根据已有的分工去履行职责,这一点很是重要。 - 利用
JML
语言描述功能必定程度上消除了天然语言的二义性。
负面
- 过于
DS
。说实在话,这个单元个人重点已经不在规格和架构上了,更多的是在想算法和数据结构的问题,这个单元的DS
难度确实有点大到喧宾夺主的地步了。不过这也是一个很矛盾的问题,课程须要一个区分度,若是把区分度加在JML
的理解上,就必然要让JML
变得复杂,这也就意味着这个类的功能设计的很复杂、很神必,这显然是个"屎"设计。那么区分度就只能在DS
上了。 OpenJML
这个工具当然是个很强大的工具,可是它的局限性和可用性并无咱们预期的效果。比起用一个很复杂的工具,我相信多数用户仍是倾向于本身搭一个简易的对拍器同时本身经过逻辑分析来保证正确性。
遗憾
- 因为近期
ddl
高发,没有时间充分地体验Junit
的测试,甚是遗憾。
建议
- 但愿课程平台增长在官方评测机上运行本身测试样例的功能,这样就解决了环境不同带来的一些争议。