JML起步---使用JML 改進你的Java程式(1) (轉)

worldblog發表於2007-08-14
JML起步---使用JML 改進你的Java程式(1) (轉)[@more@]

JML起步

:namespace prefix = o ns = "urn:schemas--com::office" /> 

使用JML 改進你的

 

by Joe Verzulli (to:joe55055@.com">joe55055@yahoo.com)

http://www-106.ibm.com/developerworks/java/library/j-jml.html

Java建模語言(Java Modeling Language,JML)是一種進行詳細設計的符號語言,他鼓勵你用一種全新的方式來看待Java的類和方法。本教程中,Java資深顧問Joe Verzulli 將會給大家介紹這一新的工具以及如何使用這個工具。

 

面向的分析和設計(D)的一個重要原則就是過程性的思考應該儘可能地推遲,不過遵循這個原則的大多數人也不過是把這個原則適用到方法實現這個級別上。一旦設計好了類和介面,下面的事情自然就是實現其中定義的方法了。對呀,我們還能做什麼呢?還有什麼其它方法可以使用嗎?畢竟,用Java進行程式設計和用其他語言進行程式設計一樣,我們都要一步一步地實現每一個方法。

 

標記本身只是表示如何做一個事情(how to do something),根本不管我們希望做什麼。如果我們在做一個事情之前就能夠知道我們能夠達到什麼樣的結果是非常好的,不過Java語言並沒有給我們提供一個可以顯示地把這些資訊插入到我們程式程式碼的方法。

 

Java建模語言(Java Modeling Language,JML)在Java程式碼中增加了一些符號,這些符號用來標識一個方法是幹什麼的,卻並不關心它的實現。如果使用JML的話,我們就能夠描述一個方法的預期的功能而不管他如何實現。透過這種方式,JML把過程性的思考延遲到方法設計中,從而擴充套件了物件導向設計的這個原則。

 

JML引入了大量用於描述行為的結構,比如有模型域、量詞、斷言可視範圍、預處理、後處理、條件繼承以及正常行為(與異常行為相對)規範等等。這些結構使得JML非常強大,不過你並不必要理解或者使用上面所述的所有方面,也不需要一次使用所有的這些方面。你可以一點一點的學習,從非常簡單的開始。

 

這篇文章中採用循序漸進的方式來介紹JML。我們要先了解一下使用JML的各種好處,特別是對開發和編譯過程的影響。然後,我們要討論一下JML的一些結構,比如前置條件、後置條件、模型域、量詞、副作用以及異常行為等等。同時,在討論這些結構的同時,我們會給出一些例程來給你一個直觀的感覺。這樣經過本文的學習,你將可以對JML是如何工作的有一個概念性的理解,從而能夠在你自己的專案中應用JML。

 

概覽


使用JML來宣告性地描述一個方法或類的預期行為可以顯著提高整體的開發程式。把建模標記加入到你的Java程式程式碼中有以下好處:

  • 能夠更為精確地描述這些程式碼是做什麼的
  • 能夠高效地發現和修正程式中的
  • 可以在應用程式升級時降低引入bug的機會
  • 可以提早發現客戶程式碼對類的錯誤使用
  • 可以提供與應用程式程式碼完全一致的JML格式的文件

JML標記總是在Java註釋的內部,所以對正常編譯的程式碼沒有任何影響。如果你想比較一下普通的類和使用了JML的類有什麼差別的話,你可以使用一個的JML(請參考 sources">如下地址)。用JML編譯器編譯的程式碼如果沒有實現JML規範所要求的事項,執行時就會丟擲一個JML異常。這個特性不僅可以幫助我們捕獲程式碼中的bug,而且可以確保JML形式的文件可以與程式程式碼高度一致。

 

文章下面的部分,我將使用開放的Jakarta Commons Collection Component (JCCC)專案中的PriorityQueue介面和BinaryHeap 類來演示JML的各種性質。在這裡你可以找到使用了JML標記完整的這個兩個。

 


本文中使用的程式碼(請參考 如下地址)包括開源專案JCCC中的PriorityQueue 介面。介面嘛,自然是宣告瞭一些方法的簽名,包括方法的引數型別、返回值的型別,並不涉及方法的實現。一般情況下或者只是按照Java語法要求的話,實現介面的類只要實現了介面中定義的各個方法即可,不論實現的方式是多麼地離奇古怪。我們並不想這樣,我們希望能夠確定一個行為規範,所有實現這個介面的類都用我們指定的方式來實現這個介面中定義的方法。透過使用JML我們可以做到這一點。

 

考慮一下PriorityQueue介面的pop()方法,對於優先順序佇列來說,pop()方法應該有什麼樣的功能要求?最起碼應該有三個:第一,如果要pop()方法,佇列中至少要有一個元素;第二,該方法應該返回佇列中優先順序最高的那個元素;第三,該方法應該從佇列中刪除返回的那個元素。

 

下面程式碼段顯示了表示滿足第一個要求的JML標記:

 

1  pop()方法功能規範的JML標記

/*@

  @  public normal_behavior

  @  requires ! isEmpty();

  @*/

pop() throws NoSuchElementException;

 

前面已經提到,JML標記是寫在Java程式碼的註釋中的。包含JML標記的多行註釋以/*@ 開頭,JML忽略任何以@開頭的空行。如果是單行的話,你也可以使用//@這種標記。

這裡JML註釋中public關鍵字與Java中的public意思是一樣的,它表示程式中其他所有的類都要遵循這個JML要求。Public要求只能應用在public方法和public成員變數上。JML同樣有private-、 protected-、 以及 package-級別的作用域。同樣,這些作用域的規則與Java語言中作用域的規則非常相似。

 

這裡normal_behavior關鍵字的意思是,這個JML要求表示這是一種正常情況,執行時不會丟擲異常。後面,我們會描述異常行為是怎麼被界定的。

 

後置條件


JML關鍵字requires用來表示前置條件,前置條件表示呼叫一個方法前必須滿足的一些要求。上面程式碼段中包含一個前置條件,它要求呼叫pop()方法的前提就是isEmpty()方法返回false,也就是說要求這個佇列至少含有一個元素。

 

一個方法的後置條件規範表示一個方法的責任,也就是說當這個方法返回時,它必須滿足這個後置條件的要求。在我們上面的例子中,pop()方法應該返回佇列中優先順序最高的元素。我們希望指定一個後置條件要求JML在執行時檢查是否滿足這個事實。要做到這一點,我們必須跟蹤所有新增到這個優先順序佇列中的元素,這樣我們就可以判斷pop()方法應該返回哪一個元素。怎麼做呢?你可能會考慮在PriorityQueue介面中加入一個成員變數來佇列中元素的值,不過這樣做有兩個問題:

  • PriorityQueue是一個介面,它可能有各種不同具體的實現方式,比如說binary heap、Fibonacci heap或者calendar queue等等,它要與它的各種實現一致,況且JML標記不應該涉及到任何具體的實現細節。
  • 作為一個介面,PriorityQueue只能擁有靜態成員變數。

為了處理這種情況,JML引入了一個叫做模型域(model fields的概念。

 


模型域類似於成員變數,它只能被應用到行為規範中。這是一個PriorityQueue中宣告模型域的例子:

 

//@ public model instance JMLObjectBag elementsInQueue;

 

這個宣告的意思是說這裡有一個叫做elementsInQueue的模型域,它的型別是JMLObjectBag (這個資料型別是在JML中定義的)。instance關鍵字表示雖然這個域是定義在介面中,可是任何實現這個介面的類都擁有一個單獨的、非靜態的elementsInQueue域。與其他JML標記一樣,這個宣告也是出現在註釋中的,所以常規的Java程式碼是不能使用這個elementsInQueue變數的。在程式執行的時候,是沒有任何物件擁有一個叫做elementsInQueue的成員變數的。

 

 

使用一個包來儲存佇列中的元素,然後檢查每一個元素找出優先順序最高的那一個會讓人覺得不高。不過這只是行為規範的一部分,而不會涉及到實現。行為規範的作用在於描述 PriorityQueue的行為介面,也就是說規定了使用 PriorityQueue的客戶程式碼所能依賴的外部行為。

 

PriorityQueue介面的各個具體實現只要可以滿足這個行為規範的要求,就可以使用任何更為高效的方法。比如說,JCCC有一個實現這個介面的 BinaryHeap類,它的實現方式就是使用一個儲存在陣列中的 binary heap 。

 

不過雖然用JML定義行為規範的時候不需要考慮效率,程式執行時JML斷言檢查卻是很重要的。所以開啟斷言檢查時程式的執行可能會有的壓力。

elementsInQueue 儲存新增到優先順序佇列的元素的值,下面的程式碼段顯示pop()方法如何使用elementsInQueue:

 

2  pop()的後置條件中使用模型域

/*@

  @ public normal_behavior

  @  requires ! isEmpty();

  @  ensures

  @  elementsInQueue.equals(((JMLObjectBag)

  @  old(elementsInQueue))

  @  .remove( esult)) &&

  @  esult.equals(old(peek()));

  @*/

Object pop() throws NoSuchElementException;

 

ensures關鍵字表示後面跟著的是pop()方法返回時必須滿足的後置條件。 esult是一個JML關鍵字,它等於pop()方法的返回值。old()是一個JML,它返回pop()方法呼叫之前引數的值。

 

這個ensures語句包含了兩個後置條件。第一,pop()方法返回的那個元素必須要從elementsInQueue刪除。第二,這個返回值要與peek()方法返回的值一致。

類級別的不變數


我們現在已經看到JML能夠讓我們規定方法的前置條件和後置條件,它同樣也允許我們指定類級別的不變數。類級別的不變數指的是進入和退出一個類中每個方法都必須滿足的條件。比方說吧,//@ public instance invariant elementsInQueue != null; 就是PriorityQueue的一個不變數,它的意思是任何實現PriorityQueue的類一旦被例項化,elementsInQueue的值就不能是null。

 

其它部分請參考:

/develop/read_article.?id=19199">http://www.csdn.net/develop/read_article.asp?id=19199 JML起步---使用JML 改進你的Java程式(2)
http://www.csdn.net/develop/read_article.asp?id=19200 JML起步---使用JML 改進你的Java程式(3)
http://www.csdn.net/develop/read_article.asp?id=19202 JML起步---使用JML 改進你的Java程式(4)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-956691/,如需轉載,請註明出處,否則將追究法律責任。

相關文章