Tải bản đầy đủ (.pdf) (12 trang)

TÌM HIỂU CÔNG NGHỆ DESIGN BY CONTRACT VÀ XÂY DỰNG CÔNG CỤ HỖ TRỢ CHO C# - 3 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (573.04 KB, 12 trang )









Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
25
n>0; x/=Void
Lưu ý cách dùng dấu chấm phẩy (“;”). Ý nghĩa của dấu chấm phẩy ở đây
tương đương với phép and. Dấu chấm phẩy có thể đặt giữa phần khai báo và chỉ thị.
Khi những mệnh đề của xác nhận nằm trên những dòng khác nhau, ta không cần
dùng dấu chấm phẩy (xem như có một phép and mặc định giữa các dòng liên tiếp).
Những quy ước này giúp ta có thể nhận biế
t các thành phần riêng biệt của một xác
nhận. Trong thực tế, ta thường dán nhãn (label) cho những thành phần này, ví dụ
như:
Positive: n>0
Not_void: x/=Void
Các nhãn như trên có vai trò nhất định trong lúc thực thi xác nhận. Tuy
nhiên, việc sử dụng chúng ở đây là nhằm làm cho văn bản của ta rõ ràng và tường
minh hơn.


Chương 6: Tiền điều kiện và hậu điều kiện
Ứng dụng đầu tiên của xác nhận là đặc tả ngữ nghĩa của thủ tục. Một thủ tục
không chỉ là một đoạn mã chương trình mà nó là cài đặt của một hàm nào đó từ đặc
tả của một kiểu dữ liệu trừu tượng, nó sẽ thực hiện một công việc hữu ích. Việc biểu
diễn công việc này một cách chính xác là vô cùng cần thiết.


Ta có thể đặ
c tả công việc cần thực thi của một thủ tục bằng 2 xác nhận liên
quan với nó là tiền điều kiện (preconditions) và hậu điều kiện (postconditions). Tiền
điều kiện chỉ ra những thuộc tính cần được thoả mãn bất cứ khi nào thủ tục được
gọi; còn hậu điều kiện chỉ ra những thuộc tính chắc chắn có sau khi thủ tục thực thi
xong.

6.1. Lớp ngăn xếp
Một ví dụ sẽ giúp ta làm quen với cách sử dụng các xác nhận:








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
26
class STACK [G] feature
…Declaration of the features:
count, empty, full, put, remove, item
end
Trước khi xem phần cài đặt, cần chú ý rằng những thủ tục được đặc trưng
bởi những thuộc tính ngữ nghĩa mạnh mẽ và độc lập với cách biểu diễn nó. Ví dụ
như:
− remove và item chỉ thực thi được khi có số phần tử lớn hơn 0.
− put tăng số phần tử lên 1, remove giảm số phần tử đi 1.
Những thuộc tính như thế là một phần trong đặc tả của kiểu dữ liệu trừu
tượng, ngay cả những người chưa bao giờ sử dụng đến kiểu dữ liệu trừu tượng cũng

phải hiểu như vậy. Nhưng trong các cách tiếp cận thông thường của việc xây d
ựng
phần mềm, những văn bản phần mềm thường không đề cập đến chúng. Thông qua
những tiền điều kiện và hậu điều kiện của thủ tục, ta có thể đưa chúng vào những
thành phần tường minh của phần mềm.
Ta sẽ biểu diễn những tiền điều kiện và hậu điều kiện như là mệnh đề được
gi
ới thiệu qua hai từ khóa require và ensure. Lưu ý rằng những phần cài đặt của
những thủ tục được chừa trống, ta sẽ tìm hiểu trong phần sau.
indexing
description: "Stacks: Cấu trúc dữ liệu với quy tắc
truy xuất LIFO”
class STACK1 [G] feature – Access
count: INTEGER
Số phần tử của Stack
item: G is
Phần tử trên cùng
require
not empty








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
27
do


end
feature – Status report
empty: BOOLEAN is
Kiểm tra Stack rỗng?
do end
full: BOOLEAN is
Kiểm tra Stack đầy?
do

end
feature Element change
put (x: G) is
Thêm phần tử x vào Stack.
require
not full
do

ensure
not empty
item = x
count = old count + 1
end
remove is
Xóa phần tử trên cùng của Stack.
require
not empty
do

ensure

not full








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
28
count = old count – 1
end
end

Cả 2 mệnh đề require và ensure đều là tùy chọn. Nếu có thì require phải
nằm trước mệnh đề local. Những phần tiếp theo giải thích chi tiết hơn ý nghĩa của
tiền điều kiện và hậu điều kiện.

6.2. Tiền điều kiện
Tiền điều kiện biểu diễn những ràng buộc mà một thủ tục sẽ thực hiện một
cách chính xác. Ví dụ như:
− put sẽ không được gọi nếu ngăn xếp đã đầy.
− remove và item sẽ không thực hiện trên một ngăn xếp rỗng.
Tiền điều kiện vào có hiệu lực đến tất cả những lời gọi của thủ tục, cả trong
lớp và từ những lớp liên quan. Một hệ thống chính xác sẽ không bao giờ thực thi
một lời gọi không thỏa mãn tiền điều kiện.

6.3. Hậu điều kiện
Hậu điều kiện biểu diễn những thuộc tính của trạng thái kết quả có được sau

khi thực thi thủ tục. Ví dụ như:
− Sau thủ tục put, ngăn xếp sẽ không thể rỗng, phần tử trên cùng là phần
tử mới được thêm vào, và số lượng phần tử sẽ tăng lên 1.
− Sau thủ tục remove, ngăn xếp không thể đầy, số phần tử giảm đi 1.
Sự có mặt của mệnh đề hậu điều kiện trong thủ tục bảo đảm kết quả của thủ
tục sẽ thoả mãn các thuộc tính (giả sử rằng thủ tục đã thỏa mãn tiền điều kiện để
được gọi).
Một từ khóa đặc biệt, old, xuất hiện trong hậu điều kiện. put và remove dùng
từ khóa này để biểu diễn sự thay đổi của biến count. Cú pháp: old e, trong đó e là








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
29
một biểu thức, thường là một thuộc tính, biểu thị giá trị của e trong đầu vào của thủ
tục. Hậu điều kiện của put có mệnh đề
count = old count + 1
để thể hiện rằng put, khi được gọi bởi bất kỳ đối tượng nào, sẽ tăng giá trị biến
count của đối tượng đó lên 1.


Chương 7: Giao ước cho tính đáng tin cậy của phần
mềm
Định nghĩa tiền điều kiện và hậu điều kiện cho một thủ tục là một cách định
nghĩa một hợp đồng (contract) giữa thủ tục và những lớp gọi nó.

7.1. Quyền lợi và nghĩa vụ
Bằng cách kết hợp những mệnh đề require pre và ensure post trong thủ tục
r, lớp đối tượng “tuyên bố” với những khách hàng của nó:


Nếu các bạn hứa gọi r thỏa mãn pre, tôi hứa sẽ trả về kết quả thỏa mãn post


Trong mối liên hệ giữa người và người hoặc giữa các công ty với nhau, hợp
đồng là một văn bản làm cho những điều khoản của mối quan hệ trở nên trong sáng,
rõ ràng. Thật đáng ngạc nhiên khi trong lĩnh vực phần mềm, nơi mà sự đúng đắn, rõ
ràng có vai trò sống còn, ý tưởng hợp đồng này lại phải mất quá nhiều thời gian để
thể hiện mình. Tiền điều kiện và hậ
u điều kiện mô tả một hợp đồng giữa thủ tục
(đóng vai trò nhà cung cấp) và những đối tượng gọi đến nó (vai trò khách hàng).
Đặc tính quan trọng nhất của hợp đồng trong công việc của con người là sự
đòi hỏi về “nghĩa vụ” (obligation) và “quyền lợi” (right) cho cả 2 bên – thường là
nghĩa vụ của bên này sẽ trở thành quyền lợi của bên kia. Điều này cũng đúng đối
với hợp đồng giữa các lớp đối tượng:








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
30
− Tiền điều kiện ràng buộc khách hàng: nó định nghĩa những điều kiện

để một lời gọi đến thủ tục trở nên hợp pháp. Đây là nghĩa vụ của khách hàng và là
quyền lợi của nhà cung cấp.
− Hậu điều kiện ràng buộc lớp đối tượng: nó định nghĩa những điều
kiện cần phải được đảm bảo bởi thủ tục khi trả về. Đây là quyền lợi của khách hàng
và là nghĩa vụ của nhà cung cấp.
7.1.1. Những quyền lợi
− Đối với khách hàng, đó là sự đảm bảo những thuộc tính phải có được
sau khi gọi thủ tục.
− Đối với nhà cung cấp, đó là sự đảm bảo những tính chất phải được
thỏa mãn ở bất cứ nơi nào thủ tục được gọi.
7.1.2. Những nghĩa vụ
− Đối với khách hàng, đó là sự đáp ứng những yêu cầu được phát biểu
trong tiền điều kiện.
− Đối với nhà cung cấp, đó là những gì phải làm mà hậu điều kiện đã
định ra.

Đây là hợp đồng cho thủ tục put của ví dụ trên:

put
Nghĩa vụ Quyền lợi
Khách hàng
Đáp ứng tiền điều kiện: Chỉ gọi
put(x) khi ngăn xếp chưa đầy.
Kết quả hậu điều kiện:
Thông tin ngăn xếp được cập
nhật: không rỗng, x nằm trên
cùng, count tăng 1.
Nhà cung cấp
Đáp ứng hậu điều kiện: Cập
nhật thông tin ngăn xếp: không

rỗng, x nằm trên cùng, count
tăng 1.
Kết quả tiền điều kiện:
Được bảo đảm là ngăn xếp
chưa đầy khi put được gọi.








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
31
7.2. Nghệ thuật của sự tin cậy phần mềm: kiểm tra ít hơn,
bảo đảm nhiều hơn
Mặc dù bạn có thể chưa để ý, một trong những nguyên tắc của hợp đồng có
xu hướng đi ngược lại những kiến thức tổng quát đã được công nhận trong ngành
công nghiệp phần mềm; gặp nhiều sự phản ứng ngay từ đầu, nhưng Design by
Contract đã là một trong số những đóng góp chính vào sự tin cậy của phần mềm và
đạt được tầm quan trọng xứng đ
áng.
Một quy tắc tương phản với sự quan sát đã nêu trên: tiền điều kiện là quyền
lợi của nhà cung cấp và được biểu diễn ở góc dưới bên phải trong bảng trên: nếu
phần khách hàng của hợp đồng không được thõa mãn, nói cách khác nếu lời gọi
không đáp ứng tiền điều kiện, sau đó lớp đối tượng không được bao bọc bởi hậu
điều kiện. Trong tr
ường hợp này thủ tục có thể làm bất cứ chuyện gì: trả ra giá trị
bất kỳ, lặp vô hạn, không trả về giá trị, thậm chí làm hỏng sự thực thi bằng một cách

nào đó. Đây là trường hợp “khách hàng sai”.
Lợi ích đầu tiên của thỏa thuận này là sự đơn giản hóa đáng kể phong cách
lập trình. Tiền điều kiện được xem như những ràng buộc mà một lời gọi đến một
thủ tục phải tuân theo, còn nếu bạn là người phát triển lớp đối tượng, bạn sẽ giả sử
rằng những ràng buộc này được thỏa mãn khi viết những thủ tục; bạn sẽ không cần
phải kiểm tra những ràng buộc đó trong thủ tục. Xét hàm căn bậc 2 như sau:
sqrt (x: REAL): REAL is
Căn bậc 2 của x
require
x >= 0
do … end
Bạn sẽ chỉ viết thuật toán tính căn bậc 2 mà không cần quan tâm đến trường
hợp x < 0, điều này đã được kiểm tra bởi tiền điều kiện và trở thành trách nhiệm của
những khách hàng – những lớp sẽ gọi tới hàm này.
Thực tế phương pháp của Design by Contract còn đi xa hơn nữa.
Viết đoạn chương trình này vào sau do








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
32
if x < 0 then
“Handle the error, somehow”
else
“Proceed with normal square root computation”

end
không chỉ là không cần thiết mà là không thể chấp nhận được. Điều này có thể biểu
diễn như một nguyên tắc:

Nguyên tắc không dư thừa
Không được kiểm tra tiền điều kiện trong thân của thủ tục trong bất kỳ trường
hợp nào


Nguyên tắc này đã đi ngược với chủ trương của rất nhiều sách giáo khoa về
công nghệ phần mềm hay phương pháp lập trình, thường được biết đến như là
phương pháp lập trình phòng thủ - với ý tưởng: để thu được một phần mềm đáng tin
cậy, bạn nên thiết kế mỗi thành phần của hệ thống sao cho nó có khả năng tự bảo vệ
cao nhất. Thà dư còn hơn thiếu, một người không bao giờ là quá cẩn thận khi tiếp
xúc với người lạ. Sự kiểm tra dư thừa có thể vô ích, nhưng ít nhất là nó không gây
tác hại.
Design By Contract đi theo một hướng ngược lại: những kiểm tra dư thừa có
thể và thật sự gây ra tác hại. Điều này mới nghe có vẻ lạ, mọi người sẽ nghĩ rằng sự
kiểm tra dư thừa có thể là vô ích, nhưng không th
ể nào gây ra tác hại. Tuy nhiên chỉ
những người có sự hiểu biết chưa sâu về tính tin cậy của phần mềm và chỉ tập trung
vào những phần riêng biệt của phần mềm mới có suy nghĩ như vậy. Nếu hạn chế
tầm nhìn trong một phạm vi hẹp của một thành phần cụ thể, ví dụ như thủ tục sqrt ở
trên, ta sẽ thấy rằng thủ tục có vẻ chặt chẽ hơn nếu có phần kiểm tra thêm trong thân
thủ tục. Nhưng một hệ thống không chỉ giới hạn trong một thủ tục cụ thể mà nó bao
gồm nhiều thủ tục trong nhiều lớp đối tượng khác nhau. Để đạt được một hệ thống









Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
33
đáng tin cậy, ta phải từ bỏ cái nhìn hạn chế để có một tầm nhìn vĩ mô, bao hàm trên
kiến trúc tổng thể.
Nếu ta có được cái nhìn như thế thì tính đơn giản sẽ trở thành một tiêu chuẩn
quyết định. Như ta đã nói từ đầu, sự phức tạp là kẻ thù chính của chất lượng. Khi
quan tâm đến điều này, ta sẽ thấy rằng những kiểm tra dư thừa không còn là vô hạ
i
nữa. Thử hình dung một hệ thống thông thường, với hàng ngàn thủ tục, những kiểm
tra dư thừa này sẽ trở thành một sự phức tạp không cần thiết khổng lồ. Nếu chúng ta
bắt đầu bằng con đường này, có thể chắc chắn một điều là: không bao giờ đạt được
một hệ thống đáng tin cậy.
Với Design By Contract, bạn sẽ nhận ra những đ
iều kiện chắc chắn và cần
thiết để thực hiện các chức năng của một liên hiệp khách hàng – nhà cung cấp (hay
gọi là một hợp đồng); và để cho rõ ràng, đối với mỗi điền kiện, cần xác định rõ
trách nhiệm là của ai: khách hàng hay nhà cung cấp. Có thể có nhiều câu trả lời,
theo một chừng mực nào đó, điều này là những phong cách thiết kế khác nhau.
Nhưng một khi bạn đã quyết định thì bạn cần phải bám vào nó. Nếu bạn có yêu cầu
về sự chính xác của tiền điều kiện, xác định rõ rằng yêu cầu là một phần trách
nhiệm của khách hàng (những lời gọi đến thủ tục) thì không được kiểm tra trong
thân của thủ tục; còn nếu yêu cầu này không nằm trong tiền điều kiện thì thủ tục cần
phải kiểm tra nó.
Đối với một h
ệ thống phần mềm, dù lớn hay nhỏ, chất lượng riêng của từng
thành phần là chưa đủ. Cái giá trị nhất chính là sự bảo đảm rằng mỗi tương tác giữa

hai thành phần bất kỳ đều phải có một sự phân công rõ ràng về quyền lợi và nghĩa
vụ, tức là cần phải có một bản hợp đồng.

7.3. Những xác nhận không phải là một cơ chế kiểm tra
đầu vào
Để tránh hiểu lầm, lưu ý rằng những hợp đồng ta đang bàn là giữa một thủ
tục (nhà cung cấp) và những thủ tục khác (khách hàng – những thủ tục gọi đến thủ
tục đó). Chúng ta đang quan tâm đến mối quan hệ phần mềm – phần mềm, không








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
34
phải là phần mềm – con người hay phần mềm – ngoại cảnh. Vì vậy, tiền điều kiện
sẽ không quan tâm đến sự đúng sai của dữ liệu người dùng nhập vào, ví dụ xét thủ
tục read_positive_integer đòi hỏi người dùng nhập vào một số dương, một tiền
điều kiện:
require
input > 0
không phải là một kỹ thuật đáng tin cậy. Trong trường hợp này không thể có sự
thay thế cho một điều kiện if … then thông thường.
Sự xác nhận không phải là một giải pháp kiểm tra sự đúng đắn của dữ liệu
nhập. Tiêu chuẩn của Modular Protection (bảo vệ tính môđun cho phần mềm)
khuyến khích rằng cần xác nhận tính hợp lệ của dữ liệu nhập từ
các đối tượng ngoài

hệ thống sao cho càng gần với mã nguồn càng tốt, có thể sử dụng “bộ lọc” nếu cần
thiết:



Hình
7-1: Sử dụng bộ lọc các module

Để nhận dữ liệu từ ngoài hệ thống (đường màu xanh nhạt), bạn không thể
dựa vào tiền điều kiện. Nhưng với những môđun màu vàng, dữ liệu phải thỏa mãn








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
35
tiền điều kiện mới có thể đi vào những môđun xử lý (màu xanh đậm). Vậy trong
trường hợp này, ta chỉ có thể sử dụng xác nhận cho đường màu xanh đậm, thể hiện
sự tương tác phần mềm – phần mềm. Và hậu điều kiện mà những môđun nhập nhận
được sẽ phải phù hợp với tiền điều kiện của những thủ tục xử lý.


Chương 8: Làm việc với những xác nhận
Trong phần này chúng sẽ tìm hiểu chi tiết hơn về cách dùng tiền điều kiện và
hậu điều kiện thông qua những ví dụ cơ bản. Những vấn đề đơn giản và phức tạp
của xác nhận sẽ được minh họa rõ ràng qua những ví dụ dưới đây.

8.1. Lớp stack
Lớp STACK được trang bị xác nhận có dạng chung như STACK1. Bây giờ, ta
có thể nghĩ ra một phiên bản đầy đủ cùng với một cài đặt được giải thích rõ ràng.
Để một lớp có thể sử dụng trực tiếp được, ta phải chọn một cài đặt. Hãy xem
một cài đặt của ngăn xếp bằng cách dùng mảng.


Hình 8-1: Stack được cài đặt bằng mảng

Mảng được gọi là representation, có phạm vi từ 1 đến capacity. Thuộc tính
count có kiểu số nguyên (integer) cho biết số phần tử trong ngăn xếp.








Tìm hiểu công nghệ Design By Contract và Xây dựng công cụ hỗ trợ cho C#
36
Gọi a là một đối tượng của lớp này, phương thức a.put(x,i) gán giá trị x cho
phần tử thứ i của stack; để lấy giá trị tại phần tử thứ i, ta dùng phương thức a.item(i)
hoặc a @ i. Lưu ý rằng phạm vi của mảng là từ 1 đến capacity, vì vậy i phải nằm
giữa 1 và capacity.

indexing
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 STACK2 [G] creation

make
feature Initialization
make (n: INTEGER) is
Cấp phát cho Stack độ lớn n phần tử
require
positive_capacity: n >= 0
do
capacity := n
!! representation . make (1, capacity)
ensure
capacity_set: capacity = n
array_allocated: representation /= Void
stack_empty: empty
end
feature Access
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

×