OO第三次博客作业
一、规格化设计的历史
最早的程序设计是用机器语言编写的,直接用二进制码来表示,但是这样太过困难,之后就出现了汇编语言,借助符号来表示操作。如此尽管提高了可读性,但本质还是面向机器的,抽象化程度还不够高。
随后出现了面向过程的“高级语言”,但是goto语句导致的面条式代码限制了程序的规模,为解决这一问题,人们抛弃goto语句,采取“自顶向下、逐步细化、模块化”的指导思想。提高了抽象化程度,从整体上降低了软件开发的复杂度。
随着需求越来越多,编程应用领域越来越广泛,产生了面向对象的思想,进一步提高了程序抽象的级别。而规格化设计能将运行细节抽象为用户所需求的行为,以此减少程序复杂度,使得程序员可以专注在处理少数重要的部分。
二、分析自己的规格bug
由于这三次测试,我遇到的测试者对于规格的报告并没有针对具体方法,大部分是泛说了,例如有部分方法EFFECTS不全这样的说法,所以我只能自己找出一些自己作业中较好的规格bug和不好的规格bug进行分析。
其实就我个人想法,大部分出现规格bug的地方都是后置条件,所以我以下列举的内容也大部分跟后置条件有关。清晰起见,在列出不好的规格的同时,也会分析其产生原因并给出改进写法。
首先列举几个自己写的个人感觉较为清晰表明了后置条件的过程规格。这些都是之前有问题的,但是之前有问题的版本找不到了(应该是都用的自然语言描述的),这是改为形式化语言之后的结果。
public void startAllTaxi() {
/**
* @REQUIRES: None;
* @MODIFIES: None;
* @EFFECTS: \all int i; 0 <= i < 100; threadTaxi[i].getState == Thread.State.RUNNABLE;
*/
for (int i = 0; i < Constants.TAXINUM; i++)
threadTaxi[i].start();
}
public void grabCustomer(Customer customer) {
/**
* @REQUIRES: customer != null;
* @MODIFIES: customer;
* @EFFECTS:
* \all int i; 0 <= i < 100 && taxi[i].state == State.WAITING; customer.acceptableTaxi.contains(taxi[i]);
*
*/
for (int i = 0; i < Constants.TAXINUM; i++) {
TaxiInfo taxiInfo = this.taxi[i].getTaxiInfo();
customer.tryAddTaxi(taxiInfo, taxi[i]);
}
}
private Direction getRandomDirection() {
/**
* @REQUIRES: None;
* @MODIFIES: \this.curPos; Map;
* @EFFECTS:
* \all Edge edge;
* Map.edgeSet[\old(\this.curPos)].contains(edge) && Map.isLinked[edge.getStartPos()][edge.getDesPos()] == true;
* Map.flow[\this.curPos][\old(\this.curPos)] <= Map.flow[edge.getStartPos()][edge.getDesPos()];
* Map.nextWinFlow[\old(this.curPos)][\this.curPos] == \old(Map.nextWinFlow)[\old(this.curPos)][\this.curPos] + 1;
* Map.nextWinFlow[this.curPos][\old(\this.curPos)] == \old(Map.nextWinFlow)[this.curPos][\old(\this.curPos)] + 1;
* @THREAD_REQUIRES:
* \locked(\this);
*/
ArrayList<Edge> edges = Map.getEdges(this.curPos);
Random rand = new Random(System.currentTimeMillis());
double minFlow = Double.MAX_VALUE;
int target = 0;
for (Edge edge: edges) {
if (checkRoad && !Map.isLinked(this.curPos, edge.getDesPos())) continue;
double flow = Map.getFlow(this.curPos, edge.getDesPos()) + rand.nextDouble();
if (flow < minFlow) {
minFlow = flow;
target = edge.getDesPos();
}
}
return Map.getDirection(this.curPos, target);
}
下面是有着不建议方式的规格。
private double waitForLights(Direction dir) throws InterruptedException {
/**
* @REQUIRES: dir != null;
* @MODIFIES: None;
* @EFFECTS:
* (isTurnLeft(this.lastDirection, dir) && \this.trafficLight.isGreen(this.curPos, this.lastDirection)) ==>\result == sleepMills * 0.01;
* (dir == \this.lastDirection && !\this.trafficLight.isGreen(this.curPos, this.lastDirection)) ==>\result == sleepMills * 0.01;
* ((!isTurnLeft(this.lastDirection, dir) || \this.trafficLight.isGreen(this.curPos, this.lastDirection)) &&
* (!dir == \this.lastDirection || \this.trafficLight.isGreen(this.curPos, this.lastDirection))) ==>\result == 0;
*/
double time = 0;
if (!trafficLight.isControlled(this.curPos)) return time;
boolean isGreen = trafficLight.isGreen(this.curPos, this.lastDirection);
if (isTurnLeft(this.lastDirection, dir) && isGreen) {
while (trafficLight.isGreen(this.curPos, this.lastDirection)) {
Thread.sleep(1);
time += 0.01;
}
return time;
}
if (dir == this.lastDirection && !isGreen) {
while (!trafficLight.isGreen(this.curPos, this.lastDirection)) {
Thread.sleep(1);
time += 0.01;
}
return time;
}
return time;
}
其实这个是一个最常见的错误,就是描述内容是实现算法,更改方式应该是从后置条件的含义入手,如下。
/**
* @REQUIRES: dir != null;
* @MODIFIES: None;
* @EFFECTS:
* \result == sleepMills * 0.01 ==>(isTurnLeft(this.lastDirection, dir) &&
* \this.trafficLight.isGreen(this.curPos, this.lastDirection)) ||
* (dir == \this.lastDirection&&!\this.trafficLight.isGreen(this.curPos, this.lastDirection));
* \result == 0 ==> ((!isTurnLeft(this.lastDirection, dir) ||
* \this.trafficLight.isGreen(this.curPos, this.lastDirection)) &&
* (!dir == \this.lastDirection || \this.trafficLight.isGreen(this.curPos, this.lastDirection)));
*/
部分方法需要抛出异常,但是没有体现在规格中。如Taxi类中的部分方法
private void dealSTOPPING() throws InterruptedException {
/**
* @REQUIRES: None;
* @MODIFIES: \this.time; \this.state; \this.waitingTimes;
* @EFFECTS:
* \this.state == State.WAITING &&
* \this.time == \old(\this.time) + 10 &&
* \this.waitingTimes == 0;
* @THREAD_EFFECTS:
* \locked(\this);
*/
Thread.sleep(Constants.STOPTIME);
synchronized (this) {
this.state = State.WAITING;
this.time += Constants.FAKESTOPTIME;
this.waitingTimes = 0;
}
}
应该改为如下形式的jsf
/**
* @REQUIRES: None;
* @MODIFIES: \this.time; \this.state; \this.waitingTimes;
* @EFFECTS:
* normal_behavior
* \this.state == State.WAITING &&
* \this.time == \old(\this.time) + 10 &&
* \this.waitingTimes == 0;
* (any thread has interrupted the current thread)==> exceptional_behavior(InterruptedException);
* @THREAD_EFFECTS:
* \locked(\this);
*/
同理对于下面的方法
private void dealWAITING() throws InterruptedException {
/**
* @REQUIRES: None;
* @MODIFIES:
* \this.state; \this.time; \this.waitingTimes;
* \this.path; \this.desPos; \this.pathIndex;
* \Map.taxiGUI;
* @EFFECTS:
* (\old(\this.waitingTimes == 40)) ==> (this.state == state.STOPPING);
* \old(\this.waitingTimes != 40) ==>
* \this.time == \old(\this.time) + sleepMills * 0.01 && this.waitingTimes == \old(\this.waitingTimes) + 1;
* \old(\this.waitingTimes != 100) && \this.customer != null ==> \this.desPos == \this.customer.getStartPos() && \this.path == Map.getPath(\this.curPos, \this.desPos)
* && \this.pathIndex == 0 && \this.state == State.PICKING &&\this.time == Math.max(\old(\this.time), customer.getCallTime() + 30);
* @THREAD_EFFECTS:
* \locked(\this);
*/
if (this.waitingTimes == 40) {
this.state = State.STOPPING;
this.setTaxiStatus();
return;
}
Thread.sleep(Constants.ONEEDGETIME);
Direction dir = this.getRandomDirection();
this.time += this.waitForLights(dir);
synchronized (this) {
this.time += Constants.FAKEONEEDGETIME;
this.waitingTimes += 1;
this.pathMoving(dir);
this.setTaxiStatus();
if (this.customer != null) {
System.out.println(id + " taxi taken: " + customer);
this.desPos = this.customer.getStartPos();
this.path = Map.getPath(this.curPos, this.desPos, checkRoad);
this.pathIndex = 0;
this.time = Math.max(this.time, customer.getCallTime() + Constants.PICKWINDOW);
this.state = State.PICKING;
this.customer.logStartInfo(this.id, this.curPos, customer.getCallTime()+ Constants.PICKWINDOW);
this.setTaxiStatus();
}
}
}
jsf也应该修改为以下形式
/**
* @REQUIRES: None;
* @MODIFIES:
* \this.state; \this.time; \this.waitingTimes;
* \this.path; \this.desPos; \this.pathIndex;
* \Map.taxiGUI;
* @EFFECTS:
* normal_behavior
* (\old(\this.waitingTimes == 40)) ==> (this.state == state.STOPPING);
* \old(\this.waitingTimes != 40) ==>
* \this.time == \old(\this.time) + sleepMills * 0.01 && this.waitingTimes == \old(\this.waitingTimes) + 1;
* \old(\this.waitingTimes != 100) && \this.customer != null ==> \this.desPos == \this.customer.getStartPos() && \this.path == Map.getPath(\this.curPos, \this.desPos)
* && \this.pathIndex == 0 && \this.state == State.PICKING &&\this.time == Math.max(\old(\this.time), customer.getCallTime() + 30);
* (any thread has interrupted the current thread)==>exceptional_behavior (InterruptedException);
* @THREAD_EFFECTS:
* \locked(\this);
*/
在Taxi类中的部分方法也有这个问题,故在此不重复列出。
另外就是有关线程安全的线程规格,原来的版本如下。
public class WaitingQueue {
/**
* @OVERVIEW: 请求队列类
*/
private Queue<Customer> customerQueue = null;
public WaitingQueue() {
/**
* @REQUIRES: None;
* @MODIFIES: \this.customerQueue;
* @EFFECTS: \this.customerQueue == new LinkedList<>();
*/
this.customerQueue = new LinkedList<>();
}
public boolean repOK() {
/**
* @REQUIRES: None;
* @MODIFIES: None;
* @EFFECTS: \result == (\this.customerQueue != null);
*/
return (this.customerQueue != null);
}
public synchronized void offer(Customer customer) {
/**
* @REQUIRES: customer != null;
* @MODIFIES: \this.customerQueue;
* @EFFECTS: \this.customerQueue == \old(\this.customerQueue).offer(customer);
*/
this.customerQueue.offer(customer);
}
public synchronized Customer poll() {
/**
* @REQUIRES: None;
* @MODIFIES: \this.customerQueue;
* @EFFECTS: \result == \old(\this.customerQueue).poll();
*/
return this.customerQueue.poll();
}
public synchronized Customer peek() {
/**
* @REQUIRES: None;
* @MODIFIES: None;
* @EFFECTS: \result == \this.customerQueue.peek();
*/
return this.customerQueue.peek();
}
public synchronized void updateTaxiList(double curTime, TaxiManager taxiManager) {
/**
* @REQUIRES: curTime >= 0 && taxiManager != null;
* @MODIFIES: customer;
* @EFFECTS:
* \all int i;
* 0 <= i < \this.customerQueue.size() && cutTime - \this.customerQueue.get(i).getCallTime < 75;
* taxiManager.grabCustomer(\this.customerQueue.get(i)) == true;
*
*/
for (Customer customer: this.customerQueue) {
if (curTime - customer.getCallTime() < Constants.PICKWINDOW) {
taxiManager.grabCustomer(customer);
}
}
}
}
其中在多线程场景下的jsf全部需要加上多线程共享后置规格。举一个例子如下,其余方法都需要添加如下的@THREAD_EFFECTS: \locked();,在此不再赘述。
/**
* @REQUIRES: None;
* @MODIFIES: None;
* @EFFECTS: \result == \this.customerQueue.peek();
* @THREAD_EFFECTS: \locked();
*/
三、功能bug和规格bug在方法上的聚集关系。
其实感觉每次被报的功能性bug都是跟多线程的随机特性有关的bug,跟规格并没有什么关系。多线程所出现的问题并非规格所能解决,还跟测试者本身cpu性能,线程调度等原因有关。在我电脑上同步的东西,到了对方电脑上就变得不同步了,这种情况是没有办法判定对错的。
更尴尬的那次就是我改完了规格以为也改了代码就忽略了后来时间改变的要求,造成了出租车停车时间的错误。
四、设计规格和撰写规格的基本思路和体会
设计阶段感觉还是需要一个宏观到具体的过程,但是给出规格是一个太过具体的过程。更由于jsf扣分的引入,一旦代码实现上变化了一些东西,有时就会出现jsf的不正确,就需要重改jsf,所以我相信近乎所有人的选择都是先写的代码再写的jsf。但是按照课程安排的构想这两个行为应该是反过来的,然而没办法。。从正确性来看,先写代码再写jsf就是能一定程度上保证jsf的正确性,降低被扣分的可能性。没人想为了贯彻一个设计理念就铤而走险,毕竟我们能看到的只有互测的结果。从设计的角度来讲也是如此,每个人都会在新的设计思想和互测结果上做一个权衡,使用自己不熟悉的东西势必会增加被扣bug的概率,因此保守的设计会减少互测被报bug的概率,唯互测结果论。这是很无奈的一点。。。
要是单从撰写规格来说就是考虑一个方法的作用是什么,再结合返回值什么的具体写。当然方法越短这个越好写。。然而方法越短意味着会留给对方更多jsf可扣,理想情况只有一个方法就有一个可扣,感觉这是比较可以钻空子的一点。。。感觉jsf的扣分制度还要斟酌一下,否则某些人的恶意扣分行为就可能导致被测试者也成为恶意扣分者,这样下去真的会很影响学生情绪,对课程当然也不利。。。