Tại sao có sự hỗ trợ hạn chế như vậy cho Thiết kế theo Hợp đồng trong hầu hết các ngôn ngữ lập trình hiện đại?


40

Gần đây tôi đã phát hiện ra Thiết kế theo Hợp đồng (DbC) và tôi thấy đó là một cách cực kỳ thú vị để viết mã. Trong số những thứ khác, nó dường như sẽ cung cấp:

  • Tài liệu tốt hơn. Vì hợp đồng là tài liệu, nên không thể hết hạn. Ngoài ra, vì hợp đồng chỉ định chính xác những gì một thói quen làm, nó giúp hỗ trợ tái sử dụng.
  • Gỡ lỗi đơn giản hơn. Vì việc thực hiện chương trình dừng thời điểm hợp đồng thất bại, lỗi không thể lan truyền và xác nhận cụ thể bị vi phạm có lẽ sẽ được nêu bật. Điều này cung cấp hỗ trợ trong quá trình phát triển và trong quá trình bảo trì.
  • Phân tích tĩnh tốt hơn. DbC về cơ bản chỉ là một triển khai logic Hoare, và các nguyên tắc tương tự nên được áp dụng.

Các chi phí, so với dường như là khá nhỏ:

  • Gõ thêm ngón tay. Vì các hợp đồng phải được đánh vần.
  • Có một số lượng đào tạo để có được thoải mái với các hợp đồng bằng văn bản.

Bây giờ, quen thuộc với Python chủ yếu, tôi nhận ra rằng trên thực tế có thể viết ra các điều kiện tiên quyết (chỉ cần đưa ra các ngoại lệ cho đầu vào không phù hợp) và thậm chí có thể sử dụng các xác nhận để kiểm tra lại các điều kiện hậu kỳ nhất định. Nhưng không thể mô phỏng một số tính năng nhất định như 'cũ' hoặc 'kết quả' mà không có một số phép thuật bổ sung mà cuối cùng sẽ được coi là không phải là Pythonic. (Ngoài ra, có một vài thư viện cung cấp hỗ trợ, nhưng cuối cùng tôi cảm thấy rung cảm khi sử dụng chúng, vì hầu hết các nhà phát triển thì không.) Tôi cho rằng đó là một vấn đề tương tự đối với tất cả các ngôn ngữ khác (tất nhiên ngoại trừ , Eiffel).

Trực giác của tôi nói với tôi rằng việc thiếu sự hỗ trợ phải là kết quả của một số loại từ chối thực hành, nhưng tìm kiếm trực tuyến đã không có kết quả. Tôi đang tự hỏi nếu ai đó có thể làm rõ tại sao hầu hết các ngôn ngữ hiện đại dường như cung cấp rất ít sự hỗ trợ? Là DbC thiếu sót hoặc quá đắt? Hay nó chỉ là lỗi thời do Lập trình cực đoan và các phương pháp khác?


Nghe có vẻ như là một cách quá phức tạp để thực hiện lập trình dựa trên thử nghiệm, mà không có lợi ích gì khi thử nghiệm chương trình của bạn.
Dan

3
@Dan, không thực sự, tôi nghĩ về nó nhiều hơn như là một phần mở rộng của hệ thống loại. ví dụ: một hàm không chỉ lấy một đối số nguyên, nó lấy một số nguyên có nghĩa vụ theo hợp đồng phải lớn hơn 0
Carson63000

4
Hợp đồng mã @Dan giảm đáng kể số lượng thử nghiệm bạn cần làm.
Rei Miyasaka

24
@Dan, tôi muốn nói rằng TDD là hợp đồng của người nghèo chứ không phải ngược lại.
SK-logic

Trong ngôn ngữ động, bạn có thể "trang trí" các đối tượng của mình bằng các hợp đồng dựa trên một cờ tùy chọn. Tôi có một ví dụ thực hiện sử dụng cờ môi trường để tùy ý khỉ vá các đối tượng hiện có bằng các hợp đồng. Có, hỗ trợ không phải là bản địa, nhưng thật dễ dàng để thêm. Điều tương tự cũng áp dụng cho các khai thác thử nghiệm, chúng không phải là bản địa nhưng chúng rất dễ thêm / ghi.
Raynos

Câu trả lời:


9

Có thể cho rằng chúng được hỗ trợ trong hầu hết mọi ngôn ngữ lập trình.

Những gì bạn cần là "khẳng định".

Chúng dễ dàng được mã hóa thành các câu lệnh "nếu":

if (!assertion) then AssertionFailure();

Với điều này, bạn có thể viết hợp đồng bằng cách đặt các xác nhận đó ở đầu mã của bạn cho các ràng buộc đầu vào; những người tại các điểm trả lại là những hạn chế đầu ra. Bạn thậm chí có thể thêm bất biến trong toàn bộ mã của mình (mặc dù không thực sự là một phần của "thiết kế theo hợp đồng").

Vì vậy, tôi cho rằng chúng không phổ biến vì các lập trình viên quá lười biếng để mã hóa chúng, không phải vì bạn không thể làm điều đó.

Bạn có thể làm cho những điều này hiệu quả hơn một chút trong hầu hết các ngôn ngữ bằng cách xác định "kiểm tra" hằng số boolean thời gian biên dịch và sửa đổi các câu lệnh một chút:

if (checking & !Assertion) then AssertionFailure();

Nếu bạn không thích cú pháp, bạn có thể sử dụng các kỹ thuật trừu tượng ngôn ngữ khác nhau như macro.

Một số ngôn ngữ hiện đại cung cấp cho bạn cú pháp hay cho điều này và đó là điều tôi nghĩ bạn muốn nói là "hỗ trợ ngôn ngữ hiện đại". Đó là hỗ trợ, nhưng nó khá mỏng.

Điều mà hầu hết các ngôn ngữ hiện đại không cung cấp cho bạn là các xác nhận "tạm thời" (trên các trạng thái trước hoặc sau tùy ý [toán tử tạm thời "cuối cùng"], mà bạn cần nếu bạn muốn viết các hợp đồng thực sự thú vị. bạn ở đây


Vấn đề tôi gặp phải khi chỉ có quyền truy cập vào các xác nhận là không có cách nào hiệu quả để kiểm tra các điều kiện hậu lệnh đối với các lệnh vì bạn thường cần so sánh trạng thái hậu điều kiện với trạng thái tiền điều kiện (Eiffel gọi điều này là 'cũ' và tự động chuyển nó sang thói quen hậu điều kiện .) Trong Python, chức năng này có thể được tái tạo một cách tầm thường bằng cách sử dụng các trình trang trí, nhưng nó sẽ xuất hiện trong thời gian ngắn để tắt các xác nhận.
Ceasar Bautista

Eiffel thực sự tiết kiệm được bao nhiêu? Vì nó hợp lý không thể biết phần nào bạn có thể truy cập / sửa đổi mà không giải quyết vấn đề tạm dừng (bằng cách phân tích chức năng của bạn), nên nó phải lưu trạng thái máy hoàn chỉnh hoặc chỉ là một phần rất nông. Tôi nghi ngờ cái sau; và chúng có thể "mô phỏng" bằng các phép gán vô hướng đơn giản trước điều kiện tiên quyết. Tôi rất vui khi biết Eiffel làm khác.
Ira Baxter

7
... Chỉ cần kiểm tra cách Eiffel hoạt động. "exp <cũ>" là giá trị của <exp> khi vào hàm, vì vậy nó đang thực hiện các bản sao nông ở lối vào hàm như tôi mong đợi. Bạn có thể làm chúng quá. Tôi đồng ý để trình biên dịch triển khai cú pháp cho trước / bài / cũ thuận tiện hơn so với thực hiện tất cả điều này bằng tay, nhưng vấn đề là người ta có thể làm điều này bằng tay và nó thực sự không khó. Chúng tôi trở lại với các lập trình viên lười biếng.
Ira Baxter

@IraBaxter Mã số trở nên đơn giản hơn nếu bạn có thể tách hợp đồng khỏi logic thực tế. Ngoài ra, nếu trình biên dịch có thể phân tách hợp đồng và mã, nó có thể giảm trùng lặp rất nhiều . Ví dụ, trong D, bạn có thể khai báo hợp đồng trên giao diện hoặc siêu lớp và các xác nhận sẽ được áp dụng cho tất cả các lớp thực hiện / mở rộng, bất kể mã trong các chức năng của chúng. Ví dụ với python hoặc Java, bạn phải gọi toàn bộ superphương thức và có thể vứt bỏ kết quả nếu bạn chỉ muốn các hợp đồng được kiểm tra mà không bị trùng lặp. Điều này thực sự giúp thực hiện mã tuân thủ LSP sạch.
marstato

@marstato: Tôi đã đồng ý rằng hỗ trợ bằng ngôn ngữ là một điều tốt.
Ira Baxter

15

Như bạn nói, Design by Contract một tính năng trong Eiffel, từ lâu đã là một trong những ngôn ngữ lập trình được tôn trọng trong cộng đồng nhưng chưa bao giờ được biết đến.

DbC không phải là ngôn ngữ phổ biến nhất bởi vì gần đây cộng đồng lập trình chính thống đã chấp nhận rằng việc thêm các ràng buộc / kỳ vọng vào mã của họ là điều "hợp lý" đối với các lập trình viên. Hiện tại, thông thường các lập trình viên hiểu được việc kiểm thử đơn vị có giá trị như thế nào và điều đó khiến cho các lập trình viên phải chấp nhận đưa mã vào để xác thực các đối số của họ và nhìn thấy lợi ích. Nhưng một thập kỷ trước, có lẽ hầu hết các lập trình viên sẽ nói rằng "đó chỉ là công việc làm thêm cho những thứ bạn biết sẽ luôn ổn."

Tôi nghĩ rằng nếu bạn đến nhà phát triển trung bình ngày hôm nay và nói về các điều kiện hậu kỳ, họ sẽ gật đầu nhiệt tình và nói "OK, đó giống như thử nghiệm đơn vị." Và nếu bạn nói về các điều kiện trước, họ sẽ nói "OK, đó giống như xác thực tham số, điều mà chúng ta không phải lúc nào cũng làm, nhưng, tôi biết, tôi đoán là ổn ..." Và sau đó nếu bạn nói về bất biến , họ sẽ bắt đầu nói "Gee, cái này có bao nhiêu chi phí? Chúng ta sẽ bắt thêm bao nhiêu lỗi nữa?" v.v.

Vì vậy, tôi nghĩ rằng vẫn còn một chặng đường dài trước khi DbC được áp dụng rộng rãi.


OTOH, các lập trình viên chính đã được sử dụng để viết các xác nhận khá lâu. Việc thiếu một bộ tiền xử lý có thể sử dụng được trong các ngôn ngữ chính hiện đại nhất khiến cho hoạt động tốt đẹp này không hiệu quả, nhưng nó vẫn phổ biến đối với C và C ++. Nó đang trở lại ngay bây giờ với Hợp đồng mã Microsoft (dựa trên, AFAIK, viết lại mã byte cho các bản dựng phát hành).
SK-logic

8

Trực giác của tôi nói với tôi rằng việc thiếu sự hỗ trợ phải là kết quả của một loại từ chối thực hành nào đó, ...

Sai.

Đó là một thực hành thiết kế . Nó có thể được thể hiện rõ ràng bằng mã (kiểu Eiffel) hoặc ngầm định trong mã (hầu hết các ngôn ngữ) hoặc trong các bài kiểm tra đơn vị. Thực hành thiết kế tồn tại và hoạt động tốt. Hỗ trợ ngôn ngữ là tất cả trên bản đồ. Tuy nhiên, nó có mặt trong nhiều ngôn ngữ trong khung kiểm tra đơn vị.

Tôi đang tự hỏi nếu ai đó có thể làm rõ tại sao hầu hết các ngôn ngữ hiện đại dường như cung cấp rất ít sự hỗ trợ? Là DbC thiếu sót hoặc quá đắt?

Nó đắt tiền. Và. Quan trọng hơn, có một số điều không thể được chứng minh bằng một ngôn ngữ nhất định. Chẳng hạn, việc chấm dứt vòng lặp không thể được chứng minh bằng ngôn ngữ lập trình, nó đòi hỏi khả năng chứng minh "bậc cao". Vì vậy, một số loại hợp đồng là kỹ thuật không thể diễn tả.

Hay nó chỉ là lỗi thời do Lập trình cực đoan và các phương pháp khác?

Không.

Chúng tôi chủ yếu sử dụng các bài kiểm tra đơn vị để chứng minh rằng DbC được thực hiện.

Đối với Python, như bạn đã lưu ý, DbC đi ở một số nơi.

  1. Các kết quả kiểm tra doc Chuỗi và doc Chuỗi.

  2. Khẳng định để xác nhận đầu vào và đầu ra.

  3. Bài kiểm tra đơn vị.

Thêm nữa.

Bạn có thể áp dụng các công cụ kiểu lập trình biết chữ để bạn viết một tài liệu bao gồm thông tin DbC của bạn và tạo ra các kịch bản kiểm tra đơn vị Python cộng với sạch. Phương pháp lập trình biết chữ cho phép bạn viết một tác phẩm văn học hay bao gồm các hợp đồng và nguồn hoàn chỉnh.


Bạn có thể chứng minh các trường hợp tầm thường của việc chấm dứt vòng lặp, chẳng hạn như lặp qua một chuỗi hữu hạn cố định. Đó là vòng lặp tổng quát không thể được hiển thị để chấm dứt một cách tầm thường (vì nó có thể đang tìm kiếm các giải pháp cho các phỏng đoán toán học thú vị của Hồi giáo); đó là toàn bộ bản chất của vấn đề dừng.
Donal Fellows

+1. Tôi nghĩ bạn là người duy nhất đã đưa ra điểm quan trọng nhất - there are some things which cannot be proven. Xác minh chính thức có thể là tuyệt vời, nhưng không phải mọi thứ đều có thể kiểm chứng! Vì vậy, tính năng đó thực sự hạn chế những gì ngôn ngữ lập trình thực sự có thể làm!
Dipan Mehta

@DonalFellows: Vì trường hợp chung không thể được chứng minh, thật khó để kết hợp một loạt các tính năng (a) đắt tiền và (b) được biết là không đầy đủ. Quan điểm của tôi trong câu trả lời này là dễ dàng hơn để tránh tất cả các tính năng đó và tránh đặt ra những kỳ vọng sai lầm về bằng chứng chính xác nói chung, khi có những hạn chế. Là một bài tập thiết kế (ngoài ngôn ngữ), rất nhiều kỹ thuật chứng minh có thể (và nên) được sử dụng.
S.Lott

Nó hoàn toàn không tốn kém trong một ngôn ngữ như C ++, nơi kiểm tra hợp đồng biên dịch trong bản dựng phát hành. Và việc sử dụng DBC có xu hướng dẫn đến việc phát hành mã xây dựng nhẹ hơn, bởi vì bạn đặt ít kiểm tra thời gian chạy hơn cho chương trình ở trạng thái hợp pháp. Tôi đã mất số lượng các loại tiền mã hóa đáng tin cậy mà tôi đã thấy khi nhiều chức năng kiểm tra trạng thái bất hợp pháp và trả về sai, khi đó KHÔNG BAO GIỜ nên ở trạng thái đó trong bản dựng phát hành được thử nghiệm đúng cách.
Kaitain

6

Chỉ cần đoán. Có lẽ một phần lý do khiến nó không phổ biến là vì "Thiết kế theo hợp đồng" được thương hiệu bởi Eiffel.


3

Một giả thuyết cho rằng đối với một chương trình đủ phức tạp, đặc biệt là các chương trình có mục tiêu di động, khối lượng hợp đồng có thể trở nên lỗi và khó gỡ lỗi, hoặc hơn thế, chỉ riêng mã chương trình. Như với bất kỳ mẫu nào, cũng có thể có một lợi nhuận giảm dần trong quá trình sử dụng, cũng như các lợi thế rõ ràng khi được sử dụng theo cách nhắm mục tiêu hơn.

Một kết luận khả dĩ khác là sự phổ biến của "ngôn ngữ được quản lý" là bằng chứng hiện tại về hỗ trợ theo hợp đồng thiết kế cho các tính năng được quản lý đã chọn (giới hạn mảng theo hợp đồng, v.v.)


> hàng loạt hợp đồng có thể trở nên lỗi và khó gỡ lỗi, hoặc hơn thế, so với mã chương trình một mình tôi chưa bao giờ thấy điều này.
Kaitain

2

Lý do mà hầu hết các ngôn ngữ chính không có các tính năng DbC trong ngôn ngữ là tỷ lệ chi phí lợi ích của việc triển khai nó là cao đối với người thực hiện ngôn ngữ.

một mặt của điều này đã được xem xét trong các câu trả lời khác, các bài kiểm tra đơn vị và các cơ chế thời gian chạy khác (hoặc thậm chí một số cơ chế thời gian biên dịch với lập trình meta mẫu) có thể cung cấp cho bạn nhiều ưu điểm của DbC. Do đó, trong khi có một lợi ích thì có khả năng nó được xem là khá khiêm tốn.

Mặt khác là chi phí, phù hợp với DbC retro vào một ngôn ngữ hiện tại có thể là một thay đổi quá lớn và rất phức tạp để khởi động. Giới thiệu cú pháp mới trong một ngôn ngữ mà không phá vỡ mã cũ là khó. Cập nhật thư viện tiêu chuẩn hiện tại của bạn để sử dụng một thay đổi sâu rộng như vậy sẽ tốn kém. Do đó, chúng tôi có thể kết luận rằng việc triển khai các tính năng DbC bằng ngôn ngữ hiện tại có chi phí cao.

Tôi cũng lưu ý rằng các khái niệm có nhiều hợp đồng cho các mẫu, và do đó phần nào liên quan đến DbC, đã bị loại bỏ khỏi tiêu chuẩn C ++ mới nhất ngay cả sau nhiều năm làm việc với chúng, người ta ước tính chúng vẫn cần nhiều năm làm việc. Những loại thay đổi lớn, rộng, sâu rộng đối với các ngôn ngữ chỉ là quá khó để thực hiện.


2

DbC sẽ được sử dụng rộng rãi hơn nếu các hợp đồng có thể được kiểm tra tại thời điểm biên dịch để không thể chạy một chương trình vi phạm bất kỳ hợp đồng nào.

Không có hỗ trợ trình biên dịch, "DbC" chỉ là tên bao phấn cho "kiểm tra bất biến / giả định và đưa ra một ngoại lệ nếu vi phạm".


Không phải điều đó chạy vào vấn đề tạm dừng sao?
Ceasar Bautista

@Ceasar Nó phụ thuộc. Một số giả định có thể được kiểm tra, một số khác thì không thể. Ví dụ, có các hệ thống loại cho phép tránh việc chuyển một danh sách trống làm đối số hoặc trả về một danh sách.
Ingo

Điểm hay (+1) mặc dù Bertrand Meyer trong phần "Chủ mưu lập trình" cũng đề cập đến hệ thống tạo lớp ngẫu nhiên của họ và kêu gọi kiểm tra vi phạm hợp đồng. Vì vậy, đó là cách tiếp cận thời gian biên dịch / thời gian chạy hỗn hợp, nhưng tôi nghi ngờ kỹ thuật này hoạt động trong mọi tình huống
Maksee

Điều đó đúng ở một mức độ nào đó, mặc dù nó sẽ là một thất bại thảm hại chứ không phải là một ngoại lệ (xem bên dưới). Ưu điểm chính của DBC là phương pháp luận, thực sự dẫn đến các chương trình được thiết kế tốt hơn và đảm bảo rằng bất kỳ phương pháp đã cho nào PHẢI ở trạng thái pháp lý khi nhập cảnh, giúp đơn giản hóa phần lớn logic bên trong. Nói chung, bạn không nên ném ngoại lệ khi hợp đồng bị vi phạm. Các ngoại lệ nên được sử dụng khi chương trình có thể HỢP PHÁP ở trạng thái ~ X và mã máy khách phải xử lý việc này. Một hợp đồng sẽ nói rằng ~ X đơn giản là bất hợp pháp.
Kaitain

1

Tôi đã giải thích đơn giản, hầu hết mọi người (bao gồm cả lập trình viên) không muốn làm thêm trừ khi họ thấy cần thiết. Lập trình hệ thống điện tử trong đó an toàn được coi là rất quan trọng Tôi đã không thấy hầu hết các dự án mà không có nó.

Nhưng nếu bạn đang xem xét trang web, máy tính để bàn hoặc lập trình di động - sự cố và hành vi không mong muốn đôi khi không được coi là xấu và lập trình viên sẽ chỉ tránh làm việc thêm khi báo cáo lỗi và sau đó sửa chúng được coi là đủ.

Đây có lẽ là lý do tôi nghĩ Ada không bao giờ chọn ngoài ngành lập trình hàng không bởi vì nó đòi hỏi nhiều công việc mã hóa hơn mặc dù Ada là ngôn ngữ tuyệt vời và nếu bạn muốn xây dựng một hệ thống đáng tin cậy, đó là ngôn ngữ tốt nhất cho công việc (không bao gồm SPARK thuộc sở hữu độc quyền ngôn ngữ dựa trên Ada).

Thiết kế bởi các thư viện hợp đồng cho C # đã được Microsoft thử nghiệm và chúng rất hữu ích để xây dựng phần mềm đáng tin cậy, nhưng chúng chưa bao giờ lấy đà trên thị trường nếu không bạn sẽ thấy chúng là một phần của ngôn ngữ C # cốt lõi.

Các xác nhận không giống như hỗ trợ đầy đủ chức năng cho các điều kiện trước / sau và bất biến. Mặc dù nó có thể cố gắng mô phỏng chúng nhưng một ngôn ngữ / trình biên dịch với sự hỗ trợ phù hợp sẽ phân tích 'cây cú pháp trừu tượng' và kiểm tra các lỗi logic mà đơn giản là các xác nhận không thể.

Chỉnh sửa: Tôi đã thực hiện tìm kiếm và theo dõi các cuộc thảo luận có liên quan có thể hữu ích: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-world-lacular-scala


-2

Hầu hết các lý do như sau:

  1. Nó chỉ có sẵn trong các ngôn ngữ không phổ biến
  2. Không cần thiết vì cùng một thứ có thể được thực hiện theo cách khác nhau trong các ngôn ngữ lập trình hiện có bởi bất kỳ ai thực sự muốn làm điều đó
  3. Thật khó để hiểu và sử dụng - nó đòi hỏi kiến ​​thức chuyên môn để thực hiện đúng, vì vậy có rất ít người thực hiện nó
  4. nó đòi hỏi một lượng lớn mã để làm điều đó - các lập trình viên thích giảm thiểu số lượng ký tự họ viết - nếu phải mất một đoạn mã dài, thì phải có điều gì đó không ổn với nó
  5. những lợi thế không có ở đó - chỉ là không thể tìm đủ lỗi để làm cho nó đáng giá

1
Câu trả lời của bạn không được tranh luận tốt. Bạn chỉ đơn giản nêu ý kiến ​​của mình rằng không có lợi thế, nó yêu cầu số lượng lớn mã và không cần thiết vì nó có thể được thực hiện với các ngôn ngữ hiện có (OP đặc biệt giải quyết vấn đề này!).
Andres F.

Tôi không nghĩ về các xác nhận, vv như là một thay thế cho nó. Đó không phải là cách chính xác để làm điều đó trong các ngôn ngữ hiện có. (Tức là nó chưa được xử lý)
tp1

@ tp1, nếu các lập trình viên thực sự muốn giảm thiểu việc gõ, họ sẽ không bao giờ rơi vào một cái gì đó dài dòng và hùng hồn như Java. Và đúng vậy, bản thân lập trình đòi hỏi "kiến thức chuyên ngành" "để thực hiện đúng". Những người không sở hữu kiến ​​thức như vậy chỉ không nên được phép viết mã.
SK-logic

@ Sk-logic: Chà, có vẻ như một nửa thế giới đang làm sai OO đơn giản vì họ không muốn viết các hàm chuyển tiếp từ các hàm thành viên sang các hàm thành viên của dữ liệu. Đó là một vấn đề lớn trong kinh nghiệm của tôi. Điều này được gây ra trực tiếp bằng cách giảm thiểu số lượng ký tự để viết.
tp1

@ tp1, nếu mọi người thực sự muốn giảm thiểu việc gõ, họ thậm chí sẽ không chạm vào OO, bao giờ. OOP tự nhiên là hùng hồn, ngay cả trong các triển khai tốt nhất của nó, như Smalltalk. Tôi sẽ không nói đó là một tài sản xấu, đôi khi có tài hùng biện.
SK-logic
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.