4.4.1. Khẳng định trong Java và Java PathFinder
Chúng ta có thể thêm các khẳng định (assertion) được biểu diễn dưới dạng biểu thức lôgic được thỏa mãn tại các vị trí nào đó trong đoạn mã vào một chương trình Java. Một khẳng định là một kiểm thử thành công hay thất bại của một điều kiện được thực hiện khi chương trình đang chạy. Các khẳng định tường minh được thêm vào từ Java 1.4. JPF sẽ kiểm tra tất cả tiến trình đan xen (thread interleaving) đang cố gắng vi phạm các khẳng định và thông báo một vi phạm khẳng định đầu tiên nó gặp phải. Chúng ta có thể hủy bỏ việc kiểm tra các khẳng định bằng tùy chọn “-no-assertions”. Đối với các phiên bản Java nhỏ hơn 1.4 thì có thể dùng phương thức AssertTrue của lớp Verify trong JPF.
Cú pháp của lệnh assert của Java như sau:
assert (<biểu thức khẳng định>) : “<Thông báo>”;
Mục đích lệnh này là để kiểm tra biểu thức lôgic <biểu thức khẳng định> được định giá là đúng tại vị trí lệnh assert được gọi. “<Thông báo>” là tùy chọn và chỉ phục vụ cho mục đính thông báo và sẽ in ra chuỗi thông báo trong trường hợp điều kiện bị vi phạm. Ví dụ một lời gọi cụ thể của lệnh như sau:
assert (balanceAmount>=0) : "ATMcard.balanceAmount<0"
Lệnh này kiểm tra thuộc tính balanceAmount có lớn hơn 0 hay không bằng 0 hay không, nếu không đúng thì JPF sẽ thông báo khẳng định bị vi phạm và thông báo cụ thể vi phạm là “ATMcard.balanceAmount<0”.
Nếu sử dụng phương thức assertTrue của JPF thì cú pháp của nó như sau:
Verify.assertTrue(“<Thông báo>”,<biểu thức khẳng định>);
Cũng như lệnh assert ở trên, tham số “<Thông báo>” là tùy chọn. Theo ví dụ trên thì lời gọi sẽ như sau:
Lời gọi này sẽ có kết quả thực hiện y như câu lệnh assert ở trên.
4.4.2. Xác định các bất biến
Như đã mô tả ở trên, một chương trình Java có thể được chú thích với các khẳng định được đặt trong mã tại các vị trí thích hợp. Khi sự thực thi (bởi công cụ kiểm chứng mô hình) bắt gặp lệnh assert thì điều kiện được đặt trong “biểu thức khẳng định” sẽ được kiểm tra. Điều đó có nghĩa là khẳng định sẽ chỉ được kiểm tra khi nó đến lượt thực thi.
Giả sử chúng ta muốn xác định một bất biến liên quan đến một biến trong chương trình hay thuộc tính của đối tượng. Ví dụ: chúng ta muốn kiểm tra giá trị của thuộc tính WithdrawAmountPerDay của lớp ATMcard có luôn luôn nhỏ hơn hay bằng 2000 hay không, vi phạm khi giá trị của nó lớn hơn 2000. Chúng ta sẽ xem các giải pháp làm thế nào để xác định và kiểm chứng điều này trong các mục sau đây.
4.4.2.1. Đặt khẳng định trong đoạn mã
Giải pháp đầu tiên theo một chiến lược chuẩn của việc chèn một khẳng định vào nơi mà thuộc tính WithdrawAmountPerDay được tăng trong đoạn mã như đoạn mã dưới đây.
public void withdrawMoney(long amount) { withdrawalAmountPerDay+=amount;
assert(withdrawalAmountPerDay<=2000) : “ATMcard.WithdrawAmountPerDay>2000”; }
Giải pháp này có ưu điểm là ngay sau khi khẳng định bị vi phạm, công cụ kiểm chứng mô hình (JPF chẳng hạn) sẽ phát hiện ra sự vi phạm này. Do vậy, nó cũng có ưu điểm về thời gian khi định vị một lỗi.
Nhược điểm của kỹ thuật này là chúng ta phải tìm ra nơi biến hay thuộc tính được cập nhật. Trong trường hợp các bất biến phức tạp hơn liên quan đến nhiều thuộc tính có thế gây ra sự lộn xộn mã, dễ gây lỗi hoặc bỏ sót lỗi. Các giải pháp còn lại sau đây không có nhược điểm này nhưng chúng đòi hỏi nhiều thời gian hơn để định vị một lỗi.
4.4.2.2. Kiểm tra bất biến trong phương thức main
Một giải pháp thay thế giải pháp trên là để khẳng định trong phương thức main ngay sau các lệnh chạy các tiến trình. Có hai cách thực hiện giải pháp này được mô tả ngay sau đây:
Đoạn mã dưới đây minh họa làm thế nào khẳng định được chèn tại vị trí cuối phương thức main, sau tất cả các lệnh tạo đối tượng và các lệnh chạy các tiến trình. Khẳng định tham chiếu đến thuộc tính c.withdrawalAmountPerDay, vì thế, nó tham chiếu đến thuộc tính thông qua đối tượng c.
Cách làm việc của nó như sau. Phương thức main sẽ khởi chạy tất cả các tiến trình và bản thân nó tiếp tục chạy như là một tiến trình chạy song song với các tiến trình khác. Đặc biệt, nó sẽ sẵn sàng để thực thi câu lệnh assert bất cứ lúc nào. Công cụ kiểm chứng mô hình vì thế sẽ thực thi nó trong bất kỳ trạng thái nào. Sau đó khẳng định sẽ thực hiện chức năng như một bất biến phải đảm bảo tại bất kỳ thời điểm nào.
public static void main(String args[]) {
ATMcard c=new ATMcard (0, 3000); c.withdrawMoney(1000);
c.withdrawMoney(1500);
assert(c.withdrawalAmountPerDay<=2000) : "ATMcard.WithdrawAmountPerDay>2000"; }
Một nhược điểm của kỹ thuật này là các nội dung của lớp ATMcard bị để lộ trong chương trình chính, ví dụ nó chỉ làm việc chỉ nếu thuộc tính withdrawalAmountPerDay truy cập được từ bên ngoài lớp. Nếu thuộc tính của lớp là bảo vệ (protected) thì phương thức main phải nằm trong cùng gói (package) với lớp. Nếu thuộc tính của lớp là riêng tư (private) thì chúng ta không thể sử dụng kỹ thuật này. Giải pháp tiếp theo giải quyết được vấn đề này.
Chèn khẳng định gián tiếp bằng việc gọi một phương thức bất biến
Để cho một bất biến là cục bộ đối với một lớp, chúng ta có thể đặt khẳng định trong một phương thức được định nghĩa trong một lớp và sau đó gọi phương thức này trong phương thức main. Điều này được minh họa trong đoạn mã dưới đây. Chú ý rằng chúng ta có thể đặt tên phương thức bất biến (invariant method) với tên tùy thích và nó có thể chứa bao nhiêu khẳng định cũng được. Chúng ta thậm chí có thể định nghĩa vài phương thức bất biến và gọi một số trong chúng tại những chỗ thích hợp.
class ATMcard {
public long withdrawalAmountPerDay; public long balanceAmount;
ATMcard(long w_amount, long b_amount) {
withdrawalAmountPerDay=w_amount;
}
public void withdrawMoney(long amount) {
withdrawalAmountPerDay+=amount;
}
public void invariant(){
assert(withdrawalAmountPerDay<=2000) : "ATMcard.WithdrawAmountPerDay>2000"; }
... }
public class Test {
public static void main(String args[]) {
ATMcard c=new ATMcard (0, 3000);
c.withdrawMoney(1000);
c.withdrawMoney(1500);
c.invariant();
} }
Ưu điểm của phương pháp này là bất biến thực sự cục bộ đối với một lớp. Nhược điểm của phương pháp này là chúng ta vẫn cần phải gọi phương thức bất biến trong chương trình chính, điều này có nghĩa là đối tượng c của lớp ATMcard phải thấy được trong chương trình chính. Giải pháp cuối cùng giải quyết được vấn đề này được trình bày trong mục dưới đây.
4.4.2.3. Bất biến như là một tiến trình (Invariant as a Thread)
Giải pháp cuối cùng bao gồm việc định nghĩa lớp ATMcard như là một mở rộng của lớp Thread và sau đó khai báo phương thức run chứa một lời gọi đến phương thức bất biến. Điều này được chỉ ra trong đoạn mã dưới đây. Đoạn mã chỉ ra lớp ATMcard được thừa kế với một phương thức bất biến, một phương thức run gọi phương thức bất biến này và một phương thức khởi tạo khởi chạy một tiến trình như thế nào. Sau đó, bất cứ khi nào một đối tượng của lớp ATMcard được tạo với phương thức mới, một tiến trình được khởi chạy mà tại bất kỳ thời điểm nào có thể kiểm tra khẳng định. Vài phương thức bất biến có thể được định nghĩa và được gọi. Chúng ta cũng có thể viết các khẳng định trực tiếp trong phương thức run.
public long withdrawalAmountPerDay; public long balanceAmount;
ATMcard(long w_amount, long b_amount) {
withdrawalAmountPerDay=w_amount;
balanceAmount=b_amount;
this.start();
}
public void withdrawMoney(long amount) {
withdrawalAmountPerDay+=amount;
}
public void invariant(){
assert(withdrawalAmountPerDay<=2000) : "ATMcard.WithdrawAmountPerDay>2000"; }
public void run() { invariant();
} ... }
public class Test {
public static void main(String args[]) {
ATMcard c=new ATMcard (0, 3000);
c.withdrawMoney(1000);
c.withdrawMoney(1500);
} }
Nhược điểm của kỹ thuật này là nó chỉ làm việc với các lớp chưa là mở rộng của lớp Thread và chương trình tạo ra nhiều tiến trình ảnh hưởng đến hiệu quả của hệ thống một cách không cần thiết.
4.4.3. So sánh phương pháp AOP và phương pháp chèn khẳng định
Hai phương pháp kiểm chứng bất biến của đối tượng sử dụng AOP và chèn khẳng định có sự khác biệt như sau:
Phương pháp kiểm chứng bất biến của đối tượng bằng việc chèn khẳng định không thời gian thực (real time) bằng phương pháp AOP. Đối với phương pháp AOP, chúng ta có thể đưa ra thông báo hoặc lưu vết các vi phạm bất biến. Phương pháp chèn khẳng định đưa ra cảnh báo khi bất biến bị vi phạm và dừng thực thi chương trình.
Đối với phương pháp AOP, chúng ta chỉ cần viết thêm các aspect kiểm chứng để sau đó đan kết vào chương trình cần kiểm chứng mà không phải sửa trực tiếp mã nguồn. Đối với phương pháp chèn khẳng định thì ta phải chèn các khẳng định trực tiếp vào mã nguồn chương trình.
Đối với phương pháp chèn khẳng định, chúng ta cần phải tổ chức lại kiến trúc chương trình để có thể chèn được các khẳng định. Ví dụ với kỹ thuật chèn các khẳng định trong chương trình chính (hàm main) thì các thuộc tính cần kiểm chứng bất biến phải được khai báo lại là công cộng (nếu chưa là công cộng). Kỹ thuật dùng phương thức bất biến thì phương thức bất biến phải là hàm công cộng. Đối với kỹ thuật sử dụng bất biến như một tiến trình thì ta phải viết lại lớp có bất biến cần được kiểm chứng để nó thừa lớp Thread và nạp chồng một số phương thức cần thiết.