Or how to make your ABAP code more efficient and up to date

Throughout the last few years, SAP updated its underlying programming language ABAP continuously and added more and more features as well as new expressions to make the life of ABAP developers more convenient. This article is meant to give an overview of the major upgrades that the ABAP syntax received with examples of how you, as a developer, can easily include them in your daily routines to improve your company’s codebase.

Inline Declaration

To start with probably the most useful change, we can now use inline declarations for variables where the datatype is defined by the called method or expression.

DATA(lv_string) = zcl_class=>get_string( ).

Method calls

Another very useful change comes with the new method call, which I already used in the previous example. We now do not need to use CALL METHOD anymore but can use a more compact form.

*For static methods use =>
DATA(lv_result) = zcl_class=>static_method( lv_variable ).

*For instance methods use ->
DATA(lv_result) = lr_class_ref->instance_method( lv_variable ).

Class constructor

Analog to the method calls, the constructor can now also be called like in other languages with the use of the keyword NEW.

DATA(lr_object_ref) = NEW zcl_class( lv_param ).

CONV

We can now convert variables to different datatypes without declaring a new helper variable. This can simply be done with the CONV keyword.

DATA(lv_var) = zcl_class=>get_result( iv_string = CONV string(lv_char) ).

To further improve this expression, we do not even have to write the datatype before the brackets. Simply using # will tell the compiler to use the datatype that is needed.

DATA(lv_var) = zcl_class=>get_result( iv_string = CONV #(lv_char) ).

COND

This expression can simply be used to make our code more elegant, as we do not need to write tedious if-else-statements for simple value allocations. It also includes the datatype syntax already described for the CONV keyword.

DATA(lv_mode) = COND #( WHEN iv_test IS INITIAL THEN ‘P‘ ELSE ‘T‘ ).

XSDBOOL

With the help of xsdbool we can easily define a variable with true or false, which in our case is defined as X or space, depending on a logical expression, like we often need as a returning parameter for methods.

data(lv_bool) = xsdbool( lv_messagetype CA ‘AE‘ ).

SWITCH

This keyword is very similar to COND, with the main difference that we can use multiple conditions.

DATA(lv_sys_name) = SWITCH #( iv_sys
                      WHEN ‘P‘ THEN ‘Production‘ 
                      WHEN ‘Q‘ THEN ‘Test‘ 
                      WHEN ‘D‘ THEN ‘Development‘ 
                      ELSE ‘Unknown‘ ).

Internal table functions

The read table expression is a great way to get specific information out of an internal table and it got a new look in the form of internal table functions. Just be careful and use TRY-CATCH blocks around it to prevent dumps.

*Instead of
READ TABLE lt_itab INDEX 1 INTO ls_struc.
*Use this
DATA(ls_itab) = lt_itab[ 1 ].

*Instead of
READ TABLE lt_itab INTO ls_struc WITH KEY id = lv_id.
*Use this
DATA(ls_struc) = lt_itab[ id = lv_id ].

*Instead of
READ TABLE lt_itab WITH KEY id = lv_id TRANSPORTING NO FIELDS.
IF sy-subrc = 0.
DATA(lv_index) = sy-tabix.
ENDIF.
*Use this
IF line_exists( lt_itab[ id = lv_id ] ).
…
ENDIF.
*And/Or this
DATA(lv_index) = line_index( lt_itab[ id = lv_id ] ).

VALUE

With the expression value, we can create new structures as well as new internal tables with a given list of values as the structure of the internal table. If we go further and use BASE, we can create a new table based on an existing one and add a new line to the new internal table in one go.

DATA(ls_struc) = VALUE zstruc_human( name = ‘Bart‘ age = 10 ). 
DATA(lt_itab1) = VALUE ztt_human( ( ls_struc )  
                                  ( name = ‘Lisa‘ age = 8 ) ). 
DATA(lt_itab2) = VALUE #( BASE lt_itab1
                          ( name = ‘Maggie‘ age = 1 ) ).

FOR

This new keyword can be used to transfer data from one internal table to another one. Here we do not have to use loops with appends anymore.
In this example we create a new table with only the gpart numbers from a but000 itab.

DATA(lt_gpart) = VALUE tt_gpart( FOR ls_but IN lt_but ( ls_but-partner ) ).

FILTER

The filter operator enables us to extract lines from one table into another one with specific conditions. This example only takes cities that only contain numbers in its postal code.

DATA(lt_clean_address) = FILTER #( lt_address USING KEY city 
                           WHERE postl CO ‘0123456789‘ ).

This keyword can also be used with a filter table. For example, to allow only entries with the sizes S, M or L in the result table.

DATA(lt_itab) = VALUE ztt_shirts( color = ‘blue‘ size = ‘XS‘ 
                                  color = ‘red‘ size = ‘L‘ 
                                  color = ‘yellow‘ size = ‘M‘ 
                                  color = ‘green‘ size = ‘XL‘ ). 

DATA(lt_filter_tabl) = VALUE #( (‘S‘) (‘M‘) (‘L‘) ). 

DATA(lt_result) = FILTER #( lt_itab IN lt_filter_tab 
                              WHERE size = table_line ). 
*Only the red and yellow shirts will be in lt_result

REDUCE

REDUCE uses the for-logic and further extends its functionality, so that we can build either new tables or simple values as well. Or we can use it to iterate text.

Notes:
If we use UNTIL in a FOR expression with a variable type n, the variable gets increased after every step.

The BASE keyword here uses the itab afterwards as the “base” for a new itab and adds the following expression, in this case the sum of the 2 previous numbers, as a new line to the itab.

INIT implicitly determines the datatype of the variable, in this case “text”.

DATA(lv_res) = REDUCE string( INIT text = ‘Count:‘ 
                              FOR n = 1 UNTIL n > 10 
                              NEXT text = text && | { n }| ). 
* lv_res is now “Count: 1 2 3 4 5”
DATA(lt_itab) = VALUE int_struc( ( 1 ) ( 2 ) ( 3 ) ( 4 ) ).
DATA(lv_sum) = REDUCE #( INIT x = 0 FOR num IN lt_itab NEXT x = x + num ).
* value of lv_sum is now 10
DATA(lt_fibonacci) = REDUCE #( 
                       INIT val = VALUE int_tab( ( 0 ) ( 1 ) ) 
                            x TYPE i 
                            y TYPE i 
                            helper TYPE i 
                       FOR n = 1 UNTIL n > 20 
                       NEXT x = val[ lines( val ) ] 
                            helper = lines( val ) - 1 
                            y = val[ helper ] 
                            val = VALUE #( BASE val ( x + y ) ) ). 
*lt_fibonacci contains now 0, 1, 1, 2, 3, 5, 8, 13, …

CORRESPONDING

Everyone knows the keyword MOVE-CORRESPONDING for assigning fields with the same name of one structure to another structure. CORRESPONDING also got a makeover with new functionalities. But be careful, if you want the same functionality as MOVE-CORRESPONDING, you need to use the BASE keyword, otherwise not only fields with the same name will be overwritten, but all other field values will be lost.

 

*This call has the same functionality as MOVE-CORRESPONDING 
ls_struc2 = CORRESPONDING #( BASE ( ls_struc2 ) ls_struc1 ). 

*This call initializes ls_struc2 first and then maps corresponding fields 
ls_struc2 = CORRESPONDING #( ls_struc1 ). 

*Also possible for internal tables 
lt_itab2 = CORRESPONDING #( BASE ( lt_itab2 ) lt_itab1 ).

The MAPPING and EXCEPT keyword can be used to overrule the mapping of only fields with the same name. In this example field_x of ls_struc2 will be filled with the value of field_y in ls_struc1 and the value of field_z, which exists in both structures, will not be mapped.

ls_struc2 = CORRESPONDING #( ls_struc1
                     MAPPING field_x = field_y
                     EXCEPT  field_z ).

String-Templates

And last but definitely not least the very powerful string templates. By simply using | | we define a string.

DATA(lv_string) = |This is a string.|.

But they can also be used to replace the tedious function call for conversion exit alpha input/output, or multiple other embedded functions like alignment, currency conversion, upper-/lower-case or many more.

DATA(lv_clean_input) = |{ iv_input ALPHA = IN }|.
DATA(lv_output) = |{ lv_value ALPHA = OUT }|.

It also substitutes the concatenate expression.

DATA(lv_string) = |The length of ({ lv_word }) is { strlen(lv_word) }|.

Conclusion

There are many possibilities to improve our ABAP code and make it more compact so let’s go out there and show the world that ABAP does not have to look like a programming language from the last century!

Just be sure to not overdo it with all these fancy new expressions, because sometimes writing a few more lines of code for the sake of better readability and maintainability go a long way for both your future self as well as other developers.