Assert is a special construct that allows you to test assumptions about the values of arbitrary data in an arbitrary place in the program. This design can automatically signal when incorrect data is detected, which usually leads to a program crash indicating the location of the incorrect data. Strange, at first glance, the design - can overwhelm the program at the most inopportune moment. What is the meaning of it?
Let's think about what will happen if during the execution of a program at some point in time some of these programs became incorrect and we didn’t “fill up” the program immediately, but continued its work, as if nothing had happened. The program can still work for a long time after that without any visible errors. And maybe at any time in the future, "fill up" itself for the reason known only to her. Or pump you a full hard drive content from gay porn sites. This is called
undefined behavior and, contrary to popular belief, it is not peculiar to programming languages with random access to memory (aka C, C ++). Because Assert terminates the program immediately after detecting incorrect data, it allows you to quickly localize and fix bugs in the program that led to incorrect data. This is its main purpose. Assertions are available in many programming languages, including java, c #, c, and python.
What kinds of asserts are there?Assert'y allow you to catch errors in programs at the stage of compilation or during execution. Checks at the compilation stage are not so important - in most cases they can be replaced by similar checks during program execution. In other words, assesses at compile time are nothing but syntactic sugar. Therefore, in the future, assertions will only mean checks during program execution.
Assertions can be divided into the following classes:- Check incoming arguments at the beginning of a function.
If an invalid value of an argument is found, it means that there may be bugs somewhere near the place where this function is called. Example:
// n. // n 0 10 . int factorial(int n) { // assert(n >= 0); // n 10, // , . assert(n <= 10); if (n < 2) { return 1; } return factorial(n - 1) * n; } // '' factorial() // 0 99. // // factorial() , // . // // , // , // . for (int i = 0; i < 100; ++i) { a[i] = factorial(i); }
What is
integer overflow .
What is
stack overflow .
It is important to understand that incoming function arguments may be implicit. For example, when a class method is called, a pointer to an object of this class (aka this and self) is implicitly passed to the function. The function can also access data declared in the global scope, or data from the scope of the lexical closure. These arguments are also desirable to check with assertions when entering a function.
If incorrect data is detected at this stage, the code of this function may contain bugs. Example:
int factorial(int n) { int result = 1; for (int i = 2; i <= n; ++i) { result *= i; }
')
What is
arithmetic with arbitrary precision .
The result of the function may be implicit. For example, a function may modify the data referenced (directly or indirectly) by the function's arguments. The function can also modify data from the global scope or from the scope of the lexical closure. The correctness of this data is desirable to check before exiting the function.
- Validation of data with which the function works, inside the function code.
If in the middle of the function incorrect data is detected, then the bugs may be somewhere in the area of this check. Example:
int factorial(int n) { int result = 1; while (n > 1) { // . // // , // n, . // // , ( // ), .. result, // , // ( ) // result . assert(result <= INT_MAX / n); result *= n; --n; } return result; }
When and where should asserts be used?The answer is simple - use assertions anytime and anywhere where they may seem a little useful. After all, they greatly simplify the localization of bugs in the code. Even checking the results of executing the obvious code can be useful during subsequent refactoring, after which the code may not become so obvious and a bug can easily creep into it. Do not be afraid that a large number of assertions will degrade the clarity of the code and slow down the execution of your program. Assertions are visually distinguished from the general code and carry important information about the assumptions on which this code works. Correctly placed assertions are able to replace most of the comments in the code. Most programming languages support disabling assertions either at compile time or during program execution, so that they have minimal impact on program performance. Typically, assertions are left enabled during program development and testing, but are disabled in release versions of programs. If the program is written in the best
traditions of OOP , or using the
enterprise methodology, then asserts can not be turned off altogether - performance is unlikely to change.
When can you do without assertions?It is clear that duplication of assertions through each line of code does not greatly improve the efficiency of catching bugs. There is no consensus about the optimal number of assertions, as well as about the optimal number of comments in the program. When I first learned about the existence of assertions, my programs began to contain 100,500 asserts, many of which duplicated each other many times. Over time, the number of assertions in my code began to decrease. The following rules allowed us to repeatedly reduce the number of assertions in my programs without a significant deterioration in the efficiency of catching bugs:
You can avoid duplicate checks on incoming arguments by placing them only in functions that directly work with this argument. Those. if the foo () function does not work with the argument, but only passes it to the bar () function, then you can omit the check of this argument in the foo () function, since it is duplicated by checking the argument in the bar () function.
You can omit assertions for unacceptable values, which are guaranteed to lead to the collapse of the program in close proximity to these assertions, i.e. if the crash of the program, you can quickly locate the bug. These assertions include checking the pointer to NULL before dereferencing it and checking for a zero value of the divider before dividing. Once again, such checks can be omitted only when the execution environment guarantees the collapse of the program in these cases.
It is possible that there are other ways to reduce the number of assertions without compromising the efficiency of catching bugs. If you are aware of these methods, share them in the comments to this post.
When can asserts not be used?Because Assertions can be deleted at compile time or at runtime; they should not change the behavior of the program. If the behavior of the program may change as a result of deleting an assert, then this is a clear sign of improper use of the assert. Thus, inside an assert, you cannot call functions that change the state of the program or the external environment of the program. For example, the following code incorrectly uses assertions:
// . // // 0, - : // - . // - mtx . // 1, . int acquire_mutex(mutex *mtx); // . // // 0, - // : // - . // - mtx . // 1, . int release_mutes(mutex *mtx); // , . assert(acquire_mutex(mtx)); // , "" . process_data(data_protected_by_mtx); // , . assert(release_mutes(mtx));
Obviously, the data may be unprotected with assertions disabled.
To correct this error, you need to save the result of executing the function in a temporary variable, and then use this variable inside the assert:
int is_success; is_success = acquire_mutex(mtx); assert(is_success); // assert'. process_data(data_protected_by_mtx); is_success = release_mutex(mtx); assert(is_success);
Because The main purpose of asserts is to catch bugs (aka programming errors), then they cannot replace the
processing of expected errors that are not programming errors. For example:
// buf_size , buf, // connection. // // 0 , . , // . // 1 . int write(connection *connection, const void *buf, size_t buf_size); int is_success = write(connection, buf, buf_size); // "", . assert(is_success);
If write () returns 0, this does not mean that there is a bug in our program. If the asserts in the program are disabled, the write error may go unnoticed, which can lead to sad results. Therefore, assert () is not suitable here. Here, the usual error handling is better. For example:
while (!write(connection, buf, buf_size)) { // . close_connection(connection); connection = create_connection(); }
I program in javascript. There are no assertions in it. What should I do?
In some programming languages, there is no explicit support for assertions. If desired, they can easily be implemented there by following the following “design pattern”:
function assert(condition) { if (!condition) { throw "Assertion failed! See stack trace for details"; } } assert(2 + 2 === 4); assert(2 + 2 === 5);
In general, assertions are usually implemented in various frameworks and libraries designed for automated testing. Sometimes they are called expect'ami there. There is much in common between automated testing and the use of assertions - both techniques are designed to quickly identify and fix bugs in programs. But, despite the common features, automated testing and assertions are not mutually exclusive, but most likely complementary to each other. Competently placed assertions simplify automated code testing, since the testing program can omit the checks duplicating assertions in the program code. Such checks usually constitute a significant proportion of all checks in a testing program.