Khi nào tôi nên sử dụng Debug.Assert ()?


220

Tôi đã là một kỹ sư phần mềm chuyên nghiệp khoảng một năm nay, khi tốt nghiệp với bằng CS. Tôi đã biết về các xác nhận trong một thời gian trong C ++ và C, nhưng không biết chúng tồn tại trong C # và .NET cho đến gần đây.

Mã sản xuất của chúng tôi không chứa bất kỳ khẳng định nào và câu hỏi của tôi là ...

Tôi có nên bắt đầu sử dụng Asserts trong mã sản xuất của chúng tôi không? Và nếu vậy, khi nào sử dụng nó là thích hợp nhất? Nó sẽ có ý nghĩa hơn để làm

Debug.Assert(val != null);

hoặc là

if ( val == null )
    throw new exception();

2
Sự phân đôi bạn thiết lập là đầu mối. Đây không phải là một câu hỏi - hoặc cho các trường hợp ngoại lệ và khẳng định, cả hai - và cho mã phòng thủ. Khi nào nên làm điều đó là những gì bạn đang muốn hiểu.
Casper Leon Nielsen

5
Tôi đã từng đọc ai đó đề xuất rằng một ngoại lệ hoặc phương pháp sụp đổ khác là phù hợp với các điều kiện trong đó "Không có cách nào tôi có thể phục hồi một cách hợp lý từ điều này", và thêm vào đó là một khẳng định phù hợp với các điều kiện "Điều này không bao giờ xảy ra, bao giờ". Nhưng hoàn cảnh thực tế nào thỏa mãn điều kiện sau mà không thỏa mãn cái trước? Đến từ nền tảng Python nơi các xác nhận tiếp tục được sản xuất, tôi chưa bao giờ hiểu cách tiếp cận Java / C # về việc tắt một số xác thực của bạn trong sản xuất. Trường hợp duy nhất cho nó tôi thực sự có thể thấy là nếu xác nhận là đắt tiền.
Mark Amery


2
Cá nhân tôi sử dụng các ngoại lệ cho các phương thức công khai và các xác nhận cho các phương thức riêng tư.
Fred

Câu trả lời:


230

Trong gỡ lỗi Ứng dụng Microsoft .NET 2.0, John Robbins có một phần lớn về các xác nhận. Điểm chính của anh ấy là:

  1. Khẳng định tự do. Bạn không bao giờ có thể có quá nhiều khẳng định.
  2. Khẳng định không thay thế ngoại lệ. Các ngoại lệ bao gồm những điều mà mã của bạn yêu cầu; khẳng định bao gồm những điều nó giả định.
  3. Một khẳng định được viết tốt có thể cho bạn biết không chỉ những gì đã xảy ra và ở đâu (như một ngoại lệ), mà tại sao.
  4. Một thông báo ngoại lệ thường có thể khó hiểu, đòi hỏi bạn phải làm việc ngược thông qua mã để tạo lại bối cảnh gây ra lỗi. Một xác nhận có thể duy trì trạng thái của chương trình tại thời điểm xảy ra lỗi.
  5. Các xác nhận tăng gấp đôi làm tài liệu, cho các nhà phát triển khác biết các giả định ngụ ý mà mã của bạn phụ thuộc vào.
  6. Hộp thoại xuất hiện khi xác nhận thất bại cho phép bạn đính kèm trình gỡ lỗi vào quy trình, do đó bạn có thể chọc xung quanh ngăn xếp như thể bạn đã đặt một điểm dừng ở đó.

PS: Nếu bạn thích Code Complete, tôi khuyên bạn nên theo dõi nó với cuốn sách này. Tôi đã mua nó để tìm hiểu về cách sử dụng WinDBG và kết xuất các tệp, nhưng nửa đầu được đóng gói với các mẹo để giúp tránh lỗi ngay từ đầu.


3
+1 cho bản tóm tắt súc tích và hữu ích. Áp dụng rất trực tiếp. Tuy nhiên, điều chính yếu còn thiếu đối với tôi là khi nào nên sử dụng Trace.Assert so với Trace.Assert. Tức là một cái gì đó về khi bạn làm / không muốn chúng trong mã sản xuất của bạn.
Jon Coombs

2
JonCoombs là "Trace.Assert vs. Trace.Assert" là một lỗi đánh máy?
thelem 21/07/2015

1
@thelem Có lẽ Jon có nghĩa Debug.Assertso với Trace.Assert. Cái sau được thực thi trong bản dựng Phát hành cũng như bản dựng Gỡ lỗi.
DavidRR

Tại sao tôi nên dùng Debug.Assert để ném ngoại lệ?
Barış Akkurt

86

Đặt Debug.Assert()mọi nơi trong mã nơi bạn muốn kiểm tra độ tỉnh táo để đảm bảo bất biến. Khi bạn biên dịch bản dựng Phát hành (nghĩa là không có DEBUGhằng số trình biên dịch), các lệnh gọi Debug.Assert()sẽ bị xóa để chúng không ảnh hưởng đến hiệu suất.

Bạn vẫn nên ném ngoại lệ trước khi gọi Debug.Assert(). Khẳng định chỉ đảm bảo rằng mọi thứ đều như mong đợi trong khi bạn vẫn đang phát triển.


35
Bạn có thể làm rõ lý do tại sao đưa ra một khẳng định nếu bạn vẫn ném một ngoại lệ trước khi gọi nó? Hay tôi đã hiểu nhầm câu trả lời của bạn?
Roman Starkov

2
@romkyns Bạn vẫn phải bao gồm chúng bởi vì nếu bạn không, khi bạn xây dựng dự án của mình ở chế độ Phát hành , tất cả các xác nhận / kiểm tra lỗi sẽ không còn nữa.
Oscar Mederos

28
@Oscar Tôi nghĩ rằng đó là toàn bộ vấn đề sử dụng các xác nhận ở vị trí đầu tiên ... OK sau đó, vì vậy bạn đặt các ngoại lệ trước chúng - vậy tại sao lại đặt các xác nhận sau?
Roman Starkov

4
@superjos: Tôi phải không đồng ý: Điểm # 2 trong câu trả lời của MacLeod nói rằng bạn thực sự cần khẳng định VÀ ngoại lệ, nhưng không ở cùng một chỗ. Thật vô ích khi ném NullRefEx vào một biến và ngay sau khi thực hiện Assert trên nó (phương thức khẳng định sẽ không bao giờ hiển thị hộp thoại trong trường hợp này, đó là toàn bộ điểm khẳng định). Điều MacLeod có nghĩa là ở một nơi nào đó, bạn sẽ cần một ngoại lệ, ở những người khác, một Assert sẽ là đủ.
David

1
Nó có thể trở nên lộn xộn khi diễn giải câu trả lời của tôi về câu trả lời của người khác :) Dù sao tôi cũng với bạn về những điều này: bạn cần cả hai, và bạn không nên đặt ngoại lệ trước khi khẳng định. Tôi không chắc về ý nghĩa của "không ở cùng một nơi". Một lần nữa, từ chối giải thích, tôi sẽ chỉ nêu suy nghĩ / sở thích của mình: đặt một hoặc nhiều xác nhận để kiểm tra các điều kiện tiên quyết trước khi một số thao tác bắt đầu hoặc để kiểm tra các điều kiện sau khi hoạt động. Bên cạnh các khẳng định, và sau khi chúng, hãy kiểm tra xem có vấn đề gì không và cần phải đưa ra ngoại lệ.
superjos

52

Từ mã hoàn thành

8 Lập trình phòng thủ

8.2 Khẳng định

Một xác nhận là mã được sử dụng trong quá trình phát triển, thường là một chương trình macro hoặc macro cho phép chương trình tự kiểm tra khi nó chạy. Khi một khẳng định là đúng, điều đó có nghĩa là mọi thứ đều hoạt động như mong đợi. Khi nó sai, điều đó có nghĩa là nó đã phát hiện ra một lỗi không mong muốn trong mã. Ví dụ: nếu hệ thống giả định rằng tệp thông tin khách hàng sẽ không bao giờ có hơn 50.000 bản ghi, chương trình có thể chứa một xác nhận rằng số lượng bản ghi là lessthan hoặc bằng 50.000. Miễn là số lượng hồ sơ ít hơn hoặc bằng 50.000, khẳng định sẽ im lặng. Tuy nhiên, nếu nó gặp hơn 50.000 bản ghi, tuy nhiên, nó sẽ lớn tiếng khẳng định rằng có lỗi trong chương trình.

Các xác nhận đặc biệt hữu ích trong các chương trình lớn, phức tạp và trong các chương trình có độ tin cậy cao. Chúng cho phép các lập trình viên nhanh chóng loại bỏ các giả định giao diện không khớp, các lỗi xuất hiện khi mã được sửa đổi, v.v.

Một xác nhận thường có hai đối số: một biểu thức boolean mô tả giả định được cho là đúng và một thông báo sẽ hiển thị nếu không.

(Càng)

Thông thường, bạn không muốn người dùng thấy thông báo xác nhận trong mã sản xuất; khẳng định chủ yếu để sử dụng trong quá trình phát triển và bảo trì. Các xác nhận thường được biên dịch thành mã tại thời điểm phát triển và được biên dịch ra khỏi mã để sản xuất. Trong quá trình phát triển, các xác nhận đưa ra các giả định mâu thuẫn, các điều kiện bất ngờ, các giá trị xấu được truyền vào các thói quen, v.v. Trong quá trình sản xuất, chúng được biên dịch ra khỏi mã để các xác nhận không làm giảm hiệu năng hệ thống.


7
Vậy, điều gì xảy ra nếu một tệp thông tin khách hàng gặp phải trong sản xuất có chứa hơn 50.000 bản ghi? Nếu xác nhận được biên dịch ra khỏi mã sản xuất và tình huống này không được xử lý, thì đây có phải là lỗi chính tả không?
DavidRR

1
@DavidRR Đúng vậy. Nhưng ngay khi sản xuất báo hiệu sự cố và một số nhà phát triển (có thể không biết rõ mã này) đã khắc phục sự cố, xác nhận sẽ thất bại và nhà phát triển sẽ biết ngay rằng hệ thống không được sử dụng như dự định.
Marc

48

FWIW ... Tôi thấy rằng các phương thức công khai của tôi có xu hướng sử dụng if () { throw; }mẫu để đảm bảo rằng phương thức đó được gọi chính xác. Phương pháp riêng tư của tôi có xu hướng sử dụng Debug.Assert().

Ý tưởng là với các phương thức riêng tư của tôi, tôi là người kiểm soát, vì vậy nếu tôi bắt đầu gọi một trong những phương thức riêng tư của mình bằng các tham số không chính xác, thì tôi đã phá vỡ giả định của chính mình ở đâu đó - tôi không bao giờ nên nhận vào trạng thái đó. Trong sản xuất, những khẳng định riêng tư này nên là công việc không cần thiết vì tôi phải giữ trạng thái nội bộ hợp lệ và nhất quán. Tương phản với các tham số được cung cấp cho các phương thức công khai, có thể được gọi bởi bất kỳ ai trong thời gian chạy: Tôi vẫn cần phải thực thi các ràng buộc tham số ở đó bằng cách ném ngoại lệ.

Ngoài ra, các phương thức riêng tư của tôi vẫn có thể đưa ra các ngoại lệ nếu một cái gì đó không hoạt động trong thời gian chạy (lỗi mạng, lỗi truy cập dữ liệu, dữ liệu xấu được truy xuất từ ​​dịch vụ của bên thứ ba, v.v.). Những khẳng định của tôi chỉ ở đó để đảm bảo rằng tôi đã không phá vỡ những giả định nội bộ của chính mình về tình trạng của đối tượng.


3
Đây là một mô tả rất rõ ràng về một thực hành tốt và nó đưa ra một câu trả lời rất hợp lý cho câu hỏi được hỏi.
Casper Leon Nielsen

42

Sử dụng các xác nhận để kiểm tra các giả định và ngoại lệ của nhà phát triển để kiểm tra các giả định về môi trường.


31

Nếu tôi là bạn, tôi sẽ làm:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

Hoặc để tránh kiểm tra tình trạng lặp đi lặp lại

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}

5
Làm thế nào là giải quyết vấn đề này? Với điều này, debug.assert trở nên vô nghĩa.
Quibbledome

43
Không, không - nó đột nhập vào mã ngay trước khi ngoại lệ được ném. Nếu bạn có một thử / bắt ở một nơi khác trong mã của bạn, bạn thậm chí có thể không nhận thấy ngoại lệ!
Đánh dấu Ingram

2
+1 Tôi đã gặp rất nhiều vấn đề khi mọi người chỉ đơn giản là thử / bắt ngoại lệ mà không làm gì cả nên việc theo dõi lỗi là một vấn đề
dance2die

5
Tôi cho rằng có những trường hợp bạn có thể muốn làm điều này, nhưng bạn không bao giờ nên bắt một ngoại lệ chung!
Casebash

8
@MarkIngram -1 cho câu trả lời của bạn và +1 cho nhận xét của bạn chứng minh điều đó. Đây là một mẹo hay cho một số trường hợp đặc biệt, nhưng có vẻ như đó là một điều kỳ quái cần làm chung cho tất cả các xác nhận.
Đánh dấu Amery

24

Nếu bạn muốn Xác nhận trong mã sản xuất của mình (ví dụ: Bản dựng phát hành), bạn có thể sử dụng Trace.Assert thay vì Debug.Assert.

Điều này tất nhiên thêm chi phí để thực hiện sản xuất của bạn.

Ngoài ra, nếu ứng dụng của bạn đang chạy ở chế độ giao diện người dùng, hộp thoại Xác nhận sẽ được hiển thị theo mặc định, điều này có thể gây ra một chút bối rối cho người dùng của bạn.

Bạn có thể ghi đè hành vi này bằng cách xóa DefaultTraceListener: xem tài liệu về Trace.Listener trong MSDN.

Tóm tắt,

  • Sử dụng Debug.Assert một cách tự do để giúp bắt lỗi trong các bản dựng Debug.

  • Nếu bạn sử dụng Trace.Assert trong chế độ giao diện người dùng, có lẽ bạn muốn xóa DefaultTraceListener để tránh làm người dùng bối rối.

  • Nếu điều kiện bạn đang kiểm tra là điều mà ứng dụng của bạn không thể xử lý, có lẽ tốt hơn hết bạn nên đưa ra một ngoại lệ, để đảm bảo việc thực thi không tiếp tục. Hãy lưu ý rằng người dùng có thể chọn bỏ qua một xác nhận.


1
+1 để chỉ ra sự khác biệt quan trọng giữa Debug.Assert và Trace.Assert, vì OP đặc biệt hỏi về mã sản xuất.
Jon Coombs

21

Các xác nhận được sử dụng để bắt lỗi lập trình viên (của bạn), không phải lỗi người dùng. Chúng chỉ nên được sử dụng khi không có cơ hội người dùng có thể khiến xác nhận bắn. Ví dụ: nếu bạn đang viết API, các xác nhận không nên được sử dụng để kiểm tra xem đối số không có giá trị trong bất kỳ phương thức nào mà người dùng API có thể gọi. Nhưng nó có thể được sử dụng trong một phương thức riêng tư không được tiết lộ như một phần của API của bạn để khẳng định rằng mã CỦA BẠN không bao giờ vượt qua đối số null khi không được yêu cầu.

Tôi thường ưu tiên các ngoại lệ hơn các khẳng định khi tôi không chắc chắn.


11

Nói ngắn gọn

Asserts được sử dụng cho bảo vệ và để kiểm tra Thiết kế theo các ràng buộc Hợp đồng, viz:

  • Assertschỉ nên dành cho các bản dựng Debug và phi sản xuất. Các xác nhận thường bị bỏ qua bởi trình biên dịch trong các bản dựng phát hành.
  • Asserts có thể kiểm tra các lỗi / điều kiện không mong muốn nằm trong sự kiểm soát của hệ thống của bạn
  • Asserts KHÔNG phải là một cơ chế để xác thực dòng đầu tiên của quy tắc người dùng hoặc quy tắc kinh doanh
  • Assertskhông nên được sử dụng để phát hiện các điều kiện môi trường không mong muốn (nằm ngoài sự kiểm soát của mã), ví dụ như hết bộ nhớ, lỗi mạng, lỗi cơ sở dữ liệu, v.v. Mặc dù hiếm gặp, các điều kiện này được dự kiến ​​(và mã ứng dụng của bạn không thể khắc phục các sự cố như lỗi phần cứng hoặc cạn kiệt tài nguyên). Thông thường, các trường hợp ngoại lệ sẽ được đưa ra - ứng dụng của bạn sau đó có thể thực hiện hành động khắc phục (ví dụ: thử lại cơ sở dữ liệu hoặc hoạt động mạng, cố gắng giải phóng bộ nhớ đệm) hoặc hủy bỏ một cách duyên dáng nếu ngoại lệ không thể được xử lý.
  • Xác nhận thất bại sẽ gây tử vong cho hệ thống của bạn - tức là không giống như ngoại lệ, không thử và bắt hoặc xử lý thất bại Asserts- mã của bạn đang hoạt động trong lãnh thổ không mong muốn. Dấu vết ngăn xếp và bãi đổ vỡ có thể được sử dụng để xác định những gì đã sai.

Khẳng định có lợi ích rất lớn:

  • Để hỗ trợ tìm kiếm xác thực bị thiếu của đầu vào người dùng hoặc lỗi ngược dòng trong mã cấp cao hơn.
  • Các xác nhận trong cơ sở mã truyền tải rõ ràng các giả định được thực hiện trong mã tới người đọc
  • Khẳng định sẽ được kiểm tra tại thời gian chạy trong các Debugbản dựng.
  • Khi mã đã được kiểm tra toàn diện, xây dựng lại mã vì Bản phát hành sẽ loại bỏ chi phí hiệu năng của việc xác minh giả định (nhưng với lợi ích là bản dựng Debug sau này sẽ luôn hoàn nguyên các kiểm tra, nếu cần).

... Chi tiết khác

Debug.Assertdiễn tả một điều kiện đã được giả định về trạng thái bởi phần còn lại của khối mã trong phạm vi kiểm soát của chương trình. Điều này có thể bao gồm trạng thái của các tham số được cung cấp, trạng thái của các thành viên của một thể hiện của lớp hoặc rằng trả về từ một cuộc gọi phương thức nằm trong phạm vi được ký hợp đồng / thiết kế của nó. Thông thường, các xác nhận sẽ đánh sập luồng / tiến trình / chương trình với tất cả thông tin cần thiết (Stack Trace, Crash Dump, v.v.), vì chúng chỉ ra sự hiện diện của một lỗi hoặc tình trạng không được xem xét mà không được thiết kế cho (ví dụ: không thử và bắt hoặc xử lý các lỗi xác nhận), với một ngoại lệ có thể xảy ra khi bản thân khẳng định đó có thể gây ra thiệt hại nhiều hơn lỗi (ví dụ: Bộ điều khiển không lưu sẽ không muốn YSOD khi máy bay đi tàu ngầm, mặc dù có nên triển khai chế tạo gỡ lỗi không sản xuất ...)

Khi nào bạn nên sử dụng Asserts? - Tại bất kỳ thời điểm nào trong hệ thống, hoặc API thư viện hoặc dịch vụ trong đó các đầu vào cho một chức năng hoặc trạng thái của một lớp được coi là hợp lệ (ví dụ: khi xác thực đã được thực hiện trên đầu vào của người dùng trong tầng trình bày của hệ thống , các lớp nghiệp vụ và lớp dữ liệu thường cho rằng kiểm tra null, kiểm tra phạm vi, kiểm tra độ dài chuỗi, v.v. trên đầu vào đã được thực hiện). - Các Assertkiểm tra phổ biến bao gồm trường hợp giả định không hợp lệ sẽ dẫn đến việc hủy bỏ đối tượng null, ước số bằng 0, tràn số học hoặc số ngày và không được thiết kế cho hành vi (ví dụ: nếu sử dụng int 32 bit để mô hình hóa tuổi của con người , sẽ là khôn ngoan Assertkhi tuổi thực sự nằm trong khoảng từ 0 đến 125 hoặc hơn - các giá trị -100 đến 10 ^ 10 không được thiết kế cho).

Hợp đồng mã .Net
Trong Ngăn xếp .Net, Hợp đồng mã có thể được sử dụng bổ sung hoặc thay thế cho việc sử dụng Debug.Assert. Hợp đồng mã có thể chính thức hóa hơn nữa việc kiểm tra trạng thái và có thể hỗ trợ phát hiện vi phạm các giả định tại ~ thời gian biên dịch (hoặc ngay sau đó, nếu được chạy dưới dạng kiểm tra lý lịch trong IDE).

Thiết kế theo hợp đồng (DBC) kiểm tra có sẵn bao gồm:

  • Contract.Requires - Điều kiện tiên quyết
  • Contract.Ensures - Hợp đồng PostConditions
  • Invariant - Thể hiện một giả định về trạng thái của một vật thể tại tất cả các điểm trong tuổi thọ của nó.
  • Contract.Assumes - bình định trình kiểm tra tĩnh khi thực hiện cuộc gọi đến các phương thức trang trí không hợp đồng.

Thật không may, Hợp đồng mã là tất cả nhưng đã chết vì MS đã ngừng phát triển nó.
Mike Lowery

10

Chủ yếu là không bao giờ trong cuốn sách của tôi. Trong phần lớn các trường hợp nếu bạn muốn kiểm tra xem mọi thứ có lành mạnh không thì hãy ném nếu không.

Điều tôi không thích là thực tế là nó làm cho một bản dựng gỡ lỗi có chức năng khác với bản dựng phát hành. Nếu một gỡ lỗi khẳng định không thành công nhưng chức năng hoạt động trong bản phát hành thì điều đó có ý nghĩa gì? Thậm chí còn tốt hơn khi trình xác nhận đã rời khỏi công ty từ lâu và không ai biết rằng đó là một phần của mã. Sau đó, bạn phải giết một số thời gian của bạn để khám phá vấn đề để xem nó có thực sự là một vấn đề hay không. Nếu đó là một vấn đề thì tại sao người đầu tiên không ném?

Đối với tôi điều này gợi ý bằng cách sử dụng Debug. Hãy giải quyết vấn đề cho người khác, hãy tự mình giải quyết vấn đề. Nếu một cái gì đó được coi là trường hợp và nó không phải là ném.

Tôi đoán có thể có các kịch bản quan trọng về hiệu suất mà bạn muốn tối ưu hóa các xác nhận của mình và chúng hữu ích ở đó, tuy nhiên tôi vẫn chưa gặp phải một kịch bản như vậy.


4
Bạn trả lời xứng đáng với một số công đức mặc dù khi bạn nhấn mạnh một số mối quan tâm thường được nêu lên về họ, thực tế họ làm gián đoạn phiên gỡ lỗi và cơ hội cho dương tính giả. Tuy nhiên, bạn đang thiếu một số điểm tinh tế và đang viết "tối ưu hóa khẳng định" - điều này chỉ có thể dựa trên suy nghĩ rằng ném một ngoại lệ và thực hiện debug.assert là như nhau. Nó không phải là, chúng phục vụ các mục đích và đặc điểm khác nhau, như bạn có thể thấy trong một số câu trả lời nâng cao. Dw
Casper Leon Nielsen

+1 cho "Điều tôi không thích là thực tế là nó làm cho bản dựng gỡ lỗi có chức năng khác với bản dựng phát hành. Nếu gỡ lỗi khẳng định không thành công nhưng chức năng hoạt động trong bản phát hành thì điều đó có ý nghĩa gì?" Trong .NET, System.Diagnostics.Trace.Assert()thực thi trong bản dựng Phát hành cũng như bản dựng Gỡ lỗi.
DavidRR

7

Theo tiêu chuẩn IDesign , bạn nên

Khẳng định mọi giả định. Trung bình, mỗi dòng thứ năm là một khẳng định.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

Từ chối trách nhiệm, tôi nên đề cập đến việc tôi chưa thấy thực tế khi thực hiện IRL này. Nhưng đây là tiêu chuẩn của họ.


Hình như Juval Lowy thích tự trích dẫn.
devlord

6

Chỉ sử dụng các xác nhận trong trường hợp bạn muốn xóa séc cho các bản dựng phát hành. Hãy nhớ rằng, các xác nhận của bạn sẽ không kích hoạt nếu bạn không biên dịch trong chế độ gỡ lỗi.

Lấy ví dụ kiểm tra null của bạn, nếu đây là API chỉ dành cho nội bộ, tôi có thể sử dụng một xác nhận. Nếu đó là một API công khai, tôi chắc chắn sẽ sử dụng kiểm tra và ném rõ ràng.


Trong .NET, người ta có thể sử dụng System.Diagnostics.Trace.Assert()để thực hiện một xác nhận trong bản dựng (sản xuất).
DavidRR

Phân tích Mã cai trị CA1062: đối số Validate các phương pháp công cộng đòi hỏi kiểm tra một đối số cho nullkhi: "Một phương pháp từ bên ngoài có thể nhìn thấy dereferences một trong những đối số tham chiếu của nó mà không cần xác minh cho dù lập luận cho rằng là rỗng ." Trong tình huống như vậy, phương pháp hoặc tài sản nên ném ArgumentNullException.
DavidRR

6

Tất cả các xác nhận phải là mã có thể được tối ưu hóa để:

Debug.Assert(true);

Bởi vì nó kiểm tra một cái gì đó mà bạn đã giả định là đúng. Ví dụ:

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

Ở trên, có ba cách tiếp cận khác nhau cho các tham số null. Người đầu tiên chấp nhận nó là cho phép (nó chỉ không làm gì cả). Thứ hai ném một ngoại lệ cho mã gọi để xử lý (hoặc không, dẫn đến một thông báo lỗi). Thứ ba cho rằng điều đó không thể xảy ra và khẳng định rằng nó là như vậy.

Trong trường hợp đầu tiên, không có vấn đề gì.

Trong trường hợp thứ hai, có một vấn đề với mã gọi - nó không nên gọi GetFirstAndConsumebằng null, vì vậy nó sẽ lấy lại ngoại lệ.

Trong trường hợp thứ ba, có một vấn đề với mã này, vì nó đã được kiểm tra rằng en != nulltrước khi nó được gọi, vì vậy nó không đúng là một lỗi. Hay nói cách khác, nó phải là mã có thể được tối ưu hóa về mặt lý thuyết Debug.Assert(true), sicne en != nullnên luôn luôn như truevậy!


1
Vì vậy, trong trường hợp thứ ba, điều gì xảy ra nếu en == nulltrong sản xuất? Bạn có thể nói rằng điều đó không bao giờen == null có thể xảy ra trong sản xuất (vì chương trình đã được gỡ lỗi kỹ lưỡng)? Nếu vậy, thì ít nhất phục vụ như là một thay thế cho một nhận xét. Tất nhiên, nếu những thay đổi trong tương lai được thực hiện, nó cũng tiếp tục có giá trị để phát hiện một hồi quy có thể. Debug.Assert(en != null)
DavidRR

@DavidRR, tôi thực sự khẳng định rằng nó không bao giờ có thể là null và do đó, khẳng định trong mã, do đó có tên. Tất nhiên tôi có thể sai, hoặc bị sai bởi một thay đổi, và đó là giá trị của cuộc gọi khẳng định.
Jon Hanna

1
Các cuộc gọi Debug.Assert()được loại bỏ trong bản dựng Phát hành. Vì vậy, nếu bạn sai, trong trường hợp thứ ba , bạn sẽ không biết nó trong sản xuất (giả sử việc sử dụng một xây dựng phát hành trong sản xuất). Tuy nhiên, hành vi của các trường hợp thứ nhất và thứ hai là giống hệt nhau trong các bản dựng Gỡ lỗi và Phát hành.
DavidRR

@DavidRR, điều này chỉ phù hợp khi tôi cho rằng điều đó không thể xảy ra, vì một lần nữa, đó là một sự khẳng định thực tế, không phải là kiểm tra trạng thái. Tất nhiên cũng là vô nghĩa nếu có sự khẳng định, có một lỗi mà nó sẽ mắc phải, và chưa bao giờ gặp trường hợp đó trong thử nghiệm.
Jon Hanna

4

Tôi nghĩ rằng tôi sẽ thêm bốn trường hợp nữa, trong đó Debug.Assert có thể là lựa chọn đúng đắn.

1) Một cái gì đó tôi chưa thấy được đề cập ở đây là phạm vi bảo hiểm khái niệm bổ sung Các xác nhận có thể cung cấp trong quá trình thử nghiệm tự động . Một ví dụ đơn giản:

Khi một số người gọi cấp cao hơn được sửa đổi bởi một tác giả tin rằng họ đã mở rộng phạm vi của mã để xử lý các kịch bản bổ sung, lý tưởng nhất là (!), Họ sẽ viết các bài kiểm tra đơn vị để bao quát điều kiện mới này. Sau đó, có thể là mã tích hợp đầy đủ có vẻ hoạt động tốt.

Tuy nhiên, thực sự một lỗ hổng tinh tế đã được giới thiệu, nhưng không được phát hiện trong kết quả thử nghiệm. Callee đã trở nên không xác định trong trường hợp này và chỉ xảy ra để cung cấp kết quả mong đợi. Hoặc có lẽ nó đã mang lại một lỗi làm tròn mà không được chú ý. Hoặc gây ra một lỗi đã được bù bằng nhau ở nơi khác. Hoặc được cấp không chỉ quyền truy cập được yêu cầu mà còn các đặc quyền bổ sung không nên được cấp. Vân vân.

Tại thời điểm này, các câu lệnh Debug.Assert () có trong callee kết hợp với trường hợp mới (hoặc trường hợp cạnh) được điều khiển bởi các bài kiểm tra đơn vị có thể cung cấp thông báo vô giá trong quá trình kiểm tra rằng các giả định của tác giả ban đầu đã bị vô hiệu và mã không nên được phát hành mà không cần xem xét thêm. Các khẳng định với các bài kiểm tra đơn vị là các đối tác hoàn hảo.

2) Ngoài ra, một số bài kiểm tra rất đơn giản để viết, nhưng chi phí cao và không cần thiết với các giả định ban đầu . Ví dụ:

Nếu một đối tượng chỉ có thể được truy cập từ một điểm nhập được bảo mật nhất định, có nên thực hiện một truy vấn bổ sung cho cơ sở dữ liệu quyền mạng từ mọi phương thức đối tượng để đảm bảo người gọi có quyền? Chắc chắn là không. Có lẽ giải pháp lý tưởng bao gồm bộ nhớ đệm hoặc một số tính năng mở rộng khác, nhưng thiết kế không yêu cầu nó. Một Debug.Assert () sẽ ngay lập tức hiển thị khi đối tượng đã được gắn vào một điểm nhập không an toàn.

3) Tiếp theo, trong một số trường hợp, sản phẩm của bạn có thể không có tương tác chẩn đoán hữu ích cho tất cả hoặc một phần hoạt động của sản phẩm khi được triển khai ở chế độ phát hành . Ví dụ:

Giả sử nó là một thiết bị thời gian thực nhúng. Ném ngoại lệ và khởi động lại khi nó gặp một gói không đúng định dạng là phản tác dụng. Thay vào đó, thiết bị có thể được hưởng lợi từ hoạt động nỗ lực tốt nhất, thậm chí đến mức gây ra tiếng ồn trong đầu ra của nó. Nó cũng có thể không có giao diện người, thiết bị ghi nhật ký hoặc thậm chí con người có thể truy cập vật lý khi được triển khai ở chế độ phát hành và nhận thức về lỗi được cung cấp tốt nhất bằng cách đánh giá cùng một đầu ra. Trong trường hợp này, các xác nhận tự do và thử nghiệm trước khi phát hành kỹ lưỡng có giá trị hơn các trường hợp ngoại lệ.

4) Cuối cùng, một số thử nghiệm không thành công chỉ vì callee được coi là cực kỳ đáng tin cậy . Trong hầu hết các trường hợp, mã có thể tái sử dụng càng nhiều, càng có nhiều nỗ lực để làm cho nó đáng tin cậy. Do đó, thông thường là Ngoại lệ đối với các tham số không mong muốn từ người gọi, nhưng Khẳng định cho kết quả không mong muốn từ callees. Ví dụ:

Nếu một String.Findhoạt động cốt lõi nói rằng nó sẽ trả về -1khi không tìm thấy tiêu chí tìm kiếm, bạn có thể thực hiện một cách an toàn một thao tác thay vì ba. Tuy nhiên, nếu nó thực sự trở lại -2, bạn có thể không có quá trình hành động hợp lý. Sẽ không có ích khi thay thế phép tính đơn giản hơn bằng phép tính riêng cho một -1giá trị và không hợp lý trong hầu hết các môi trường phát hành để xả mã của bạn bằng các kiểm tra đảm bảo các thư viện lõi hoạt động như mong đợi. Trong trường hợp này Khẳng định là lý tưởng.


4

Trích dẫn từ lập trình viên thực dụng: Từ Journeyman đến Master

Để lại các xác nhận đã bật

Có một sự hiểu lầm phổ biến về các xác nhận, được ban hành bởi những người viết trình biên dịch và môi trường ngôn ngữ. Nó đi một cái gì đó như thế này:

Các xác nhận thêm một số chi phí vào mã. Vì họ kiểm tra những thứ không bao giờ xảy ra, nên họ sẽ chỉ bị kích hoạt bởi một lỗi trong mã. Khi mã đã được kiểm tra và vận chuyển, chúng không còn cần thiết nữa và cần được tắt để làm cho mã chạy nhanh hơn. Các xác nhận là một cơ sở gỡ lỗi.

Có hai giả định sai lầm ở đây. Đầu tiên, họ cho rằng thử nghiệm tìm thấy tất cả các lỗi. Trong thực tế, đối với bất kỳ chương trình phức tạp nào, bạn không có khả năng kiểm tra ngay cả một tỷ lệ phần trăm rất nhỏ của các hoán vị mà mã của bạn sẽ được đưa vào (xem Thử nghiệm tàn nhẫn).

Thứ hai, những người lạc quan đang quên rằng chương trình của bạn chạy trong một thế giới nguy hiểm. Trong quá trình thử nghiệm, chuột có thể sẽ không gặm được cáp truyền thông, ai đó chơi trò chơi sẽ không làm cạn bộ nhớ và các tệp nhật ký sẽ không lấp đầy ổ cứng. Những điều này có thể xảy ra khi chương trình của bạn chạy trong môi trường sản xuất. Tuyến phòng thủ đầu tiên của bạn đang kiểm tra xem có bất kỳ lỗi nào không và thứ hai của bạn đang sử dụng các xác nhận để cố gắng phát hiện những lỗi bạn đã bỏ lỡ.

Tắt các xác nhận khi bạn cung cấp một chương trình cho sản xuất cũng giống như vượt qua một dây cao mà không có mạng vì bạn đã từng thực hiện nó trong thực tế . Có giá trị đáng kể, nhưng thật khó để có được bảo hiểm nhân thọ.

Ngay cả khi bạn có vấn đề về hiệu suất, chỉ tắt những xác nhận thực sự đánh bạn .


2

Bạn nên luôn luôn sử dụng cách tiếp cận thứ hai (ném ngoại lệ).

Ngoài ra, nếu bạn đang sản xuất (và có bản phát hành), tốt hơn hết là nên ném ngoại lệ (và để ứng dụng gặp sự cố trong trường hợp xấu nhất) hơn là làm việc với các giá trị không hợp lệ và có thể phá hủy dữ liệu của khách hàng của bạn (có thể tốn hàng nghìn bằng đô la).


1
Không, giống như một số câu trả lời khác ở đây: bạn không thực sự hiểu sự khác biệt nên bạn chọn không tham gia vào một trong các dịch vụ, thiết lập sự phân đôi giả giữa chúng trong quy trình. Dw
Casper Leon Nielsen

3
Đây là câu trả lời đúng duy nhất trong danh sách này IMO. Đừng bỏ qua nó một cách dễ dàng Casper. Debug Assert là một mô hình chống. Nếu bất biến của nó tại thời điểm gỡ lỗi thì bất biến của nó khi chạy. Cho phép ứng dụng của bạn tiếp tục với một bất biến bị hỏng khiến bạn rơi vào trạng thái không xác định và có thể có vấn đề nghiêm trọng hơn so với sự cố. IMO tốt hơn là có cùng một mã trong cả hai bản dựng không thành công nhanh với các hợp đồng bị hỏng và sau đó thực hiện xử lý lỗi mạnh mẽ ở cấp cao nhất. ví dụ: Cách ly các thành phần và thực hiện khả năng khởi động lại chúng (như tab bị sập trong trình duyệt không làm sập toàn bộ trình duyệt).
justin.m.chase

1
Có thể hữu ích khi đưa Trace.Assert vào cuộc thảo luận của bạn ở đây, vì nó không thể bị bác bỏ bởi cùng một lập luận.
Jon Coombs

0

Bạn nên sử dụng Debug.Assert để kiểm tra các lỗi logic trong các chương trình của bạn. Trình biên dịch chỉ có thể thông báo cho bạn các lỗi cú pháp. Vì vậy, bạn chắc chắn nên sử dụng các câu lệnh Assert để kiểm tra các lỗi logic. Giống như nói thử nghiệm một chương trình bán những chiếc xe chỉ có BMW màu xanh mới được giảm giá 15%. Trình biên dịch có thể cho bạn biết không có gì nếu chương trình của bạn đúng về mặt logic trong việc thực hiện điều này nhưng một tuyên bố khẳng định có thể.


2
xin lỗi nhưng ngoại lệ làm tất cả những điều tương tự, vì vậy câu trả lời này không giải quyết được câu hỏi thực sự.
Roman Starkov

0

Tôi đã đọc câu trả lời ở đây và tôi nghĩ rằng tôi nên thêm một sự khác biệt quan trọng. Có hai cách rất khác nhau trong đó khẳng định được sử dụng. Một là như một lối tắt dành cho nhà phát triển tạm thời cho "Điều này không thực sự xảy ra vì vậy nếu nó cho tôi biết để tôi có thể quyết định phải làm gì", giống như một điểm dừng có điều kiện, trong trường hợp chương trình của bạn có thể tiếp tục. Mặt khác, là một cách để đặt các giả định về trạng thái chương trình hợp lệ trong mã của bạn.

Trong trường hợp đầu tiên, các xác nhận thậm chí không cần phải nằm trong mã cuối cùng. Bạn nên sử dụng Debug.Asserttrong quá trình phát triển và bạn có thể loại bỏ chúng nếu / khi không còn cần thiết. Nếu bạn muốn để lại chúng hoặc nếu bạn quên loại bỏ chúng thì không vấn đề gì, vì chúng sẽ không có bất kỳ hậu quả nào trong các phần tổng hợp phát hành.

Nhưng trong trường hợp thứ hai, các xác nhận là một phần của mã. Họ, tốt, khẳng định, rằng các giả định của bạn là đúng, và cũng ghi nhận chúng. Trong trường hợp đó, bạn thực sự muốn để lại chúng trong mã. Nếu chương trình ở trạng thái không hợp lệ thì không được phép tiếp tục. Nếu bạn không đủ khả năng đạt được hiệu suất, bạn sẽ không sử dụng C #. Một mặt có thể hữu ích để có thể đính kèm trình gỡ lỗi nếu nó xảy ra. Mặt khác, bạn không muốn dấu vết ngăn xếp xuất hiện trên người dùng của mình và có lẽ quan trọng hơn là bạn không muốn họ có thể bỏ qua nó. Ngoài ra, nếu đó là một dịch vụ thì nó sẽ luôn bị bỏ qua. Do đó, trong sản xuất, hành vi chính xác sẽ là ném Ngoại lệ và sử dụng xử lý ngoại lệ thông thường của chương trình của bạn, điều này có thể hiển thị cho người dùng một thông điệp hay và ghi nhật ký chi tiết.

Trace.Assertcó cách hoàn hảo để đạt được điều này. Nó sẽ không bị xóa trong sản xuất và có thể được cấu hình với các trình nghe khác nhau bằng app.config. Vì vậy, để phát triển trình xử lý mặc định là tốt, và để sản xuất, bạn có thể tạo một TraceListener đơn giản như bên dưới để ném ngoại lệ và kích hoạt nó trong tệp cấu hình sản xuất.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

Và trong tập tin cấu hình sản xuất:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>

-1

Tôi không biết nó như thế nào trong C # và .NET, nhưng trong C sẽ khẳng định () chỉ hoạt động nếu được biên dịch với -DDEBUG - trình xác nhận sẽ không bao giờ thấy một xác nhận () nếu nó được biên dịch mà không có. Nó chỉ dành cho nhà phát triển. Tôi sử dụng nó rất thường xuyên, đôi khi việc theo dõi lỗi dễ dàng hơn.


-1

Tôi sẽ không sử dụng chúng trong mã sản xuất. Ném ngoại lệ, bắt và đăng nhập.

Cũng cần phải cẩn thận trong asp.net, vì một xác nhận có thể hiển thị trên bảng điều khiển và đóng băng (các) yêu cầu.

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.