This is a repost of an article originally posted to the SAP Community Network.
During our basic programming lessons, we all learned how to control the flow of data throughout a series of method calls
and why that is important. Hopefully, you’ve come to dislike FORM foo USING bar
as much as I do and substitute it with
METHOD foo IMPORTING x EXPORTING x CHANGING z
, omitting the CHANGING
part wherever possible. This way, you can
always tell which parameters you have to supply the method with and which parameters only yield output values. You can
also rely on the value of the exporting parameter being exclusively determined by the method call, unaffected of other
actions - or can you? Let’s take a closer look:
CLASS lcl_myclass DEFINITION.
PUBLIC SECTION.
METHODS get_text EXPORTING e_data TYPE string.
ENDCLASS.
CLASS lcl_myclass IMPLEMENTATION.
METHOD get_text.
e_data = 'This one goes out to the one...'.
ENDMETHOD.
ENDCLASS.
DATA: gr_myclass TYPE REF TO lcl_myclass,
g_string TYPE string.
CREATE OBJECT gr_myclass.
g_string = 'Nothing to fear'.
CALL METHOD gr_myclass->get_text
IMPORTING
e_data = g_string.
WRITE: / g_string.
The result is most definitely boring:
This one goes out to the one…
Now, let’s try this in a different way. We need to be able to pass multiple lines of text for some reason, so we’ll just use a table of strings.
CLASS lcl_myclass DEFINITION.
PUBLIC SECTION.
METHODS get_text EXPORTING et_data TYPE string_table.
ENDCLASS.
CLASS lcl_myclass IMPLEMENTATION.
METHOD get_text.
INSERT 'first line' INTO et_data INDEX 1.
APPEND 'last line' TO et_data.
ENDMETHOD.
ENDCLASS.
DATA: gr_myclass TYPE REF TO lcl_myclass,
gt_strings TYPE string_table.
FIELD-SYMBOLS: <g_string> TYPE string.
CREATE OBJECT gr_myclass.
APPEND 'I Think I'll Disappear Now' TO gt_strings.
CALL METHOD gr_myclass->get_text
IMPORTING
et_data = gt_strings.
LOOP AT gt_strings ASSIGNING <g_string>.
WRITE: / sy-tabix, <g_string>.
ENDLOOP.
Ready for a test run?
first line
I Think I’ll Disappear Now
last line
Ahem. Perhaps not quite what we expected. We can even take this one step further. When an exception occurs, we usually expect that the method we just called has failed to perform its assigned function and not have any secondary side effects. We particularly don’t want it to return data that has been half-processed. So how about this:
CLASS lcl_myclass DEFINITION.
PUBLIC SECTION.
METHODS get_text EXPORTING et_data TYPE string_table RAISING cx_no_such_entry.
ENDCLASS.
CLASS lcl_myclass IMPLEMENTATION.
METHOD get_text.
INSERT 'Right Between' INTO et_data INDEX 1.
RAISE EXCEPTION TYPE cx_no_such_entry.
APPEND 'Here And Nowhere' TO et_data.
ENDMETHOD.
ENDCLASS.
DATA: gr_myclass TYPE REF TO lcl_myclass,
gt_strings TYPE string_table.
FIELD-SYMBOLS: <g_string> TYPE string.
CREATE OBJECT gr_myclass.
APPEND 'The Eyes' TO gt_strings.
TRY.
CALL METHOD gr_myclass->get_text
IMPORTING
et_data = gt_strings.
CATCH cx_no_such_entry.
WRITE: / 'Whoops, silly me!'.
ENDTRY.
LOOP AT gt_strings ASSIGNING <g_string>.
WRITE: / sy-tabix, <g_string>.
ENDLOOP.
After this introduction, you’ve probably expected the outcome:
Whoops, silly me!
Right Between
The Eyes
So what is going on here? These are EXPORTING
parameters, not CHANGING
parameters, so why do the methods actually
add data to the table instead of overwriting it? The answer is actually rather simple - because ABAP uses call by
reference by default. This means that the method receives a reference to the original variable passed by the caller and
therefore operates on this variable. This also implies that whatever content the variables passed as EXPORTING
parameters have when the method is called are passed on to the method implementation. By the way, the same thing happens
when you pass structures to the method and don’t fill all fields of the structure during the execution of the method -
you might end up with left-over data.
The obvious solution is to just replace EXPORTING et_data
with EXPORTING VALUE(et_data)
in the examples above to
switch from call by reference to call by value. This will cause the method to operate on its own private variable that
is initialized before the method is executed. The results are then copied back to the variable when the method exits
normally. If an exception occurs, the original data is not touched at all.
Unfortunately, this also causes severe performance degradation, especially where large tables are involved. It’s a
clumsy workaround that forces the system to copy around potentially massive amounts of data - sometimes that’s the only
option, but using this as a default is not advisable. Most of the time, you just have to keep in mind that it’s a good
idea to REFRESH
the exporting tables at the beginning of your method implementation.
The fact that ABAP defaults to call by reference parameter passing also explains why the following rather irritating code is possible:
CLASS lcl_super DEFINITION.
PUBLIC SECTION.
METHODS constructor IMPORTING it_text TYPE string_table.
METHODS write_text.
PROTECTED SECTION.
DATA gt_text TYPE string_table.
ENDCLASS.
CLASS lcl_super IMPLEMENTATION.
METHOD constructor.
IF it_text IS NOT INITIAL.
APPEND 'Message In A Bottle' TO gt_text.
ENDIF.
APPEND 'A Standard Chorus Line.' TO gt_text.
IF it_text IS NOT INITIAL.
APPEND 'Whoops, where did that input value come from?' TO gt_text.
ENDIF.
ENDMETHOD.
METHOD write_text.
FIELD-SYMBOLS: <l_line> TYPE string.
LOOP AT gt_text ASSIGNING <l_line>.
WRITE: / sy-tabix, <l_line>.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
CLASS lcl_sub DEFINITION INHERITING FROM lcl_super.
PUBLIC SECTION.
METHODS constructor.
ENDCLASS.
CLASS lcl_sub IMPLEMENTATION.
METHOD constructor.
* set break point here to verify that gt_text is really empty
CALL METHOD super->constructor
EXPORTING
it_text = gt_text.
ENDMETHOD.
ENDCLASS.
DATA: gr_instance TYPE REF TO lcl_sub.
START-OF-SELECTION.
CREATE OBJECT gr_instance.
gr_instance->write_text( ).
The result:
A Standard Chorus Line.
Whoops, where did that input value come from?
This is surprising at a first glance because the constructor of lcl_super
checks the importing parameter twice, and
between these checks the parameter value changes mysteriously. Again, this happens because the constructor is passed a
reference to its own attribute, and by changing gt_text
, it also changes its own input parameter implicitly. Something
like this was spotted by a colleague of reader Peter Inotai in the famous class CL_GUI_ALV_GRID
. From my point of
view, this is rather pointless and can lead to really confusing behavior. Moreover, if happen to work on a shared
codebase in a larger project and you rely on this behavior, it’s probably only a question of time before someone who is
not aware of the implications and/or your intentions breaks something.