Như những người khác đã chỉ ra, đây assert
là 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ẽ assert
kiể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 vector
việ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::vector
mấ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ỏasserts
hoà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::at
bao 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_assert
việ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.