Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C# Hình 13-1: Một vòng lặp tính toán Một tính toán bằng vòng lặp gồm những thành phần sau: Mục tiêu post, là một hậu điều kiện, được định nghĩa như là một thuộc tính mà bất cứ trạng thái cuối nào của sự tính toán đều phải thỏa mản. Ví dụ như: “Result là giá trị lớn nhất của mảng”. Mục tiêu này được biểu diễn trong hình minh họa là một tập hợp những trạng thái POST thỏa mãn post. Điều kiện bất biến inv,...
Trang 1Hình 13-1: Một vòng lặp tính toán
Một tính toán bằng vòng lặp gồm những thành phần sau:
Mục tiêu post, là một hậu điều kiện, được định nghĩa như là một thuộc tính
mà bất cứ trạng thái cuối nào của sự tính toán đều phải thỏa mản Ví dụ như:
“Result là giá trị lớn nhất của mảng” Mục tiêu này được biểu diễn trong hình minh
họa là một tập hợp những trạng thái POST thỏa mãn post
Điều kiện bất biến inv, là một sự tổng quát hóa của mục tiêu (post là một trường hợp đặc biệt của inv) Ví dụ: “Result là giá trị lớn nhất của phần mảng không rỗng bắt đầu từ biên thấp nhất” Điều kiện bất biến được biểu diễn trong hình minh
họa là một tập hợp những trạng thái INV thỏa mãn inv
Điểm khởi động init thuộc INV, điểm này thỏa mãn điều kiện bất biến Ví
dụ: khi giá trị của i là biên dưới của mảng và giá trị của Result là phần tử mảng
tương ứng tại vị trí đó, thỏa mãn điều kiện bất biến bởi vì phần tử lớn nhất của mảng một phần tử chính là phần tử đó
Trang 2Sự biến đổi body (bắt đầu từ một điểm trong INV chứ không phải trong POST) phát sinh ra một điểm tiến đến gần POST hơn nhưng vẫn thuộc INV Trong
ví dụ trên, sự biến đổi này mở rộng mảng lên từng phần tử và thay thế Result bằng phần tử thêm đó nếu nó lớn hơn Result Thân vòng lặp trong hàm maxarray ví dụ ở trên là cài đặt của sự biến đổi này
Biên trên dựa vào số body cần thiết để đưa điểm trong INV đến POST Đây
là biến
Tính toán xấp xỉ là phương pháp chính của toán giải tích, nhưng ý tưởng này thì được áp dụng rộng rãi hơn Sự khác biệt cơ bản là trong toán học thuần túy, ta chấp nhận có sự tồn tại của một giới hạn, mặc dù không thể đạt được giới hạn đó
Ví dụ như 1/n có giới hạn là 0 nhưng không có n cụ thể nào để đạt được giới hạn
đó Còn trong tin học, chúng ta cần có được kết quả cụ thể, cho nên chúng ta phải nhấn mạnh rằng tất cả các sự xấp xỉ đều tiến đến kết quả cụ thể sau một số lần lặp đi lặp lại nhất định
13.4 Cú pháp của vòng lặp
− Gồm những thành phần sau:
− Điều kiện bất biến (invariant) của vòng lặp inv – đây là một xác nhận
− Điều kiện thoát khỏi vòng lặp exit
− Điều kiện biến đổi (variant) của vòng lặp var – đây là một biểu thức nguyên
− Tập lệnh khởi tạo init, tập lệnh này luôn tạo ra một trạng thái thỏa inv
và làm var không âm
− Tập lệnh body, tập lệnh này, khi khởi đầu một trạng thái mà inv được giữ và var không âm, sẽ bảo vệ điều kiện bất biến và làm điều kiện biến đổi giảm
đi (nhưng vẫn bảo đảm không âm) Vì vậy, trong trạng thái kết quả, inv được thỏa
và var có giá trị nhỏ hơn trước (nhưng luôn không âm)
Tóm lại, vòng lặp có cú pháp như sau:
Trang 3from
init
invariant
inv
variant
var
until
exit
loop
body
end
Đây là phiên bản vòng lặp cho hàm maxarray:
from
i := t.lower; Result := t @ lower
invariant
Result là giá trị lớn nhất của t khi chỉ số
t.lower = i
variant
t.lower – i
until
i = t.upper
loop
i := i + 1 Result := Result.max (t @ i)
end
Dưới đây là một ví dụ khác về điều kiện bất biến, đây là hàm tính ước chung lớn nhất (greatest common divisor - gcd) của 2 số nguyên dương a, b bằng thuật toán Euclid
Phiên bản không có không có điều kiện bất biến và điều kiện biến đổi:
Trang 4gcd (a, b: INTEGER): INTEGER is
Uớc chung lớn nhất của a và b
require
a > 0; b > 0
local
x, y: INTEGER
do
from
x := a; y := b
until
x = y
loop
end
Result := x
ensure
Result là ước chung lớn nhất của a và b
end
Làm sao chắc chắn được rằng hàm gcd trả về đúng giá trị ước chung lớn nhất của a và b Có một cách để kiểm tra điều này, lưu ý rằng thuộc tính này luôn đúng trong suốt quá trình lặp:
x > 0; y > 0
Cặp x, y có cùng ước chung lớn nhất với cặp a, b
Đây chính là điều kiện bất biến mà ta cần cho hàm này
Làm sao chúng ta biết vòng lặp này luôn luôn kết thúc? Chúng ta cần một điều kiện biến đổi để xác định điều này Không thể chọn x làm điều kiện biến đổi vì
ta không chắc rằng một bước lặp tuỳ ý sẽ làm giảm x, cũng vì lý do đó, y không thể được chọn Nhưng ta chắc rằng hoặc x hoặc y sẽ giảm giá trị, vì vậy giá trị lớn nhất của chúng (được biểu diễn bằng hàm x.max(y)) là lựa chọn tốt nhất
Ta có phiên bản có điều kiện bất biến và điều kiện biến đổi của vòng lặp cho hàm gcd:
Trang 5from
x := a; y := b
invariant
x > 0; y > 0
Cặp <x, y> có cùng UCLN với cặp <a, b>
variant
x.max (y)
until
x = y
loop
end
Chương 14: Sử dụng những xác nhận
Xem qua tất cả các cấu trúc có liên quan đến những xác nhận, ta thấy có 4 loại ứng dụng chính liên quan đến những xác nhận:
− Trợ giúp cho việc viết phần mềm chính xác
− Trợ giúp việc viết tài liệu
− Hỗ trợ kiểm tra (testing), chạy từng bước (debugging) và đảm bảo chất lượng
− Hỗ trợ khả năng chịu lỗi của phần mềm
Chỉ có 2 ứng dụng cuối có khả năng kiểm tra những xác nhận lúc thực thi
14.1 Những xác nhận như một công cụ để viết phần mềm chính xác
Ứng dụng đầu tiên này hoàn toàn mang tính phương pháp và có vai trò quan trọng nhất Điều này đã được làm rõ trong những phần trước: giải thích rõ ràng những yêu cầu chính xác trên mỗi thủ tục, những thuộc tính tổng quát của lớp đối
Trang 6tượng và vòng lặp, giúp cho người lập trình có thể tạo ra những phần mềm chính xác ngay từ lần đầu tiên (khác với những cách tiếp cận khác, cố gắng sửa lỗi cho đến khi đạt được tính đúng đắn)
Từ khóa xuyên suốt ở đây là Design By Contract Trong thế giới thực, một hợp đồng tốt là một hợp đồng chỉ định rõ ràng quyền lợi và nghĩa vụ của mỗi bên
tham gia, và giới hạn của những quyền và nghĩa vụ này Trong thiết kế phần mềm,
tính đúng đắn và tính vững chắc vô cùng quan trọng, vì vậy ta cần chỉ rõ những điều khoản của hợp đồng như là một điều kiện tiên quyết trước khi hợp đồng có hiệu lực
Những xác nhận cung cấp phương tiện nhằm chỉ rõ những điều được mong đợi và
được bảo đảm cho mỗi đối tác của sự ký kết này
14.2 Sử dụng những xác nhận cho việc viết tài liệu: thể rút gọn của một lớp đối tượng
Ứng dụng thứ hai này rất cần thiết cho việc sản xuất những thành phần của phần mềm có tính tái sử dụng cao, và tổng quát hơn, trong việc tổ chức những interface của một môđun trong một hệ thống lớn Tiền điều kiện, hậu điều kiện và những điều kiện bất biến của lớp cung cấp cho những khách hàng tiềm năng của một môđun thông tin cơ bản về những dịch vụ được cung cấp bởi môđun đó Những thông tin này được biểu diễn bằng một hình thức ngắn gọn và chính xác Không một tài liệu dài dòng nào có thể thay thế được một tập những xác nhận dùng để biểu diễn xuất hiện trong chính phần mềm
Thể rút gọn này sử dụng xác nhận như là một thành phần quan trọng trong việc trích những thông tin thích hợp cho những khách hàng tiềm năng từ lớp đối tượng Thể rút gọn này chỉ bao gồm những thông tin có ích cho tác giả của những lớp client, vì vậy nó không cho thấy những đặc tính ẩn cũng như cài đặt của lớp (mệnh đề do) Nhưng thể rút gọn này vẫn giữ lại những xác nhận, chúng sẽ cung cấp những tài liệu cần thiết của hợp đồng mà lớp này quy định với những client
Thể rút gọn của lớp STACK4
Trang 7indexing
description: " Stacks: Cấu trúc dữ liệu với quy tắc truy xuất LIFO, và có độ lớn cố định."
class interface STACK4 [G] creation
make
feature Khởi tạo
make (n: INTEGER) is
Cấp phát cho stack độ lớn n phần tử
require
non_negative_capacity: n >= 0
ensure
capacity_set: capacity = n
end
feature -– Truy cập
capacity: INTEGER
Số phần tử tối đa của stack
count: INTEGER
Số phần tử của stack
item: G is
Phần tử trên cùng
require
not_empty: not empty –- i.e: count > 0
end
feature -– Báo cáo tình trạng
empty: BOOLEAN is
Kiểm tra stack rỗng?
ensure
empty_definition: Result = (count = 0)
end
Trang 8full: BOOLEAN is
Kiểm tra stack đầy?
ensure
full_definition: Result = (count = capacity)
end
feature –- Thay đổi thành phần
put (x: G) is
Thêm phần tử x vào stack
require
not_full: not full
ensure
not_empty: not empty
added_to_top: item = x
one_more_item: count = old count + 1
end
remove is
Xóa phần tử trên cùng của stack
require
not_empty: not empty –- i.e: count > 0
ensure
not_full: not full one_fewer: count = old count – 1
end
invariant
count_non_negative: 0 <= count
count_bounded: count <= capacity
empty_if_no_elements: empty = (count = 0)
end class interface STACK4
Trang 9Thể rút gọn này không phải là những mã chương trình đúng cú pháp Nếu ta
so sánh những xác nhận trong thể rút gọn này với những xác nhận trong lớp đối
tượng, ta thấy rằng tất cả những mệnh đề bao gồm representation đều biến mất
Thể rút gọn này gây sự chú ý đặc biệt bởi những lý do sau:
− Tài liệu này có mức độ trừu tượng hơn so với những gì chúng mô tả, đây là một yêu cầu chủ yếu để có một tài liệu có chất lượng Những cài đặt thực tế (trả lời câu hỏi như thế nào) được loại bỏ, nhưng những xác nhận (trả lời câu hỏi cái
gì – hoặc tại sao) vẫn được giữ lại Lưu ý rằng những ghi chú ở đầu thủ tục (để bổ sung cho xác nhận bằng những giải thích sơ lược về mục đích của thủ tục) và mô tả trong mệnh đề clause cũng được giữ lại
− Với thể rút gọn này, tài liệu không còn được xem như một sản phẩm riêng biệt mà bao gồm trong chính phần mềm Điều này có nghĩa là chỉ có duy nhất một sản phẩm để bảo trì Và cũng vì vậy, tài liệu chắc chắn sẽ đúng đắn hơn, bởi vì bạn sẽ không quên việc cập nhật tài liệu mỗi khi thay đổi phần mềm hay ngược lại
− Thể rút gọn này có thể được phát sinh từ lớp đối tượng bằng những công cụ tự động
Chương 15: Giới thiệu công cụ XC#
15.1 Giới thiệu
XC# là một phần mở rộng của trình biên dịch C#
Luôn biên dịch sau trình biên dịch C#
Có khả năng kiểm tra mã nguồn và đưa ra các cảnh báo lỗi nếu có
Cho phép người lập trình tự định nghĩa các ràng buộc, luật của riêng mình Cho phép thêm hay loại bỏ các các khai báo tiền tố, hậu tố, rất hữu dụng cho việc debug chương trình
Trang 1015.2 XC# hoạt động như thế nào
XC# bao gồm các component thực hiện các tác vụ biên dịch, đưa ra các cảnh báo lỗi, và một số tác vụ khác
XCSharp.Compiler.dll đây là trình biên dịch của XC#
XCSharp.Parser.dll đây là bộ phân tích cú pháp của XC#, nó tạo ra CodeDom ASTs từ mã nguồn
XCSharp.Interface.dll tạo sự tương tác giữa trình biên dịch và các thuộc tính
XCSharp.Attributes.dll and XCSharp.Verifier.dll nắm giữ toàn bộ tập các thuộc tính (như obfuscation, declarative assertions, verification, etc.)
XCSharp.VisualStudio.dll nhận dạng và biên dịch Visual Studio solutions XCSharp.AddIn.dll quản lý việc Add_in trong Visual Studio
xcsc.exe biên dịch bằng dòng lệnh của XC#
Trình biên dịch thông thường như MS C# lấy dữ liệu đầu vào là tập các file
mã nguồn và đầu ra là mã thực thi Tuy nhiên, cách này vẫn còn một hạn chế là không thể hiệu chỉnh được các cư xử bên trong của quá trình biên dịch
XC# cho phép can thiệp vào bên trong quá trình biên dịch bằng cách khai báo các câu lệnh, các luật ngay trong mã nguồn Điều này cho phép trình biên dịch
có thể đưa ra các cảnh báo và thông báo lỗi theo các ràng buộc đã được định nghĩa
15.3 Khai báo các xác nhận
Như đã mô tả ở trên, khai báo các xác nhận cho phép can thiệp vào bên trong quá trình biên dịch, đưa ra các cảnh báo lỗi… Các xác nhận có thể là tiền điều kiện hay hậu điều kiện
− Kiểm tra điều kiện ban đầu của phương thức
Trang 11− Thuộc tính Requires giúp xác định điều kiện ban đầu của phương thức Đối của Requires đơn giản chỉ là một đoạn text mô tả điều kiện ban đầu của phương thức
Ví dụ :
[Requries (“obj != null”)]
public void SetData(Object obj)
{
…
}
− Kiểm tra điều kiện kết thúc của phương thức
− Thuộc tính Ensures giúp xác định điều kiện kết thúc của phương thức Đối của Ensures cũng là một đoạn text mô tả điều kết thúc của phương thức
Ví dụ :
[Ensures (“result != null”)]
public Object GetData()
{
………
rerurn obj;
}
NotNull
Yêu cầu đối tượng nhập vào hay trả về phải
“not null”
Void SetData([NotNull] Object
obj) {}
//obj != null
Equal Yêu cầu đối Void SetData([Equal (5)] int
Trang 12tượng nhập vào hoặc trả về phải
“equal” với một giá trị cho trước
value) {}
//value == 5
ExclusiveBetween
Yêu cầu đối tượng nhập vào hoặc trả về phải nẳm trong một khoảng cho trước
Void SetData([ExclusiveBetween (1, 5)] int value) {}
//1 <= value <= 5
GreaterThan
Yêu cầu đối tượng nhập vào hoặc trả về phải lớn hơn với một giá trị cho trước
Void SetData([GraeterThan (5)]
int value) {}
//value > 5
GreaterThanOrEqual
Yêu cầu đối tượng nhập vào hoặc trả về phải lớn hơn hau bằng với một giá trị cho trước
Void
SetData([GraeterThanOrEqual (5)] int value) {}
//value >= 5
Is
Yêu cầu đối tượng nhập vào hoặc trả về phải
“Is” một giá trị cho trước
Void SetData([Is (typeof (HOCSINH))] Object obj) {}
//kiểu của obj phải là HOCSINH
LessThan
Yêu cầu đối tượng nhập vào hoặc trả về phải
Void SetData([LessThan (5)] int
value) {}
//value < 5