Next, the previous article < <编程精粹--编写高质量c语言代码(2):自己设计并使用断言(一)> >, Continue to learn how to design and use assertions to make it easier and more effortless to automatically find errors in the program.
First, let's look at a simple compression and restoration program:
Byte * pbExpand (byte * pbFrom, byte * pbTo, size_t sizeFrom) {byte B, * bpEnd; size_t size; pbEnd = pbFrom + sizeFrom; while (pbFrom
0) * pbTo ++ = B;} the following code in else {* pbTo ++ = B;}/** is not commented out, I personally think there is a problem * // return pbTo;}/** this line of code does not exist in the original text */return pbTo ;}
The above is not a program in the original book. I personally feel that there is a problem with the program in the original book, so I made some minor changes. One of the key points of this program is that if the bReapeatCode is found in the input data, it considers the last two bytes to represent the repeated characters and the number of repetitions of the character. Follow the previous article < <编程精粹--编写高质量c语言代码(2):自己设计并使用断言(一)> > As mentioned above, in order to improve program robustness, we can use assertions to check the validity of function parameters. In addition, there are many other things that can be done, such as verifying the data in the buffer zone.
Think about it carefully. The decoding of the above program requires three bytes in total. Therefore, the compression program should not compress two consecutive characters. Of course, it is no good to compress three consecutive characters. Therefore, the compression program should only compress three or more consecutive characters. Another case is that if the raw data contains bReatCode, it must be specially processed. Otherwise, the decompression program will mistakenly assume that it is the start of a compressed character sequence. So when bReatCode appears in the raw data, repeat it again to distinguish it from the truly compressed character sequence.
So we can use assertions to test these two features:
ASSERT (size> = 4 | (size = 1 & B = Reaptcode ));
If the assertion fails, it indicates that the data pointed to by pbFrom is faulty or the compression program is faulty. So with assertions, we can not only check situations that are not syntactically impossible, but also use assertions to test errors that are logically impossible for the program.
Use assertions to check for situations that are impossible.
Let's take a look at another version of the character extraction program:
Byte * pbExpand (byte * pbFrom, byte * pbTo, size_t sizeFrom) {byte B, * bpEnd; size_t size; pbEnd = pbFrom + sizeFrom; while (pbFrom! = PbEnd) {B = * pbFrom ++ if (B = bRepeatCode) {/** store "size" B */B = * pbFrom ++ at the beginning of pbTo; size = (size_t) * pbFrom ++; /** check the validity of raw data */ASSERT (size> 3 | (size = 1 & B = bReaptCode); do * pbTo ++ = B; /** the original text is while (size --! = 0), I personally feel that there is a problem, because it will be changed to the following statement once multiple cycles */while (-- size! = 0);} the following code in else {* pbTo ++ = B;}/** is not commented out. I personally think there is a problem * // return pbTo ;} /** this line of code does not exist in the original text */return pbTo ;}
Although the functions of these two programs are the same, the first version uses error-proof programming. We can analyze that although the outer loop is unlikely to show that pbEnd will be larger than pbFrom, once it appears, the first version of the program will jump out of the outer loop and continue to run, the second version may crash. For the internal loop, once the size is 0, the first version of the program can exit the loop, but the second version cannot.
It seems that the first version is more reasonable and intelligent, but if pbFrom is added with pbEnd for some reason, the first version of the program can cause too much damage to the program, it will exit, and the second version of the program will attempt to decompress the content in the entire memory, causing the program to crash, the user will certainly find this error. Therefore, the actual situation is as follows: although error-proof programming is often hailed as having a better coding style, it hides errors. However, this does not mean that we should give up error-proof programming. We hope that errors will not be concealed during error-proof programming. So for the above program, we can use the error-proof program design as always, on the other hand, when things change slot, we can use assertions for alarm.
Byte * pbExpand (byte * pbFrom, byte * pbTo, size_t sizeFrom) {byte B, * bpEnd; size_t size; pbEnd = pbFrom + sizeFrom; while (pbFrom
0) * pbTo ++ = B;} the following code in else {* pbTo ++ = B;}/** is not commented out, I personally think there is a problem * // return pbTo;} ASSERT (pbFrom = pbEnd);/** this line of code does not exist in the original text */return pbTo ;}
ASSERT (bpFrom = pbEnd) is used to verify the normal termination of the function. Due to the adoption of the corresponding error-proof program design, the delivery version of the program can ensure that the user is not lost when there is a problem, and in the debugging version of the program, errors can still be reported. Therefore, before coding, you should ask yourself: "is an error concealed in the program during error-proof programming ?" If the answer is yes, you need to add assertions in the program to alert these errors.
Do not conceal errors when designing error-proof programs.
At the same time, when writing code, we should seize every opportunity to verify the results of the program. We should try to use different algorithms as much as possible, and make them more than just another implementation of the same algorithm. If different algorithms produce different results, the assertion is triggered. Different algorithms can be used to discover not only Algorithm Implementation errors, but also the possibility of discovering algorithm errors. Of course, this does not mean that each function has two versions. The correct method is to do this only for the key part of the program.
The program results should be validated using different algorithms.
Although the execution results of different algorithms can be used to confirm the program, we can find errors in the program. However, after all, the error cannot be found until the Algorithm Execution is complete. Sometimes the programmer should perform an initial check in the program, so that errors can be found as soon as possible, otherwise the errors will be hidden for a period of time.
Do not wait for an error to occur. Use the initial check program.
Summary:
1. Error-tolerant programming will conceal errors. When an error-proof code is performed, if the "impossible" situation does occur, use an asserted alarm.
2. Use different algorithms to confirm the program results. When different algorithms of the same problem have different results, the assertion is triggered.
3. Use the initial check program to detect errors in the program as soon as possible.
Finally, end this article with the author's sentence:
The tester's job is not just to test your program. It is your job to find out the errors in your program.