Nếu có các xác nhận trong bản dựng phát hành


20

Hành vi mặc định của assertC ++ là không làm gì trong các bản dựng phát hành. Tôi cho rằng điều này được thực hiện vì lý do hiệu suất và có thể để ngăn người dùng nhìn thấy các thông báo lỗi khó chịu.

Tuy nhiên, tôi lập luận rằng những tình huống mà một người assertsẽ bị sa thải nhưng bị vô hiệu hóa thậm chí còn rắc rối hơn vì ứng dụng có thể sẽ bị sập theo cách thậm chí còn tồi tệ hơn vì một số bất biến đã bị phá vỡ.

Ngoài ra, đối số hiệu suất đối với tôi chỉ được tính khi đó là một vấn đề có thể đo lường được. Hầu hết assertcác mã trong mã của tôi không phức tạp hơn nhiều

assert(ptr != nullptr);

mà sẽ có tác động nhỏ trên hầu hết các mã.

Điều này dẫn tôi đến câu hỏi: Các xác nhận (có nghĩa là khái niệm, không phải là triển khai cụ thể) có được kích hoạt trong các bản dựng phát hành không? Tại sao không)?

Xin lưu ý rằng câu hỏi này không phải là về cách bật xác nhận trong các bản dựng phát hành (như #undef _NDEBUGhoặc sử dụng triển khai xác nhận tự xác định). Hơn nữa, nó không phải là về việc cho phép xác nhận trong mã thư viện tiêu chuẩn / bên thứ ba mà là mã do tôi kiểm soát.


Tôi biết một người chơi toàn cầu trong thị trường chăm sóc sức khỏe đã cài đặt phiên bản gỡ lỗi của hệ thống thông tin bệnh viện của họ tại các trang web của khách hàng. Họ cũng có một thỏa thuận đặc biệt với Microsoft để cài đặt các thư viện gỡ lỗi C ++ ở đó. Chà, chất lượng sản phẩm của họ là ...
Bernhard Hiller

2
Có một câu nói cũ rằng "loại bỏ các xác nhận cũng giống như mặc áo phao để thực hành ở bến cảng, nhưng sau đó để lại áo phao phía sau khi tàu của bạn rời khỏi đại dương mở" ( wiki.c2.com/?AssertionsAsDefensiveProgramming ) . Cá nhân tôi giữ chúng được kích hoạt trong các bản dựng phát hành theo mặc định. Thật không may, cách làm này không phổ biến lắm trong thế giới C ++, nhưng ít nhất James Kanze, cựu chiến binh C ++ nổi tiếng luôn sử dụng để tranh luận về việc giữ các xác nhận trong các bản dựng phát hành: stackoverflow.com/a/12162195/333030
Christian Hackl

Câu trả lời:


20

Cổ điển assertlà một công cụ từ thư viện C tiêu chuẩn cũ, không phải từ C ++. Nó vẫn có sẵn trong C ++, ít nhất là vì lý do tương thích ngược.

Tôi không có dòng thời gian chính xác của libs tiêu chuẩn C trong tay, nhưng tôi khá chắc chắn assertđã có sẵn ngay sau thời điểm K & R C ra đời (khoảng năm 1978). Trong C cổ điển, để viết các chương trình mạnh mẽ, việc thêm kiểm tra con trỏ NULL và kiểm tra giới hạn mảng cần được thực hiện thường xuyên hơn so với trong C ++. Tổng kiểm tra con trỏ NULL có thể tránh được bằng cách sử dụng các tham chiếu và / hoặc con trỏ thông minh thay vì con trỏ và bằng cách sử dụng std::vector, kiểm tra giới hạn mảng thường không cần thiết. Hơn nữa, hiệu suất đạt được vào năm 1980 chắc chắn quan trọng hơn nhiều so với ngày nay. Vì vậy, tôi nghĩ rằng đó rất có thể là lý do tại sao "khẳng định" được thiết kế để chỉ hoạt động trong các bản dựng gỡ lỗi theo mặc định.

Ngoài ra, để xử lý lỗi thực sự trong mã sản xuất, một hàm chỉ kiểm tra một số điều kiện hoặc bất biến và làm hỏng chương trình nếu điều kiện không được đáp ứng, trong hầu hết các trường hợp không đủ linh hoạt. Để gỡ lỗi có lẽ là ổn, vì người chạy chương trình và quan sát lỗi thường có một trình gỡ lỗi trong tay để phân tích những gì xảy ra. Tuy nhiên, đối với mã sản xuất, một giải pháp hợp lý cần phải là một chức năng hoặc cơ chế

  • kiểm tra một số điều kiện (và dừng thực thi ở phạm vi điều kiện không thành công)

  • cung cấp một thông báo lỗi rõ ràng trong trường hợp điều kiện không giữ

  • cho phép phạm vi bên ngoài nhận thông báo lỗi và đưa nó đến một kênh truyền thông cụ thể. Kênh này có thể là một cái gì đó như stderr, tệp nhật ký tiêu chuẩn, hộp thông báo trong chương trình GUI, gọi lại xử lý lỗi chung, kênh lỗi kích hoạt mạng hoặc bất cứ điều gì phù hợp nhất với phần mềm cụ thể.

  • cho phép phạm vi bên ngoài trên cơ sở từng trường hợp để quyết định xem chương trình sẽ kết thúc một cách duyên dáng hay liệu nó có nên tiếp tục hay không.

(Tất nhiên, cũng có những tình huống kết thúc chương trình ngay lập tức trong trường hợp điều kiện chưa hoàn thành là lựa chọn hợp lý duy nhất, nhưng trong những trường hợp như vậy, nó cũng sẽ xảy ra trong bản dựng phát hành, không chỉ trong bản dựng gỡ lỗi).

Vì cổ điển assertkhông cung cấp các tính năng này, nó không phù hợp cho việc xây dựng bản phát hành, giả sử bản dựng phát hành là thứ mà người ta triển khai vào sản xuất.

Bây giờ bạn có thể hỏi tại sao không có chức năng hoặc cơ chế như vậy trong lib tiêu chuẩn C cung cấp loại linh hoạt này. Trên thực tế, trong C ++, có một cơ chế tiêu chuẩn có tất cả các tính năng này (và hơn thế nữa), và bạn biết điều đó: nó được gọi là ngoại lệ .

Tuy nhiên, trong C, khó có thể thực hiện một cơ chế tiêu chuẩn tốt, mục đích chung để xử lý lỗi với tất cả các tính năng được đề cập do thiếu ngoại lệ như một phần của ngôn ngữ lập trình. Vì vậy, hầu hết các chương trình C đều có cơ chế xử lý lỗi riêng với mã trả về hoặc "goto" hoặc "nhảy dài" hoặc hỗn hợp đó. Đây thường là những giải pháp thực dụng phù hợp với loại chương trình cụ thể, nhưng không "đủ mục đích chung" để phù hợp với thư viện chuẩn C.


Ah, hoàn toàn quên mất di sản C (sau tất cả #include <cassert>). Điều đó hoàn toàn có ý nghĩa bây giờ.
Không ai vào

1
Tôi hoàn toàn không đồng ý với toàn bộ câu trả lời này. Ném ra một ngoại lệ khi ứng dụng của bạn phát hiện ra rằng mã nguồn của nó bị lỗi là biện pháp cuối cùng trong các ngôn ngữ như Java không thể làm tốt hơn, nhưng trong C ++, bằng mọi cách có thể chấm dứt chương trình ngay lập tức trong tình huống như vậy. Bạn không muốn ngăn xếp ngăn chặn để thực hiện bất kỳ hoạt động tiếp theo nào được thực hiện tại thời điểm đó. Một chương trình lỗi có thể ghi dữ liệu có thể bị hỏng vào các tệp, cơ sở dữ liệu hoặc ổ cắm mạng. Chỉ cần sập ASAP (và có thể khởi động lại quá trình bởi một cơ chế bên ngoài).
Christian Hackl

1
@ChristianHackl: Tôi đồng ý có những tình huống chấm dứt chương trình là lựa chọn khả thi duy nhất, nhưng sau đó nó cũng xảy ra trong chế độ phát hành, và assertcũng là một công cụ sai cho việc này (ném một ngoại lệ thực sự cũng có thể là phản ứng sai ). Tuy nhiên, tôi khá chắc chắn trong thực tế assertcũng đã được sử dụng cho rất nhiều bài kiểm tra trong C trong đó trong C ++ người ta sẽ sử dụng ngoại lệ ngày hôm nay.
Doc Brown

15

Nếu bạn thấy mình muốn xác nhận đã được bật trong một bản phát hành, bạn đã yêu cầu xác nhận làm sai công việc.

Điểm của khẳng định là chúng không được kích hoạt trong một bản phát hành. Điều này cho phép kiểm tra các bất biến trong quá trình phát triển với mã mà nếu không phải là mã giàn giáo. Mã phải được gỡ bỏ trước khi phát hành.

Nếu bạn có thứ gì đó mà bạn cảm thấy nên được kiểm tra ngay cả trong khi phát hành thì hãy viết mã kiểm tra nó. Các If throwcông trình xây dựng rất tốt. Nếu bạn muốn nói điều gì đó khác với những cú ném khác, chỉ cần sử dụng một ngoại lệ mô tả nói lên điều bạn muốn nói.

Không phải là bạn không thể thay đổi cách bạn sử dụng các xác nhận. Đó là làm như vậy sẽ không giúp bạn có được bất cứ điều gì hữu ích, chạy ngược lại với mong đợi và khiến bạn không có cách nào sạch sẽ để làm những gì khẳng định là phải làm. Thêm các bài kiểm tra không hoạt động trong một bản phát hành.

Tôi không nói về việc thực hiện cụ thể của khẳng định mà là khái niệm về một khẳng định. Tôi không muốn lạm dụng khẳng định hoặc gây nhầm lẫn cho độc giả. Tôi muốn hỏi tại sao nó lại theo cách này ngay từ đầu. Tại sao không có phát hành bổ sung_assert? Có cần không cho nó? Lý do đằng sau khẳng định bị vô hiệu hóa trong phát hành là gì? - Không ai

Tại sao không có relase_assert? Thẳng thắn bởi vì khẳng định không đủ tốt cho sản xuất. Có, có một nhu cầu nhưng không có gì đáp ứng tốt nhu cầu đó. Oh chắc chắn bạn có thể thiết kế của riêng bạn. Về mặt cơ học, hàm throw If của bạn chỉ cần một bool và một ngoại lệ để ném. Và điều đó có thể phù hợp với nhu cầu của bạn. Nhưng bạn đang thực sự giới hạn thiết kế. Đó là lý do tại sao nó không làm tôi ngạc nhiên rằng không có một hệ thống ném ngoại lệ nào trong thư viện ngôn ngữ của bạn. Nó chắc chắn không phải là bạn không thể làm điều đó. Những người khác có . Nhưng đối phó với trường hợp xảy ra sự cố là 80% công việc cho hầu hết các chương trình. Và cho đến nay không ai chỉ cho chúng ta một kích thước tốt phù hợp với tất cả các giải pháp. Xử lý hiệu quả với những trường hợp này có thể trở nên phức tạp. Nếu chúng ta có một hệ thống phát hành đóng hộp không đáp ứng nhu cầu của mình, tôi nghĩ rằng nó sẽ gây hại nhiều hơn thì tốt. Bạn đang yêu cầu một sự trừu tượng tốt có nghĩa là bạn sẽ không phải suy nghĩ về vấn đề này. Tôi cũng muốn một cái nhưng nó không giống như chúng ta ở đó.

Tại sao các khẳng định bị vô hiệu hóa trong phát hành? Các xác nhận được tạo ra ở độ cao của tuổi của mã giàn giáo. Mã chúng tôi phải xóa vì biết rằng chúng tôi không muốn nó trong sản xuất nhưng biết rằng chúng tôi muốn chạy trong phát triển để giúp chúng tôi tìm ra lỗi. Các xác nhận là một thay thế sạch hơn cho if (DEBUG)mẫu cho phép chúng tôi để lại mã nhưng vô hiệu hóa nó. Điều này là trước khi thử nghiệm đơn vị diễn ra như là cách chính để tách mã thử nghiệm khỏi mã sản xuất. Các xác nhận vẫn được sử dụng cho đến ngày nay, ngay cả bởi những người kiểm tra đơn vị chuyên gia, cả hai để làm cho kỳ vọng rõ ràng và bao gồm các trường hợp mà họ vẫn làm tốt hơn so với thử nghiệm đơn vị.

Tại sao không để lại mã gỡ lỗi trong sản xuất? Bởi vì mã sản xuất cần không gây rắc rối cho công ty, không định dạng ổ cứng, không làm hỏng cơ sở dữ liệu và không gửi email đe dọa tổng thống. Nói tóm lại, thật tuyệt khi có thể viết mã gỡ lỗi ở một nơi an toàn mà bạn không phải lo lắng về điều đó.


2
Tôi đoán câu hỏi của tôi là: Tại sao phải xóa mã này trước khi phát hành? Việc kiểm tra không làm giảm hiệu suất và nếu chúng thất bại, chắc chắn có một vấn đề mà tôi muốn thông báo lỗi trực tiếp hơn. Lợi thế gì khi sử dụng một ngoại lệ thay vì một lời khẳng định cung cấp cho tôi? Tôi có lẽ sẽ không muốn mã không liên quan để có thể mã được catch(...).
Không ai vào

2
@Nobody Ưu điểm lớn nhất của việc không sử dụng các xác nhận cho các nhu cầu không khẳng định là không gây nhầm lẫn cho độc giả của bạn. Nếu bạn không muốn mã bị vô hiệu hóa thì đừng sử dụng thành ngữ báo hiệu rằng nó sẽ như vậy. Điều đó cũng tệ như việc sử dụng if (DEBUG)để kiểm soát một cái gì đó ngoài mã gỡ lỗi. Tối ưu hóa vi mô có thể có nghĩa là không có gì trong trường hợp của bạn. Nhưng thành ngữ không nên bị lật đổ chỉ vì bạn không cần nó.
candied_orange

Tôi nghĩ rằng tôi đã không làm cho ý định của tôi đủ rõ ràng. Tôi không nói về việc thực hiện cụ thể assertnhưng khái niệm về một khẳng định. Tôi không muốn lạm dụng asserthoặc gây nhầm lẫn cho độc giả. Tôi muốn hỏi tại sao nó lại theo cách này ngay từ đầu. Tại sao không có thêm release_assert? Có cần không cho nó? Lý do đằng sau khẳng định bị vô hiệu hóa trong phát hành là gì?
Không ai vào

2
You're asking for a good abstraction.Tôi không chắc chắn về điều đó. Tôi chủ yếu muốn giải quyết các vấn đề không thể phục hồi và điều đó không bao giờ xảy ra. Kết hợp điều này với Because production code needs to [...] not format the hard drive [...]và tôi sẽ nói trong trường hợp bất biến bị phá vỡ, tôi sẽ khẳng định sẽ phát hành trên UB bất cứ lúc nào.
Không ai vào

1
@Nobody "các vấn đề không thể phục hồi" có thể được giải quyết bằng cách ném ngoại lệ và không bắt chúng. Bạn có thể lưu ý rằng chúng không bao giờ nên xảy ra trong văn bản thông báo của ngoại lệ.
candied_orange

4

Các xác nhận là một công cụ sửa lỗi , không phải là một kỹ thuật lập trình phòng thủ. Nếu bạn muốn thực hiện xác nhận trong mọi trường hợp, thì hãy thực hiện xác nhận bằng cách viết một điều kiện - hoặc tạo macro của riêng bạn để giảm bản tóm tắt.


4

assertlà một hình thức của tài liệu, như ý kiến. Giống như nhận xét, thông thường bạn sẽ không gửi chúng cho khách hàng - chúng không thuộc về mã phát hành.

Nhưng vấn đề với các bình luận là chúng có thể trở nên lỗi thời, nhưng chúng vẫn bị bỏ lại. Đó là lý do tại sao các xác nhận là tốt - chúng được kiểm tra trong chế độ gỡ lỗi. Khi khẳng định trở nên lỗi thời, bạn nhanh chóng khám phá ra nó, và vẫn sẽ biết cách khắc phục khẳng định đó. Nhận xét đó đã trở nên lỗi thời 3 năm trước? Ai cũng đoán được.


1
Vì vậy, hủy bỏ một bất biến thất bại trước khi một cái gì đó xảy ra là không có trường hợp sử dụng hợp lệ?
Không ai vào

3

Nếu bạn không muốn tắt "xác nhận", vui lòng viết một hàm đơn giản có tác dụng tương tự:

void fail_if(bool b) {if(!b) std::abort();}

Đó là, assertlà dành cho các bài kiểm tra nơi bạn làm muốn họ ra đi trong sản phẩm xuất xưởng. Nếu bạn muốn kiểm tra đó là một phần của hành vi được xác định của chương trình, assertlà công cụ sai.


1
Tôi nhận thức rõ về điều này. Câu hỏi là nhiều hơn về lý do tại sao mặc định là như vậy.
Không ai vào

3

Thật vô nghĩa khi tranh luận những gì khẳng định nên làm, nó làm những gì nó làm. Nếu bạn muốn một cái gì đó khác nhau, viết chức năng của riêng bạn. Ví dụ: tôi có Assert dừng trong trình gỡ lỗi hoặc không làm gì cả

Đối với bất kỳ vấn đề bất ngờ nào, bạn cần quyết định đâu là cách tốt nhất để cả nhà phát triển và người dùng xử lý.


Đó không phải là ý định của tôi để tranh luận về những gì khẳng định nên làm. Động lực của tôi cho câu hỏi này là tìm ra lý do đằng sau khẳng định không có trong bản phát hành vì tôi sắp tự viết và muốn thu thập thông tin về các trường hợp sử dụng, kỳ vọng và bẫy.
Không ai vào

Hừm, sẽ không phải verify(cond)là cách bình thường để khẳng định và trả lại kết quả chứ?
Ded repeatator

1
Tôi đang viết mã bằng Ruby, không phải C, nhưng tôi không đồng ý với hầu hết các câu trả lời ở trên, vì việc dừng chương trình bằng cách in một backtrace hoạt động tốt đối với tôi như là một kỹ thuật lập trình phòng thủ ....... Nhận xét của tôi không phù hợp với kích thước bình luận tối đa trong các ký tự vì vậy tôi đã viết một câu trả lời dưới đây.
Nakilon

2

Như những người khác đã chỉ ra, đây assertlà loại pháo đài cuối cùng của bạn để chống lại những sai lầm của lập trình viên không bao giờ xảy ra. Họ kiểm tra sự tỉnh táo mà hy vọng sẽ không thất bại trái và phải vào thời điểm bạn giao hàng.

Nó cũng được thiết kế để được bỏ qua khỏi các bản phát hành ổn định, vì bất kỳ lý do gì các nhà phát triển có thể thấy hữu ích: tính thẩm mỹ, hiệu suất, bất cứ điều gì họ muốn. Đó là một phần của những gì tách biệt bản dựng gỡ lỗi khỏi bản dựng phát hành và theo định nghĩa, bản dựng bản phát hành không có các xác nhận như vậy. Vì vậy, có một sự lật đổ của thiết kế nếu bạn muốn phát hành "bản phát hành phát hành tương tự với các xác nhận tại chỗ" , đó sẽ là một nỗ lực trong bản dựng phát hành với _DEBUGđịnh nghĩa tiền xử lý và không NDEBUGđược xác định; nó không thực sự là một bản phát hành nữa.

Thiết kế mở rộng thậm chí vào thư viện tiêu chuẩn. Là một ví dụ rất cơ bản trong số rất nhiều, rất nhiều triển khai std::vector::operator[]sẽ assertkiểm tra sự tỉnh táo để đảm bảo bạn không kiểm tra véc tơ ngoài giới hạn. Và thư viện chuẩn sẽ bắt đầu thực hiện nhiều, tệ hơn nhiều nếu bạn kích hoạt các kiểm tra như vậy trong bản dựng phát hành. Điểm chuẩn của vectorviệc sử dụngoperator[]và một ctor điền với các xác nhận như vậy được bao gồm trong một mảng động cũ đơn giản sẽ thường cho thấy mảng động nhanh hơn đáng kể cho đến khi bạn vô hiệu hóa các kiểm tra đó, vì vậy chúng thường thực hiện tác động theo những cách rất xa. Kiểm tra con trỏ null ở đây và kiểm tra giới hạn thực sự có thể trở thành một chi phí rất lớn nếu việc kiểm tra đó được áp dụng hàng triệu lần trên mỗi khung hình trong các vòng lặp quan trọng trước mã đơn giản như hủy bỏ một con trỏ thông minh hoặc truy cập vào một mảng.

Vì vậy, rất có thể bạn đang mong muốn một công cụ khác cho công việc và một công cụ không được thiết kế để được bỏ qua khỏi các bản dựng phát hành nếu bạn muốn các bản dựng phát hành thực hiện kiểm tra độ tỉnh táo như vậy trong các khu vực chính. Cá nhân tôi thấy hữu ích nhất là đăng nhập. Trong trường hợp đó, khi người dùng báo cáo lỗi, mọi thứ trở nên dễ dàng hơn rất nhiều nếu họ đính kèm nhật ký và dòng cuối cùng của nhật ký cho tôi một manh mối lớn về nơi xảy ra lỗi và lỗi có thể xảy ra. Sau đó, khi tái tạo các bước của họ trong bản dựng gỡ lỗi, tôi cũng có thể gặp phải một lỗi xác nhận và thất bại khẳng định đó tiếp tục cho tôi manh mối lớn để sắp xếp thời gian của tôi. Tuy nhiên, vì việc ghi nhật ký tương đối tốn kém, tôi không sử dụng nó để áp dụng kiểm tra độ tỉnh cực thấp như đảm bảo một mảng không bị truy cập ngoài giới hạn trong cấu trúc dữ liệu chung.

Cuối cùng, và phần nào đồng ý với bạn, tôi có thể thấy một trường hợp hợp lý khi bạn thực sự muốn trao cho người kiểm tra một cái gì đó giống như bản dựng gỡ lỗi trong quá trình kiểm tra alpha, ví dụ, với một nhóm nhỏ người kiểm tra alpha, người đã nói, đã ký một NDA . Ở đó, nó có thể hợp lý hóa việc kiểm tra alpha nếu bạn trao cho người kiểm tra của mình một thứ gì đó ngoài bản dựng phát hành đầy đủ với một số thông tin gỡ lỗi được đính kèm cùng với một số tính năng gỡ lỗi / phát triển như kiểm tra mà họ có thể chạy và đầu ra dài hơn trong khi họ chạy phần mềm. Ít nhất tôi đã thấy một số công ty game lớn làm những điều như vậy cho alpha. Nhưng đó là cho một cái gì đó như alpha hoặc thử nghiệm nội bộ, nơi bạn thực sự đang cố gắng cung cấp cho những người thử nghiệm thứ gì đó ngoài bản dựng phát hành. Nếu bạn thực sự đang cố gắng gửi một bản phát hành, thì theo định nghĩa, nó không nên có_DEBUG được định nghĩa hoặc cách khác thực sự gây nhầm lẫn giữa sự khác biệt giữa bản dựng "gỡ lỗi" và "phát hành".

Tại sao mã này phải được gỡ bỏ trước khi phát hành? Việc kiểm tra không làm giảm hiệu suất và nếu chúng thất bại, chắc chắn có một vấn đề mà tôi muốn thông báo lỗi trực tiếp hơn.

Như đã chỉ ra ở trên, các kiểm tra không nhất thiết là tầm thường từ quan điểm hiệu suất. Nhiều người có thể tầm thường nhưng một lần nữa, ngay cả lib tiêu chuẩn cũng sử dụng chúng và nó có thể ảnh hưởng đến hiệu suất theo nhiều cách không thể chấp nhận được đối với nhiều người trong nhiều trường hợp nếu, giả sử truy cập ngẫu nhiên std::vectormất 4 lần thời gian được cho là bản dựng phát hành được tối ưu hóa bởi vì giới hạn của nó kiểm tra mà không bao giờ được coi là thất bại.

Trong một nhóm trước đây, chúng tôi thực sự phải làm cho thư viện vectơ và ma trận của chúng tôi loại trừ một số khẳng định trong các đường dẫn quan trọng nhất định chỉ để làm cho các bản dựng gỡ lỗi chạy nhanh hơn, bởi vì các xác nhận đó đã làm chậm các hoạt động toán học theo một mức độ lớn đến mức độ của nó bắt đầu yêu cầu chúng tôi đợi 15 phút trước khi chúng tôi có thể theo dõi mã quan tâm. Các đồng nghiệp của tôi thực sự muốn loại bỏassertshoàn toàn bởi vì họ thấy rằng chỉ cần làm điều đó đã tạo ra một sự khác biệt lớn. Thay vào đó, chúng tôi giải quyết chỉ làm cho các đường gỡ lỗi quan trọng tránh chúng. Khi chúng tôi thực hiện các đường dẫn quan trọng đó, sử dụng trực tiếp dữ liệu vectơ / ma trận mà không cần kiểm tra giới hạn, thời gian cần thiết để thực hiện toàn bộ hoạt động (bao gồm nhiều hơn chỉ là toán học vectơ / ma trận) giảm từ vài phút xuống còn vài giây. Vì vậy, đó là một trường hợp cực đoan nhưng chắc chắn các khẳng định không phải lúc nào cũng không đáng kể từ quan điểm hiệu suất, thậm chí không gần gũi.

Nhưng đó cũng chỉ là cách assertsđược thiết kế. Nếu chúng không có tác động hiệu suất lớn như vậy trên bảng, thì tôi có thể thích nó nếu chúng được thiết kế nhiều hơn tính năng xây dựng gỡ lỗi hoặc chúng tôi có thể sử dụng vector::atbao gồm các giới hạn kiểm tra ngay cả trong các bản dựng phát hành và ném ra ngoài giới hạn truy cập, ví dụ (nhưng với một hiệu suất rất lớn). Nhưng hiện tại tôi thấy thiết kế của chúng hữu ích hơn rất nhiều, do ảnh hưởng hiệu năng rất lớn của chúng trong các trường hợp của tôi, như là một tính năng chỉ gỡ lỗi được xây dựng được bỏ qua khi NDEBUGđược xác định. Đối với các trường hợp tôi đã làm việc với ít nhất, nó tạo ra một sự khác biệt lớn cho bản dựng phát hành để loại trừ kiểm tra độ tỉnh táo mà không bao giờ thực sự thất bại ở nơi đầu tiên.

vector::at so với vector::operator[]

Tôi nghĩ rằng sự khác biệt của hai phương pháp này là cốt lõi của phương pháp này cũng như phương án thay thế: ngoại lệ. vector::operator[]việc triển khai thường assertđể đảm bảo rằng việc truy cập ngoài giới hạn sẽ gây ra lỗi dễ tái tạo khi cố gắng truy cập vào một vectơ ngoài giới hạn. Nhưng những người triển khai thư viện làm điều này với giả định rằng nó sẽ không tốn một xu trong một bản dựng phát hành được tối ưu hóa.

Trong khi đó, vector::atđược cung cấp luôn kiểm tra giới hạn và ném ngay cả trong các bản dựng phát hành, nhưng nó có một hình phạt hiệu năng đến mức tôi thường thấy sử dụng nhiều mã vector::operator[]hơn vector::at. Rất nhiều thiết kế của C ++ lặp lại ý tưởng "trả tiền cho những gì bạn sử dụng / cần" và rất nhiều người thường ủng hộ operator[], thậm chí không bận tâm đến các giới hạn kiểm tra trong các bản dựng phát hành, dựa trên khái niệm rằng họ không tặng Không cần giới hạn kiểm tra trong các bản dựng phát hành được tối ưu hóa của họ. Đột nhiên, nếu các xác nhận được kích hoạt trong các bản dựng phát hành, hiệu suất của hai loại này sẽ giống hệt nhau và việc sử dụng vectơ sẽ luôn chậm hơn một mảng động. Vì vậy, một phần rất lớn của thiết kế và lợi ích của các xác nhận dựa trên ý tưởng rằng chúng trở nên miễn phí trong bản dựng phát hành.

release_assert

Điều này thật thú vị sau khi khám phá những ý định này. Tất nhiên các trường hợp sử dụng của mọi người sẽ khác nhau, nhưng tôi nghĩ rằng tôi sẽ tìm thấy một số sử dụng cho release_assertviệc kiểm tra và sẽ làm sập phần mềm hiển thị số dòng và thông báo lỗi ngay cả trong các bản dựng phát hành.

Nó sẽ là một số trường hợp mơ hồ trong trường hợp của tôi khi tôi không muốn phần mềm khôi phục một cách duyên dáng như nếu một ngoại lệ được ném ra. Tôi muốn nó bị sập ngay cả khi phát hành trong những trường hợp đó để người dùng có thể được cung cấp số dòng để báo cáo khi phần mềm gặp phải điều gì đó không bao giờ xảy ra, vẫn trong lĩnh vực kiểm tra sự tỉnh táo cho các lỗi lập trình viên, không phải lỗi đầu vào bên ngoài như ngoại lệ, nhưng đủ rẻ để được thực hiện mà không phải lo lắng về chi phí phát hành.

Thực tế, có một số trường hợp tôi sẽ tìm thấy một sự cố nghiêm trọng với số dòng và thông báo lỗi thích hợp hơn để phục hồi một cách duyên dáng từ một ngoại lệ bị ném có thể đủ rẻ để giữ bản phát hành. Và có một số trường hợp không thể khôi phục từ một ngoại lệ, như lỗi gặp phải khi cố gắng khôi phục từ một lỗi hiện có. Ở đó tôi sẽ tìm thấy một sự phù hợp hoàn hảo cho một cách release_assert(!"This should never, ever happen! The software failed to fail!");tự nhiên và sẽ rẻ mạt vì việc kiểm tra sẽ được thực hiện bên trong một con đường đặc biệt ở nơi đầu tiên và sẽ không tốn bất cứ điều gì trong các đường dẫn thực thi thông thường.


Ý định của tôi là không kích hoạt các xác nhận trong mã của bên thứ ba, xem phần chỉnh sửa làm rõ. Trích dẫn từ nhận xét của tôi bỏ qua bối cảnh từ câu hỏi của tôi: Additionally, the performance argument for me only counts when it is a measurable problem.Vì vậy, ý tưởng là quyết định theo từng trường hợp khi đưa ra một khẳng định ra khỏi bản phát hành.
Không ai vào

Một trong những vấn đề với nhận xét cuối cùng là thư viện tiêu chuẩn sử dụng cùng một assertthứ phụ thuộc vào cùng một _DEBUG/NDEBUGbộ xử lý tiền xử lý giống như những gì bạn sử dụng khi bạn sử dụng assertmacro. Vì vậy, không có cách nào để chọn lọc các xác nhận chỉ cho một đoạn mã mà không kích hoạt nó cho lib chuẩn, ví dụ, giả sử nó sử dụng lib chuẩn.

Có STL iterator kiểm tra mà bạn có thể vô hiệu hóa riêng biệt mà vô hiệu hóa một số xác nhận, nhưng không phải tất cả.

Về cơ bản, nó là một công cụ thô, không phải dạng hạt và luôn có ý định thiết kế bị vô hiệu hóa khi phát hành, vì vậy các đồng đội của bạn có thể sử dụng nó trong các khu vực quan trọng chống lại giả định rằng nó sẽ không ảnh hưởng đến hiệu suất phát hành, thư viện tiêu chuẩn sử dụng nó trong các khu vực quan trọng, các lib của bên thứ ba khác sử dụng nó theo cách đó, v.v. Và khi bạn muốn một cái gì đó, bạn có thể vô hiệu hóa / kích hoạt cụ thể hơn một đoạn mã chứ không phải cái khác, chúng tôi sẽ quay lại nhìn vào thứ khác assert.

vector::operator[]vector::at, ví dụ, trên thực tế sẽ có cùng hiệu suất nếu các xác nhận được kích hoạt trong các bản dựng phát hành và sẽ không còn phải sử dụng operator[]nữa từ quan điểm hiệu suất vì dù sao nó cũng sẽ kiểm tra giới hạn.

2

Tôi đang mã hóa trong Ruby, không phải C / C ++, và vì vậy tôi sẽ không nói về sự khác biệt giữa các khẳng định và ngoại lệ nhưng tôi muốn nói về nó như một thứ ngăn chặn thời gian chạy . Tôi không đồng ý với hầu hết các câu trả lời ở trên, vì việc dừng chương trình bằng cách in một backtrace hoạt động tốt đối với tôi như là một kỹ thuật lập trình phòng thủ.
Nếu có một cách để xác nhận thói quen (hoàn toàn bất kể nó được viết theo cú pháp như thế nào và nếu từ "khẳng định" được sử dụng hoặc tồn tại trong ngôn ngữ lập trình hoặc dsl) thì có nghĩa là một số công việc nên được thực hiện và sản phẩm ngay lập tức quay trở lại từ "phát hành, sẵn sàng sử dụng" thành "cần vá" - bây giờ hoặc viết lại nó để xử lý ngoại lệ thực sự hoặc sửa một lỗi khiến dữ liệu sai xuất hiện.

Ý tôi là Assert không phải là thứ mà bạn nên sống và gọi thường xuyên - đó là tín hiệu dừng cho thấy rằng bạn nên làm điều đó để khiến nó không bao giờ xảy ra nữa. Và nói rằng "bản dựng phát hành không nên có xác nhận" cũng giống như nói "bản phát hành bản dựng không nên có lỗi" - anh bạn, gần như không thể, giải quyết nó.
Hoặc suy nghĩ về chúng như là "thất bại trong việc thực hiện và điều hành bởi người dùng cuối". Bạn không thể dự đoán tất cả những điều người dùng sẽ làm với chương trình của bạn nhưng nếu quá nghiêm trọng thì nó sẽ dừng lại - điều này tương tự như cách bạn xây dựng các đường ống xây dựng - bạn dừng quá trình và không xuất bản, bạn có làm không ? Khẳng định buộc người dùng dừng lại, báo cáo và chờ đợi sự giúp đỡ của bạn.

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.