Skip to Content
Author's profile photo Former Member

ABAP Trapdoors: Morphing Method Parameters

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.

  1. ENDCLASS.

CLASS lcl_myclass IMPLEMENTATION.

  METHOD get_text.

    e_data = ‘This one goes out to the one…’.

  ENDMETHOD.

  1. 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.

  1. ENDCLASS.

CLASS lcl_myclass IMPLEMENTATION.

  METHOD get_text.

    INSERT ‘first line’ INTO et_data INDEX 1.

    APPEND ‘last line’ TO et_data.

  ENDMETHOD.

  1. 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?

   1 first line

   2 I Think I’ll Disappear Now

   3 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!

   1 Right Between

   2 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:

   1  A Standard Chorus Line.

   2 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.

Assigned Tags

      15 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Former Member
      Former Member
      Hello Volker,
      I am starting to become a fan of your ABAP Trapdoors series! Being a novice in OO ABAP, it's really nice to take a note of these tricky points. Expecting more on OO ABAP trapdoors from you.

      One question though:

          CALL METHOD super->constructor
            EXPORTING
              it_text = gt_text.

      Why would someone pass an attribute of the class to it's constructor? Did SAP do this in CL_GUI_ALV_GRID class?

      Would be hHappy to hear your comments.

      Nice Weekend!
      Suhas

      Author's profile photo Former Member
      Former Member
      Blog Post Author
      Suhas,

      I'm glad you like it - I've got a few subjects lined up already.

      Regarding your question - I have absolutely no idea why anyone would want to do this besides to prove the point that it can be done. And yes, this gem can be found in the constructor of CL_GUI_ALV_GRID, at least in 7.01. Try searching for "create object m_cl_variant"...

        Volker

      Author's profile photo Peter Inotai
      Peter Inotai
      >this gem can be found in the constructor of CL_GUI_ALV_GRID, at least in 7.01.
      I also checked in our highest available releases 7.02 and 7.10 and it's still the same.
      Peter
      Author's profile photo Ethan Jewett
      Ethan Jewett
      I really appreciate you writing these blogs. ABAP has some serious quirks like this that belie its age and make it very difficult to write what are now considered "good" programs, from either an OO or functional perspective.

      I wonder, are these sorts of concerns getting routed back into the development of ABAP? For example, is it really necessary that "pass by value" has bad performance? I'm totally not an expert in this area, but every modern programming language I'm familiar with (that I remember at least) has the behavior of "pass by value" by default. If I remember correctly, they pretty much all achieve this efficiently through a "copy on change" technique by which they actually pass a reference but it's marked to indicate that if it is changed it needs to first be copied (passed by value). Why can't ABAP do something similar?

      Food for thought.

      Ethan

      P.S. I'll admit I created, tracked down, and fixed a bug due to this sort of behavior a couple weeks ago and tweeted my dismay (Gist here: https://gist.github.com/851008), so maybe I'm a *little* bit sensitive about it at the moment 🙂

      Author's profile photo Former Member
      Former Member
      Blog Post Author
      Ethan,

      I'm not connected to SAP so I can't comment on how the langue might be developed in the future. I agree that copy-on-write could be an idea to bypass the performance issues, but as I said in another comment, I'll probably run some tests to determine the real performance difference in the near future. (Just to add some ball-and-chains to the trapdoors :-))

      And yes, I've run into this issue several times myself. A code inspector check to prevent this would be nice - hummm.....
       
        Volker

      Author's profile photo Ethan Jewett
      Ethan Jewett
      I somehow got the idea into my head that you had a little SAP logo by your name on SCN. Sorry about the mixup 🙂 Thanks again for the blog. Very nice.

      Ethan

      Author's profile photo Former Member
      Former Member
      One turn around would be to use functional methods which has returning parameters.

      The returning parameters will always have pass by value parameters . But there can only be one returning parameter per method

      Regards
      Arshad

      Author's profile photo Former Member
      Former Member
      Blog Post Author
      Arshad,

      this would not change the situation as much as it might appear to in the first place. Functional methods are syntactically different from "procedural methods", but the basic idea (and I suppose the internal details) are the same. As you said, returning parameters are never passed as reference parameters. If you use returning parameters for large data objects (for example tables), you might experience severe performance drawbacks. AFAIR there's even a Code Inspector check that warns about returning parameters with large or variably-sized data types. I'll have to run some tests to verify this some day... When it comes to performance issues, there are only three rules: Measure, measure, measure. 😉

        Volker

      Author's profile photo Former Member
      Former Member
      Halo Volker,
      Thanks for reply.
      But if you think from a logical perspective , pass by reference should be perfomance wise taxing in the sense that the filled up data object needs to be transported to the method implementation part . Whereas in the case of pass by values this data transport are not at all required as we are working with fresh variables .

      Any reason why pass by value is perfomance wise not efficient?

      Regards
      Arshad

      Author's profile photo Former Member
      Former Member
      Hello Arshad,
      Even from a logical perspective pass-by-value cannot be more effecient!

      As a matter-of-fact nothing gets "transported" for pass-by-reference params. More read on this topic can be found on: [http://help.sap.com/abapdocu_702/en/abenformal_parameters_oview.htm]

      BR,
      Suhas

      Author's profile photo Vinod Kumar
      Vinod Kumar
      Dear Volker,
      Nice & Informative blogs. Eagarly waiting for next one in the series.

      Regards
      Vinod Kumar

      Author's profile photo Alejandro Bindi
      Alejandro Bindi
      Although I already knew about this one 🙂 I agree that using always (blindly) the value passing is not a nice solution. I developed the habit of putting a CLEAR for each exporting parameter at the beginning of any method which uses them.
      But even that is no guarantee of success, I have another example to prove it:

      CLASS lcl_test DEFINITION.
      PUBLIC SECTION.
      CLASS-METHODS: select IMPORTING i_carrid TYPE scarr-carrid
      EXPORTING es_scarr TYPE scarr
      EXCEPTIONS ex_no_data.
      ENDCLASS. "lcl_test DEFINITION

      CLASS lcl_test IMPLEMENTATION.
      METHOD select.
      CHECK i_carrid IS NOT INITIAL.
      CLEAR es_scarr. "Depending on the actual parameters, this also clears I_CARRID!

      SELECT SINGLE * FROM scarr
      INTO es_scarr
      WHERE carrid = i_carrid.
      IF sy-subrc <> 0.
      RAISE ex_no_data.
      ENDIF.
      ENDMETHOD. "select
      ENDCLASS. "lcl_test IMPLEMENTATION

      PARAMETERS: p_carrid TYPE scarr-carrid VALUE CHECK OBLIGATORY.
      DATA: gs_scarr TYPE scarr.

      START-OF-SELECTION.

      gs_scarr-carrid = p_carrid.
      lcl_test=>select( EXPORTING i_carrid = gs_scarr-carrid
      IMPORTING es_scarr = gs_scarr
      EXCEPTIONS ex_no_data = 1 ).
      IF sy-subrc <> 0.
      MESSAGE i000(oo) WITH 'No data for Airline "' gs_scarr-carrid '"'.
      ENDIF.

      (normally in this case, you would pass p_carrid directly to the method instead of copying and passing gs_scarr-carrid, but I did this intentionally to show the problem).

      Regards

      Author's profile photo Peter Inotai
      Peter Inotai
      Hi Volker,
      Thanks for this new blog in the ABAP Trapdoor series. Already looking forward for the next one 🙂
      Peter
      Author's profile photo Former Member
      Former Member

      Hi Volker,

      Nice blogs, there sure is a lot of helpful and valuable information. 🙂

      I may be a little late to be commeting here, but I'd like to expose something that I have been doing to avoid the problems of the EXPORTING parameter.

      I always use the RETURNING parameter, which can only be pass by value, thus avoiding the many inconsistencies that can happen with an EXPORTING parameter.

      However, as you pointed out, pass by value is not good, performance-wise, when returning big amounts of data, since double the memory would have to be allocated in order to perform the data copy. But in these cases, I return the data as a reference(TYPE REF TO). Of course it may seem strange to do so, but this is (or should be) a very rare scenario, since most of the time, the data being returned are just references to objects, instead of plain old structures, as would be the best practice in any new developments.

      Although, in ABAP, we will always have to use internal tables, since it does not have any Collections API, or at least not as useful and powerful as internal tables. In this case, even the code inspector warns us of passing an internal table by value, as would happen if we return an internal table from a method.

      However, in my experience, this data-copy, as warned by the code inspector, does not happen for most of the time.

      ABAP has an optimization mechanism that avoids the data-copy process for internal tables when doing a simple assignment. It internally points to the same internal table in memory, from two or more different variables. It will only perform the duplication of data when a change operation is performed upon any of the variables. This eliminates the performance issue if you are simply reading the contents of the returned internal table. But, even when a change operation is triggered upon the internal table, there is a case that there would still be no duplication:

            If the internal table is filled only within the method, in other words, if it does not come directly from an attribute, this means that the internal table is first initialized as the RETURNING parameter's local variable. When the internal table is returned, the variable to which it was assigned is still pointing to the same memory area, so no duplication was performed yet. But at this point, since the method's execution ended, the RETURNING parameter's local variable no longer exists, and now the contents of the internal table belong exclusively to the variable that received it. No data duplication was performed at all.

      This leaves us with the last case, which is when we have an internal table as an attribute of the object, that is returned in a getter method. This case, however, is when you would actually need to return the contents by value, since allowing the contents of the actual internal table to be changed outside of the object would break encapsulation.

      In conclusion, it seems to me that the RETURNING parameter is a better option overall, even with performance in mind. Along with its protection from inconsistencies, it also provides modern features such as calling methods in a functional style, which can be used in expressions, conditionals and even as parameters to other methods.

      I hope not to have complicated things too much. 😐

      Best regards,

      Michel

      Author's profile photo Former Member
      Former Member
      Blog Post Author

      Michael,

      you have a point there. RETURNing references to other things than objects looks like a sensible option, but it makes matters a lot more complicated for both the caller and the called method. I have rarely seen this pattern used. Also note that a method can only ever have one RETURNING parameter, so this isn't always an option.

        Volker