Extending the assert () macro to implement minimal error handling

    “Sire, I invented protection from the dragon.” He is not afraid of us anymore! It is triggered by the flapping of the wings of the dragon and includes a loud siren, so that everyone can hear that the dragon is approaching.
    “Does this defense do anything else?”
    - No, why? We will be warned!
    - Yes ... Eaten by the siren howl ... And yet ... remind me when we have planned outages of electricity? ...

    Description of the problem

    This method does not claim to the concept of error handling in complex and complex projects. Rather, it is an example of what can be done with minimal resources.

    A good rule is to assume that no assert () should work during the execution of a program. And if at least one assert () worked when testing the application, then you need to send this error to the developer. But what if the application is not fully tested? And assert () will work for the client? Send error to developer? Abort program execution? In reality, this will be a release version of the application and the standard assert () will simply be disabled. There is also a question about the internal contradiction of the system: assert () should be a lot, which would be easier to detect errors, but assert () should be less to interrupt the user and his work with the application less. I would not particularly like to “fall” if it depends on the stability of work

    Such reflections make it necessary to modify assert () c / c ++. And define your macros that extend the functionality of the standard assert () - and by adding minimal error handling. Let such macros be.

    VERIFY_EXIT (Condition);
    VERIFY_RETURN (Condition, ReturnValue);
    VERIFY_THROW (Condition, Exception);
    VERIFY_DO (Condition) {/ * fail block * /};

    (These macros can be called differently. For example, VERIFY_OR_EXIT (), VERIFY_OR_RETURN (), VERIFY_OR_THROW (), VERIFY_OR_DO (). Or vice versa in a more abbreviated version.)

    These macros, firstly, have an implementation for both the debug compilation version and the release version. That allows them to have a behavior in the release version of the program. Those. perform actions not only during testing, but also with the user.

    Macro Description

    (The description of macros is approximate, their other design is also possible.)

    1) VERIFY_EXIT (Condition);

    Checks the Condition Condition and if it is false, it calls the standard assert () (debug version), and also leaves the current function (debug and release version).

    2) VERIFY_RETURN (Condition, ReturnValue);

    Checks the Condition Condition and if it is false, it calls the standard assert () (debug version), and also returns from the current function returning the value ReturnValue (debug and release version).

    3) VERIFY_THROW (Condition, Exception);

    Checks Conditionand if it is false, it calls the standard assert () (debug version), and also throws an Exception exception (debug and release versions).

    4) VERIFY_DO (Condition) {/ * fail block * /};

    Checks the Condition Condition and if it is false, it calls the standard assert () (debug version), and also performs a block of operations ( fail block ) or an operation immediately following the macro (debug and release versions).

    For all macros it is important:

    • In all cases, Condition must be true for the macro to “pass through” and false to activate the minimum error handling path.
    • Each of the macros implements some minimal error handling method. This is necessary to implement the behavior in case of errors that were not detected during testing, but occurred to the user. Depending on the implementation, you can tell the developer about the error that occurred with the client, but also each implementation provides a minimal way to recover from an error.

    Macro patterns

    Of course, the most interesting, entropy supermen (heroes of reducing errors in programs), is the use of these macros.

    1) Pre and post conditions.

    The first use is pre and post conditions. Let me remind you that the pre conditions check the state of the program (input arguments, the state of the object, the variables used) for compliance with the necessary requirements of the executed code fragment. Post conditions (they are less common in programs) are designed to verify that we have achieved the desired result and the state of the objects remain valid for the current code fragment.

    The use of the proposed straight line macros - we register each check in a separate macro. We select macros based on what kind of error handling we need. (VERIFY_EXIT () - error handling with exit from this function, VERIFY_RETURN () - error handling with returning some value, VERRIFY_THROW () - error handling with exception generation, etc.)

    You can also add or use the VERIFY () macro, which will not make any error handling. This can be useful, for example, in post conditions at the end of a function.

    These macros are completely self-sufficient, if you use the principles of clean code and allocate a sufficient number of functions to implement atomic actions. Each function can check the state of an object, input arguments, etc. to perform your atomic action.

    2) Transaction semantics.

    Also, these macros can be used to implement code with transaction semantics. Such semantics is understood as: 1) gradual preparation for the operation with verification of the results of each of the preparation stages; 2) the implementation of the action only if all the stages of preparation were successful; 3) refusal to fulfill, if some conditions are not met at the preparation stage (with possible rollback from performance).

    3) Designing the code with a possible extension.

    This is especially true for libraries and general code, which may initially be developed within one context of execution conditions, and later may begin to be used with other conditions (start to use differently). In this case, these macros can describe the "boundaries" of the functionality of the code. Determine what was initially viewed as an error, and what was a successful implementation. (This approach is close to the classic pre post conditions.) Of course, I write “boundaries” in quotes, because These boundaries can be revised, but it is important to determine (or rather to pass on to future developers) knowledge of the permissible boundaries of code design.

    Implementation of macros

    I suppose that the majority of developers already have an average level that the implementation of these macros will not cause problems. But if you need information, then I will give some important points.

    Macros must be representable as a single statement. What can be done with the help of do {} while (false) or similar constructs. For example:

    #define VERFY_EXIT(cond)	\
    do{bool _= (bool)(cond); assert(_); if(!_) {return;}} while(false)	\
    /*end macro VERIFY_EXIT()*/

    Then you can write the following code:

    if(a > 0) VERIFY_EXIT(a%2==0);

    Of course, this is only one of the possibilities of implementation. You can also implement macros in other ways.

    PS Successful battle with entropy, supermen!

    Also popular now: