Một bằng chứng khó hơn nhiều trong thế giới OOP vì các tác dụng phụ, thừa kế không hạn chế và null
là thành viên của mọi loại. Hầu hết các bằng chứng đều dựa trên một nguyên tắc cảm ứng để cho thấy rằng bạn đã đề cập đến mọi khả năng và cả 3 điều đó khiến việc chứng minh trở nên khó khăn hơn.
Giả sử chúng ta đang triển khai các cây nhị phân có chứa các giá trị nguyên (để giữ cho cú pháp đơn giản hơn, tôi sẽ không đưa lập trình chung vào điều này, mặc dù nó sẽ không thay đổi bất cứ điều gì.) Trong ML chuẩn, tôi sẽ định nghĩa như thế điều này:
datatype tree = Empty | Node of (tree * int * tree)
Điều này giới thiệu một loại mới gọi là tree
giá trị của chúng có thể có chính xác hai loại (hoặc các lớp, không bị nhầm lẫn với khái niệm OOP của một lớp) - một Empty
giá trị không mang thông tin và Node
các giá trị mang 3-tuple có giá trị đầu tiên và cuối cùng các phần tử là tree
s và có phần tử ở giữa là một int
. Giá trị gần đúng nhất của tuyên bố này trong OOP sẽ giống như thế này:
public class Tree {
private Tree() {} // Prevent external subclassing
public static final class Empty extends Tree {}
public static final class Node extends Tree {
public final Tree leftChild;
public final int value;
public final Tree rightChild;
public Node(Tree leftChild, int value, Tree rightChild) {
this.leftChild = leftChild;
this.value = value;
this.rightChild = rightChild;
}
}
}
Với sự cảnh báo rằng các biến của loại Cây không bao giờ có thể null
.
Bây giờ chúng ta hãy viết một hàm để tính chiều cao (hoặc độ sâu) của cây và giả sử chúng ta có quyền truy cập vào một max
hàm trả về số lớn hơn của hai số:
fun height(Empty) =
0
| height(Node (leftChild, value, rightChild)) =
1 + max( height(leftChild), height(rightChild) )
Chúng ta đã định nghĩa height
hàm theo các trường hợp - có một định nghĩa cho Empty
cây và một định nghĩa cho Node
cây. Trình biên dịch biết có bao nhiêu lớp cây tồn tại và sẽ đưa ra cảnh báo nếu bạn không xác định cả hai trường hợp. Các biểu hiện Node (leftChild, value, rightChild)
trong chữ ký chức năng liên kết với các giá trị của 3-tuple các biến leftChild
, value
và rightChild
tương ứng vì vậy chúng tôi có thể tham khảo chúng trong định nghĩa hàm. Nó giống như đã khai báo các biến cục bộ như thế này bằng ngôn ngữ OOP:
Tree leftChild = tuple.getFirst();
int value = tuple.getSecond();
Tree rightChild = tuple.getThird();
Làm thế nào chúng ta có thể chứng minh chúng ta đã thực hiện height
đúng? Chúng ta có thể sử dụng cảm ứng cấu trúc , bao gồm: 1. Chứng minh height
là đúng trong trường hợp cơ sở của tree
loại ( Empty
) 2. Giả sử rằng các cuộc gọi đệ quy height
là chính xác, chứng minh rằng đó height
là chính xác cho trường hợp không phải là cơ sở ) (khi cây thực sự là a Node
).
Đối với bước 1, chúng ta có thể thấy rằng hàm luôn trả về 0 khi đối số là một Empty
cây. Điều này đúng theo định nghĩa về chiều cao của cây.
Đối với bước 2, hàm trả về 1 + max( height(leftChild), height(rightChild) )
. Giả sử rằng các cuộc gọi đệ quy thực sự trả lại chiều cao của trẻ em, chúng ta có thể thấy rằng điều này cũng đúng.
Và điều đó hoàn thành bằng chứng. Bước 1 và 2 kết hợp tất cả các khả năng. Tuy nhiên, lưu ý rằng chúng tôi không có đột biến, không có giá trị và có chính xác hai loại cây. Bỏ đi ba điều kiện đó và bằng chứng nhanh chóng trở nên phức tạp hơn, nếu không thực tế.
EDIT: Vì câu trả lời này đã tăng lên hàng đầu, tôi muốn thêm một ví dụ ít tầm thường hơn về một bằng chứng và bao gồm cảm ứng cấu trúc kỹ lưỡng hơn một chút. Ở trên chúng tôi đã chứng minh rằng nếu height
trả về , giá trị trả về của nó là chính xác. Chúng tôi đã không chứng minh rằng nó luôn trả về một giá trị, mặc dù. Chúng ta cũng có thể sử dụng cảm ứng cấu trúc để chứng minh điều này (hoặc bất kỳ tài sản nào khác.) Một lần nữa, trong bước 2, chúng ta được phép đảm nhận quyền sở hữu của các cuộc gọi đệ quy miễn là tất cả các cuộc gọi đệ quy hoạt động trên một con trực tiếp của cây.
Một hàm có thể không trả về giá trị trong hai tình huống: nếu nó ném ngoại lệ và nếu nó lặp lại mãi mãi. Trước tiên, hãy chứng minh rằng nếu không có ngoại lệ nào được đưa ra, hàm sẽ chấm dứt:
Chứng minh rằng (nếu không có ngoại lệ nào được ném) thì hàm kết thúc cho các trường hợp cơ sở ( Empty
). Vì chúng tôi trả về vô điều kiện 0, nó chấm dứt.
Chứng minh rằng hàm kết thúc trong các trường hợp không phải là cơ sở ( Node
). Có ba chức năng cuộc gọi ở đây: +
, max
, và height
. Chúng tôi biết điều đó +
và max
chấm dứt vì chúng là một phần của thư viện tiêu chuẩn của ngôn ngữ và chúng được xác định theo cách đó. Như đã đề cập trước đó, chúng tôi được phép giả định tài sản mà chúng tôi đang cố chứng minh là đúng đối với các cuộc gọi đệ quy miễn là chúng hoạt động trên các cây con ngay lập tức, do đó, các cuộc gọi height
cũng chấm dứt.
Điều đó kết luận bằng chứng. Lưu ý rằng bạn sẽ không thể chứng minh chấm dứt bằng một bài kiểm tra đơn vị. Bây giờ tất cả những gì còn lại là để cho thấy rằng height
không ném ngoại lệ.
- Chứng minh rằng
height
không ném ngoại lệ vào trường hợp cơ sở ( Empty
). Trở về 0 không thể ném ngoại lệ, vậy là xong.
- Chứng minh rằng
height
không ném ngoại lệ vào trường hợp không có cơ sở ( Node
). Giả sử một lần nữa rằng chúng ta biết +
và max
không ném ngoại lệ. Và cảm ứng cấu trúc cho phép chúng ta giả sử các cuộc gọi đệ quy sẽ không ném (vì hoạt động trên con ngay lập tức của cây.) Nhưng chờ đã! Hàm này là đệ quy, nhưng không đệ quy đuôi . Chúng ta có thể thổi bay đống! Bằng chứng cố gắng của chúng tôi đã phát hiện ra một lỗi. Chúng ta có thể sửa nó bằng cách thay đổi height
thành đệ quy đuôi .
Tôi hy vọng điều này cho thấy bằng chứng không phải đáng sợ hay phức tạp. Trên thực tế, bất cứ khi nào bạn viết mã, bạn đã xây dựng một bằng chứng trong đầu một cách không chính thức (nếu không, bạn sẽ không tin rằng bạn vừa thực hiện chức năng.) Bằng cách tránh null, đột biến không cần thiết và thừa kế không hạn chế, bạn có thể chứng minh trực giác của mình là sửa khá dễ. Những hạn chế này không khắc nghiệt như bạn nghĩ:
null
là một lỗ hổng ngôn ngữ và làm cho nó đi là tốt vô điều kiện.
- Đột biến đôi khi không thể tránh khỏi và cần thiết, nhưng nó cần ít thường xuyên hơn bạn nghĩ - đặc biệt là khi bạn có cấu trúc dữ liệu liên tục.
- Đối với việc có số lượng lớp hữu hạn (theo nghĩa chức năng) / lớp con (theo nghĩa OOP) so với số lượng không giới hạn trong số chúng, đó là một chủ đề quá lớn cho một câu trả lời . Đủ để nói rằng có một sự đánh đổi thiết kế ngoài đó - khả năng chính xác và tính linh hoạt của việc gia hạn.