Skip to Content
Technical Articles
Author's profile photo Sougata Chatterjee

Write Smart ABAP – Not Boring ABAP! – Part 1

 

 

Introduction

I do not see many developers using the new, modern and powerful enhancements to the ABAP language yet as a result most of these modern features are not leveraged in the applications which, I believe, were made with an intention to modernise the language as well as simplify the development and maintenance of the application lifecycle.

The intention of this blog series is to highlight the benefits of functional programming by embracing the new ABAP features (and some old techniques as well which are powerful enough but not very popular for reasons unknown) to write smart, exciting, reusable, sustainable code and avoid writing boring, repetitive (often violating the Don’t Repeat Yourself (DRY) principle or the Separation of Concern (SoC) principle), redundant, old fashioned (and often procedural) code which is a nightmare to maintain and/or to be able to extend these applications.

Back to the Future

We will start with one of the powerful features which is already “old” i.e. it’s been there in the system surely for more than a decade but I do not see many developers using it at all – it is the Resumable Exception. I will demonstrate the handling of a same business scenario exception with and without its Resumable option to understand the difference and also the benefits of a modern development design which leverages these powerful features ultimately saving time & effort which equals money to the customer in the long run.

Business Scenario

The business scenario is quite simple. A list of employees are to be reported with their respective Employee Group (EMPTYP), Salary amount (SALARY) and Phone Numbers (PHONE).

However, for EMPTYP = ‘Contractor’, there will be no salary record found in the system (as it is managed by their recruitment agencies) however other information of these contractors like Employee Type and Phone Numbers are still required to be reported.

Also, if Employee Group (EMPTYP) data is not found in the system (data not maintained), they need to be reported as exceptions for those employees.

Technical Design

In the context of the business scenario, the internal table we will be looping on contains References to employee objects for all Types of employees (Employee Type) i.e. Full-Time, Part-Time, Contractors, Casual and so on.

In each iteration of the loop, we will read all required employee data and move them to build another internal table to display the results.

Things to note in the design phase:

  • The contractor group has no pay details stored in the system as they are managed externally – the business however would still like to see the other available data in the system for them.
    • Enter Exception Objects – we could treat this scenario as an exception and handle them separately however we need to make sure that the exception object is managed in a way to be able to still process the employee attributes for which the data is available.
  • As the attribute Employee Type is quite important to the organisation, any employee with missing Employee Type record needs to reported as exceptions so that their master data can then be maintained.
    • Sounds like a straight forward case of an Exception Object which needs to be handled and then excluded from processing of the report.

Implementation

Exception handling WITH the Resumable addition

Report ztest.

CLASS lcl_employee DEFINITION FINAL CREATE PUBLIC.

  PUBLIC SECTION.

    TYPES: BEGIN OF empl_data,
             empid  TYPE int4,          "Employee ID
             emptyp TYPE string,        "Org Assignment data
             salary TYPE decfloat16,    "Pay data
             phone  TYPE numc10,        "Communication data
           END OF empl_data,
     empl_data_t TYPE SORTED TABLE OF empl_data WITH UNIQUE KEY empid.

    METHODS constructor IMPORTING VALUE(i_empid)   TYPE int4.
    METHODS get_data    RETURNING VALUE(rs_result) TYPE empl_data
                        RAISING RESUMABLE(cx_no_data_found).

  PRIVATE SECTION.

    DATA  emp_id       TYPE int4.

    METHODS get_emptyp RETURNING VALUE(r_result) TYPE string
                       RAISING cx_no_data_found.
    METHODS get_salary RETURNING VALUE(r_result) TYPE decfloat16
                       RAISING RESUMABLE(cx_no_data_found).
    METHODS get_phone  RETURNING VALUE(r_result) TYPE numc10.
    METHODS get_emp_id RETURNING VALUE(r_result) TYPE int4.

ENDCLASS.

CLASS lcl_employee IMPLEMENTATION.

  METHOD constructor.
    me->emp_id = i_empid.
  ENDMETHOD.

  METHOD get_data.
    rs_result = VALUE #( empid  = me->get_emp_id( )
                         emptyp = me->get_emptyp( )
                         salary = me->get_salary( )
                         phone  = me->get_phone( )
                       ).
  ENDMETHOD.

  METHOD get_emptyp.
    r_result = SWITCH #( me->get_emp_id( )
                WHEN 1 THEN |Full-Time|
                WHEN 2 THEN |Part-Time|
                WHEN 3 THEN |Contractor|
                WHEN 4 THEN |Casual|
                ELSE THROW cx_no_data_found(
                            rel_proc_id = CONV #( me->get_emp_id( ) ) )
              ).
  ENDMETHOD.

  METHOD get_phone.
    r_result = SWITCH #( me->get_emptyp( )
                WHEN `Full-Time` THEN |1234567890|
                WHEN `Part-Time` THEN |5678901234|
                WHEN `Casual`    THEN |7890123456|
                ELSE |0399999999|
              ).
  ENDMETHOD.

  METHOD get_salary.
    r_result = SWITCH #( me->get_emptyp( )
                WHEN `Full-Time` THEN 50000
                WHEN `Part-Time` THEN 25000
                WHEN `Casual`    THEN 5000
                ELSE THROW RESUMABLE cx_no_data_found( 
                            rel_proc_id = CONV #( me->get_emp_id( ) ) )
               ).
  ENDMETHOD.

  METHOD get_emp_id.
    r_result = me->emp_id.
  ENDMETHOD.

ENDCLASS.

 DATA extract_t TYPE lcl_employee=>empl_data_t.
 DATA error_t   TYPE string_table.

START-OF-SELECTION.

 DATA(all_employees_t) = VALUE int4_table(  ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ).

  LOOP AT all_employees_t REFERENCE INTO DATA(dref).
    TRY.
      INSERT NEW lcl_employee( dref->* )->get_data( ) INTO TABLE extract_t.

      CATCH BEFORE UNWIND cx_no_data_found INTO DATA(lx_no_data).
        IF lx_no_data->is_resumable = abap_true. 
         "Resumable Exception was raised
          RESUME.
        ELSE.
         "Non-Resumable Exception was raised
          error_t = VALUE #( BASE error_t ( lx_no_data->get_text( ) ) ).
        ENDIF.
    ENDTRY.
  ENDLOOP.

 cl_demo_output=>new( )->write( extract_t )->write( error_t )->display( ).

 

Exception handling WITHOUT the Resumable addition

The implementation of the Local Class (below code snippet) needs to be slightly different in this case as the calling program would want to know for which specific data the exception was raised whereas the above implementation (with the Resumable addition) simply does not care.

CLASS lcl_employee DEFINITION FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES: BEGIN OF empl_data,
             empid  TYPE int4,          "Employee ID
             emptyp TYPE string,        "Org Assignment data
             salary TYPE decfloat16,    "Pay data
             phone  TYPE numc10,        "Communication data
           END OF empl_data,
     empl_data_t TYPE SORTED TABLE OF empl_data WITH UNIQUE KEY empid.

    METHODS constructor IMPORTING VALUE(i_empid)   TYPE int4.
    METHODS get_emptyp  RETURNING VALUE(r_result) TYPE string
                        RAISING cx_no_data_found.
    METHODS get_salary  RETURNING VALUE(r_result)  TYPE decfloat16
                        RAISING cx_no_data_found.
    METHODS get_phone   RETURNING VALUE(r_result)  TYPE numc10.
    METHODS get_emp_id  RETURNING VALUE(r_result)  TYPE int4.

  PRIVATE SECTION.
    DATA emp_id TYPE int4.

ENDCLASS.

CLASS lcl_employee IMPLEMENTATION.

  METHOD constructor.
    me->emp_id = i_empid.
  ENDMETHOD.

  METHOD get_emptyp.
    r_result = SWITCH #( me->get_emp_id( )
                WHEN 1 THEN |Full-Time|
                WHEN 2 THEN |Part-Time|
                WHEN 3 THEN |Contractor|
                WHEN 4 THEN |Casual|
                ELSE THROW cx_no_data_found(
                            rel_proc_id = CONV #( me->get_emp_id( ) ) )
              ).
  ENDMETHOD.

  METHOD get_phone.
    r_result = SWITCH #( me->get_emptyp( )
                WHEN `Full-Time` THEN |1234567890|
                WHEN `Part-Time` THEN |5678901234|
                WHEN `Casual`    THEN |7890123456|
                ELSE |0399999999|
              ).
  ENDMETHOD.

  METHOD get_salary.    
    r_result = SWITCH #( me->get_emptyp( )
                 WHEN `Full-Time` THEN 50000
                 WHEN `Part-Time` THEN 25000
                 WHEN `Casual`    THEN 5000
                 ELSE THROW cx_no_data_found(
                            rel_proc_id = CONV #( me->get_emp_id( ) ) )
               ).
  ENDMETHOD.

  METHOD get_emp_id.
    r_result = me->emp_id.
  ENDMETHOD.

ENDCLASS.

And the calling program (the consumer) is different as well.

Report ztest.

DATA extract_t TYPE lcl_employee=>empl_data_t.
DATA error_t   TYPE string_table.

START-OF-SELECTION.

 DATA(all_employees_t) = VALUE int4_table(  ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ).

  LOOP AT all_employees_t REFERENCE INTO DATA(dref).
 
   DATA(ref) = NEW lcl_employee( dref->* ).
   
   TRY.
      DATA(extract) = VALUE lcl_employee=>empl_data(
                        empid = ref->get_emp_id( )
                        emptyp = ref->get_emptyp( )
                        phone  = ref->get_phone( )
                      ).
 
      CATCH cx_no_data_found INTO DATA(lx_no_data).
        error_t = VALUE #( BASE error_t ( lx_no_data->get_text( ) ) ).
        CONTINUE.
    ENDTRY.
   
"Note that the call to the method GET_SALARY( ) is separated 
    TRY.
      extract = VALUE #( BASE extract
                   salary = ref->get_salary( )
                 ).
      CATCH cx_no_data_found INTO lx_no_data.
    ENDTRY.

    INSERT extract INTO TABLE extract_t.
  ENDLOOP.

  cl_demo_output=>new( )->write( extract_t )->write( error_t )->display( ).

 

Flexibility of the Resumable Exceptions

Now another similar requirement comes along in the near future but this time the Contractor group is required to be excluded from the report when their salary records are not found in the system and they need to be reported as exceptions as well. All other requirements remain the same.

New Implementation

    – Changes to the Resumable Exception handling

To meet this requirement, the developer who used the Resumable option achieves it very quickly by making minimal changes to the calling program as seen in the code snippet below.

The only change he does is that he handles the Resumable exception (that was raised by the method) as a normal exception object without having to modify the local class; but most importantly, he does not disturb the main processing of the calling program (the consumer).

Report ztest_new.

DATA extract_t TYPE lcl_employee=>empl_data_t.
DATA error_t   TYPE string_table.

START-OF-SELECTION.

 DATA(all_employees_t) = VALUE int4_table(  ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ).

  LOOP AT all_employees_t REFERENCE INTO DATA(dref).
    TRY.
      INSERT NEW lcl_employee( dref->* )->get_data( ) INTO TABLE extract_t.

"This time around it was not handled as a Resumable Exception 
     CATCH cx_no_data_found INTO DATA(lx_no_data). "<--Handled normally
       error_t = VALUE #( BASE error_t ( lx_no_data->get_text( ) ) ).
    ENDTRY.
  ENDLOOP.

cl_demo_output=>new( )->write( extract_t )->write( error_t )->display( ).

 

   – Changes to the Non-Resumable Exception handling

Now the implementation for the calling program (the consumer) needs to change to accommodate the changes to the business requirement.

The developer who did not use the Resumable option has to now modify the processing logic of the consumer to accommodate the changes in the business requirement.

Report ztest_new.

DATA extract_t TYPE lcl_employee=>empl_data_t.
DATA error_t   TYPE string_table.

START-OF-SELECTION.

DATA(all_employees_t) = VALUE int4_table(  ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ).

LOOP AT all_employees_t REFERENCE INTO DATA(dref).

  TRY.
    INSERT VALUE #( 
            LET ref = NEW lcl_employee( dref->* ) IN
             empid  = ref->get_emp_id( )
             emptyp = ref->get_emptyp( )
             phone  = ref->get_phone( )
             salary = ref->get_salary( )
           )
    INTO TABLE extract_t.

   CATCH cx_no_data_found INTO DATA(lx_no_data).
      error_t = VALUE #( BASE error_t ( lx_no_data->get_text( ) ) ).
  ENDTRY.

ENDLOOP.

cl_demo_output=>new( )->write( extract_t )->write( error_t )->display( ).

 

Conclusion

We have demonstrated the difference between raising and handling of an exception object with and without the Resumable addition/option.

We have also demonstrated the benefits of using Resumable exceptions to simplify developments.

Resumable option of the Exception object is a powerful feature and they are extremely flexible yet simple to implement. They can save developers a lot of time and effort especially in regards to reusability of common objects / framework in the system as well as simplifying implementations across the SAP system landscape.

In the next blog of this series we will focus on some of the important principles of OO development and apply those concepts on the modern (smart) ABAP.

Assigned Tags

      26 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Christopher Linke
      Christopher Linke

      That must look like another programming language to many ABAPers 😀

      Author's profile photo Mithun Kumar
      Mithun Kumar

      LoL... You mean, to the 'boring' ABAPers 😀

      Author's profile photo Sandra Rossi
      Sandra Rossi

      Thank you for this example. Resumable exceptions are available since ABAP 7.02 ("more than a decade" as you say).

      More information in the ABAP documentation:

      Demo program:

      • DEMO_CATCH_EXCEPTION

      Other blog posts:

      Author's profile photo Lubomir Markovic
      Lubomir Markovic

      I consider the blogs you mentioned as more relevant for using resumable feature.

      Example from this blog has key point:  Raising of the resumable exceptions is motivated by caller needs.

      The proper usage should be motivated by the needs of the method raising the exception ... it want to provide the possibility to continue in processing even some exceptional situations appeared.

       

      Author's profile photo Javier Rodriguez Prieto
      Javier Rodriguez Prieto

      Hello,

      Thanks for sharing, one quick question here:

            extract = VALUE #( BASE extract
                         salary = ref->get_salary( )
                       ).

      What is the idea? i mean what is the result to get here?

      But if i remember well the command extract is obsolete syntax. is the same command?

      Thanks

      Author's profile photo Sougata Chatterjee
      Sougata Chatterjee
      Blog Post Author

      Hi Javier,

      DATA(extract) = VALUE lcl_employee=>empl_data(
                              empid = ref->get_emp_id( )
                              emptyp = ref->get_emptyp( )
                              phone  = ref->get_phone( )
                            ).

      extract is a workarea of type lcl_employee=>empl_data. I'm also not familiar with any extract command 🙂

      extract = VALUE #( BASE extract
                         salary = ref->get_salary( )
                       ).

      The idea above is to copy the value of the salary field into the same salary field of the workarea extract without touching the other fields of the workarea extract.

      Regards,

      Sougata.

      Author's profile photo Shai Sinai
      Shai Sinai

      Functional programming to the extreme 😉

      Author's profile photo Gurpreet Jaspal
      Gurpreet Jaspal

      That is perfect example. ABAP should be written in a way that it resembles a natural spoken language. And thanks to the new updates, all of these are possible. Less code and more meaning is the key to it and of course with such flexibility as demonstrated.

      Just my 2 cents. These statements which provides a boolean output can be directly added in a IF statement without have to check its result.

      Eg.

      IF lx_no_data->is_resumable IS NOT INITIAL. 

      can be written as

      IF lx_no_data->is_resumable

      Thanks for sharing this and hope to see more content from you.

      Regards
      Gurpreet

      Author's profile photo Sougata Chatterjee
      Sougata Chatterjee
      Blog Post Author

      Well said - but without the IS NOT INITIAL produces a syntax error in the NW version I'm on 750 SP 0013 - calling a method however without the IS NOT INITIAL part is OK for e.g.

      IF lx_no_data->get_text( ).

       Does lx_no_data->is_resumable work for you? Which NW version are you on?

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Since it's a boolean (well, ABAP "pseudo-boolean" 🙂 ) result, then this option would be better, I think:

      IF lx_no_data->is_resumable = abap_true.
      Author's profile photo Sougata Chatterjee
      Sougata Chatterjee
      Blog Post Author

      Jelena Perfiljeva Agreed.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      It is very good. But not quite perfect. lx_ ???!!! How about no_data_error !

      Author's profile photo Sougata Chatterjee
      Sougata Chatterjee
      Blog Post Author

      Oops! 😬 😊

      Author's profile photo Gurpreet Jaspal
      Gurpreet Jaspal

      My bad. It does not work for me either. I guess it only works for methods which returns boolean value like below. Hopefully in upcoming releases this will be taken care of.

      REPORT  ztest_gj.
      
      CLASS lcl_mpc DEFINITION CREATE PUBLIC.
        PUBLIC SECTION.
          METHODS constructor.
          METHODS is_valid
            IMPORTING i_read_this        TYPE char10
            RETURNING VALUE(rv_answer) TYPE abap_bool.
      ENDCLASS.
      
      CLASS lcl_mpc IMPLEMENTATION.
        METHOD constructor.
      
        ENDMETHOD.
        METHOD is_valid.
      * Some validation logic
          IF i_read_this NE space.
            rv_answer = abap_true.
          ENDIF.
        ENDMETHOD.
      
      ENDCLASS.
      
      
      START-OF-SELECTION.
        DATA(lo_model) = NEW lcl_mpc( ).
        IF lo_model->is_valid( 'check this' ).
          WRITE 'All good'.
        ELSE.
          WRITE 'Not valid'.
        ENDIF.

      Regards
      Gurpreet

      Author's profile photo Matthew Billingham
      Matthew Billingham

      I remember when the IF conditional_method( ) syntax came in (as a result of a suggestion in this forum). Horst Keller explained then why you never get IF ref->conditional on its own. Something to do with backwards compatability.

      Author's profile photo Manfred Klein
      Manfred Klein

      Ha ha, funny.

      The resumable exception. Sounds like the ignorable stop sign(and wrong way sign). This proofs again: Traffic signs have only advisory function.

      Thank god traffic rules are not designed by ABAP OO architects.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Great blog. Nicely written, only one prefixed variable 😀, clear clean code and clear explanation.

      I'll look out for opportunities to use it.

      Author's profile photo Lahiru Madhumadha
      Lahiru Madhumadha

      Thank You! Very Informative.

      Author's profile photo C Currey
      C Currey

      I appreciate your effort, but IMO a business language should be understandable by business-types.  Sorry, but the above code to me is "forward into the past" gobbly gook, like C/Java/JS and the rest of the C-derivatives.  IMO being professional is to make code readable to those who are not experts in ABAP (newer or older variants) as well as to those who are not privy to the specifications.  To me, that means LOTS of comments no matter how readable you may think that the code is.  Code without comments is not just impolite, but also unprofessional to those who follow (which might just be you a year down the road).  That is smart ABAP to me.

      Author's profile photo Sougata Chatterjee
      Sougata Chatterjee
      Blog Post Author

      Thanks for your comments C Currey . You are not alone, I come across developers every day who are unwilling to change their ways for what they had been doing for decades; the word "Change" scares the living daylight out of them.

      As for your comment on the (LOTS of) comments part, in the source code examples enumerated in this blog the method names are descriptive enough to understand their purpose; the processing logic and the design of the application is discussed under the Technical Design part of this blog which you may have overlooked.

      Author's profile photo C Currey
      C Currey

      Ok, then we disagree.  I always write lots of comments describing the "what" is to be done and not just the technical "how".  No matter how self-descriptive I think that the code may be method/variable names etc. may not be to others.  What is descriptive enough to me when I am in the weeds of the project may fall short later.  Also, I want a functional consultant to be able to skim the comment blocks and have an idea of what is beng done and why.  Thereafter, I use these comments as the base to write the online help (e.g. Ctrl+Shift+F9).

      I have been using the "newer" 7.4+ ABAP syntax for a good five plus years now, OO in ABAP before that along with ADT/Eclipse.  I like most of the newer syntax for brevity and the new functionalities, but I am not a fan when it makes the code less like English.  I have to code so that others in my organization can know what is going on, too. Change is not foreign to me nor I notice to most developers, quite the opposite.  TDD I like to use for making the code less fragile even though it takes longer at first.

      BTW, my first technical manager made us write the user manual and have that presented to the end users and accepted before we were allowed to code even one line.  I hated it, but there was less miscommunication and change requests/fixes later.  KISS (keep it simple, stupid) principle.

      Again, I like your blogs and thanks for doing them.

      Author's profile photo Gianpietro Dal Zio
      Gianpietro Dal Zio

      Hello C Currey,

      I understand both aspects, but we belong more to the faction: the code must be self-explanatory.

      Bad comments (superflous?)degrade the readability and comprehensibility of the code
      Comments make the most sense to understand why you do something in the place and not after.
      But you never stop learning.
      Can you maybe give a concrete example how you would document the code of Sougata Chatterjee ?

      For example, we find such comments rather disadvantageous for the code readability and comprehensibility:

      "setting matrial number to the filter
      my_filter_object->set_material_no( other_obj->get_material_no( ) ).
      
      "setting date from to the filter
      my_filter_object->set_date_from( other_obj->get_creation_date( ) ).

       

      Author's profile photo C Currey
      C Currey

      I guess that puts me in a different "faction" then. ;>)

      Your example is to me why "self-explanatory" objects still need comments,
      especially when objects are chained. To my non-OO colleagues, functional
      colleagues, or even "future-me" in 10 months is it immediately clear if I am reading or writing a material number and date somewhere?  I would rather be lazy in the future and read the comments rather than lazy at development time and not write comments.  Maintaining the comments I see as just keeping code from becoming brittle and keeping it up to date.

      *--------------------------------------------------------------------------------------*
      * Store material number w/which we are working in an instance variable.
      *--------------------------------------------------------------------------------------*
      my_filter_object->set_material_no( other_obj->get_material_no( ) ).

      *--------------------------------------------------------------------------------------*
      * Store date of material number w/which we are working in an instance variable.
      *--------------------------------------------------------------------------------------*
      my_filter_object->set_date_from( other_obj->get_creation_date( ) ).

      Author's profile photo Gianpietro Dal Zio
      Gianpietro Dal Zio

      :>) Thanks for the insight.
      It's certainly also dependent on the company context.
      We, model software since ~25 years according to the OOA/OOD principle.
      Started with Smalltalk then Java, etc.
      So it is normal that almost all colleagues who have to look at code or change it, understand the OO principles and notation.

      One of our important rules is: a method should be completely readable on the current monitor resolution without scrolling (the monitors are horizontally oriented :)).

      This is not always possible, but most of the time it is possible.

      Author's profile photo C Currey
      C Currey

      Ahh, Smalltalk and Rexx - first love programming languages!

      Nice point about line lengths, that  will make debugging easier, too.

      Author's profile photo Harikiran Mitnala
      Harikiran Mitnala

      Initiation is most important rather keep calm, You did it . Now it is floating. Thanks for the updates.