Tại sao tôi sử dụng hợp đồng mã


26

Gần đây tôi đã vấp phải khuôn khổ của Microsoft cho các hợp đồng mã.

Tôi đọc một chút tài liệu và thấy mình liên tục hỏi: "Tại sao tôi lại muốn làm điều này, vì nó không và thường không thể thực hiện phân tích tĩnh".

Bây giờ, tôi đã có một kiểu lập trình phòng thủ rồi, với các ngoại lệ bảo vệ như thế này:

if(var == null) { throw new NullArgumentException(); }

Tôi cũng đang sử dụng NullObject Pattern rất nhiều và hiếm khi gặp vấn đề gì. Thêm bài kiểm tra đơn vị cho nó và bạn đã hoàn tất cài đặt.

Tôi chưa bao giờ sử dụng khẳng định và không bao giờ bỏ lỡ chúng. Hoàn toàn ngược lại. Tôi thực sự ghét mã có rất nhiều khẳng định vô nghĩa trong đó, nó chỉ gây ồn cho tôi và làm tôi mất tập trung khỏi những gì tôi thực sự muốn thấy. Hợp đồng mã, ít nhất là cách của Microsoft rất giống nhau - và thậm chí còn tệ hơn. Họ thêm rất nhiều tiếng ồn và độ phức tạp vào mã. Trong 99%, một ngoại lệ sẽ được đưa ra bằng mọi cách - vì vậy tôi không quan tâm liệu đó là từ sự khẳng định / hợp đồng hay vấn đề thực tế. Chỉ rất, rất ít trường hợp trong đó các trạng thái chương trình thực sự trở nên bị hỏng.

Thành thật mà nói, lợi ích của việc sử dụng hợp đồng mã là gì? Có cái nào không? Nếu bạn đã sử dụng các bài kiểm tra đơn vị và mã phòng thủ, tôi cảm thấy rằng việc giới thiệu hợp đồng chỉ là không đáng giá và gây nhiễu cho mã của bạn mà người bảo trì sẽ nguyền rủa khi anh ta cập nhật phương pháp đó, giống như tôi làm khi tôi không thể thấy mã đang làm gì do khẳng định vô ích. Tôi vẫn chưa thấy một lý do tốt để trả giá đó.



1
Tại sao bạn nói "nó không và thường không thể thực hiện phân tích tĩnh"? Tôi nghĩ rằng đó là một trong những điểm của dự án này (trái ngược với việc chỉ sử dụng Asserts hoặc ném ngoại lệ phòng thủ)?
Carson63000

@ Carson63000 Đọc tài liệu cẩn thận và bạn sẽ thấy rằng trình kiểm tra tĩnh sẽ chỉ đưa ra rất nhiều cảnh báo, rất cần thiết, (các nhà phát triển thừa nhận là như vậy) và hầu hết các hợp đồng được xác định sẽ chỉ đưa ra một ngoại lệ và không làm gì ngoài .
Falcon

@Falcon: lạ thay, kinh nghiệm của tôi hoàn toàn khác. Phân tích tĩnh hoạt động không hoàn hảo, nhưng khá tốt, và tôi hiếm khi thấy các cảnh báo không cần thiết, ngay cả khi làm việc ở mức cảnh báo cấp 4 (cao nhất).
Arseni Mourzenko

Tôi đoán tôi sẽ phải thử nó để tìm hiểu cách nó hoạt động.
Falcon

Câu trả lời:


42

Bạn cũng có thể đã hỏi khi gõ tĩnh tốt hơn gõ động. Một cuộc tranh luận đã nổ ra trong nhiều năm mà không có hồi kết. Vì vậy, hãy xem các câu hỏi như nghiên cứu ngôn ngữ gõ động và tĩnh

Nhưng đối số cơ bản sẽ là tiềm năng để khám phá và khắc phục các sự cố tại thời điểm biên dịch có thể đã rơi vào sản xuất.

Hợp đồng so với Guards

Ngay cả với những người bảo vệ và trường hợp ngoại lệ, bạn vẫn phải đối mặt với vấn đề hệ thống sẽ không thực hiện được nhiệm vụ dự định của mình trong một số trường hợp. Có thể là một ví dụ sẽ khá quan trọng và tốn kém khi thất bại.

Hợp đồng so với bài kiểm tra đơn vị

Lập luận thông thường về vấn đề này là các thử nghiệm chứng minh sự hiện diện của lỗi, trong khi các loại (hợp đồng) chứng minh sự vắng mặt. F.ex sử dụng một loại bạn biết rằng không có đường dẫn nào trong chương trình có thể cung cấp đầu vào không hợp lệ, trong khi thử nghiệm chỉ có thể cho bạn biết rằng đường dẫn được bảo hiểm đã cung cấp đầu vào chính xác.

Hợp đồng so với mẫu Null Object

Bây giờ điều này ít nhất là trong cùng một công viên bóng. Các ngôn ngữ như Scala và Haskell đã thành công lớn với phương pháp này để loại bỏ các tài liệu tham khảo null hoàn toàn khỏi các chương trình. (Ngay cả khi Scala chính thức cho phép null, quy ước là không bao giờ sử dụng chúng)

Nếu bạn đã sử dụng mẫu này để loại bỏ NRE, về cơ bản bạn đã loại bỏ nguồn thất bại lớn nhất về cơ bản, đó là cách thức hợp đồng cho phép bạn thực hiện.

Sự khác biệt có thể là các hợp đồng có một tùy chọn để tự động yêu cầu tất cả mã của bạn để tránh null và do đó buộc bạn phải sử dụng mẫu này ở nhiều nơi hơn để vượt qua quá trình biên dịch.

Trên hết các hợp đồng đó cũng cung cấp cho bạn sự linh hoạt để nhắm mục tiêu những thứ vượt quá null. Vì vậy, nếu bạn không còn thấy bất kỳ NRE nào trong các lỗi của mình, bạn có thể muốn sử dụng hợp đồng để bóp nghẹt vấn đề phổ biến nhất tiếp theo mà bạn có thể gặp phải. Tắt bởi một? Chỉ số ngoài phạm vi?

Nhưng...

Tất cả những gì đang được nói. Tôi đồng ý rằng các hợp đồng nhiễu cú pháp (và thậm chí cả nhiễu cấu trúc) thêm vào mã là khá đáng kể và tác động của phân tích đối với thời gian xây dựng của bạn không nên được đánh giá thấp. Vì vậy, nếu bạn quyết định thêm hợp đồng vào hệ thống của mình, có lẽ nên làm điều đó thật cẩn thận với sự tập trung hẹp vào loại lỗi mà người ta cố gắng giải quyết.


6
Đó là một câu trả lời đầu tiên tuyệt vời cho các lập trình viên. Vui mừng khi có sự tham gia của bạn trong cộng đồng.

13

Tôi không biết khẳng định rằng "nó không và thường không thể thực hiện phân tích tĩnh" đến từ đâu. Phần đầu tiên của khẳng định là sai rõ ràng. Cái thứ hai phụ thuộc vào ý của bạn là "thường". Tôi muốn nói rằng thường xuyên , nó thực hiện phân tích tĩnh, và hiếm khi, nó thất bại ở đó. Trong ứng dụng kinh doanh thông thường, hiếm khi trở nên gần gũi hơn bao giờ hết .

Vì vậy, ở đây nó đến, lợi ích đầu tiên:

Lợi ích 1: phân tích tĩnh

Các xác nhận thông thường và kiểm tra đối số có một nhược điểm: chúng bị hoãn cho đến khi mã được thực thi. Mặt khác, các hợp đồng mã biểu hiện ở mức độ sớm hơn nhiều, ở bước mã hóa hoặc trong khi biên dịch ứng dụng. Bạn càng bắt lỗi sớm thì càng ít tốn kém để sửa nó.

Lợi ích 2: loại tài liệu luôn cập nhật

Hợp đồng mã cũng cung cấp một loại tài liệu luôn cập nhật. Nếu nhận xét XML của phương thức SetProductPrice(int newPrice)cho biết newPricenên vượt trội hoặc bằng 0, bạn có thể hy vọng rằng tài liệu này được cập nhật, nhưng bạn cũng có thể phát hiện ra rằng ai đó đã thay đổi phương thức để newPrice = 0ném ArgumentOutOfRangeException, nhưng không bao giờ thay đổi tài liệu tương ứng. Dựa vào mối tương quan giữa các hợp đồng mã và chính mã, bạn không gặp phải vấn đề tài liệu không đồng bộ.

Loại tài liệu được cung cấp bởi các hợp đồng mã cũng rất quý theo cách mà thông thường, các nhận xét XML không giải thích tốt các giá trị chấp nhận được. Đã bao nhiêu lần là tôi tự hỏi nếu nullhay string.Emptyhoặc \r\nlà một giá trị được uỷ quyền cho một phương pháp, và ý kiến XML đã im lặng về điều đó!

Tóm lại, không có hợp đồng mã, rất nhiều đoạn mã giống như vậy:

Tôi sẽ chấp nhận một số giá trị nhưng không phải giá trị khác, nhưng bạn sẽ phải đoán hoặc đọc tài liệu, nếu có. Trên thực tế, đừng đọc tài liệu: nó đã lỗi thời. Đơn giản chỉ cần lặp qua tất cả các giá trị và bạn sẽ thấy những giá trị khiến tôi ném ngoại lệ. Bạn cũng phải đoán phạm vi của các giá trị có thể được trả về, bởi vì ngay cả khi tôi sẽ nói thêm một chút với bạn về chúng, điều đó có thể không đúng, với hàng trăm thay đổi được thực hiện cho tôi trong những năm qua.

Với các hợp đồng mã, nó trở thành:

Đối số tiêu đề có thể là một chuỗi không null có độ dài 0..500. Số nguyên theo sau là một giá trị dương, chỉ có thể bằng 0 khi chuỗi trống. Cuối cùng, tôi sẽ trả lại một IDefinitionđối tượng, không bao giờ null.

Lợi ích 3: hợp đồng giao diện

Một lợi ích thứ ba là các hợp đồng mã trao quyền cho các giao diện. Hãy nói rằng bạn có một cái gì đó như:

public interface ICommittable
{
    public ICollection<AtomicChange> PendingChanges { get; }

    public void CommitChanges();

    ...
}

Làm thế nào bạn, chỉ sử dụng các xác nhận và ngoại lệ, đảm bảo chỉ CommitChangescó thể được gọi khi PendingChangeskhông trống? Làm thế nào bạn sẽ đảm bảo rằng PendingChangeskhông bao giờ null?

Lợi ích 4: thực thi các kết quả của một phương pháp

Cuối cùng, lợi ích thứ tư là có thể đạt Contract.Ensuređược kết quả. Điều gì sẽ xảy ra nếu, khi viết một phương thức trả về một số nguyên, tôi muốn chắc chắn rằng giá trị không bao giờ thấp hơn hoặc bằng 0? Bao gồm năm năm sau, sau khi chịu nhiều thay đổi từ nhiều nhà phát triển? Ngay khi một phương thức có nhiều điểm trả về, Asserts trở thành cơn ác mộng bảo trì cho điều đó.


Xem xét các hợp đồng mã không chỉ là một phương tiện chính xác cho mã của bạn, mà còn là một cách chặt chẽ hơn để viết mã. Theo cách tương tự, một người sử dụng các ngôn ngữ động độc quyền có thể hỏi tại sao bạn sẽ thực thi các loại ở cấp ngôn ngữ, trong khi bạn có thể làm điều tương tự trong các xác nhận khi cần. Bạn có thể, nhưng gõ tĩnh dễ sử dụng hơn, ít bị lỗi hơn so với một loạt các xác nhận và tự ghi lại tài liệu.

Sự khác biệt giữa gõ động và gõ tĩnh là cực kỳ gần với sự khác biệt giữa lập trình thông thường và lập trình theo hợp đồng.


3

Tôi biết điều này hơi muộn với câu hỏi, nhưng có một lợi ích khác đối với Hợp đồng mã mà gấu đề cập

Hợp đồng được kiểm tra bên ngoài phương pháp

Khi chúng tôi có các điều kiện bảo vệ bên trong phương thức của mình, đó là nơi kiểm tra xảy ra và nơi báo cáo lỗi. Sau đó, chúng tôi có thể phải điều tra theo dõi ngăn xếp để tìm ra nguồn lỗi thực sự ở đâu.

Hợp đồng mã được đặt trong phương thức, nhưng điều kiện tiên quyết được kiểm tra bên ngoài phương thức khi có bất cứ điều gì cố gắng gọi phương thức đó. Các hợp đồng trở thành một phần của phương thức và xác định xem nó có thể được gọi hay không. Điều đó có nghĩa là chúng tôi nhận được một lỗi được báo cáo gần hơn với nguồn thực tế của vấn đề.

Nếu bạn có một phương thức bảo vệ hợp đồng được gọi cho nhiều nơi, đây có thể là một lợi ích thực sự.


2

Hai điều thực sự:

  1. Hỗ trợ công cụ, VS có thể làm cho dữ liệu hợp đồng tách rời khỏi sự xâm nhập để nó là một phần của trợ giúp hoàn thành tự động.
  2. Khi một lớp con ghi đè một phương thức, bạn sẽ mất tất cả các séc của mình (vẫn còn hiệu lực nếu bạn tuân theo LSP) với các hợp đồng mà chúng tự động theo dõi các lớp con của chúng.
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.