Good Programming Practice In Macro Development
Introduction We focus on recommended Good Programming Practices (GPP) that should be followed when developing SAS Macros and are meant to compliment the PHUSE Guidance on Good Programming Practice from Good Programming Practice in Health and Life Sciences and summarised in the PHUSE GPP Summary. The macro capability is offered in many computer programming languages. A SAS macro implements code generation, based on dynamic, data-driven, run-time conditions. The macro language variables, functions, and statements are interpreted by the macro processor during compilation. The text that is generated (resolved) by the macro language can then be interpreted as SAS code and run by the SAS compiler. A SAS macro is used where the underlying program is well understood, and certain parts need to be modified based on changes in run-time conditions. |
Importance of Good Programming Practices for Macros Following Good Programming Practices help to enhance the quality of a macro. An effective macro is one that is simple, readable and adaptable. If the macro is poorly designed, does not have a logical flow, is not adaptable, or not readable, it defeats the very purpose of being a macro. Because a macro is composed of SAS code and generates resolved SAS code based on the macro parameter settings, Good Programming Practice (GPP) for coding a SAS program applies equally to coding the SAS code within a macro and the SAS code that the macro generates, as discussed in PHUSE GPP and summarised in PHUSE GPP Summary. GPP for macro coding extends these concepts to the SAS macro language and the unique capabilities it brings to the SAS programming environment. Macros generally have a long use life and are often updated by different programmers who adopt them in their work. The scope of a macro may initially be small and grow over time as multiple programmers make updates to the macro. If GPP are not followed the programs often become overly complex, hard to maintain and hard to use. A few overarching guiding principles follow.
|
Gathering User Requirements Like any other program, a macro is built based on the requirements that triggered its need. Understanding the requirement clearly is the key to building a strong base for a macro. As a programmer, when you start to write a macro, you will straight away head for the %macro statement. This is not necessarily the best approach. The starting point is summarising the requirements. The scope and requirements of a macro must be thoroughly documented in order to facilitate easy execution of the macro, ensure the correct output is generated and assist the developers in understanding the reasoning and methodology so they can accurately develop or modify the program. It is extremely important to gather the user requirements accurately and completely, and document them clearly and concisely. Documenting clear requirements will reduce the chance of ambiguity and misunderstanding in the development stage of the program and will reduce rework. It’s important to identify and involve the correct users in the requirement phase, especially when much functionality is needed. The developer must collaborate closely with the users and communicate possible coding or technical challenges. User requirements are often collected in an indexed manner as described in Appendix I. It is helpful, in consolidating the requirements input, to store the requirements documentation in a shared space that all involved users and developers have write access to. Some of the user requirements may be copied to the program specification document and can also be used when drafting user-guides. |
Architecture/Design of a Macro Sharing a standard macro template that models a consistent layout and includes standard programming code for routine functions will help maintain consistency in design of all the macros in an organisation or department. See a sample in Appendix II.
%put ---------------------------------------------------------------; %put --- Start of %upcase (&sysmacroname) macro ; %put --- ; %put --- Macro parameter values ; %put --- input_dataset = &input_dataset ; %put --- output_dataset = &output_dataset ; %put ---------------------------------------------------------------;
%let omautolocdisplay=%sysfunc(getoption(mautolocdisplay)); %let omprint=%sysfunc(getoption(mprint)); filename mprint "directory_path/output_filename.sas" lrecl=2048; options nomautolocdisplay mfile mprint;
filename mprint clear; options nomfile &omautolocdisplay &omprint ;
|
Testing/Validating Macros It’s helpful to include a DEBUG parameter in macros, to enable one to switch the debugging options on and off. Strategically placed put statements should be added to write macro variable values and comments on the logical execution of the macro out to the SAS log when the debug option is on. Users don’t like to see a lot of unnecessary output in the SAS log and the debug output can be suppressed when debug is turned off. When the debug option is on the temporary datasets created by the macro should be retained so they can be used to debug. Debugging and testing are important steps to ascertain the macro is doing what it is supposed to. To achieve this, it’s important to perform adequate levels of testing on macros. Validating a macro requires more extensive testing than needed for a single use program. The most important premise is that all conditions and combination of conditions in the macro program code must be tested to assure quality. If code is included in a program and not executed in the macro test cases, it’s considered not validated. A few areas that may be overlooked follow. All possible valid parameter values should be tested. For example, a macro that generates a summary of exposure table may contain the following 3 parameters. |
Parameter | Description | Valid Values |
PARAMCDS | List of MARAMCDs to be included in the table | TRTDUR = Treatment Duration in Days DAYSONSM = Days on Study Medication |
TIME_UNIT | The unit to be used when displaying the time values. Assumes all input values are stored in day units. Default = 'D' | D = Days W = Weeks Y = Years |
All possible values of these parameters should be included in the test cases to assure all conditions in the program code are tested. Focusing on these 2 parameters, test cases should be included to test the 2 valid values of the PARAMCDS parameter and to test the 3 valid values for the TIME_UNIT parameter. An additional test should be included to assure the default value is appropriately applied to TIME_UNIT. All error checking code in the macro should be tested. Each condition that should be met to generate the error message should be forced and the generated error message reviewed for clarity. The cleanup section of the macro should also be validated to assure all temporary datasets and other artefacts generated by the macro are appropriately deleted and no SAS system options were altered. The macro debugging system options MPRINT, MLOGIC, SYMBOLGEN and MPRINTNEST, MLOGICNEST for nested macros are helpful when debugging, as are including strategically placed put statements throughout the macro when the DEBUG parameter mentioned above, is turned on. |
Documentation Macros must always be thoroughly documented so that users are able to run the macro and get the desired output as easily as possible and developers are able to understand the reasoning and methodology in order to correct or modify a macro. For macros that are not complex, information in the header and comments in the code is sufficient. In addition, for complex macros, there should be a separate document which is referenced in the header of the macro. It is suggested that companies maintain a location for documents relating to macros and that standard naming conventions be used for those documents. As a general principle all the documentation elements should be “easy-to-understand, easy-to-use and easy-to-find-important-things”, and descriptions should be short and precise. The macro programmer should adhere to the PHUSE Guidance on Good Programming Practice when commenting the code and strive to write the comments when programming is being performed. This will ensure that the initial thought behind the code is written down. After program development is complete, the programmer should again review the commented code to make sure it reflects any updates made in the code. If versioning is used for macro development, documentation must exist for each version. Outdated versions of the documentation should be archived at the same time as the macro. As stated in the GPP, dates and names of programmers should be included in the program header whenever updates or revisions are done. |
Contact Information Acknowledgements The primary contributors include Mark Foxwell, Ginger Redner (Merck & Co., Inc.), Eduan Cronjé, Dennis Gianneschi, Jesper Zeth and Ninan Luke. Additional contributors include Salaja Sirsalewala, Wendy Dobson, Geoff Long and Raghava Palamupati. Thank you, Eric Qi (Merck & Co., Inc.), for sharing program code to generate a resolved, executable program. Apologies to contributors/reviewers that we may have missed. Disclaimer The opinions expressed in this document are those of the authors and should not be construed to represent the opinions of PHUSE members; respective companies/organisations or Regulator’s views or policies. The content in this document should not be interpreted as a data standard and/or information required by Regulatory Authorities. |
Appendix User Requirements Sample | |||
Request ID | Request date | Request information | Contact details |
001 | 01-jan-2020 | Macro should be able to calculate incidence rate of AEs | |
002 | 05-jan-2020 | It should have functionality to display the N (population count) and calculate the percentage based on subgroup analysis |
Sample Macro Design Template /****************************************************************************************** PROGRAM: <program name>.sas AUTHOR: xxxxxxx SAS VERSION: 9.4 DESCRIPTION: xxxxx
INPUT: OUTPUT: ASSUMPTIONS: CONSTRAINTS: DEPENDENCIES: VERSION HISTORY: Ver Author Date Description --- ------------ ----------- ------------------------------- 1.0 PARAMETER DESCRIPTION: Parameter Required? Default Description AAAA Yes xxxx BBBB No xxxx CCCC No xxxx DEBUG No xxxx switch on options mprint, mlogic, symbolgen ******************************************************************************************/ %******************************************************************************************; %** start macro; %******************************************************************************************; %macro <macro name> ( Aaaa = , bbbb = , cccc = , debug = ); %******************************************************************************************; %** Module 1 – Block 1 - <Description> %******************************************************************************************; %**----------------------------------------------------------------------------------------; %** Step 1; %**----------------------------------------------------------------------------------------; < … SAS Code … > %**----------------------------------------------------------------------------------------; %** Step 2; %**----------------------------------------------------------------------------------------; < … SAS Code … > %******************************************************************************************; %** Module 1 – Block 2 - <Description> %******************************************************************************************; %**----------------------------------------------------------------------------------------; %** Step 1; %**----------------------------------------------------------------------------------------; < … SAS Code … > %**----------------------------------------------------------------------------------------; %** Step 2; %**----------------------------------------------------------------------------------------; < … SAS Code … > %******************************************************************************************; %** Module 2 - <Description> %******************************************************************************************; %**----------------------------------------------------------------------------------------; %** Step 1; %**----------------------------------------------------------------------------------------; < … SAS Code … > %**----------------------------------------------------------------------------------------; %** Step 2; %**----------------------------------------------------------------------------------------; < … SAS Code … > %******************************************************************************************; %** Clean-up %******************************************************************************************; %**----------------------------------------------------------------------------------------; %** Delete work datasets; %**----------------------------------------------------------------------------------------; %mend <macro name>; The study level programmer is responsible for writing the macro and documenting it so that others can understand it. The programmer may want to suggest to the standards committee that the study level macro be made into a global macro. |