%Assert() your way to sleep-filled nights: A one line data validation macro

From sasCommunity
Jump to: navigation, search
Quentin McMullen
Siemens Healthcare Diagnostics Inc., Norwood MA

Abstract

Data are messy, and should never be trusted. This paper presents a simple SAS® macro, %Assert, which allows the user to state a Boolean expression that is expected to be true, e.g.: %assert(Age>0). If the expression is false, an error message is printed to the log. The macro may be used to verify expectations about data at any point in a DATA step. Use of the %Assert macro provides two important benefits: it provides a method for automated error detection, enhancing confidence that results are correct; and it allows the programmer to explicitly state expectations regarding the data being processed, enhancing the readability of code. Assertions are a critical tool for both defensive coding and test-driven development (Wright, 2006). Principles of macro design encountered during the development of the macro are addressed.

%ASSERT macro

%macro assert(assertion);
  if NOT (&assertion) then
      putlog "ERROR: Assertion (%superq(assertion)) is FALSE.";
%mend assert;

Enhanced %ASSERT

While the one-line version of the macro is useful, the functionality can be expanded through the addition of parameters. Below is an enhanced version of the macro, followed by comments.

%macro Assert
        (assertion  /*(Req) Expression being asserted                */
        ,msg=       /*(Opt) Message to put to log if false           */
        ,msglevel=1 /*(Opt) Message level, 1=error, 2= warn, 3=note  */
        ,errors=10  /*(Opt) Max number of messages to put to log     */
        ,errorflag= /*(Opt) Name of variable to create, flagging
                            records where assertion is false         */
        ,force=0    /*(Opt) Boolean: force assertion to run, even if
                            the global parameter &debug is set to 0  */
          );
 
    %* if user set global program parameter debug=0 (off),
    and assertion is not forced on by local parameter force=1,
    skip assertion;
    %if %symglobl(debug) %then %do;
        %global debug; %*Boolean: 1=debug mode on (assertions run);
        %if &debug=0 and &force=0 %then %goto mexit;
    %end;
 
    %*set flag for false assertion to 0;
    %if %superq(errorflag) ne %str() %then %do;
        if &errorflag=. then &errorflag=0;
    %end;
 
    if NOT (&assertion) then do;
        %*only print as many messages as specified in &errors;
        if __msg&sysindex < &errors then do;
            putlog
              %if &msglevel=1 %then %do;
                "ERROR: "
              %end;
              %else %if &msglevel=2 %then %do;
                "WARNING: "
              %end;
              %else %if &msglevel=3 %then %do;
                "NOTE: "
              %end;
              %else %put ERROR: (%nrstr(%%)&sysmacroname) Bad 
                    parameter for msglevel=&msglevel;
 
              "Assertion (%superq(assertion)) is FALSE. " &msg
            ;
        __msg&sysindex ++1;
        if __msg&sysindex = &errors then putlog
           "NOTE: Max number of false assertions
            (%superq(assertion)) reached, no more messages.";
        end;
 
      %if %superq(errorflag) ne %str() %then %do;
        &errorflag=1; %*set flag for assertion false;
      %end;
    end;
 
    drop __:;
 
    %mexit:
%mend assert;

Online resources

You can download a .pdf of this paper here.

References