Capture untested return value

A previously translated article "Using Error Code Objects for C ++ Error Handling" mentioned that the author's inspiration came from another document "Capturing Untested Return Values", so I translated this article again as Contrast can be regarded as a series of articles.

Foreword

The function return value is usually used to indicate whether the function is executed without error. However, it is difficult to ensure that the caller uses this information properly (refers to the return value). Maybe some commercial tools can do the job, but you do n’t always get a purchase license, especially in small projects. You probably heard this saying: "I believe you, you will not make such a mistake."

The idea I put forward here was inspired by a mistake we made in the project a few weeks ago. It only appeared in a production environment, and it took several days to discover that it came from the failure of the initialization routine of a particular environment. In fact, the code that called this routine did not test its return code.

Add responsibility sign

In my experience (but not for a long time), I often see that function returns are grouped into an enumeration by type. As shown in Figure 1, the caller can more or less ignore such return values. In order to control what happens to the return values, I do not return these values ​​directly, but return an instance of the class ErrorCode. It contains two member variables: the value (enValue_) indicates the function error code, and the responsibility flag (PboResp_):

class ErrorCode
{
private:
   ErrorCodeValue enValue_;
   bool * PboResp_;
public:
   // some code
}

The purpose of the responsibility flag is to indicate whether you are responsible for the value of the ErrorCode object (refers to whether you need to check). When the ErrorCode object uses the copy constructor or assignment function to copy the object, the value (enValue_) is copied, and the responsibility contained in the responsibility flag PboResp_ needs to be "transferred". The so-called "transfer" refers to the copy operation to transfer responsibility from the source instance to the target instance. After copying, the source instance is no longer responsible for its content (that is, the caller no longer needs to check). (In fact, the semantics of the copy constructor and the assignment function are slightly different, but the general idea is the same. The details are explained below.)

Because the parameters of the copy constructor and assignment function use the const modifier, I chose to implement the responsibility flag PboResp_ as a pointer to a Boolean value instead of a Boolean value. This enables the copy function to modify the responsibility flag PboResp_ of the source instance passed in as a parameter. There is another one applicable to operator = constraint: if the responsibility flag PboResp_ of the parameter ErrorCode object is true, the previous value enValue_ will be lost and must be recorded (referring to the output log). (The transfer of responsibilities described here is similar to the copy that occurs on auto_ptr)

ErrorCode objects also use == and! = Operators. These operators are required when comparing with temporary ErrorCode objects returned from functions. These temporary ErrorCode objects have been constructed to indicate a specific error status (eg success). As you might expect, these operator functions compare the value enValue_ inside two ErrorCode objects. Moreover, the responsibility flag PboResp_ of these ErrorCode objects should also be set to false, so that there will be no "untested error code" log records. If necessary, other test operators (<,>) can also be implemented.

Finally, the destructor should check whether the instance is still responsible for the error code value (responsibility flag PboResp_ is true), and record it if this exists.

Integration with existing code

In my opinion, the success of this technology depends on whether it can be easily integrated into existing programs. Figure 1 shows a situation, which is a good starting point to demonstrate how to integrate.

First, in class definition, I use the name of the previously defined enumeration as the class name (ie the old enumeration name is ErrorCode, then the new class name is ErrorCode). Therefore, all functions that previously returned enumeration values ​​now become an error code object, as long as the program is recompiled. I also changed the enumeration name ErrorCode to ErrorCodeValue. To make this technology truly effective, you must also define a constructor with ErrorCodeValue as a parameter. This constructor will be implicitly called in two situations: first, the previous function returned ErrorCodeValue, not an object of ErrorCode class. Second, when the ErrorCode object and ErrorCodeValue are compared (via the operator == or! =). In this case, ErrorCodeValue will be used to construct a temporary ErrorCode object, which is used as the parameter of the comparison operator. As mentioned earlier, the comparison operator also "turns off" the responsibility flags of the two objects (responsibility flag PboResp_ is set to true).

Implement

Figure 2 shows the new implementation of error handling, which uses the ErrorCode class. The ErrorCode class is added to the existing file ErrorCodes.h. In addition, you also need to create a file ErrorCodes.cpp to implement the member functions of the ErrorCode class.

As mentioned above, copy constructors and assignment operators must transfer the responsibility of error codes to their target objects. But this is not enough, the function must always be ErrorCode object, which is responsible for its content. Therefore, the copy constructor and the constructor that accepts ErrorCodeValue set the responsibility flag PboResp_ to true when they construct the object (they do so unconditionally). This is the difference between the rf constructor and the assignment operator: the assignment operator copies the responsibility flag from the source object to the target instance, and the copy constructor must set the responsibility flag to true.

Finally, the default constructor is different from all other constructors because it initializes the responsibility flag to false. The ErrorCode object constructed by default does not represent an untested error code, it completely depends on the programmer who created it to decide how to deal with it.

The enhanced version of the ErrorCode class can add assertions to the destructor and assignment operator functions to intercept "error leaks" (that is, know which error codes are not checked) during development and testing.

The new error code class is implemented in the program in Figure 1, and the following message will be output on the standard error output after running:

Destruction of untested error code: 
value 1
Untested error code (value 0) erased by 
new value 2

Conclusion

I believe this coding technique is very helpful for detecting certain types of errors. Please note that this method does not eliminate any errors, just report untested return codes after the problem occurs. I believe it is easy to implement even in existing projects. At the beginning, you may get a log file that contains a lot of untested return codes. Therefore, in our project, we had to clean up the code a bit.

Welcome to pay attention to my public account [Brother Lin's programming notes], and welcome to appreciate, thank you!

Guess you like

Origin www.cnblogs.com/qinwanlin/p/12681178.html