Skip to Content
Technical Articles
Author's profile photo Jagdish Patil

ABAP 7.5 : Are Subroutines Obsolete?

I recently stumbled upon ABAP 7.5 documentation. I was interested to see what is new. But rather I got to know what is old. SAP now says that subroutines are obsolete.

Shocked? Well, I was. I have been using performs and have always preferred them over methods. A habit that I am trying to change but not very successful at it so far.

So when I saw the statement ‘Performs are Obsolete’. I was bit worried – how would I cope with this?

As per the guidelines available at this link, whenever you are developing new code, do not create new Subroutines(Performs) or Function Modules, use objects/methods instead.

The rules say, and there are couple of them –

Rule 1 : Do not implement in function modules and subroutines

Use FMs and Performs only if they are a must. Use Methods instead.

Rule 2 : Use ABAP Objects

Use ABAP objects wherever possible for new and further developments. Classic processing blocks should only be created in exceptional cases.

And as per every rule, these rules also have exceptions. Not everything can be done in classes. Well, not yet. So the exception is long. But over the time SAP will reduce the exceptions. Some of the exceptions are as below –

  1. RFC function modules – RMI or Remote Method Invocation is not yet available
  2. Update function modules – These are still to be used
  3. CALL SCREEN and CALL SELECTION-SCREEN – Object-oriented handling of classic dynpros is still not available. This is a feature that I personally would love. 
  4. PERFORM ON COMMIT|ROLLBACK – We anyways do not use these generally, but if you have to – go ahead.
  5. GENERATE SUBROUTINE POOL – I had to use this only once in 13 years. So not really looking forward.

So, our new ABAP reports should still be created using a program, have selection screen and have events, but instead of performs we should have methods. Here is an example of how it should look.

*&--------------------------------------------------------*
*& Report YABAP_750_REPORT
*&--------------------------------------------------------*
*&
*&--------------------------------------------------------*
REPORT yabap_750_report.

CLASS lcl_alv DEFINITION FINAL.

  PUBLIC SECTION.
    CONSTANTS :
      information TYPE sy-msgty VALUE 'I'.

    TYPES :
      tt_flights TYPE STANDARD TABLE OF sflight
        WITH EMPTY KEY.
    "... add more types here

    METHODS :
      constructor
        IMPORTING
          i_carrier    TYPE sflight-carrid
          i_connection TYPE sflight-connid,
      start_report_process
        RETURNING
          VALUE(r_error) TYPE char1.

    "... add more public methods here

  PROTECTED SECTION.
    "Add protected data/methods if needed

  PRIVATE SECTION.
    DATA :
      carrier    TYPE sflight-carrid,
      conn_id    TYPE sflight-connid,
      flights    TYPE tt_flights.
    "... add more data declarations here

    METHODS :
      get_flights 
        RETURNING 
          VALUE(rt_flights) TYPE tt_flights,
      display_flights 
        RETURNING 
          VALUE(r_error) TYPE char1.
    "... add more private methods here

ENDCLASS.

CLASS lcl_alv IMPLEMENTATION.
  METHOD constructor.
    "Set variables based on called parameters
    carrier    = i_carrier.
    conn_id    = i_connection.
    "Initialize attributes
    CLEAR flights.
  ENDMETHOD.

  METHOD start_report_process.
    CLEAR r_error.
    flights = get_flights( ).
    IF flights IS INITIAL.
      MESSAGE e001(00) WITH 'No data found'(002)
        INTO DATA(lv_error).
      r_error = abap_true.
    ELSE.
      r_error = display_flights( ).
    ENDIF.
  ENDMETHOD.

  METHOD get_flights.
    CLEAR rt_flights.
    SELECT * FROM sflight INTO TABLE @rt_flights
      WHERE carrid = @carrier
      AND   connid = @conn_id.
  ENDMETHOD.

  METHOD display_flights.
    CLEAR r_error.
    TRY.
        "Create ALV table object for the output data table
        cl_salv_table=>factory( IMPORTING r_salv_table = DATA(lo_table)
                                CHANGING  t_table      = flights ).
        lo_table->get_functions( )->set_all( ).
        lo_table->get_columns( )->set_optimize( ).
        lo_table->display( ).
      CATCH cx_salv_msg.  "ALV can not be displayed, issue error
        MESSAGE e001(00) WITH 'Error in ALV creation'(003) INTO DATA(lv_error).
        r_error = abap_true.
    ENDTRY.
  ENDMETHOD.

  "... add more methods implementation here
ENDCLASS.

"... build selection screen here or in another include
SELECTION-SCREEN BEGIN OF BLOCK main_block WITH FRAME TITLE TEXT-001.
  PARAMETERS : carrid TYPE sflight-carrid,
               connid TYPE sflight-connid.
SELECTION-SCREEN END OF BLOCK main_block.

START-OF-SELECTION.
  "Create object for the class
  DATA(o_alv) = NEW lcl_alv( i_carrier    = carrid
                             i_connection = connid ).

  "Below code starts the main process and issues error
  "It can be coded as per requirement 
  IF o_alv->start_report_process( ) IS NOT INITIAL.
    MESSAGE ID sy-msgid TYPE lcl_alv=>information NUMBER sy-msgno
      WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

 

Conclusion:

  • If you have not switched to using methods over performs, this is the time to do so.
  • Using this or a similar template, reports can be created very easily.
  • This style can also be applied to other programs, like file/proxy based interface program.

In short, the message is, even if we are used to a way of coding, which is also comfortable, we should change with the ABAP advancements.

And this, is my first step.

This is also my first blog, so your comments and suggestions are welcome. Please use the comment section below or you can also post your questions here.

 

– Jagdish Patil

Assigned Tags

      31 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Michael Biber
      Michael Biber

      Welcome and keep on learning on that path. In my opinion it it totally worth it and overdue.

      Just another hint: some of your Exceptions are only partially exceptions: at least for the use cases 1 and 2 you can use the function Module as pure wrapper. They can contains only one Method call.

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Hi Michael

      Overdue !! So true.

      On the exceptions - yes, I agree that this code sample does not use exceptions well. Something to improve for me. Will definitely work on it.

      Author's profile photo Jörgen Lindqvist
      Jörgen Lindqvist

      Yes, you have the right approach! Let's go!

      (And to be fair, PERFORM have been obsolete since long before 7.50 🙂)

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Hi Jörgen

      Thanks for the link. I feel a little embarrassed now that I never knew this. 7.3 has been around for so long, and I am using subroutines as well for so long that I never thought this could be the case.

      As I said, I just happened to come across this by mistake. And I am glad that I did, because for past so many years, I kept avoiding using objects.

      Author's profile photo Sandra Rossi
      Sandra Rossi

      If the documentations before 7.31 had not been removed, you could find out that PERFORM is obsolete since 7.02 (2009) 😀

      Author's profile photo Jörgen Lindqvist
      Jörgen Lindqvist

      Well, the information has in fact been out there, but nobody MAKES us read it... Between that and the fact that things are backwards compatible and that obsolete things still actually work (for a good reason), we either need to find it or be told it. And the more the better!

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Yes, the information needs to be shared.

      I heard a story today -

      Developer used Inline Declarations and a SME in a external(outside project) SME review asked to move all the data declaration to TOP include. 🙂

      Author's profile photo Kerem Koseoglu
      Kerem Koseoglu

      When you have a local class with a constructor, extended check may warn you that you have a global variable.

      To prevent that, I tend to make all methods and variables of "lcl_alv" static.

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Thanks Kerem for this tip. Its definitely useful.

      I used to create the object inside a subroutine, but then, you know. 🙂

       

      Author's profile photo Sandra Rossi
      Sandra Rossi

      As you say, "there are exceptions", but "exception classes" are not exceptions and I would use them so that to write the starting code differently, but it's a matter of preference I guess 😉

      TRY.
          o_alv->start_report_process( ).
        CATCH cx_root INTO DATA(x_root).
          MESSAGE x_root TYPE 'I' DISPLAY LIKE 'E'.
      ENDTRY.

      NB: some people prefer to catch only what is expected to be raised, I prefer to catch everything at the lowest level.

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Thanks Sandra. Its been a long time since we interacted.

      Yes, I have this confusion, which one is the correct way. I also prefer the cx_root, but not because of any particular reason but its simpler as I don't have to check which ones to handle.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Having initially felt that, I now use class based exceptions exclusively. They're so much more flexible. As you find the first time you have to pass the return code up to a higher level.

      Furthermore, it might be an indication that you've not really layered your development properly.

      Fwiw, my model classes always throw exceptions - which are then handled by the controller class.

      Author's profile photo Paul Hardy
      Paul Hardy

      In some sense subroutines became obsolete when ABAP Objects was introduced in SAP 4.6 back in the year 2000, even if they were only "officially" obsolete in 2009.

      However I would imagine that of all the new ABAP code that has been written all around the world on this very day, 75%+ would be done in FORM routines.

      21 years and counting and still very little uptake.

      So I would disagree with one of the statements in the above blog.

      • If you have not switched to using methods over performs, this is the time to do so.

      Should be replaced by

      • If you have not switched to using methods over performs, the year 2000 was the time to start doing so, so you are a bit late to the party

      Whilst I am asking for the impossible, how about people investigating ABAP Unit and TDD as well?

      Cheersy Cheers

      Paul

       

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Hi Paul

      Late to the party indeed.

      Its too bad that all the new ABAPers that get trained on the job or through the Fresher's training or whatever the term different organization use - all use this old style.

      I got trained in 2007 in ABAP, and the 21 days training did not cover CREATE OBJECT at all. OOABAP was treated as a different skillset than ABAP.

      Only if I could go back in time and get started on this in 2007.

       

      Author's profile photo Michael Keller
      Michael Keller

      Little addition: One of the very rare use cases for subroutines in these days is message control. You need a subroutine (form) in a processing program (report) to process a message.

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Hi Michael

      Can you share more information on this? A code snippet or any link may be?

      Author's profile photo Michael Keller
      Michael Keller

      Here's some documentation.

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Thanks Michael. I got it now.

      I believe we would find similar application of subroutines in table maintenance generator events or a substitution routine.  But these would be a must have subroutines till the time there is replacement.

      FMs would also be used in scenarios like screen enhancement for transactions like BP(Business Partner) using the BDT.

      Author's profile photo Arseni Gallardo
      Arseni Gallardo

      Hi Jardish,

       

      You do not need the PERFORM ON COMMIT|ROLLBACK functionality. You can create handler methods for event TRANSACTION_FINISHED of class CL_SYSTEM_TRANSACTION_STATE. The KIND parameter will tell you whether you are in COMMIT or in ROLLBACK.

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Hi Arseni

      Thanks for this tip, I will definitely try this.

      We don't get such use case too often for development, but I will try to check this as I am curious to see how this works.

      Author's profile photo Suhas Saha
      Suhas Saha

      Well, i am not quite sure what you mean. If you want to control the order of execution of the routines you have to use the PERFORM ON COMMIT.

      I don't think there is an alternate solution available yet.

      Author's profile photo Georgi Slavov
      Georgi Slavov

      I started with ABAP in 2008 on 7.02. And form routines were considered obsolete already. Whether you like them or not, or you prefer them to OO artefacts, is a matter of own taste, i guess.

      What really irritates me from the programming language point of view, is that the syntaxes are not identical - who the FORM signature looks like(syntax) and the METHOD signature(syntax).

      Meaning, that there are multiple ways to get the same task done, which i personally don't like.

      Also, they drive you to think differently( slightly different flow, because of the different syntactic possibilities ).

      I would say choose of them, and stick to the approach  - personally try to do everything which is technically possible with the OO syntax, use FMs when needed, and form routines only in exceptional cases.

      Developers using other technologies would probably ask the question 'From when on will the obsolete stuff will become really not supported' 🙂

      I would guess, the way i know SAP, that the language features will be really restricted only on special platforms, like ABAP for Cloud Development(which dont have a huge codebase, yet... ) or NGAP, etc.. So we will be seeing form routines for many years to come 🙂

      Author's profile photo Lars Hvam
      Lars Hvam

      ABAP is such a large language, that personally I cannot remember what is preferred/obsolete, so every time I encounter something, I implement tooling to remind me, this so far has resulted in

      https://github.com/larshp/abapOpenChecks

      and

      https://abaplint.org

      which both have multiple rules to find obsolete syntax,

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Thanks Lars for these links. Its very interesting stuff. I will try to use this and share the result.

      Author's profile photo Jenelyn Tidalgo
      Jenelyn Tidalgo

      Interesting. I also found out recently (this week) that subroutines are already obsolete. Good read!

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Get rid of r_error and use the exceptions all the way.

      The exception handling is one of the best things about using classes/methods. It used to be a pain in the back to do something like that in the routines (you'd have to declare a bunch of r_error's in every one of them just to display an error message somewhere at the top). Once you realize the advantage of exceptions, there is no going back to the subroutines. Makes the code more readable too.

      Thanks for sharing!

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Thanks Jelena. Yes, this is absolutely valid suggestion and I am adopting the use for exceptions in the actual programs. In this blog post I avoided its use because lot of people who are not used to classes and methods will see the exceptions and think its more complicated and stick to performs.

      Author's profile photo Madhu Vadlamani
      Madhu Vadlamani

      Nice.

      Author's profile photo Jagdish Patil
      Jagdish Patil
      Blog Post Author

      Thanks Madhu.

      Author's profile photo Joseph Pamisetti
      Joseph Pamisetti

      Starting Point

      Author's profile photo Bärbel Winkler
      Bärbel Winkler

      This may be a stupid question, but here goes:

      I recently had to apply changes to a typical userexit's Z-Include:

      FUNCTION EXIT_SAPLV50E_003.
      *"----------------------------------------------------------------------
      *"*"Lokale Schnittstelle:
      *"       IMPORTING
      *"             VALUE(I_DOCUMENT) LIKE  RL50E-K_FLAG DEFAULT '2'
      *" ....
      *"       CHANGING
      *"             VALUE(C_EXPORT_LINE_ITEM_DATA) LIKE  EIPO
      *"                             STRUCTURE  EIPO DEFAULT ' '
      *" ...
      *"----------------------------------------------------------------------
        INCLUDE ZXV50U03.
      ENDFUNCTION.

      The logic had thus far been added directly in ZXV50U03 and I wanted to a) create my new (small) logic in its own routine and b) move the existing code from the include into it's own routine. As I was working in the function module context I went the easy route and simply created new form routines which ended up in their own (new) include ZXV50F01 as is usually the case for these user exits. They now get called from within IF-statements:

      IF i_sales_org EQ 'ABCD'.
        PERFORM zz_determine_exart USING i_invoice_item
                                CHANGING c_exart.
      ENDIF.
      
      IF i_invoice_item-werks = 'ABCD'.
        PERFORM zz_determine_verld  USING i_invoice_item
                                 CHANGING c_export_line_item_data-verld.
      ENDIF.
      

      Could I have easily avoided these PERFORMs and if so how? I just can't picture how working with classes and methods would look like in this particular context and how much "overhead" it would generate to set up. Especially given that I did this as part of a fairly quick code change and not some general clean-up task.

      Thanks much and cheers

      Bärbel

      Note: I created a stand-alone question for this now over in Q&A to give it more visibility.