Use Macros. Use Them Wisely.
Usually, macros are not welcomed by developers, and in many teams they are banned in the programming guidelines. Obviously, there are arguments that speak against macros. But in recent years, I learned to appreciate their positive features. I would not recommend a general macro ban in an ABAP development project. In this blog, I will explain why.
No doubt: Macros are no real modularization technique, like includes they are lacking an own execution context. And, yes, with the exception of extremely simple macros, they are difficult to debug (I will come to that later). They simply are source code transformations at a pre-compile stage. But they have a number of advantages.
The positive points that I see with macros are:
- They help to make code more readable.
- They help hide implementation details from the code’s main intentions.
- They help avoid code repetition.
A Sample Refactoring Session
Consider the following, typical example. It is a piece from our codebase (although 10 years old, if you accept this as excuse…).
FORM alv1_fieldcat_create CHANGING ct_fieldcat TYPE slis_t_fieldcat_alv.
DATA: ls_fieldcat TYPE slis_fieldcat_alv.
ls_fieldcat-fieldname = 'CHECKBOX'.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = 0.
APPEND ls_fieldcat TO ct_fieldcat.
CLEAR ls_fieldcat.
ls_fieldcat-fieldname = 'ABELN'.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = 1.
ls_fieldcat-hotspot = gc_x.
APPEND ls_fieldcat TO ct_fieldcat.
CLEAR ls_fieldcat.
ls_fieldcat-fieldname = 'BEZCH'.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = 2.
ls_fieldcat-hotspot = gc_x.
APPEND ls_fieldcat TO ct_fieldcat.
CLEAR ls_fieldcat.
ls_fieldcat-fieldname = 'STAT_FIELD'.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = 3.
APPEND ls_fieldcat TO ct_fieldcat.
CLEAR ls_fieldcat.
ls_fieldcat-fieldname = 'AUFAR'.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = 4.
APPEND ls_fieldcat TO ct_fieldcat.
CLEAR ls_fieldcat.
ls_fieldcat-fieldname = 'COUNT_POS'.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = 5.
APPEND ls_fieldcat TO ct_fieldcat.
CLEAR ls_fieldcat.
ls_fieldcat-fieldname = 'MESSAGE'.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = 6.
APPEND ls_fieldcat TO ct_fieldcat.
ENDFORM. " alv1_fieldcat_create
What is the intention of this code? A field catalog has to be filled: For each field, the column name and the position in the grid (col_pos) are specified. Two of the fields are designed as hotspot fields (hotspot = ‘X’).
The code repetition is annoying. It distracts the eye from the essential part of the code. Only the parts that are varying from block to block, are what is really essential in this code. The rest is inessential boilerplate code. It is waste of time to be forced to study this repetitive code line by line, when this routine has to be inspected in a support or enhancement session.
An obvious improvement would be to parametrize the code and to use a modularization unit – in this case, a subroutine:
form add_field using iv_fieldname type c
iv_col_pos type i
changing ct_fieldcat type slis_t_fieldcat_alv.
data: ls_fieldcat type slis_fieldcat_alv.
ls_fieldcat-fieldname = iv_fieldname.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = iv_col_pos.
append ls_fieldcat to ct_fieldcat.
endform. "add_field
form set_hotspot using iv_fieldname type c
changing ct_fieldcat type slis_t_fieldcat_alv.
data: ls_fieldcat type slis_fieldcat_alv.
ls_fieldcat-hotspot = 'X'.
modify ct_fieldcat from ls_fieldcat
transporting hotspot
where fieldname = iv_fieldname.
assert sy-subrc eq 0.
endform. "set_hotspot
Note the assertion in routine set_hotspot which makes the program crash if a non-existing fieldname has been typed by the developer as actual parameter. This tribute to the “crash early” principle helps avoid typographical errors.
With these routines available, ALV1_FIELDCAT_CREATE could be identically rewritten as follows:
form alv2_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.
if list_var = 3.
perform add_field using 'CHECKBOX' 0
changing ct_fieldcat.
endif.
perform add_field using 'ABELN' 1
changing ct_fieldcat.
perform add_field using 'BEZCH' 2
changing ct_fieldcat.
perform add_field using 'STAT_FIELD' 3
changing ct_fieldcat.
perform add_field using 'AUFAR' 4
changing ct_fieldcat.
perform add_field using 'COUNT_POS' 5
changing ct_fieldcat.
perform add_field using 'MESSAGE' 6
changing ct_fieldcat.
perform set_hotspot using 'ABELN'
changing ct_fieldcat.
perform set_hotspot using 'BEZCH'
changing ct_fieldcat.
endform. " alv2_fieldcat_create
With this change, we effectively saved 12 lines of code in comparison to the original version. Moreover, 14 lines of the new code, the two form routines, appear to be reusable in other programs as well. They could be extracted into a general-purpose subroutine pool for filling ALV field catalogs. In comparison, not a single line of the original code was reusable.
But the real win is that the routine alv2_fieldcat_create is now much better readable. Also, opening an extra stack level for each add_field call is no performance problem, since we are calling it only for a couple of fields (and not thousands of times – and even then: in the majority of the cases, not the call of a routine is a performance issue, but the code contained within that routine!).
Moreover, we do not need to explicitly initialize the workarea ls_fieldcat any more, since we have a little box with fresh, initialized data for each call. The need to expliticitly initialize something is bad, since it could be forgotten. In fact, I consider it as a code smell when I encounter a “CLEAR something” statement several times in the middle of a code block: The variable “something” obviously is reused for several purposes in the same code block. It is better to extract the code parts beginning with that “CLEAR something” statement into an own module (form routine, method, function module).
But I am still not happy with ALV2_FIELDCAT_CREATE. There is too much ado, too much “code noise” for such a simple task as adding some rows with field names to an internal table. The many “perform add_field” are still disturbing. I would like to employ the really useful “Colon/Comma” notation in order to write “perform add_field” only once.
Applying the “Colon/Comma” notation, gives the following:
form alv3_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.
if list_var = 3.
perform add_field using 'CHECKBOX' 0
changing ct_fieldcat.
endif.
perform add_field using :
'ABELN' 1
changing ct_fieldcat,
'BEZCH' 2
changing ct_fieldcat,
'STAT_FIELD' 3
changing ct_fieldcat,
'AUFAR' 4
changing ct_fieldcat,
'COUNT_POS' 5
changing ct_fieldcat,
'MESSAGE' 6
changing ct_fieldcat.
perform set_hotspot using :
'ABELN'
changing ct_fieldcat,
'BEZCH'
changing ct_fieldcat.
endform. " alv3_fieldcat_create
This is slightly better, but still contains unwanted repetition. It’s clear that we are working with ct_fieldcat. We don’t want our attention led every second line to this fact. On the other hand, by the syntax rules, a CHANGING parameter has to come at the end of a PERFORM call and therefore always is part of the statement’s “tail” which can’t be abbreviated away with the Colon/Comma syntax.
This is where macros come into play. We could use these two very short macros for encapsulating the perform calls:
define _add_field.
perform add_field using &1 &2 changing ct_fieldcat.
end-of-definition.
define _set_hotspot.
perform set_hotspot using &1 changing ct_fieldcat.
end-of-definition.
Note that these macros require the existence of certain named variables in the context where they are called: In this case, there needs to exist a variable with the name ct_fieldcat. This implies that these macros are not universally usable, but only for a specific context.
With these macros, the form routine now shrinks to its minimum size and almost maximum expressiveness:
form alv4_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.
if list_var = 3.
_add_field 'CHECKBOX' 0.
endif.
_add_field :
'ABELN' 1,
'BEZCH' 2,
'STAT_FIELD' 3,
'AUFAR' 4,
'COUNT_POS' 5,
'MESSAGE' 6.
_set_hotspot :
'ABELN',
'BEZCH'.
endform. " alv4_fieldcat_create
If we add the code lines for the macros, the savings in lines of code are not gigantic. But that is not the important part. What really matters is that the routine for filling the field catalog is now reduced to its intended parts: What you see written in ALV4_FIELDCAT_CREATE is precisely what the developer intended. Not more. The rest is code noise, banned behind the scenes.
If the assignment of the col_pos to the field is not used elsewhere (and why should it?), I could even think of a version which auto-generates the col_pos. Since the code itself has a sequence, it is not necessary to specify this sequence a second time with the col_pos parameter.
The routine add_field could look like this:
form add_field using iv_fieldname type c
changing ct_fieldcat type slis_t_fieldcat_alv.
data: ls_fieldcat type slis_fieldcat_alv.
ls_fieldcat-fieldname = iv_fieldname.
ls_fieldcat-tabname = gc_tab_head.
ls_fieldcat-col_pos = lines( ct_fieldcat ).
append ls_fieldcat to ct_fieldcat.
endform. "add_field
Correspondingly, the macro passes only one parameter:
define _add_field.
perform add_field using &1 changing ct_fieldcat.
end-of-definition.
And finally the explicit col_pos can be thrown out of the code:
form alv5_fieldcat_create using ct_fieldcat type slis_t_fieldcat_alv.
if list_var = 3.
_add_field
'CHECKBOX'.
endif.
_add_field :
'ABELN',
'BEZCH',
'STAT_FIELD',
'AUFAR',
'COUNT_POS',
'MESSAGE'.
_set_hotspot :
'ABELN',
'BEZCH'.
endform. " alv5_fieldcat_create
Summing up, we have refactored by using a combination of reusable subroutines and macros, taking advantage of both of these techniques:
- Subroutines (or methods or function modules) contribute by providing an own execution context, with auto-initialized local variables and clear parameters for import and export.
- Macros contribute by simplifying the notation of the call, improving readability of the client code (“client” in the sense of: being a caller of the subroutines).
All the three advantages I mentioned above are achieved:
- Nobody would deny that the final version alv5_fieldcat_create is better readable than the original.
- The implementation details (here the “boring” population of an internal table) has been extracted from the code.
- Instead of several almost-identical code segments, we are now reusing code, and only the variable parts are visible in the top-level code block.
Mapping Structures
Another examples for illustration: move-corresponding is a great statement for mapping between different structures, if you control the DDIC definition of both source and target structure. At the edge between two different code parts, however, this “move-by-name” rule will not suffice, and an explicit mapping of structure components is required. This produces noisy code (again, a real-life example with very old, but still productive code):
gs_kundendaten-customer = gs_kundenauftragsdaten-partn_numb.
gs_kundendaten-title_key = gs_kundenauftragsdaten-title_key.
gs_kundendaten-title_p = gs_kundenauftragsdaten-title.
gs_kundendaten-firstname = gs_kundenauftragsdaten-name_2.
gs_kundendaten-lastname = gs_kundenauftragsdaten-name.
* gs_kundendaten-secondname
gs_kundendaten-street = gs_kundenauftragsdaten-street.
* gs_kundendaten-house_no
gs_kundendaten-postl_cod1 = gs_kundenauftragsdaten-postl_code.
gs_kundendaten-city = gs_kundenauftragsdaten-city.
gs_kundendaten-tel1_numbr = gs_kundenauftragsdaten-telephone.
gs_kundendaten-tel2_numbr = gs_kundenauftragsdaten-telephone2.
* gs_kundendaten-tel3_numbr
gs_kundendaten-fax_number = gs_kundenauftragsdaten-fax_number.
* gs_kundendaten-e_mail
gs_kundendaten-langu_p = gs_kundenauftragsdaten-langu.
* gs_kundendaten-langu_descr
gs_kundendaten-country = gs_kundenauftragsdaten-country.
When I had to change another piece of the method which contained that code, I placed this little macro in the classes macro section:
define _move_from_to.
&2-&4 = &1-&3.
end-of-definition.
With this one-line macro, that code part could be rewritten in a more readable way [sorry for the screenshot, but I see no way to customize “jive” code sections with a monospaced font, as would be natural]:
As was the case in the original code, mistyped column names will be detected at compile-time – which is good. Macro-based code only looks like free text, but is equally strict concerning the syntax as any other ABAP code.
Declarative programming: A button states example
Suppose, there is a number of buttons requested for a new web application. The state of these buttons – whether they are invisible, inactive (i.e. visible but not clickable), or active, has been specified depending on the current transactional mode (create mode, change mode, display mode).
The change request might contain the following little table:
With macros, it is possible to take this table over into the coding as it is written in the spec!
For this, let us create a little dictionary: We need a data type for the fcode (the user command = the button he pressed), for the transactional mode (create, change or display), and for the button state (active, inactive, invisible).
Have in mind that such a dictionary will be used throughout the whole application, it is not defined only for this particular purpose. It is good style to define data types and structured constants for all the discrete variables appearing in the data model of the application.
When using code instead of DDIC for illustration, the types could look like this
types:
ty_fcode type c length 4, " User command
ty_state type x length 1, " Button state (active, inactive, invisible)
ty_mode type n length 1. " Mode (create, change, display)
To define the different values these types may have, we use structured constants. In real life, we would place these constants in a type pool or in a class section. Anyway, the declaration of the possible values of all these data will look like this:
constants:
begin of fcode,
actualize type ty_fcode value 'ACTU',
save type ty_fcode value 'SAVE',
undo type ty_fcode value 'UNDO',
reject type ty_fcode value 'CANC',
print type ty_fcode value 'PRNT',
end of fcode,
begin of mode,
create type ty_mode value '1',
change type ty_mode value '2',
display type ty_mode value '3',
end of mode,
begin of state,
active type ty_state value 0,
inactive type ty_state value 1,
invisible type ty_state value 2,
end of state.
Let’s say, we define a method which retrieves the button state, depending on transactional mode and button code:
methods get_state importing iv_fcode type ty_fcode
iv_mode type ty_mode
returning value(ev_state) type ty_state.
Then we can introduce the following macro:
define _set_button_state_per_mode.
if iv_fcode eq fcode-&1.
case iv_mode.
when mode-create.
ev_state = state-&2.
when mode-change.
ev_state = state-&3.
when mode-display.
ev_state = state-&4.
endcase.
endif.
end-of-definition.
This finally allows us to implement the method simply by copy-pasting the change request!
How is the change behaviour of this code when requirements will change?
There will hardly ever be a new transactional mode. Therefore, the macro definition will almost never be changed. But there may be new buttons in the future, and the given state definitions may be changed in forthcoming versions of the software. Therefore, what is most likely to be changed, is the implementation of the method get_state. In this form, with the ABAP code actually hidden behind the macro, the part that is most likely to be changed, can be changed with maximum ease, readability – and safety. There are no direct values like strings (where a typo would appear at runtime, not at compile-time): the symbols in use – like active or reject – need to be defined in the appropriate structured constant – otherwise the method will not compile.
Debugging
Since macros do not have an own execution context, they cannot be displayed properly in the debugger. All the statements listed in the macro will be shown and executed as only one statement in the debugger. It is not possible to single-step through the instructions of a macro.
This clearly is a problem – but only for highly complex macros which of course should be avoided. A rule of thumb: If you find out that your macro spans more than three statements, you might consider to extract these lines in a form routine or a private method, leaving only the call of this method in the macro.
Symbols and Literals
Since macros operate on a pre-compile level, they don’t distinguish literals and symbols. The actual parameters can be used for building symbols (like method names or variable names) as well as for character literals – or both at the same time.
Just for illustration, think of the following macro:
define _set_field.
ls_fieldcat-&2 = &3.
modify ct_fieldcat from ls_fieldcat transporting &2 where fieldname = '&1'.
assert sy-subrc eq 0.
end-of-definition.
It could be applied like this:
_set_field ABELN :
hotspot 'X',
scrext_m text-001.
Here, &2 and &3 are replaced by a part of a symbol name and a literal, respectively. When using a subroutine or method instead, the component &2 of a structure would be accessible only by dynamic programming – using a statement like ASSIGN COMPONENT … OF STRUCTURE … TO … – which is more complicated. Also, a typographical error in the macro would be detected immediately by the compiler (if there is no component of the given structure).
The first macro parameter is special. It is passed without quotation marks, but is used inside the macro to build a string (… where name = ‘&1’ ). This works, but assumes that the macro parameter is translated to upper case (which actually is granted by the ABAP compiler). Just to make this clear, one can normalize the case of the passed parameter first:
define _set_field.
ls_fieldcat-fieldname = &1.
translate ls_fieldcat-fieldname to upper case.
ls_fieldcat-&2 = &3.
modify ct_fieldcat from ls_fieldcat transporting &2 where fieldname = ls_fieldcat-name.
assert sy-subrc eq 0.
end-of-definition.
In this case it is clearly better to pass &1 as a literal:
define _set_field.
ls_fieldcat-&2 = &3.
modify ct_fieldcat from ls_fieldcat transporting &2 where fieldname = &1.
assert sy-subrc eq 0.
end-of-definition.
Macros as Building Blocks for Internal DSLs
Recently, my interest for macros was renewed, when I was reading Martin Fowler’s book on Domain Specific Languages (DSL).
What is a DSL? Here is a definition by Martin Fowler:
Domain-specific language (noun): a computer programming language of limited expressiveness focused on a particular domain.
Important points:
- He intentionally writes “computer language”, not “programming language”. Of course, there is logic and processing behind any DSL. But often, a DSL feels more declarative than imperative. The text processing system TeX and CSS are examples of DSLs with a declarative taste, while SQL, regular expressions or the language of makefiles are DSLs that feel more like a programming language. May this be as it is – at least, all DSLs have language features: They have a grammar, a dictionary and a set of allowed expressions.
- Important is the limited expressiveness of the language. You can’t use a DSL to solve any software problem. This is an important difference to programming languages like ABAP, Java and so on. With Turing complete languages like ABAP, you can solve any problem, if it is algorithmically solvable at all. But you can’t use a DSL to solve any problem. A DSL is designed for one particular problem, or a particular class of problems.
- Being restricted to a particular domain, a good DSL requires a syntax which makes it as readable as possible for the expert of that particular domain. Keywords and statement structure should be chosen in such a way that the code displays the intention with maximum readability, minimum code noise. In the examples above (CSS, makefiles and regular expressions), developers are the users. In a software for business applications, the code should be business-readable.
Martin Fowler describes two categories of domain-specific languages:
- The first category, the external DSLs, are defined and parsed outside of the host language, thus requiring an own parser.
- On the other hand, the internal DSLs are in fact fragments of code written in the host language (under certain rules). They are parsed, compiled and executed like any other part of the host language. There is no separate parser necessary.
Here is one of Fowler’s examples for an internal DSL. Using the “.” member access operator, which is a standard feature of many languages like Java, JavaScript, or C/C++, he can write down a code fragment which configures a PC, following the so-called method chaining pattern:
computer()
.processor()
.cores(2)
.i386()
.disk()
.size(150)
.disk()
.size(75)
.speed(7200)
.sata()
.end();
Due to the language restrictions, there still is some syntactic noise in this code: The brackets, the dots, and the computer() and end() statement are not part of the code intention. But everybody understands directly that this text is about specifying the parts of a computer.
How would one design that part in ABAP? Using macros, we could write:
processor :
cores 2,
type i386.
disk 1 :
size 150.
disk 2 :
size 75,
speed 7200,
type sata.
We will see in a minute how to implement the underlying macros.
For the moment, I just want to say that I find this version more expressive, more to-the-point than the Java example with method chaining.
Only the ‘1’ and ‘2’, the numbering of the disks, is a small technical debt (why couldn’t the system increment such a counter behind the scenes?). This is necessary because otherwise we couldn’t detect when the configuration of the first is finished, and the second disk begins. But we could sell this as a feature: Instead of ‘1’ and ‘2’, we could e.g. use this parameter to assign names to the different disks…
Everything in this code is under syntax control: If you typed ‘xata’ instead of ‘sata’, and xata is not a supported disk type, the compiler will send a syntax error.
Structured Constants as Dictionary
The first technique for implementing such a DSL is using structured constants. Suppose we want to specify a disk type from a given list of disk types, using a macro “disk_type”. So in the DSL we just want to type
disk_type sas.
Then an appropriate field of the semantic model, let’s say a component of a structure ls_disk, should be filled with the disk type, here ‘SAS’. Also, it should be impossible to type others than the disk types known to the system.
In order to specify the known disk types, we use a structured constant:
begin of gc_disk_type,
sata type string value 'SATA',
sas type string value 'SAS',
scsi type string value 'SCSI',
end of gc_disk_type.
Now, we can define the macro (a one-liner, as usual):
define disk_type.
ls_disk->type = gc_disk_type-&1.
end-of-definition.
We proceed similarly with a macro processor_type for the different processor types (i386, i686, PPC,…).
The advantage of the structured constant is that it can be used elsewhere in the code, too, as a replacement of a string literal by a symbolic name (which is good practice anyway).
Name Composition
There is another useful technique in this context. I would like to call it name composition. The idea is to use one of the macro arguments to build a known symbolic name – e.g. of another macro, of a method, subroutine, function module.
In the example above, the instructions for the processor
processor :
type i386,
cores 2.
are using the colon / comma notation, which fits nicely – the colon has an explanatory meaning, in the sense: “The following parts specify in detail what is preceded by me.” That is exactly what is intended here.
Technically, however, this expands into two calls of a macro named ‘processor’:
processor type i386.
processor cores 2.
We will now use the second macro argument for building the name of a specific macro that has to be employed instead. This is name composition:
define processor.
processor_&1 &2.
end-of-definition.
Now, we can add macros for all the things we want to specify for the processor (which is designed as a component of the object eo_processor here):
define processor_type.
eo_computer->gs_processor-type = gc_processor_type-&1.
end-of-definition.
define processor_cores.
eo_computer->gs_processor-cores = &1.
end-of-definition.
This works similarly for the disk specification. The only point here is that there are several disks. Therefore, we need the name argument (the second argument) of the macro, to get the right table line, before the data can be called.
If we have a local reference variable ls_disk, pointing to a disk structure, the macro “disk” is written as follows:
define disk.
if ls_disk is not bound or ls_disk->name ne &1.
ls_disk = eo_computer->get_disk( &1 ).
endif.
disk_&2 &3.
end-of-definition.
Here, a method is called first to position or generate the correct entry in the table of disks:
method get_disk.
data: ls_disk type ty_disk.
read table gt_disks reference into es_disk
with table key name = iv_name.
if sy-subrc ne 0.
ls_disk = get_default_disk( ).
ls_disk-name = iv_name.
insert ls_disk
into table gt_disks
reference into es_disk.
endif.
endmethod. "get_disk
The following form shows how the internal DSL is embedded into the ABAP code: It returns an instance eo_computer which carries the semantic model. Such a routine (or method, or function) can be called by other programs in order to get the data of the configured PC.
The DSL section is marked with special comments: The part enclosed by these comments could be edited (and changed) separate from the rest of the program, if an appropriate editor tool is used. In a forthcoming blog, I plan to explain such an internal DSL editor tool in more detail.
form get_computer changing eo_computer type ref to lcl_computer.
data: ls_disk type ref to lcl_computer=>ty_disk.
create object eo_computer.
* <<< BEGIN DSL
processor :
type i386,
cores 2.
disk 1 :
size 2000,
speed 750,
type sata.
disk 2 :
type sas.
* <<< END DSL
endform.
The full source code of this internal DSL example can be inspected on my pastebin.
Order of the parameters
If a macro is called several times, there may be parameters whose values vary frequently, and others varying only rarely. The macro should be designed with the frequently-changing parameters at the end of its parameter list. This, again, is for being able to exploit the colon/comma mechanism.
Observe my choice of the parameter order in the _move_from_to example above. Only in the order chosen above ‘&2-&4 = &1-&3’, I can arrange the macro’s actual parameter in such a ways that the move process is nicely visible. This shows that the order of the macro arguments is important. Sometimes, a macro with the wrong parameter order makes the code worse than it was before.
Naming
For macro names, the same considerations are valid as for any other symbolic name in programming: They should be chosen in such a way that the code which uses them becomes as readable as possible. This is particularly important in the scope of a DSL.
Prefixes: In my opinion, it is useful to mark a non-DSL macro (which is only used for simplifying, saving and clarifying ABAP code) with a special prefix. I use an underscore. The purpose of such a prefix is to distinguish the macro on the first view from an ABAP language keyword.
For macros used in DSLs, however, a prefix would be inappropriate, as the DSL idea is to separate away the “technical level”.
Summary
Macros are a code reuse technique which, like all the other reuse techniques, have the three advantages of improving code readability, simplifying code, and removing code duplications.
Macros come out to be particularly useful as building blocks of internal DSLs.
Of course, there always is the possibility of abuse, in particular when the macro contains too many lines. But this is no reason to forbid them in the programming guidelines. As already the Romans said: Abusus non tollit usum.
References
Martin Fowler with Rebecca Parsons: Domain-Specific Languages. Addison-Wesley 2010.
Debasish Ghosh: DSL – grow your syntax on top of a clean semantic model. Blog, from 2/22/2010
Complete ABAP code for the PC configuration example: http://pastebin.com/6gqZdvQJ
I wish even 1% of the developers (i have worked with) used any one of these points into their coding ...
For the brave ones who dare to use macros, they have to face the self-proclaimed QA guru who would fry them for using macros. Even though the developer's honest intention was to do away with the "clumsy looking" source-target structures mapping code 😉
All-in-all a great blog. Good read!
Cheers,
Suhas 🙂
Great Post! You've put Macros in a better light, at least in my mind. However the big concern I have after reading this is the (apparent, correct me if I'm wrong) lack of static type checking. For example, in the disk_type macro, couldn't the programmer specify a field of gc_disk_type which doesn't exist, without the error bieng caught until runtime? Or for macro processor_cores, couldn't they accidently specify a non-numeric character string for the number of cores without it bieng caught by the compiler?
In my eyes one of the biggest sins an ABAP developer can make is to not specify as strong a type as possible for subroutine parameters. Using strong types not only shifts errors from runtime to compile-time (making it much harder for them to find their way into production), it also improves the 'self-documenting' nature of the code (the type of a parameter gives you quite a lot of information about what's expected to be passed in that parameter). Macros seem like a major step backwards in that sense.
Hi Alex,
No, he can't. He will get a syntax error if he mistypes the argument, for example if he typed "disk_type xata." instead of "disk_type sata". Also, of course, he gets a syntax error if he types "disk_xype sata." So macro-based code can be made as safe against typos as plain ABAP code.
Even the where-used list resolves the macro internally and includes composed names. A where-used list for the constant "gc_disk_type-sata" will show the source code statement "disk_type sata." !
I totally agree that typechecks are a useful feature of compiled languages, and you surely can't assign types to macro arguments. In my eyes, typed parameters make sense for all units which have an own execution context. Macros should not be viewed like methods, rather they are simple source code transformations which can help us to improve the readability of source code.
Regards,
Rüdiger
I obviously didn't realize that the syntax checks were done after the macro was interpreted. That's a fantastic bit of functionality. Thanks Rüdiger for the post and the follow-up!
That's why I wrote this blog! 🙂
Alex, thank you for your friendly feedback.
I agree that macros can simplify ABAP source code. But if your development team/structure is more complex you need to have development guidelines and you cant give a clear statement what a simple macro is and a complex amcro is. Here it comes always to discussions like "why is the macro of XYZ allowed and mine not?". This is the point why i completly forbid macros in our development.
Rainer - development in itself is a complex, creative process! This is why Donald E. Knuths famous book has the title "The Art of Computer Programming". It is more a craft than a science: It is impossible to define beyond discussion what a "good program" is. Code metrics only give hints, they can't be used for a clear demarcation.
With your argument, you could, like a dictator, forbid any techniques that you personally dislike:
"There are poorly-written methods and well-written methods. I can't tell precisely what makes the difference, and I don't want to argue about it. Therefore, I completely ban OO from our team."
"There are poorly-written subroutines and well-written subroutines. I can't tell precisely what makes the difference, ... "
To reuse code is the highest rule of development. I would never exclude any technique by law which supports reuse. In the ABAP stack, these are (sorted from good to better): Includes, macros, subroutines, function modules, and methods.
Even Horst Keller in his ABAP programming guidelines doesn't ban macros completely, but recommends to use them in situations when they make sense.
Regards,
Rüdiger
Well, as i'am responsible for QA and have limited ressources in this i must take a decision. And as macros can be fully replaced by methods/classes or performs i did take the decision that macros are not allwoed in our development. If you you have a small team where everybody knows what to do you can slacken the reins. But with off-shore and near-shore development where the devlopers are replaced all the time you need to have clear and strict rules. Complex rules will never be read completly and more often not remembered.
In such an environment you have even too much discussions about OO techniques (this is mandantory to use here except for some clear rules). You dont want to compare macros and OO techniques at the same level building stable, correct and easy-to-support reports? Macros for me do not have the same significant value as other techniques. They are nice, buts that all. And you dont loose a lot ´forbidding them.
Rainer,
this blog war a contribution to the craft of programming, it was not about programming guidelines (I think there are plenty discussions on programming guidelines elsewhere in the SCN). Do with it what you want.
Exactly because I am very interested in stable, correct and easy-to-maintain code, I would never exclude a technique like pre-compile source code transformations (e.g. macros) from my repertoire.
Regards,
Rüdiger
Sorry for not sharing your enthusiasm using macros. And i am aware that this is not a blog about development guidelines. But this is a fact against your programming technique. Bit i will stop commenting here....
I love a good debate! I have been watching this one for awhile. It shows both the pros and cons of using macros.
Honestly I only used a macro once, and it was a couple of years ago. I had to because ot the way the sales / apo or something like that was set up...
But I appreciate the blog. Why? Because macros were used on the system by XPS. I am faced with the issue of enhancing them. A big yuck for me. But if I understand them more, it helps.
Also it never hurts to have something extra in the back of your tool belt for unexpected times when you will use it. I had to comment on this, as I've been watching and loving this dialog .
Michelle
Hello Michelle,
thanks for your positive feedback! Yes, it's good to know all your options as a developer!
Hello Rainer, I am not particularly "enthusiastic" about macros, but I welcome them in my desire to write maximum readable code. And a guideline is not, as you say, a "fact" which speaks as an argument against a programming technique (it is a "fact" only for those that are forced to live with it). But, well, discussing this would automatically lead us into the realm of a guideline debate. So I'll quit here, too...
Regards,
Rüdiger
Agree to disagree. I have to respect that.
Great presentation of pros and cons. I know I'll be back to this page many times while working on Macros. (And thinking.... Mmmmm do I really want to use them?
Michelle
Hello Rainer/Rudiger,
Tbh i thought macros were "obsolete" modularization techniques, but they are not (although subroutines & FMs are classified as obsolete from 702: http://help.sap.com/abapdocu_731/en/abenfunct_module_subroutine_guidl.htm) !
SAP does say to exercise caution while using macros (as expected) & to use them in exceptional conditions: http://help.sap.com/abapdocu_731/en/abenmacros_guidl.htm.
BR,
Suhas
Hi Suhas,
Thanks for the information.
I am a bit confused about the first reference link provided by you, which says "In ABAP objects Function modules & sub-routines should be avoided".
But in the "Obsolete language elements" part of documentation, it say " while creating function modules Usage of Table parameters, Obsolete typing and global parameters are obsolete" http://help.sap.com/abapdocu_731/en/abenfunction_modules_obsolete.htm.
Correct me If I missed out something.
Apologies for being off-topic to the main discussion.
Regards, Vinod
They should be avoided cause you should go to classes/methods instead, either to local classes or to global classes. There are only a few circumstances where function modules are really needed, two of them are dialog elements and RFC capability.
Hello Vinod,
Subroutines/FMs are obsolete modularization techniques (as of Release 702) 🙂 You should try to replace them with local/global classes. Imo SAP won't be able to do away with these because of downward compatibility!
There are a few cases where OO-capability is still not supported e.g., RFC. In these cases you are "kind-of" forced to use them.
Hope i'm clear.
BR,
Suhas
Hi Suhas,
Thanks for the clarification. I was amused with the "Recommended" Vs "Obsolete" statements in the SAP documentation references mentioned above.
Regards,Vinod
Excellent blog! And timely for me. I have only lightly used macros and that was a couple of years ago.
Now I'm looking at some custom code written in our system that uses macros.
Even if you don't use macros, this is a nice blog to read, bookmark, and save for a different day. For me - it's today.
Thank you!
Michelle
Recommended vs. Obsolete - no comments on that. I belive field exits have been obselete forever and a day. But I still see them used in places. And subroutines / FM - well - those aren't even close to being obselete.
Congratulations!
As a matter of fact you made me rethink my attitude towards macros and although I still cannot wholeheartedly embrace them I might now consider their use - at least from time to time.
Thank you.
Best Regards,
Chris
Hi Chris, I am glad to read that I could give you a new impact! Of course, nobody can "wholeheartedly embrace" macros. There are much better techniques for reusable code. It may suffice that macros supplement these techniques, in many cases providing more readable code and reducing repetitive parts of it. Regards, Rüdiger
Hello
Yes Its a nice blog and an argument whether to use or not.
For making code readable and understandable i think is more important
and if you are familiar why not!!
Good to read and laysout the types of use in a good way!!
good work!!
Thanks, Solen - enjoying your laurels! 🙂 Cheers, Rüdiger
Hi Rüdiger,
thanks for this blog - it's hitting the center of my thoughts about macros, allthough more precisely to the point.
With your blog in mind, i doublechecked some of my earlier implemented macros and did find some room for improvement.
I competely agree, that macros are seldom first choice - but as with filed-symbols - they do have advantages in some cases, that are hard to get by without them.
Better readability, especially by omitting boring code repetitions, improves the maintainability of coding. I know from own experience, how easyly excessive copy and paste of code can lead to errors, that are hard to find.
@all those responsible for QA
These errors will never show in a syntax check, nor will they produce a dump. Nevertheless, they can hold you entertained for some time or even worse, go unrecognized for a while, corrupting the result of your sophisticated work.
Best regards
Jörg
Hi Jörg, thanks for your positive feedback! Indeed, it's another advantage of macros that they follow the "Fail early" paradigm: If you use symbolic names for constants in contrast to literals (as in the computer example), you will get typos immediately reported as syntax errors. Otherwise, they uncover at runtime (and if there is no complete coverage with runtime tests: who knows when?). Cheers, Rüdiger
Food for thought, for sure. However, I find in most cases the criteria of:
are more than adequately met by the functional style of OO. The second point I don't agree as a use for macros. Implementation details/logical parts of the program should never be hidden in macros - they should be in methods (or forms of FM if you really have to) for debugging purposes.
However, having read your blog, I will relax my own internal rule of "no macros" and will consider their use in specialised situations.
Matthew,
thanks for your feedback. Glad to read that you are about to "relax your internal rule of 'no macros'"!
Yes, the functional notation is great. But ABAP is not C++ (not to speak of a really functional language). The syntax of method calls often remains too verbose in my eyes, giving relevant and non-relevant parts of the call the same optical importance. Macros can help to bring the relevant parts of the code into the foreground. See my examples in the blog.
I am optimistic that the functional notation will be better integrated in future ABAP versions. There are many little things to do.
Just an example: In a recent implementation, I tried to use method chaining - and although it basically works, it doesn't read as natural as one would expect, when trying to use vertical space for readability.
As for the second point - this seems to be a misunderstanding. The opposition here is: "Implementation details vs. the code's intentions". On the top level, the code should show the intentions - and not how you proudly managed to implement these intentions. A clumsy method call can distract the reader from this intention.
As for debugging: I have discussed it above and I totally agree that a macro should essentially be used as a shortcout for a method call. Also, there must be good reasons for using a macro, otherwise avoiding them is better (this means: use them wisely 🙂 ). Also, logic in macros - except the most short and boring one which can be overseen without debugging - would be "unwise"!
Kind regards,
Rüdiger
Just to add that since 7.40, ABAP is considerably less verbose so the need for macros is reduced further.
Nice stuff.
Yep, I will follow suit.
Good One.
Hi Rudiger,
Great Article.... I use macro frequently but still I never thought its this powerful... Great...
Thanks a lot
Regards
Fareez
VERY VERY good Article!
worth for every developer to read. ➕
Hi Rüdiger,
REALLY Great article.
Congratulations.
Regards,
Sandro Ramos
Good article. Although i don't use macros very often but it will make me try it for sure. 🙂
Thanks,
Anupam
There is one error. At least it does not work this way on my system and I suspect you never tested this assumption:
In ABAP when you pass a literal like this and embed it into a string inside a macro, it is ALWAYS uppercased. Even if the source is lower case. The only way to get lowercase is to pass a literal string. Case is never preserved, rather it is always uppercased.
You can write a very simple example to test this and verify.
Thanks for correcting. You are right, I was wrong.
It's a really annoying side-effect that has bitten me a few times now...
My interest dropped significantly when I read the lines:
For one-line macros, including those which just call a method, this is no problem. With F5, you enter into the method implementation, or if it sets a variable, you can immediately verify the intended effect of the statement. Most of the macros that I presented are of this type. And the benefit is that the client code is simplified while still being subject to the syntax check, i.e. it has to be statically valid.