tìm hiểu công nghệ DESIGN BY CONTRACT và xây dựng công cụ hỗ trợ cho C#
Trang 1TRƯỜNG ĐẠI HỌC KHOA HỌC TỰ NHIÊN KHOA CÔNG NGHỆ THÔNG TIN
BỘ MÔN CÔNG NGHỆ PHẦN MỀM
LÊ TRẦN HOÀNG NGUYÊN – 0112103 NGUYỄN BÁCH KHOA - 0112140
TÌM HIỂU CÔNG NGHỆ DESIGN BY CONTRACT VÀ XÂY DỰNG
CÔNG CỤ HỖ TRỢ CHO C#
KHÓA LUẬN CỬ NHÂN TIN HỌC
GIÁO VIÊN HƯỚNG DẪN Th.s: NGUYỄN ĐÔNG HÀ
NIÊN KHÓA 2001 – 2005
Trang 2LỜI CẢM ƠN
Đầu tiên, xin chân thành cảm ơn cô Nguyễn Đông Hà đã trực tiếp hướng dẫn cũng như cung cấp tài liệu để chúng em có thể tiếp cận và tìm hiểu về công nghệ Design By Contract hữu ích này
Bên cạnh đó, xin đồng gửi lời cảm ơn đến các thầy cô của bộ môn Công nghệ Phần mềm Nâng cao đã tạo điều kiện cho chúng em dành nhiều thời gian nghiên cứu đề tài này
Cuối cùng, quả là một điều thiếu sót nếu không kể đến sự ủng hộ to lớn về mặt tinh thần cũng như sự giúp đỡ tận tình của gia đình, bạn bè, đặc biệt là bạn Nguyễn Lương Ngọc Minh và Nguyễn Ngọc Khánh
Xin chân thành cảm ơn tất cả, những người đã góp phần giúp cho luận văn này được hoàn thành
Thành phố Hồ Chí Minh,
Tháng 7, 2005
Trang 3Chương 7: Giao ước cho tính đáng tin cậy của phần mềm 29 7.1 Quyền lợi và nghĩa vụ 29
7.1.1 Những quyền lợi 30 7.1.2 Những nghĩa vụ 30 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 31
7.3 Những xác nhận không phải là một cơ chế kiểm tra đầu vào 33
Trang 4Chương 8: Làm việc với những xác nhận 35
8.2 Mệnh lệnh và yêu cầu 388.3 Lưu ý về những cấu trúc rỗng 418.4 Thiết kế tiền điều kiện: tolerant hay demanding? 428.5 Một môđun tolerant 43Chương 9: Những điều kiện bất biến của lớp 47 9.1 Định nghĩa và ví dụ 489.2 Định dạng và các thuộc tính của điều kiện bất biến của lớp 499.3 Điều kiện bất biến thay đổi 519.4 Ai phải bảo quản điều kiện bất biến? 529.5 Vai trò của những điều kiện bất biến của lớp trong kỹ thuật xây dựng phần mềm 53
9.6 Những điều kiện bất biến và hợp đồng 54
10.1 Tính đúng đắn của một lớp 5710.2 Vai trò của những thủ tục khởi tạo 60
Chương 11: Kết nối với kiểu dữ liệu trừu tượng 62 11.1 So sánh đặc tính của lớp với những hàm ADT 6311.2 Biểu diễn những tiên đề 64
Trang 5Chương 14: Sử dụng những xác nhận 77 14.1 Những xác nhận như một công cụ để viết phần mềm chính xác 7714.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 78
16.2.2 Chi tiết các lớp đối tượng 95 16.2.2.1 Màn hình Configuration 95 16.2.2.2 Lớp Connect 98 16.2.2.3 Lớp ProjectInfo 99 16.2.2.4 Lớp ClassInfo 101 16.2.2.5 Lớp FunctionInfo 104 16.2.2.6 Lớp Assertion 106 16.2.2.7 Lớp Extra 109
KẾT LUẬN 111 HƯỚNG PHÁT TRIỂN 112 TÀI LIỆU THAM KHẢO 113
Ý KIẾN CỦA GIÁO VIÊN PHẢN BIỆN 114
Trang 6BẢNG CÁC HÌNH VẼ
Hình 1-1: Giao diện EiffelStudio - 11
Hình 1-2: Thông báo khi lỗi xảy ra ở tiền điều kiện - 14
Hình 1-3: Code gây ra lỗi ở tiền điều kiện - 14
Hình 1-4: Thông báo khi lỗi xảy ra ở hậu điều kiện - 15
Hình 1-5: Code gây ra lỗi ở hậu điều kiện - 15
Hình 1-6: Thông báo khi lỗi xảy ra ở điều kiện bất biến - 16
Hình 1-7: Code gây ra lỗi ở điều kiện bất biến - 16
Hình 7-1: Sử dụng bộ lọc các module - 34
Hình 8-1: Stack được cài đặt bằng mảng - 35
Hình 9-1: Thời gian tồn tại của một đối tượng - 50
Hình 10-1: Thời gian tồn tại của một đối tượng - 58
Hình 11-1: Sự biến đổi giữa những đối tượng trừu tượng và cụ thể - 65
Hình 11-2: Hai cài đặt của cùng một đối tượng trừu tượng - 67
Hình 13-1: Một vòng lặp tính toán - 73
Hình 16-1: Sơ đồ thiết kế tổng thể - 94
Hình 16-2: Màn hình Configuration - 95
Hình 16-3: Chi tiết màn hình Configuration - 96
Hình 16-4: Lớp Connect - 98
Hình 16-5: Lớp ProjectInfo - 99
Hình 16-6: Lớp ClassInfo -101
Hình 16-7: Lớp FunctionInfo -104
Hình 16-8: Lớp Assertion -106
Hình 16-9: Lớp Extra -109
Trang 7LỜI NÓI ĐẦU
Trong ngành công nghệ thông tin, thay đổi là một tất yếu diễn ra hết sức thường xuyên mà ta phải chấp nhận và cố gắng điều chỉnh nó Phần mềm này ra đời thay thế phần mềm khác là một điều vô cùng bình thường, dễ hiểu Tại sao lại như thế? Bởi vì người sử dụng luôn mong muốn có được một phần mềm hữu ích Tuy nhiên, dù phần mềm có thể đáp ứng những nhu cầu của người sử dụng trong thời gian hiện tại thì cũng không thể đảm bảo nó sẽ luôn được ưa chuộng Để có thể tồn tại lâu dài, phần mềm phải thật sự chất lượng Điều này đồng nghĩa với việc nó phải không ngừng được cập nhật Mà ta cũng biết, phần mềm càng đúng đắn, đáng tin cậy và rõ ràng bao nhiêu thì công việc nâng cấp và phát triển nó càng dễ dàng bấy nhiêu Do đó, có thể nói, một trong những tiêu chí của ngành công nghệ phần mềm
mà bất kỳ thời đại nào, bất kỳ sản phẩm phần mềm nào cũng đều hướng đến là tính đáng tin cậy và đúng đắn Xuất phát từ nhu cầu ấy, công nghệ Design By Contract
đã ra đời nhằm giúp đảm bảo cho tính đáng tin cậy của phần mềm Đó cũng chính là
tên gọi là DCS (Design By Contract for C Sharp)
Đối tượng và phạm vi nghiên cứu: ý tưởng chính của Design By Contract là lập một “hợp đồng” giữa một lớp đối tượng (supplier) và những khách hàng (client) của nó, tức là những lớp đối tượng khác gọi đến các phương thức của lớp này Những client này phải bảo đảm một số điều kiện nhất định khi gọi một phương thức của một supplier gọi là tiền điều kiện (precondition); đáp lại, sau khi thực thi thủ tục, supplier phải đáp ứng một số điều kiện tương ứng gọi là hậu điều kiện (postcondition) Những điều kiện của hợp đồng sẽ được kiểm tra bởi trình biên dịch,
và bất cứ sự vi phạm nào của phần mềm cũng sẽ được phát hiện
Trang 8TỔNG QUAN
Các hướng nghiên cứu đã có của một số tác giả:
- Bertrand Meyer, tác giả của công nghệ Design By Contract và ngôn ngữ Eiffel, ngôn ngữ hỗ trợ hoàn toàn Design By Contract
Vấn đề tồn tại: Bởi vì đây là ngôn ngữ lập trình do chính tác giả của Design
By Contract tạo ra nên hỗ trợ rất đầy đủ và rõ ràng cho công nghệ này, nhưng vấn
đề ở đây là ngôn ngữ Eiffel còn xa lạ với người lập trình dù đã ra đời gần 10 năm, được ít người sử dụng ngôn ngữ này để phát triển phần mềm
- ResolveCorp và eXtensible C# (XC#), một Add-In hỗ trợ Design By Contract cho C# Đây là một công cụ rất tốt, hỗ trợ đầy đủ Design By Contract cho C# Tuy nhiên, công cụ này chỉ được sử dụng miễn phí một vài DLL và source code không mở
- Man Machine Systems và JMSAssert, công cụ hỗ trợ Design By Contract cho Java Đây cũng là một công cụ tốt Tuy nhiên, JMSAssert chỉ hỗ trợ biên dịch command line và sử dụng cho JDK từ 1.2 trở xuống, không thể tích hợp vào các môi trường hỗ trợ lập trình Java như JBuilder, Sun One Studio hay Eclipse
Trang 9Chương 1: Giới thiệu về Eiffel
1.1 Giới thiệu
Đầu tiên, chúng ta sẽ làm quen với phần mềm Eiffel trước khi tìm hiểu về công nghệ Design By Contract Vì sao lại như vậy? Vì tất cả ví dụ dùng trong luận văn đều sử dụng cấu trúc của ngôn ngữ Eiffel Còn những khái niệm nào mới được
đề cập trong chương này sẽ được giải thích kỹ hơn trong các phần sau khi giới thiệu
về Design By Contract
Qua hơn 10 năm tồn tại, Eiffel hiện nay được coi là một trong những môi trường phát triển phần mềm tốt nhất Trước sức mạnh to lớn của Eiffel trong lĩnh vực phần mềm thì dù muốn dù không, bạn cũng nên biết qua về nó Vậy thực chất Eiffel là gì?
Eiffel là khung làm việc trợ giúp cho việc suy nghĩ, thiết kế và thực thi phần mềm hướng đối tượng
Eiffel là một phương pháp, một ngôn ngữ hỗ trợ mô tả một cách hiệu quả và phát triển những hệ thống có chất lượng
Eiffel là ngôn ngữ thiết kế
Vai trò của Eiffel còn hơn một ngôn ngữ lập trình Những gì nó đem lại không chỉ giới hạn trong ngữ cảnh lập trình mà trải rộng khắp công việc phát triển phần mềm: phân tích, lên mô hình, viết đặc tả, thiết kế kiến trúc, thực hiện, bảo trì, làm tài liệu
Trang 101.2 Design By Contract trong Eiffel
Eiffel hỗ trợ rất nhiều tính năng: tiếp cận hướng đối tượng hoàn thiện, khả năng giao tiếp bên ngoài (có thể giao tiếp với các ngôn ngữ C, C++, Java,…), hỗ trợ vòng đời phần mềm bao gồm việc phân tích, thiết kế, thực thi và bảo trì, hỗ trợ Design By Contract, viết xác nhận, quản lý ngoại lệ…
Design By Contract hầu như là vấn đề luôn được nhắc đến khi đề cập về Eiffel Trong Eiffel, mỗi thành phần của hệ thống đều có thể được thực hiện theo một đặc tả tiên quyết về các thuộc tính trừu tượng của nó, liên quan đến những thao tác nội tại và những giao tác của nó với các thành phần khác
Eiffel thực thi một cách trực tiếp ý tưởng Design By Contract, một phương pháp làm nâng cao tính đáng tin cậy của phần mềm, cung cấp một nền tảng cho việc đặc tả, làm tài liệu và kiểm nghiệm phần mềm, cũng như việc quản lý các ngoại lệ
và cách sử dụng kế thừa thích hợp
1.3 EiffelStudio
EiffelStudio là trình biên dịch của Eiffel Ngoài ra, nó còn là một IDE rất mạnh với những tính năng độc nhất như: công cụ công nghệ đảo tích hợp, bộ máy phân tích mã nguồn định lượng
Tùy vào nhu cầu của mình, bạn có thể sử dụng EiffelStudio như một môi trường lập trình hoặc chỉ như một công cụ giúp mô hình hóa, xây dựng các mô tả hệ thống bao gồm các lớp trừu tượng mà không thực thi bằng công cụ Diagram hoặc kết hợp cả 2 khả năng để đạt đến hiệu quả cao nhất
Trang 111.3.1 Giao diện
Hình 1-1: Giao diện EiffelStudio
Giao diện làm việc của EiffelStudio có 4 khung chính: Features, Class, Clusters, Context Để thuận tiện cho việc lập trình, các bạn có thể đóng bớt các
khung cửa sổ đi Tất cả các khung cửa sổ này đều có thể đóng lại ngọai trừ Class
1.3.2 Các thao tác căn bản trên EiffelStudio
Khởi động chương trình: Programs > EiffelStudio Version > EiffelStudio Chọn "Create a new project" > OK
Class view là khung làm việc chính của bạn Sau khi lập trình xong, bạn có thể biên dịch và cho chạy chương trình bằng công cụ Compile (F7)
Debug chương trình: F10, F11
Lưu project: File > Save
Trang 12Biểu diễn Design By Contract trong Eiffel:
… assertion_clausen
Trang 13var
loop
body
end
Demo: Project stack
STACK_CLASS: lớp stack chính, chứa các định nghĩa các thao tác trên stack
make: Hàm khởi tạo của stack
item: hàm lấy phần tử trên cùng stack
get(t): hàm lấy phần tử thứ t
empty: kiểm tra stack có rỗng
full: kiểm tra stack có đầy
put(x): thêm phần tử x vào stack
remove: bỏ phần tử trên cùng stack
TEST_CLASS: lớp chính(main), lớp gọi các hàm của lớp STACK_CLASS
Ta sẽ thử vài trường hợp cho thấy khả năng bắt lỗi của Eiffel
Lưu ý: Sau mỗi trường hợp hãy sửa lại code như ban đầu rồi mới thử tiếp trường hợp khác
Tại dòng end end if , nhấn tổ hợp phím Ctrl-K
Recompile (Shift-F7) và cho chạy lại chương trình (F5)
Trang 14Xuất hiện thông báo ngoại lệ sau:
Hình 1-2: Thông báo khi lỗi xảy ra ở tiền điều kiện
và con trỏ dừng lại ở câu lệnh
Hình 1-3:Code gây ra lỗi ở tiền điều kiện
Nguyên nhân:
Khi bạn gọi thủ tục a.make(n), do trước đó khởi tạo n là một số âm (=-8), client không đảm bảo contract, nên trong thủ tục make của lớp STACK_CLASS, thủ tục make kiểm tra không thỏa tiền điều kiện positive_capacity: n>=0, nó dừng lại và thông báo cho người lập trình biết
TH2: Lỗi xảy ra ở hậu điều kiện
Trong lớp TEST_CLASS, tại thủ tục make, sửa như sau:
Trang 15Capacity := n capacity := n-1
Recompile (Shift-F7) và cho chạy lại chương trình (F5)
Xuất hiện thông báo ngoại lệ sau:
Hình 1-4: Thông báo khi lỗi xảy ra ở hậu điều kiện
và con trỏ dừng lại ở câu lệnh
Hình 1-5: Code gây ra lỗi ở hậu điều kiện
Nguyên nhân:
Trước đó, ta gán capacity := n-1, hậu điều kiện lại yêu cầu capacity = n
TH3: Lỗi xảy ra ở điều kiện bất biến
Trong lớp TEST_CLASS, tại thủ tục make, thêm vào dòng sau:
count:=-1
Trang 16Chọn menu Project > Project Setting… Bỏ dấu check ở ensure Đánh dấu check ở invariant Hành động này nhằm bỏ qua chế độ kiểm lỗi ở hậu điều kiện Ở
đây chỉ muốn minh họa cho việc phát hiện lỗi ở điều kiện bất biến
Recompile (Shift-F7) và cho chạy lại chương trình (F5)
Xuất hiện thông báo ngoại lệ sau:
Hình 1-6: Thông báo khi lỗi xảy ra ở điều kiện bất biến
và con trỏ dừng lại ở câu lệnh
Hình 1-7: Code gây ra lỗi ở điều kiện bất biến Nguyên nhân:
Trước đó, ta gán count := -1, điều kiện bất biến yêu cầu count>=0
Trang 17Chương 2: Một số cơ chế mang lại tính đáng tin cậy cho phần mềm
Trước hết, phải nói rằng kỹ thuật định nghĩa thuộc tính của một đối tượng gần như là có liên quan với cấu trúc của những hệ thống phần mềm Những kiến trúc đơn giản, riêng biệt và có khả năng mở rộng sẽ giúp chúng ta đảm bảo tính đáng tin cậy của phần mềm dễ dàng hơn so với những cấu trúc vặn vẹo Đặc biệt, cố gắng giới hạn sự liên quan giữa các môđun với nhau đến mức tối thiểu nhất sẽ là tiêu điểm cho việc thảo luận về tính riêng biệt Điều này giúp ngăn chặn những rủi
ro thông thường của tính đáng tin cậy, ví dụ như những biến toàn cục và việc định nghĩa những cơ chế liên lạc bị giới hạn, client và những mối quan hệ kế thừa Nói đến chất lượng phần mềm thì không thể bỏ qua tính đáng tin cậy Chúng ta cố gắng giữ cho những cấu trúc càng đơn giản càng tốt Tuy rằng điều này vẫn chưa đủ đảm bảo cho tính đáng tin cậy của phần mềm, nhưng dù sao, nó cũng là một điều kiện cần thiết
Một điều kiện khác cũng cần thiết nữa là làm cho phần mềm của chúng ta tối
ưu và dễ đọc Văn bản phần mềm không những được viết một lần mà nó còn phải được đọc đi đọc lại và viết đi viết lại nhiều lần Sự trong sáng và tính đơn giản của các câu chú thích là những yêu cầu cơ bản để nâng cao tính đáng tin cậy của phần mềm
Một vũ khí khác cũng rất cần thiết là việc quản lý bộ nhớ một cách tự động, đặc biệt là bộ thu gom rác (garbage collection) Bất kỳ hệ thống nào có khởi tạo và thao tác với cấu trúc dữ liệu động mà lại thực hiện thu hồi bộ nhớ bằng tay (tức là
do người lập trình điều khiển) hoặc bộ nhớ không hề được thu hồi thì thật là nguy hiểm Bộ thu gom rác không hề là một sự xa xỉ mà nó là thành phần thiết yếu để mở rộng tính đáng tin cậy cho bất kỳ một môi trường hướng đối tượng nào
Một kỹ thuật khác nữa mà cũng có thể là thiết yếu mà có liên quan đến genericity là static typing Nếu không có những luật như thế thì chúng ta sẽ không kiểm soát được những lỗi xảy ra lúc run-time do quá trình gõ code gây nên
Trang 18Tóm lại, tất cả những kỹ thuật này cung cấp một nền tảng cần thiết để ta có cái nhìn gần hơn về một hệ thống phần mềm đúng đắn và bền vững
Chương 3: Tính đúng đắn của phần mềm
Giả sử có một người đưa cho bạn một chương trình C với 300 000 dòng lệnh
và hỏi rằng nó có đúng không Tôi nghĩ rằng rất có khả năng bạn thấy khó và thậm chí là không thể trả lời được Tuy nhiên, nếu là một cố vấn viên, bạn hãy trả lời
“Không” và sau đó tính một giá thật cao vì rất có thể bạn đúng
Thật sự, để có thể trả lời câu hỏi trên một cách đúng nghĩa, bạn không những cần phải lấy chương trình đó mà còn phải lấy cả lời diễn giải về những gì mà chương trình đó làm được hay ta gọi chúng là những đặc tả của chương trình
Có những chú thích giống nhau cũng chẳng sao, dĩ nhiên, khi đó ta không để
ý đến kích thước của chương trình Ví dụ, câu lệnh x := y+1 không đúng cũng không sai Vì đúng hay sai chỉ có ý nghĩa khi xét trong quan hệ của nó với một lời chú dẫn, tức là cái mà người ta mong đợi có được sau khi thực hiện câu lệnh hay ít
ra thì cũng là sự ảnh hưởng đến trạng thái của các biến trong chương trình Do đó, câu lệnh trên sẽ đúng với đặc tả:
“Điều này đảm bảo cho x và y có giá trị khác nhau”
nhưng nó sẽ sai với đặc tả:
“Điều này đảm bảo rằng x có giá trị âm”
(giả sử các thực thể có kiểu số nguyên Như vậy, x có thể có kết quả không âm sau khi gán Điều đó tùy thuộc vào giá trị của y)
Ví dụ này nhằm minh họa cho khái niệm “tính đúng đắn” được trình bày bên dưới:
Trang 19Tính đúng đắn của phần mềm
Tính đúng đắn là một khái niệm quan hệ
Một hệ thống phần mềm hay một thành phần phần mềm thì không đúng cũng không sai Nó chỉ đúng hay sai khi có liên quan với một đặc tả nào đó Nói một cách chính xác, ta không thảo luận những thành phần phần mềm có đúng hay không, mà là thảo luận chúng có phù hợp với những đặc tả của chúng hay không
Do đó, thuật ngữ “tính đúng đắn” không được dùng cho những thành phần phần mềm, mà nó được dùng cho từng cặp, mỗi cặp bao gồm một thành phần phần mềm
và một đặc tả
Trong phần này, ta sẽ biết cách biểu diễn những đặc tả thông qua một xác nhận (assertion) để giúp ta xác nhận tính đúng đắn của phần mềm Điều này cho thấy kết quả của việc viết những đặc tả là một bước đầu tiên quan trọng để đảm bảo rằng phần mềm thật sự đúng Việc viết những xác nhận cùng lúc hoặc đúng ra là trước khi viết phần mềm sẽ mang lại những lợi ích tuyệt vời như sau:
− Sản xuất được phần mềm đúng với khi bắt đầu vì nó được thiết kế đúng Ích lợi này đã được Harlan D.Mills (một trong những người khởi đầu đề xướng việc lập trình có cấu trúc “Structured Programming”) trình bày vào năm
1970 trong quyển sách “How to write correct programs and know it” (có nghĩa là
“Làm thế nào để viết được những chương trình đúng và biết được nó đúng”) “Biết”
ở đây có nghĩa là trang bị cho phần mềm những đối số khi ta viết nó nhằm hiển thị tính đúng đắn của nó
− Có được sự hiểu biết tốt hơn về vấn đề và những cách giải quyết cuối cùng của nó
− Việc thực hiện các tài liệu cho phần mềm dễ dàng Chúng ta sẽ thấy được ở phần sau rằng những xác nhận sẽ đóng một vai trò trung tâm trong việc hướng đối tượng đến gần tài liệu
Trang 20− Cung cấp một căn bản cho việc kiểm tra và debug hệ thống
Trong những phần còn lại chúng ta sẽ tìm hiểu những ứng dụng này
Trong C, C++ và một số ngôn ngữ khác (dưới sự chỉ đạo của Algol W), ta có thể viết một câu lệnh đóng vai trò một xác nhận để kiểm tra một tình trạng nào đó
có được giữ ở một trạng thái nào đó như mong muốn hay không khi thực thi phần mềm, và chương trình sẽ không được thực thi nếu nó không thoả Mặc dù như thế cũng có thể làm được những gì mà ta muốn, nhưng việc làm vậy chỉ tượng trưng cho một phần nhỏ của việc sử dụng những lời xác nhận trong phương pháp hướng đối tượng Do đó, nếu giống như nhiều người phát triển phần mềm khác thì bạn sẽ quen với những câu lệnh như thế nhưng lại không thấy được bức tranh toàn cảnh Hầu hết tất cả những khái niệm được bàn ở đây đều sẽ mới lạ với bạn
Chương 4: Biểu diễn một đặc tả
Chúng ta có thể trở lại nhận xét trước với hình ảnh một ký hiệu toán học đơn giản được mượn từ lý thuyết của việc kiểm tra một chương trình hình thức và những
lý do quý giá để lập luận về tính đúng đắn của các thành phần phần mềm
4.1 Những công thức của tính đúng đắn
Giả sử A thực hiện một vài thao tác (ví dụ A là một câu lệnh hay thân của một thủ tục) Một công thức của tính đúng đắn là một cách biểu diễn theo dạng sau:
{P} A {Q}
Ý nghĩa của công thức tính đúng đắn {P} A {Q}
Bất kỳ thi hành nào của A, bắt đầu ở trạng thái P thì sẽ kết thúc với trạng thái Q
Những công thức của tính đúng đắn (còn được gọi là bộ ba Hoare) là một ký hiệu toán học, không phải là một khái niệm lập trình; chúng không phải là một trong
Trang 21số những ngôn ngữ phần mềm mà chỉ được thiết kế nhằm giúp cho việc thể hiện những thuộc tính của các thành phần phần mềm Trong {P} A {Q}, A biểu thị cho một thao tác, P và Q là những thuộc tính của những thực thể khác nhau có liên quan hay còn được gọi là những xác nhận (chúng ta sẽ định nghĩa từ “xác nhận” (“assertion”) một cách chính xác hơn ở phần sau) Trong hai xác nhận này, P được gọi là tiền điều kiện (precondition) và Q được gọi là hậu điều kiện (postcondition)
Ví dụ, ta có một công thức bình thường của tính đúng đắn như sau với giả sử rằng x là một số nguyên:
{x>=9} x := x+5 {x>=13}
Công thức tính đúng đắn được sử dụng để đánh giá tính đúng đắn của phần mềm Điều đó cũng có nghĩa là tính đúng đắn chỉ được xét đến khi nó gắn với một đặc tả nào đó Như vậy, khi thảo luận về tính đúng đắn của phần mềm, ta không nói đến những thành phần phần mềm riêng lẻ A, mà nó là bộ ba bao gồm một thành phần phần mềm A, một tiền điều kiện P và một hậu điều kiện Q Mục đích duy nhất của việc này là thiết lập kết quả cho những công thức tính đúng đắn {P} A {Q}
Trong ví dụ trên, con số 13 ở hậu điều kiện không phải là lỗi do in ấn hay gõ phím! Giả sử thực hiện đúng phép tính trên số nguyên ở công thức trên: với điều kiện x>=9 là đúng trước câu lệnh, x>=13 sẽ đúng sau khi thực hiện câu lệnh
Tuy nhiên, ta thấy được nhiều điều thú vị hơn:
− Với một tiền điều kiện như vậy, hậu điều kiện hay nhất phải là điều kiện mạnh nhất, và trong trường hợp này là x>=14
− Còn với hậu điều kiện đã đưa ra thì tiền điều kiện hay nhất phải là tiền điều kiện yếu nhất, ở đây là x>=8
Từ một công thức đã cho, ta luôn có thể có được một công thức khác bằng cách mở rộng tiền điều kiện hay nới lỏng đi hậu điều kiện Bây giờ, ta sẽ cùng nhau xem xét nhiều hơn về những khái niệm “mạnh hơn” và “yếu hơn” là thế nào
Trang 224.2 Những điều kiện yếu và mạnh
Một cách để xem xét đặc tả theo dạng {P} A {Q} là xem nó như một mô tả các công việc cho A Điều này cũng giống như có một mục quảng cáo tuyển người trên báo đăng rằng “Cần tuyển một người có khả năng thực hiện công việc A khi A
có trạng thái bắt đầu là P, và sau khi A được hoàn tất thì nó phải thỏa mãn Q”
Giả sử, một người bạn của bạn đang kiếm việc và tình cờ đọc được những quảng cáo tương tự như thế này, tất cả lương và lợi ích của chúng đều như nhau, chỉ
có điều là chúng khác nhau ở những cái P và Q Cũng giống như nhiều người, bạn của bạn thì lười nhác, có thể nói rằng, anh ta muốn có một công việc dễ nhất Và anh ta hỏi ý kiến bạn là nên chọn công việc nào Trong trường hợp này, bạn sẽ khuyên anh ấy thế nào?
Trước hết, với P: bạn khuyên anh ta nên chọn một công việc với tiền điều kiện yếu hay mạnh? Câu hỏi tương tự cho hậu điều kiện Q Bạn hãy suy nghĩ và chọn cho mình một quyết định trước khi xem câu trả lời ở phần dưới
Trước hết, ta nói về tiền điều kiện Từ quan điểm của người làm công tương lai, tức là người sẽ thực hiện công việc A, tiền điều kiện P định nghĩa những trường hợp mà ta sẽ phải thực hiện công việc Do đó, một P mạnh là tốt, vì P càng mạnh thì các trường hợp bạn phải thực hiện A càng được giới hạn Như vậy, P càng mạnh thì càng dễ cho người làm công Và tuyệt vời nhất là khi kẻ làm công chẳng phải làm gì
cả tức là hắn ta là kẻ ăn không ngồi rồi Điều này xảy ra khi công việc A được định nghĩa bởi:
Công thức 1
{False} A {…}
Trong trường hợp này, hậu điều kiện không cần thiết phải đề cập bởi dù nó
có là gì thì cũng không có ảnh hưởng Nếu có bao giờ bạn thấy một mục tuyển người như vậy thì đừng mất công đọc hậu điều kiện mà hãy chớp lấy công việc đó
Trang 23ngay lập tức Tiền điều kiện False là sự xác nhận mạnh nhất có thể vì nó không bao giờ thỏa mãn một trạng thái nào cả Bất cứ yêu cầu nào để thực thi A cũng sẽ không đúng, và lỗi không nằm ở trách nhiệm của A mà là ở người yêu cầu hay khách hàng (client) bởi nó đã không xem xét bất cứ tiền điều kiện nào cần đến Dù cho A có làm hay không làm gì đi nữa thì nó cũng luôn đúng với đặc tả
Còn với hậu điều kiện Q, tình trạng bị đảo ngược Một hậu điều kiện mạnh là một tin xấu: nó chỉ ra rằng bạn phải mang lại nhiều kết quả hơn Q càng yếu, càng tốt cho người làm thuê Thực tế, công việc ăn không ngồi rồi tốt nhì trên thế giới là công việc được định nghĩa mà không chú ý đến tiền điều kiện:
Công thức 2
{…} A {True}
Hậu điều kiện True là một xác nhận yếu nhất vì nó thỏa mãn mọi trường hợp Khái niệm “mạnh hơn” hay “yếu hơn” được định nghĩa một cách hình thức từ logic: P1 được gọi là mạnh hơn P2, và P2 yếu hơn P1 nếu P1 bao hàm P2 và chúng không bằng nhau Khi mọi lời xác nhận bao hàm True, và False bao hàm mọi xác nhận thì thật là hợp lý để nói rằng True như là yếu nhất và False như là mạnh nhất với tất cả xác nhận có thể
Đến đây, có một câu hỏi được đặt ra: “Tại sao công thức 2 là công việc tốt nhì trên thế giới?” Câu trả lời chính là một điểm tinh tế trong phần định nghĩa ý nghĩa của công thức {P}A{Q}: sự kết thúc Định nghĩa nói rằng sự thực thi phải kết thúc trong tình trạng thoả Q miễn là khi bắt đầu nó thoả P
Với công thức 1, không có trường hợp nào thoả P Do đó, A là gì cũng không thành vấn đề, ngay cả nó là một đoạn code mà khi thi hành, chương trình sẽ
bị rơi vào một vòng lặp không điểm dừng hay là sẽ gây hỏng máy cũng chẳng sao
Vì dù A là gì thì cũng đúng với đặc tả của nó Tuy nhiên, với công thức 2, vấn đề là
ở trạng thái kết thúc, nó không cần thoả mãn bất kỳ thuộc tính đặc biệt nào nhưng trạng thái kết thúc phải được đảm bảo là có tồn tại
Trang 24Những đọc giả mà đã quen với lý thuyết khoa học máy tính hay những kỹ thuật chứng minh lập trình sẽ thấy rằng công thức {P} A {Q} dùng ở đây ám chỉ toàn bộ tính đúng đắn, bao gồm cả sự kết thúc cũng như việc phù hợp với đặc tả (Tính chất mà một chương trình sẽ thoả mãn với đặc tả của nó lúc kết thúc là một phần của tính đúng đắn)
Nãy giờ, ta thảo luận một xác nhận mạnh hơn hay yếu hơn là những “tin tốt” hay “tin xấu” là dựa trên quan điểm của một người làm công trong tương lai Nếu bây giờ thay đổi cách nhìn, đứng trên cương vị của một người chủ, ta thấy mọi thứ
sẽ thay đổi: một tiền điều kiện yếu sẽ là những tin tốt nếu ta muốn các trường hợp thực thi công việc được tăng lên, và một hậu điều kiện mạnh sẽ là tốt nếu ta muốn những kết quả của công việc thật sự có ý nghĩa
Sự đảo ngược của những tiêu chuẩn này là đặc trưng của việc thảo luận về tính đúng đắn của phần mềm, và sẽ xuất hiện lại như khái niệm chính trong luận văn này: hợp đồng giữa những khách hàng và những môđun cung cấp là một sự ràng buộc giữa hai bên Để sản xuất ra một phần mềm hiệu quả và đáng tin cậy thì cần phải có một hợp đồng đại diện cho sự thoả hiệp tốt nhất của tất cả mối liên hệ giữa những nhà cung cấp và khách hàng
Chương 5: Giới thiệu về sự xác nhận trong văn bản của phần mềm
Để biểu diễn một đặc tả, chúng ta sẽ tin cậy vào những xác nhận Một xác
nhận là một biểu thức bao gồm những thực thể của phần mềm và phát biểu một
thuộc tính rằng các thực thể này có thể thoả mãn ở những giai đoạn xác định khi thực thi phần mềm Một xác nhận tiêu biểu có thể biểu diễn một số nguyên có giá trị dương hoặc một tham chiếu không phải rỗng
Về mặt cú pháp, khái niệm xác nhận đơn giản là một biểu thức logic Ví dụ như:
Trang 25n>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ư:
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:
Trang 26class 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ư:
− 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
Trang 27count = old count + 1
Trang 28count = 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
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 putvà 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à
Trang 29mộ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 putcó 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:
Trang 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
Đây là hợp đồng cho thủ tục putcủa ví dụ trên:
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, xnằm trên cùng, counttă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, xnằm trên cùng, counttă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
Trang 317.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:
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
Trang 32Nguyê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
Trang 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
Trang 34phả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ủ
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
Trang 35tiề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
Trang 36Gọ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ứ 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
Trang 38one_more_item: count = old count + 1
in_top_array_entry: representation @ count = x
… Sẽ tìm hiểu trong phần sau
end class STACK2
Phần biểu diễn về lớp ở trên cho ta thấy sự đơn giản khi làm việc với những xác nhận Ngoại trừ mệnh đề invariant còn thiếu sẽ được bổ sung trong phần sau,
chúng ta hãy cùng nhau xem xét tỉ mỉ những thuộc tính khác nhau của nó
8.2 Mệnh lệnh và yêu cầu
Những xác nhận trong lớp STACK2 minh họa một khái niệm cơ bản mà ta đã
có cái nhìn lướt qua về sự chuyển tiếp từ những kiểu dữ liệu trừu tượng sang những lớp: sự khác nhau giữa những khung nhìn “imperative” và “applicative”
Những xác nhận trong empty và full có thể làm bạn băn khoăn Xét thủ tục
full trong lớp trên:
Trang 39Result = (count = capacity) (1)
Bởi vì trong thân thủ tục đã gán
Result := (count = capacity) (2)
Vậy thì liệu hậu điều kiện ở đây có dư thừa không?
Có sự khác biệt rất lớn giữa (1) và (2), vì vậy không hề có sự dư thừa
(2) là một câu lệnh, thể hiện một hành động, gán giá trị true hay false của
biểu thức count = capacity cho biến Result Trong khi đó, (1) chỉ là một xác nhận, không làm gì hết Nó chỉ đặc tả thuộc tính của trạng thái cuối cùng được mong đợi
Một câu lệnh, tức (2), thì mang tính chất ra lệnh, còn một xác nhận, tức (1),
thì chỉ mang tính chất mô tả Câu lệnh mô tả cho câu hỏi “như thế nào”, còn xác nhận mô tả cho câu hỏi “cái gì” Một câu lệnh là một phần của cài đặt, còn một xác
nhận chỉ là một thành phần của đặc tả
Câu lệnh thì mang tính mệnh lệnh, bắt buộc, còn xác nhận thì chỉ mang tính
yêu cầu Hai thuật ngữ này nhấn mạnh sự khác nhau cơ bản giữa tin học và toán học
− Những thao tác của tin học có thể làm thay đổi trạng thái của máy tính Những chỉ thị của các ngôn ngữ lập trình thông thường là những câu lệnh tác động trực tiếp đến máy tính
Trang 40− Những lý luận toán học không thể thay đổi bất cứ gì Ví dụ như khi ta lấy căn bậc hai của 2 thì số 2 trước khi lấy căn và sau khi lấy căn vẫn như nhau
Tóm lại, xác nhận là mô tả một kết quả được mong đợi, còn chỉ thị (thân vòng lặp) là ra lệnh bằng cách nào đó đạt được kết quả Ta không được nhằm lẫn giữa hai khái niệm này cũng như giữa hai khái niệm “:=” và “=” Những người sử dụng một lớp nào đó để tạo ra một môđun của riêng mình sẽ quan tâm đến các xác nhận hơn là các chỉ thị
Nguyên nhân của sự gần giống nhau giữa dấu gán (:=) và dấu bằng (=) là việc gán trong nhiều trường hợp là một cách đơn giản để đạt đến sự ngang bằng Cài đặt Result := (count = capacity) thật sự là một ví dụ rõ ràng dễ nhầm lẫn Nhưng trong những ví dụ cao hơn thì sự khác nhau giữa đặc tả và cài đặt sẽ lớn hơn, ngay cả trong một ví dụ đơn giản là hàm tính căn của số thực x có hậu điều kiện là abs(Result^2-x)<=tolerance (với abs là trị tuyệt đối, còn tolerance là dung sai) Các chỉ thị trong thân hàm sẽ có tầm quan trọng thấp hơn
vì chúng là những cài đặt cho thuật toán chung của việc tính căn bậc hai
Thậm chí đối với thủ tục put trong STACK2, có cùng đặc tả nhưng có thể có
sự thực thi khác nhau, mặc dù sự khác biệt có thể là rất nhỏ Chẳng hạn nếu thân thủ tục như sau:
có thể được đơn giản (nhờ những quy tắc khởi tạo mặc định) thành:
if count = capacity then Result:=True end
Do đó, sự hiện diện của những thành tố giống nhau trong thủ tục và hậu điều kiện không phải là bằng chứng của sự dư thừa Nó là bằng chứng của tính bền vững giữa cài đặt và đặc tả - có thể nói là của tính đúng đắn
Qua đây, ta thấy một đặc tính của những xác nhận mà sẽ được phát triển cao hơn: sự thích hợp của chúng đối với tác giả của các lớp client, người mà chúng ta không nên thắc mắc khi đọc những cài đặt thủ tục, nhưng là người cần một mô tả trừu tượng hơn về vai trò của thủ tục Ý tưởng này đưa đến ý niệm về một dạng thức ngắn gọn (short form) sẽ được thảo luận sau