第三单元总结
第三单元总结
- 如何利用jml规格准备测试数据
在本单元中,按照jml进行测试是可以达到完备的效果的,但考虑到自己去手动构造样例太过费力,我最终选择了与同学对拍,利用随机生成的数据来进行测试。
于是问题的关键是这个数据怎么样来随机可以达到完备的效果,完全随机肯定是不能达到所需的效果,所以我采取的方案是人为提供思路,由程序实现随机构造。换句话说,先实现一个基本函数,然后为它提供参数,再由程序输出结果。举个例子,如果我希望测试pinf的异常就向我的程序键入ap 1 - 500,500,ar 1-1000 1-1000,500这时我的程序就会自己构造500条id在1-500的ap和500条id在1-1000的ar指令。
利用这样的方式最大的好处在于可控性,我可以依据自己的构想控制生成的数据,当然也保留了完全随机的方式供轰炸使用,即使如此,我也没有完全按照jml测试,因为采取人力的话太过耗时,而是对重点方法、异常做人为生成,其余全部随机。值得一提的是,因为教程组的方法是有共性和统一规范的,一个个去写函数太过麻烦,我还写了一个函数生成器,解析教程组的函数规范,直接生成一个函数,相当于用一个1.c文件生成另一个genData.c文件。(或许类似于编译原理的文法解析) - 架构分析与策略
在这个单元中,我有种一直在调侃的程序员绝不动祖传代码的感觉。如果我们抛开这个单元比较复杂的几个方法,就会发现,什么也不做,跟着jml写本身就是一个很好的合理的架构。但总得面对现实,对于并查集、最小生成树、最短路这些要求较高的算法,好像原有架构不能满足。对此,我的方案其实相当狂野,缺什么补什么,原框架一点也不改,新功能全部另写,直接装载在原框架上。
虽然听起来,这好像是相当糟糕的事情。但我觉得这其实也是TradeOff。我们不妨去仔细思考一个问题,这些并查集、最小生成树、最短路在这个NetWork里真的有存在感吗?这些方法的出现有其模型意义吗?还是仅仅是课程组想让这个单元不是在水代码。甚至在里面Group的存在感也很低,它的存在感觉只是为了几条指令能够运行。
那么再去思考这样的写法,它其实是达到了一个解耦的效果,这样的功能没有被架构束缚,而是以一种模块化的形式加载进去。这个时候对原有架构的移植、更新会更加简单。这里可能有了问题,那是不是架构越简单越好,当然不是。这是tradeOff,简单的架构加功能是快,但随着功能的添加、需求的改变,代码只会越来越臃肿而且难以理解,这里的难以理解更多是从逻辑上难以理解,完全搞不明白系统的任务是什么。相比之下,复杂系统难以理解在它是怎么工作的,但一旦理解了,就会知道这样好的架构设计带来的收益是无穷的(但相应的成本高、上线时间慢),比如操作系统这样的复杂系统。在本单元的作业中,这样装载在不复杂的系统上或许也不是很糟糕的方案。 - bug分析
三次作业中。我的性能应该说都采用了最快的算法,最短路也使用了堆优化,即使如此我也在第三次作业超时了,原因是自己多添加了一些功能(比如保存了最短路的路径),而在提交代码时没有注释掉调用,导致了超时。应对策略就是注释掉调用。 - 扩展与JML规格
- 扩展思路
不如从Person的角度去展开,我们去考察每一个新的Person添加什么样的功能。(具体实现与对应异常放在JML规格里)-
Advertiser
广告商的目标是挣钱,任务是向潜在客户发送某种产品的广告。所以从两个角度去说明它。一个是广告的准备,另一个是发送。
广告需要生产商(甲方)的确认和请求,所以需要一个可交互的行为,那么可以设计一个新的Message,这个Message用来甲方和乙方沟通。public class AdMessage { String Request; ArrayList<String> ideas; ArrayList<String> suggesstions; ArrayList<Mp4> Ads; Boolean isOk; public void AddRequest() {} public void addIdeas() {} public void addSuggestions() {} public void addAds() {} public void setOk() {} //这里省略了get方法 }
类似于这样的一个类就可以保证广告的成品符合双方的预期,而这个message也可以通过sendMessage交互,不需要新添加方法。
然后考虑发送,广告商要非常小心地选取潜在客户,所以他自然希望尽可能得把广告全发给符合偏好的消费者和有相似偏好的消费者,所以广告商要实现一个新的send方法,通过检查自己认识人的消费属性精准投放,同时由于购买行为是客户委托广告商做的,于是需要提供向广告商下单和接受客户请求的方法。public void sendAds() { } public void BuyProduct() { }
-
Producer
产品销售商的基本属性必然是产品的属性,它要能够储存产品的基本参数,同时还需要维护销售额和销售路径。这里的销售路径是指用户从哪位广告商那里下单。
所以对于producer可以这样搭建类。public class Producer { private Message produce; //商品的参数 private long sum;//销售额 private ArrayList<Pair> road;//路径,一个pair包括广告商和消费者。 }
接下来考虑需要的方法,依次考虑需要1.向广告商商议广告(利用sendMessage)2.收到订单后发出货物(这里姑且把货物也当作message)。
public void sendProduct() { //维护值 }
-
Customer
消费者接收到的广告是由广告商发出的,虽然按照实际消费者也有一个接收函数,但考虑到单线程的设定,不需要设计接收函数。那么消费者的核心功能就是下单。public void BuyProduct() { }
-
JML规格实现
/*@ public normal_behavior @ requires (\exists int i; 0 <= i && i < peoples.length; peoples[i].getId() == id2 && peoples[i] instanceof Advertiser) && @ (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1 && people[i] instanceof Customer) @ assignable getPeople(id1).Ads; @ ensures \old(getPeople(id1).Ads.length) == getPeople(id1).Ads.length - 1; @ ensures (\exists int i; 0 <= i && i < getPeople(id1).Ads.length;getPeople(id1).Ads[i] == ad) @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < peoples.length; peoples[i].getId() == id2 && peoples[i] instanceof Advertiser) || @ !(\exists int i; 0 <= i && i < people.length; people[i].getId() == id1 && people[i] instanceof Customer) @*/ //发送Ad的方法 public void sendAds(int id1, int id2, AdMessage ad) throws PersonIdNotFoundException { } /*@ public normal_behavior @ assignable suggesstions; @ ensures (\forall suggestion i; 0 <= i && i < \old(suggestions).size; suggestions[i+1] == \old(suggestions[i])); @ ensures suggestions.length == \old(suggestions).length + 1; @*/ //用于广告商和产品商交互的方法 public void addSuggestions(String suggestion); /*@ public normal_behavior @ reauires (\exist int i; 0 <= i && i < peoples.length;peoples[i].getId == id && peoples[i] instanceof Customer) @ assignable getPerson(id).messages @ ensures getPerson(id).messages.length = \old(getPerson(id).messages).length + 1 @ ensures (\forall Message i; 0 <= i && i < \old(getPerson(id).messages).size; getPerson(id).messages[i+1] == \old(getPerson(id).messages[i])); @*/ public void sendProduct(int id) { } //由于只有有人下单才会发单,所以不会出现异常。
-
- 扩展思路
- 总结与体会
这一单元最主要的任务就是利用jml语言来实现各种各样的接口,这里我简单谈一谈我对为什么使用jml或者其衍生物的理解。可以看到的是,jml本身是一种逻辑描述的语言,它通过对对象内容的逻辑抽象来描述一个方法的作用。那么这种方案的好处在哪里?
1.将方法的实现与功能剥离开来,让coder无需理解这个系统的全貌就可以上手。
2.逻辑性的语言保证了所有方法的规格符合统一的标准。
但同时jml也有着一些的缺陷,比如方法过于复杂时,逻辑抽象就会非常冗长,不便于理解,但这样的缺陷是可以通过一些方案去解决,让jml语言更加适用。我觉得jml语言可以引入中间变量和方法的语言描述,这样可以让方法不至于冗长也能让coder从大局理解方法的功能。