无常笔记ABAP Cheat Sheets中文导读全站搜索

英文原文:SAP-samples · 上游版本 d43eb13c4c7c · 原始 Markdown

Program Flow Logic

This cheat sheet gathers information on program flow logic. Find more details here in the ABAP Keyword Documentation.

Introduction

In ABAP, the flow of a program is controlled by control structures, procedure calls and the raising or handling of exceptions.

Using control structures as an example, you can determine the conditions for further processing of code, for example, if at all or how often a statement block should be executed. Control structures - as, for example, realized by an IF ... ELSEIF ... ELSE ... ENDIF. statement - can include multiple statement blocks that are executed depending on conditions.

In a very simple form, such an IF statement might look as follows:

DATA(num) = 1 + 1.

"A simple condition: Checking if the value of num is 2
IF num = 2.
  ... "Statement block
      "Here goes some code that should be executed if the condition is true.
ELSE.
  ... "Statement block
      "Here goes some code that should be executed if the condition is false.
      "For example, if num is 1, 8, 235, 0 etc., then do something else.
ENDIF.

⬆️ back to top

Expressions and Functions for Conditions

"Single relational expression
IF num = 1.
 ...
ENDIF.

"Multiple expressions
IF num = 1 AND flag = 'X'.
 ...
ENDIF.

IF num = 1 OR flag = 'X'.
 ...
ENDIF.

"Multiple expressions can be parenthesized explicitly
IF ( num = 1 AND flag = 'X' ) OR ( num = 2 AND flag = 'X' ).
 ...
ENDIF.

The following code snippet shows a selection of possible expressions and operands of such expressions using a big IF statement. Certainly, such a huge statement is far from ideal. Here, the intention is to just cover many syntax options in one go for demonstration purposes. For more information on built-in functions, you can refer to the Built-In Functions cheat sheet.

"Some declarations to be used in the IF statement below
DATA(num) = 2.                    "integer
DATA(empty_string) = ``.          "empty string
DATA(flag) = 'x'.
DATA(dref) = NEW string( `ref` ). "data reference variable

"Object reference variable
DATA oref TYPE REF TO object.
"Creating an object and assigning it to the reference variable
oref = NEW zcl_demo_abap_prog_flow_logic( ).

"Declaration of and assignment to a field symbol
FIELD-SYMBOLS <fs> TYPE string.
ASSIGN `hallo` TO <fs>.

"Creating an internal table of type string inline
DATA(str_table) = VALUE string_table( ( `a` ) ( `b` ) ( `c` ) ).

"The following IF statement includes multiple expressions combined by AND to demonstrate different options

"Comparisons
IF 2 = num    "equal, alternative EQ
AND 1 <> num  "not equal, alternative NE
AND 1 < num   "less than, alternative LT
AND 3 > num   "greater than, alternative GT
AND 2 >= num  "greater equal, alternative GE
AND 2 <= num  "less equal, alternative LE

"Checks whether the content of an operand is within a closed interval
AND num BETWEEN 1 AND 3
AND NOT num BETWEEN 5 AND 7   "NOT negates a logical expression
AND ( num >= 1 AND num <= 3 ) "Equivalent to 'num BETWEEN 1 AND 3';
                              "here, demonstrating the use of parentheses

"Comparison operators CO, CN ,CA, NA, CS, NS, CP, NP for character-like data types;
"see the cheat sheet on string processing

"Predicate Expressions
AND empty_string IS INITIAL  "Checks whether the operand is initial. The expression
                             "is true, if the operand contains its type-dependent initial value
AND num IS NOT INITIAL       "NOT negates

AND dref IS BOUND  "Checks whether a data reference variable contains a valid reference and
                   "can be dereferenced;
                   "Negation (IS NOT BOUND) is possible which is also valid for the following examples
AND oref IS BOUND  "Checks whether an object reference variable contains a valid reference

"IS INSTANCE OF checks whether for a
"a) non-initial object reference variable the dynamic type
"b) for an initial object reference variable the static type
"is more specific or equal to a comparison type.
AND oref IS INSTANCE OF zcl_demo_abap_prog_flow_logic
AND oref IS INSTANCE OF if_oo_adt_classrun

AND <fs> IS ASSIGNED  "Checks whether a memory area is assigned to a field symbol

"See the predicate expression IS SUPPLIED in the executable example.
"It is available in method implementations and checks whether a formal parameter
"of a procedure is filled or requested.

"Predicate function: Some examples
AND contains( val = <fs> pcre = `\D` )  "Checks whether a certain value is contained;
                                        "the example uses the pcre parameter for regular expressions;
                                        "it checks whether there is any non-digit character contained
AND matches( val = <fs> pcre = `ha.+` ) "Compares a search range of the argument for the val parameter;
                                        "the example uses the pcre parameter for regular expressions;
                                        "it checks whether the value matches the pattern 'ha'
                                        "and a sequence of any characters

"Predicate functions for table-like arguments
"Checks whether a line of an internal table specified in the table expression
"exists and returns the corresponding truth value.
AND line_exists( str_table[ 2 ] )

"Predicative method call
"The result of the relational expression is true if the result of the functional method call
"is not initial and false if it is initial. The data type of the result of the functional method call,
"i. e. the return value of the called function method, is arbitrary.
"A check is made for the type-dependent initial value.
AND check_is_supplied( )
"It is basically the short form of such a predicate expression:
AND check_is_supplied( ) IS NOT INITIAL

"Boolean Functions
"Determine the truth value of a logical expression specified as an argument;
"the return value has a data type dependent on the function and expresses
"the truth value of the logical expression with a value of this type.

"Function boolc: Returns a single-character character string of the type string.
"If the logical expression is true, X is returned. False: A blank is returned.
"Not to be compared with the constants abap_true and abap_false in relational expressions,
"since the latter convert from c to string and ignore any blanks. Note: If the logical
"expression is false, the result of boolc does not meet the condition IS INITIAL since
"a blank and no empty string is returned. If this is desired, the function xsdbool
"can be used instead of boolc.
AND boolc( check_is_supplied( ) ) = 'X'

"Result has the same ABAP type as abap_bool.
AND xsdbool( check_is_supplied( ) ) = abap_true

"Examples for possible operands

"Data objects as shown in the examples above
AND 2 = 2
AND num = 2

"Built-in functions
AND to_upper( flag ) = 'X'
AND NOT to_lower( flag ) = 'X'

"Numeric functions
AND ipow( base = num exp = 2 ) = 4

"Functional methods
"Assume such a method exists having one return value
AND addition( num1 = 1 num2 = 1 ) = 2

"Calculation expressions
AND 4 - 3 + 1 = num

"String expressions
AND `ha` && `llo` = <fs>

"Constructor expression
AND VALUE i( ) = 0
AND VALUE string_table( ( `a` ) ( `b` ) ( `c` ) ) = str_table

"Table expression
AND str_table[ 2 ] = `b`.

  ... "All of the logical expressions are true.

ELSE.

  ... "At least one of the logical expressions is false.

ENDIF.

[!NOTE]

[!TIP] Find more information in the Logical Expressions and Functions ABAP cheat sheet.

⬆️ back to top

Control Structures

IF Statements

DATA(abap) = `ABAP`.
FIND `AB` IN abap.
IF sy-subrc = 0.
  "found
  ...
ELSE.
  "not found
  ...
ENDIF.

"IF statement with multiple included ELSEIF statements
DATA(current_utc_time) = cl_abap_context_info=>get_system_time( ).
DATA greetings TYPE string.

IF current_utc_time BETWEEN '050000' AND '115959'.
  greetings = |Good morning, it's { current_utc_time TIME = ISO }.|.
ELSEIF current_utc_time BETWEEN '120000' AND '175959'.
  greetings = |Good afternoon, it's { current_utc_time TIME = ISO }.|.
ELSEIF current_utc_time BETWEEN '180000' AND '215959'.
  greetings =  |Good evening, it's { current_utc_time TIME = ISO }.|.
ELSE.
  greetings = |Good night, it's { current_utc_time TIME = ISO }.|.
ENDIF.

Control structures can be nested.

DATA(num) = 1.
DATA(flag) = 'X'.

IF num = 1.

  IF flag = 'X'.
   ...
   ELSE.
    ...
  ENDIF.

ELSE.

  ... "statement block, e. g.
      "ASSERT 1 = 0.
      "Not to be executed in this example.

ENDIF.

[!NOTE]

⬆️ back to top

Excursion: COND Operator

DATA greetings_cond TYPE string.
DATA(current_utc_time_cond) = cl_abap_context_info=>get_system_time( ).
greetings_cond = COND #( WHEN current_utc_time_cond BETWEEN '050000' AND '115959' THEN |Good morning, it's { current_utc_time_cond TIME = ISO }.|
                         WHEN current_utc_time_cond BETWEEN '120000' AND '175959' THEN |Good afternoon, it's { current_utc_time_cond TIME = ISO }.|
                         WHEN current_utc_time_cond BETWEEN '180000' AND '215959' THEN |Good evening, it's { current_utc_time_cond TIME = ISO }.|
                         ELSE |Good night, it's { current_utc_time_cond TIME = ISO }.| ).

[!NOTE]
There are special rules regarding type inference when using expressions with COND and SWITCH with # for actual parameters in case of generic formal parameters. Find more information here.

⬆️ back to top

CASE: Case Distinctions

"Getting a random number
DATA(random_num) = cl_abap_random_int=>create( seed = cl_abap_random=>seed( )
                                               min  = 1
                                               max  = 5 )->get_next( ).
DATA num TYPE string.

CASE random_num.
  WHEN 1.
    num = `The number is 1.`.
  WHEN 2.
    num = `The number is 2.`.
  WHEN 3 OR 4.
    num = `The number is either 3 or 4.`.
  WHEN OTHERS.
    num = `The number is not between 1 and 4.`.
ENDCASE.

⬆️ back to top

Control Structures Using CASE TYPE OF

Special control structure introduced by CASE TYPE OF: Checks the type of object reference variables. An object reference variable with the static type of a class or an interface must be specified after CASE TYPE OF.

"The example shows the retrieval of type information at runtime (RTTI).
"For more information on RTTI, refer to the Dynamic Programming cheat
"sheet. The result of the method call is a type description object
"that points to one of the classes specified.
DATA stringtab TYPE TABLE OF string WITH EMPTY KEY.
DATA(type_description) = cl_abap_typedescr=>describe_by_data( stringtab ).

CASE TYPE OF type_description.
  WHEN TYPE cl_abap_elemdescr.
    ...
  WHEN TYPE cl_abap_refdescr.
    ...
  WHEN TYPE cl_abap_structdescr.
    ...
  WHEN TYPE cl_abap_tabledescr.
    "This is the class the type description object points to for 
    "the example data object.
    ...
  WHEN OTHERS.
    ...
ENDCASE.

⬆️ back to top

Excursion: SWITCH Operator

The conditional operator SWITCH can also be used to make case distinctions in operand positions. As mentioned above for COND, a result is constructed. The same criteria apply for SWITCH as for COND regarding the type. See also the ABAP Keyword Documentation and the Constructor Expressions cheat sheet for more information and examples.

DATA(random_num_switch) = cl_abap_random_int=>create( seed = cl_abap_random=>seed( )
                                                      min  = 1
                                                      max  = 5 )->get_next( ).

DATA(num_switch) = SWITCH #( random_num_switch
                             WHEN 1 THEN `The number is 1.`
                             WHEN 2 THEN `The number is 2.`
                             WHEN 3 OR 4 THEN `The number is either 3 or 4.`
                             ELSE `The number is not between 1 and 4.` ).

⬆️ back to top

Loops

DO: Unconditional Loops

⬆️ back to top

Interrupting and Exiting Loops

The following ABAP keywords are available for interrupting and exiting loops:

Keyword Syntax Details
CONTINUE CONTINUE. The current loop pass is terminated immediately and the program flow is continued with the next loop pass.
CHECK CHECK log_exp. Conditional termination. If the logical expression log_exp is false, the current loop pass is terminated immediately and the program flow is continued with the next loop pass.
EXIT EXIT. The loop is terminated completely. The program flow resumes after the closing statement of the loop.
*&---------------------------------------------------------------------*
*& CONTINUE
*&---------------------------------------------------------------------*

DATA str_c TYPE string.
DO 15 TIMES.
  "Continue with the next loop pass if the number is even
  "Terminating the loop pass and continuing with the next loop pass if the condition specified
  "with the IF statement is true (if the number is even)
  IF sy-index MOD 2 = 0.
    CONTINUE.
  ELSE.
    str_c = |{ str_c }{ COND #( WHEN str_c IS NOT INITIAL THEN `, ` ) }{ sy-index }|.
  ENDIF.
ENDDO.
"str_c: 1, 3, 5, 7, 9, 11, 13, 15

*&---------------------------------------------------------------------*
*& CHECK
*&---------------------------------------------------------------------*

DATA str_d TYPE string.
DO 15 TIMES.
  "Terminating the loop pass and continuing with the next loop pass if the condition is
  "true (if the number is odd)
  CHECK sy-index MOD 2 = 0.
  str_d = |{ str_d }{ COND #( WHEN str_d IS NOT INITIAL THEN `, ` ) }{ sy-index }|.
ENDDO.
"str_d: 2, 4, 6, 8, 10, 12, 14

"CHECK NOT
DATA str_e TYPE string.
DO 15 TIMES.
  "Here, it is checked whether the sy-index value is not an even number
  CHECK NOT sy-index MOD 2 = 0.
  str_e = |{ str_e }{ COND #( WHEN str_e IS NOT INITIAL THEN `, ` ) }{ sy-index }|.
ENDDO.
"str_e: 1, 3, 5, 7, 9, 11, 13, 15

*&---------------------------------------------------------------------*
*& EXIT
*&---------------------------------------------------------------------*

DATA str_f TYPE string.
DO 15 TIMES.
  "Terminating the entire loop
  IF sy-index = 7.
    EXIT.
  ELSE.
    str_f = |{ str_f }{ COND #( WHEN str_f IS NOT INITIAL THEN `, ` ) }{ sy-index }|.
  ENDIF.
ENDDO.
"str_f: 1, 2, 3, 4, 5, 6

[!NOTE]

⬆️ back to top

WHILE: Conditional Loops

DATA int_itab TYPE TABLE OF i WITH EMPTY KEY.

WHILE lines( int_itab ) < 5.
  int_itab = VALUE #( BASE int_itab ( sy-index ) ).
ENDWHILE.

"Content of int_itab:
"1
"2
"3
"4
"5

"The following string replacement example uses a WHILE
"statement for demo purposes - instead of using a REPLACE ALL 
"OCCURRENCES statement or the replace function to replace all 
"occurrences. The WHILE loop exits when there are no more '#' 
"characters to be replaced in the string. The value of the data 
"object, which is checked in the logical expression, is then set 
"to make the logical expression false.
DATA(str_to_replace) = `Lorem#ipsum#dolor#sit#amet`.
DATA(subrc) = 0.
DATA(counter) = 0.
WHILE subrc = 0.
  REPLACE `#` IN str_to_replace WITH ` `.
  IF sy-subrc <> 0.
    subrc = 1.
  ELSE.
    counter += 1.
  ENDIF.
ENDWHILE.

"str_to_replace: Lorem ipsum dolor sit amet
"counter: 4

⬆️ back to top

Loops Across Tables

Further keywords for defining loops are as follows. They are not dealt with here since they are covered in other ABAP cheat sheets.

⬆️ back to top

Calling Procedures

Procedures can be explicitly called within an ABAP program, thereby influencing the program flow logic.

Methods of Classes

In modern ABAP programs, classes and methods are the way to go for modularization purposes (instead of function modules and subroutines in most cases; the latter is only available in Standard ABAP). Note that methods and calling methods are described in the context of the ABAP Object Orientation cheat sheet. Find more information and examples there.

[!NOTE]
Find information on events in the ABAP Object Orientation cheat sheet.

⬆️ back to top

Function Modules

[!NOTE] In ABAP for Cloud Development, function modules can technically be used, but they are not recommended for new implementations. Many features available in standard ABAP, such as various includes, are not compatible with ABAP for Cloud Development (for example, dynpro-related functionality).

Function modules ...

Function pools ...

Calling Function Modules

Syntax to call function modules:

CALL FUNCTION func params.

Example function module calls with parameter passing and exception handling:

"Handling non-class-based exception when they are defined using EXCEPTIONS
DATA it TYPE some_table_type.
CALL FUNCTION 'SOME_FUNCTION_MODULE_A'
  EXPORTING
    param_a = 'somevalue'
  IMPORTING
    param_b = it
  EXCEPTIONS
    not_found = 4.

IF sy-subrc <> 0.
   ...
ENDIF.

"Handling class-based exception when they are defined using RAISING
TRY.
    CALL FUNCTION 'SOME_FUNCTION_MODULE_B'
      EXPORTING
          param_c = 'somevalue'
      IMPORTING
          param_d = it.
  CATCH cx_some_exception INTO DATA(exc).
    ...
ENDTRY.

*&---------------------------------------------------------------------*
*& Dynamic function method calls
*&---------------------------------------------------------------------*

"Function module name contained in a variable
DATA(func_name) = 'SOME_FUNCTION_MODULE_C'.

CALL FUNCTION func_name ...

"For parameters in a parameter table, use the addition ... PARAMETER-TABLE ptab ...
"ptab: Sorted table of type abap_func_parmbind_tab (line type abap_func_parmbind)
"For exceptions, use the addition ... EXCEPTION-TABLE ...
DATA(ptab) = VALUE abap_func_parmbind_tab( ... ).

CALL FUNCTION func_name PARAMETER-TABLE ptab.

⬆️ back to top

Function Module Example

Expand the following section to get a simple executable example:

🟢 Click to expand for more information and example code

The following example demonstrates a simple function module:

To get started quickly with copiable code snippets, you can proceed as follows.

  1. Create a function pool.
    • In ADT, you can, for example, right-click the package in which you want to create the function pool and module.
    • Choose New -> Other ABAP Repository Object.
    • In the input field, enter function, select ABAP Function Group (which is the function pool), and choose Next.
    • Make entries in the Name (e.g. Z_DEMO_ABAP_TEST_FUNC_P) and Description Field (e.g. Demo function group) fields, and choose Next.
    • If prompted, select a transport request and choose Finish.
  2. Create a function module.
    • In ADT, you can, for example, right-click the package in which you have created the function pool.
    • Choose New -> Other ABAP Repository Object.
    • In the input field, enter function, select ABAP Function Module, and choose Next.
    • Make entries in the Name (e.g. Z_DEMO_ABAP_TEST_FUNC_M), Description Field (e.g. Demo function module), and Function Group (e.g. the previously created Z_DEMO_ABAP_TEST_FUNC_P) fields and choose Next.
    • If prompted, select a transport request and choose Finish.
    • The function module is created and can be filled with an implementation.
    • You can copy and paste the code below to have a sample implementation.
  3. To demonstrate the function module, you can create a class, for example, with the name ZCL_DEMO_ABAP_FUNC_TEST, and copy and paste the code below.
    • In ADT, you can run the class by choosing F9. Some output is displayed in the ADT console, demonstrating the result of function module calls.

Code for the function module Z_DEMO_ABAP_TEST_FUNC_M:

FUNCTION z_demo_abap_test_func_m
  IMPORTING
    num1 TYPE i
    operator TYPE string
    num2 TYPE i
  EXPORTING
    result TYPE string
  RAISING
    cx_sy_arithmetic_error.





  "ABAP 'allows' zero division if both operands are 0.
  IF num1 = 0 AND num2 = 0.
    RAISE EXCEPTION TYPE cx_sy_zerodivide.
  ENDIF.

  DATA op TYPE c LENGTH 1.
  op = condense( val = operator to = `` ).

  result = SWITCH #( op
                     WHEN '+' THEN |{ num1 } + { num2 } = { num1 + num2 STYLE = SIMPLE }|
                     WHEN '-' THEN |{ num1 } - { num2 } = { num1 - num2 STYLE = SIMPLE }|
                     WHEN '*' THEN |{ num1 } * { num2 } = { num1 * num2 STYLE = SIMPLE }|
                     WHEN '/' THEN |{ num1 } / { num2 } = { CONV decfloat34( num1 / num2 ) STYLE = SIMPLE }|
                     ELSE `Use one of the operators + - * /` ).

ENDFUNCTION.

Code for the class ZCL_DEMO_ABAP_FUNC_TEST that can be run in ADT choosing F9:

CLASS zcl_demo_abap_func_test DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_demo_abap_func_test IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.

    "Calling a function module
    "For handling a possible wrong operator in the example, you may want to implement
    "a separate exception. The simple example just catches calculation errors
    "and uses a data object of type string for storing the calculation result.
    DATA calculation_result TYPE string.

    TRY.
        CALL FUNCTION 'Z_DEMO_ABAP_TEST_FUNC_M'
          EXPORTING
            num1     = 1
            operator = `+`
            num2     = 2
          IMPORTING
            result   = calculation_result.
      CATCH cx_sy_arithmetic_error INTO DATA(exc).
        calculation_result = exc->get_text( ).
    ENDTRY.

    out->write( calculation_result && |\n\n| ).

    "More calculation examples; calculations are stored in a table
    DATA calculation_result_table TYPE string_table.

    TYPES: BEGIN OF s,
             num1     TYPE i,
             operator TYPE string,
             num2     TYPE i,
           END OF s,
           it_type TYPE TABLE OF s WITH EMPTY KEY.
    DATA(itab) = VALUE it_type( ( num1 = 10 operator = `-` num2 = 12 )
                                ( num1 = 15 operator = `*` num2 = 4 )
                                ( num1 = 7 operator = `/` num2 = 2 )
                                ( num1 = 1 operator = `/` num2 = 0 )
                                ( num1 = 0 operator = `/` num2 = 0 )
                                ( num1 = 9999999 operator = `*` num2 = 9999999 ) ).

    LOOP AT itab INTO DATA(wa).
      TRY.
          CALL FUNCTION 'Z_DEMO_ABAP_TEST_FUNC_M'
            EXPORTING
              num1     = wa-num1
              operator = wa-operator
              num2     = wa-num2
            IMPORTING
              result   = calculation_result.
        CATCH cx_sy_arithmetic_error INTO exc.
          calculation_result = |{ wa-num1 } { wa-operator } { wa-num2 } -> Error: { exc->get_text( ) }|.
      ENDTRY.
      APPEND calculation_result TO calculation_result_table.
    ENDLOOP.

    out->write( calculation_result_table ).
    out->write( |\n\n\n| ).

*&---------------------------------------------------------------------*
*& Dynamic function module call
*&---------------------------------------------------------------------*

    DATA(func_name) = 'Z_DEMO_ABAP_TEST_FUNC_M'.
    DATA(ptab) = VALUE abap_func_parmbind_tab( ( name  = 'NUM1'
                                                 kind  = abap_func_exporting
                                                 value = NEW i( 3 ) )
                                               ( name  = 'OPERATOR'
                                                 kind  = abap_func_exporting
                                                 value = NEW string( `+` ) )
                                               ( name  = 'NUM2'
                                                 kind  = abap_func_exporting
                                                 value = NEW i( 5 ) )
                                               ( name  = 'RESULT'
                                                 kind  = abap_func_importing
                                                 value = NEW string( ) ) ).

    CALL FUNCTION func_name PARAMETER-TABLE ptab.

    out->write( data = ptab name = `ptab` ).
  ENDMETHOD.

ENDCLASS.

⬆️ back to top

Special Function Modules in Standard ABAP

Special function modules exist in Standard ABAP (and not in ABAP for Cloud Development), and for which special properties are specified in the Function Builder:

⬆️ back to top

Subroutines in Standard ABAP

Expand the following collapsible sections for example code. To try the examples out, create a demo program and paste the code into it. After activation, choose F8 to execute the program. The purpose is to give an idea of the functionality.

🟢 Example 1 (Syntax options for creating and calling subroutines)

The following demo program illustrates various syntax options for creating and calling subroutines, including:


PROGRAM.

DATA number1 TYPE i VALUE 10.
DATA number2 TYPE i VALUE 20.
DATA number3 TYPE i VALUE 30.
DATA number4 TYPE i.
DATA number5 TYPE i.
DATA flag TYPE abap_boolean.
DATA text TYPE string.
DATA prog LIKE sy-repid VALUE sy-repid.
DATA subr TYPE string VALUE `SUBR_NO_PARAMS_A`.
DATA error TYPE REF TO cx_root.

START-OF-SELECTION.

*&---------------------------------------------------------------------*
*& Subroutine calls
*&---------------------------------------------------------------------*

  "Syntax variants of the PERFORM statement

  WRITE / `********************** 1) **********************`.
  SKIP.

  "The following PERFORM statements call a subroutine without parameters.

  "Directly specifying the subroutine name.
  PERFORM subr_no_params_a.

  "IN PROGRAM addition: External subroutine call, specifying the subroutine
  "name and the program name.
  "If the program or the subroutine is not found, a catchable exception is raised.
  TRY.
      PERFORM subr_no_params_a IN PROGRAM demo_abap_report.
    CATCH cx_sy_program_not_found INTO error.
      WRITE / error->get_text( ).
      SKIP.
  ENDTRY.

  "IF FOUND addition: Preventing the catchable exception if the subroutine or
  "program is not found
  PERFORM subr_no_params_a IN PROGRAM demo_abap_report IF FOUND.

**********************************************************************

  WRITE / `********************** 2) **********************`.
  SKIP.

  "2) Dynamic subroutine calls with IN PROGRAM
  "Note that the IF FOUND addition is optional.
  "The program and subroutine names can be specified as a literal or as a variable.
  "Variations are possible such as specifying the subroutine name dynamically and
  "the program name directly, or vice versa.
  PERFORM subr_no_params_a IN PROGRAM ('DEMO_ABAP_REPORT') IF FOUND.
  PERFORM subr_no_params_a IN PROGRAM demo_abap_report IF FOUND.
  PERFORM subr_no_params_a IN PROGRAM (prog) IF FOUND.
  PERFORM ('SUBR_NO_PARAMS_A') IN PROGRAM (prog) IF FOUND.

  PERFORM ('SUBR_NO_PARAMS_A') IN PROGRAM (prog) IF FOUND.
  PERFORM (subr) IN PROGRAM (prog) IF FOUND.

  "Note case-sensitivity of the subroutine name.
  TRY.
      PERFORM subr_does_not_exist IN PROGRAM (prog).
    CATCH cx_sy_dyn_call_illegal_form INTO error.
      WRITE / error->get_text( ).
      SKIP.
  ENDTRY.

  "Non-existent subroutine name directly specified
  TRY.
      PERFORM subr_does_not_exist IN PROGRAM (prog).
    CATCH cx_sy_dyn_call_illegal_form INTO error.
      WRITE / error->get_text( ).
      SKIP.
  ENDTRY.

**********************************************************************

  WRITE / `********************** 3) **********************`.
  SKIP.

  "3) Selecting a subroutine from a list of subroutines of the current program
  "It is only possible to specify subroutines without parameter list.
  "The IN PROGRAM and IF FOUND additions cannot be specified.
  PERFORM 1 OF subr_no_params_a subr_no_params_b.
  PERFORM 2 OF subr_no_params_a subr_no_params_b.

  DO.
    TRY.
        PERFORM sy-index OF subr_no_params_a subr_no_params_b.
      CATCH cx_sy_dyn_call_illegal_form INTO DATA(err).
        WRITE / err->get_text( ).
        SKIP.
        EXIT.
    ENDTRY.
  ENDDO.

**********************************************************************

  WRITE / `********************** 4) **********************`.
  SKIP.

  "4) Subroutines with parameters
  "The examples cover the additions USING and CHANGING.
  "The additions are also reflected in the subroutine definition.
  "Assignment of parameters is done by position, not by name.
  "If both USING and CHANGING are specified, the USING parameters must be specified first.

  "Subroutine with only USING parameters.
  "All of the parameters are passed by reference.
  number1 = 1.
  number2 = 10.
  number3 = 100.

  PERFORM subr_using USING number1
                           number2
                           number3.

  "Subroutine with USING and CHANGING parameters.
  number1 = 1.
  number2 = 10.
  number3 = 100.

  PERFORM subr_using_change USING number1
                                  number2
                            CHANGING number3.

  WRITE / |number3 = "{ number3 }"|.
  SKIP.

  "Subroutine with USING parameter and defining RAISING
  DO 3 TIMES.
    CASE sy-index.
      	WHEN 1.
        		number1 = 10.
        		number2 = 2.
      	WHEN 2.
        		number1 = 10.
        		number2 = 0.
      	WHEN 3.
        		number1 = 0.
        		number2 = 0.
    ENDCASE.

    TRY.
        PERFORM subr_using_raising USING number1
                                         number2.
      CATCH cx_sy_zerodivide INTO error.
        		WRITE / error->get_text( ).		
    		ENDTRY.
    		SKIP.
  ENDDO.

  "The following example subroutine demonstrates various parameter definitions
  "Both USING and CHANGING additions, as well as RAISING are defined.
  "The parameters passed to the subroutine are passed by value and by reference,
  "respectively. Note that a parameter without explicit typing is implicitly typed
  "with the fully generic type 'any'.

  number1 = 1.
  number2 = 10.
  number3 = 100.
  number4 = 1000.
  flag = abap_false.
  "Passing by reference: number1, number3, error, text
  "Passing by value: number2, number4
  PERFORM subr_pass_by_val_ref USING number1 number2 error
                               CHANGING number3 number4 text.

  WRITE / `--- Values after subroutine call ---`.
  WRITE / |number1 after subroutine call = "{ number1 }"|.
  WRITE / |number2 after subroutine call = "{ number2 }"|.
  WRITE / |text = "{ text }"|.
  WRITE / |number4 = "{ number4 }"|.
  SKIP.

  "Demonstrating that the actual parameter of a CHANGING parameters passed by value
  "is not changed when the subroutine is ended by an exception.
  number1 = 1.
  number2 = 10.
  number3 = 100.
  number4 = 1000.
  flag = abap_true.
  TRY.
      PERFORM subr_pass_by_val_ref USING number1 number2 error
                                   CHANGING number3 number4 text.
    CATCH cx_sy_zerodivide.
  ENDTRY.
  WRITE / `--- Values after subroutine call ---`.
  WRITE / |number1 after subroutine call = "{ number1 }"|.
  WRITE / |number2 after subroutine call = "{ number2 }"|.
  WRITE / |text = "{ text }"|.
  WRITE / |number4 = "{ number4 }"|.
  SKIP.

  "The following example subroutine calls illustrate that it is advisable to specify
  "the additions USING and CHANGING in the PERFORM statement according to the subroutine
  "definition for program documentation purposes.
  "Although the syntax does not show any errors for all statements, only the fourth statement
  "is recommended because it is the only one that documents the interface of the called subroutine.
  number1 = 1.
  number2 = 10.
  number3 = 100.
  number4 = 1000.

  PERFORM subr_call_demo USING number1 number2 number3 number4.
  PERFORM subr_call_demo CHANGING number1 number2 number3 number4.
  PERFORM subr_call_demo USING number1 CHANGING number2 number3 number4.
  "The following statement is a valid call according to the subroutine definition,
  "documenting the interface of the called subroutine.
  PERFORM subr_call_demo USING number1 number2 CHANGING number3 number4.
  PERFORM subr_call_demo USING number1 number2 number3 CHANGING number4.

**********************************************************************
**********************************************************************
**********************************************************************

*&---------------------------------------------------------------------*
*& Subroutines
*&---------------------------------------------------------------------*

  "Notes:
  "- The additions in FORM statements define the formal parameters.
  "- Class-based exceptions can also be specified.
  "- Within FORM ... ENDFORM., local data types and objects can be declared.
  "  Global data types and objects can be used within.
  "- Actual parameters passed to formal parameters of USING and CHANGING can
  "  be passed by value or reference.
  "- Passing by reference: Any changes made to the formal parameter also change
  "    the value of the actual parameter. Used in combination with USING, actual
  "    parameters passed by reference should not be changed in the subroutine.
  "    For this purpose, CHANGING should be used.
  "  - Pass by value: A local data object with the same data type is created. Any
  "    changes made to the formal parameter do not directly change the value of the
  "    actual parameter.

FORM subr_no_params_a.
  WRITE / |subr_no_params_a called at { utclong_current( ) }|.
  SKIP.
ENDFORM.

FORM subr_no_params_b.
  WRITE / |subr_no_params_b called at { utclong_current( ) }|.
  SKIP.
ENDFORM.

FORM subr_call_demo USING num1 TYPE i
                          num2 TYPE i
          CHANGING VALUE(num3) TYPE i
                   VALUE(num4) TYPE i.

  num4 = num1.
  num3 = num2.

  WRITE / |subr_call_demo called at { utclong_current( ) }|.
ENDFORM.

"Subroutines demonstrating the USING and CHANGING additions. The parameters passed to the
"subroutines are passed by reference.
FORM subr_using
  USING num1 TYPE i
        num2 TYPE i
        num3 TYPE i.
  WRITE / |subr_using called at { utclong_current( ) }|.
  WRITE / |num1 = '{ num1 }', num2 = '{ num2 }', num1 = '{ num3 }'|.
  SKIP.
ENDFORM.

FORM subr_using_change
  USING    num1 TYPE i
           num2 TYPE i
  CHANGING result  TYPE i.
  WRITE / |subr_using_change called at { utclong_current( ) }|.
  result = num1 + num2.
  WRITE / |{ num1 } + { num2 } = { result }|.
  SKIP.
ENDFORM.

FORM subr_using_raising
  USING num1 TYPE i
        num2 TYPE i
  RAISING cx_sy_zerodivide.
  WRITE / |subr_using_raising of program { prog } called at { utclong_current( ) }|.

  IF num1 = 0 AND num2 = 0. "ABAP 'allows' zero division
    RAISE EXCEPTION TYPE cx_sy_zerodivide.
  ELSE.
    WRITE / |{ num1 } / { num2 } = { num1 / num2 }|.
  ENDIF.	
ENDFORM.


"The following subroutine demonstrates the use of both USING and CHANGING additions
"in a single subroutine definition. The parameters passed to the subroutine are passed
"by value and by reference, respectively. Note that a parameter without explicit typing is
"implicitly typed with the fully generic type 'any'.

FORM subr_pass_by_val_ref
  USING using_pass_by_ref_num TYPE i
        VALUE(using_pass_by_value_num) TYPE i
        using_param
  CHANGING changing_pass_by_ref_num TYPE i
           VALUE(changing_pass_by_value_num) TYPE i
           changing_param
  RAISING cx_sy_zerodivide.
  WRITE / |subr_pass_by_val_ref called at { utclong_current( ) }|.

  "Using the value of the parameter in read position
  DATA(num1) = using_pass_by_value_num + 1.
  WRITE / |num1 = "{ num1 }"|.
  "Actual parameters passed by value can be changed in the subroutine.
  "A change to the parameter in the subroutine does not change the actual value.
  "Even after the subroutine has ended, the actual parameter retains its
  "original value.
  using_pass_by_value_num += 2.
  WRITE / |Value of using_pass_by_value_num changed in subroutine (number2) = "{ using_pass_by_value_num }"|.

  "Using the value of the parameter in read position
  DATA(num2) = using_pass_by_ref_num + 3.
  WRITE / |num2 = "{ num2 }"|.
  "Actual parameters passed by reference specified with USING should not be changed in the subroutine.
  "A syntax warning is displayed if CHANGING parameters are specified at the same time
  "for the subroutine.
  "using_pass_by_ref_num += 4.

  "The following assignment circumvents the syntax warning, and changes the value.
  "The demonstration purpose is that a change does not change the value of the actual
  "parameter. Even after the subroutine has ended, the actual parameter retains its
  "original value.
  ASSIGN using_pass_by_ref_num TO FIELD-SYMBOL(<fs>).
  <fs> += 5.
  WRITE / |Value of using_pass_by_ref_num changed in subroutine (number1) = "{ using_pass_by_ref_num }"|.


  "using_param is implicitly typed with the fully generic type 'any'.
  "The data type of the actual parameter is determined at runtime.
  DATA(type_info) = cl_abap_typedescr=>describe_by_data( using_param ).
  changing_param = SWITCH string( type_info->kind WHEN cl_abap_typedescr=>kind_elem THEN `elementary`
                                                    WHEN cl_abap_typedescr=>kind_class THEN `class`
                                                    WHEN cl_abap_typedescr=>kind_intf THEN `interface`
																										WHEN cl_abap_typedescr=>kind_ref THEN `reference`
																										WHEN cl_abap_typedescr=>kind_struct THEN `structure`
																										WHEN cl_abap_typedescr=>kind_table THEN `table`
																										ELSE `unknown` ).

  "Changing parameters passed by reference
  changing_pass_by_ref_num = 987.
  WRITE / |changing_pass_by_ref_num = "{ changing_pass_by_ref_num }"|.

  "Changing parameters passed by value
  "When ending the subroutine with ENDFORM, RETURN, CHECK, or EXIT, the
  "value of the formal parameter is assigned to the actual parameter.
  "However, if the subroutine is ended by a message or an exception,
  "the actual parameter is not changed.
  changing_pass_by_value_num = 123.
  WRITE / |changing_pass_by_value_num = "{ changing_pass_by_value_num }"|.
  IF flag = abap_true.
    SKIP.
    RAISE EXCEPTION TYPE cx_sy_zerodivide.
  ENDIF.
  SKIP.
ENDFORM.

🟢 Example 2 (Calculator example using various subroutines)

The following demo program illustrates the use of obsolete subroutines and related syntax in the context of a basic calculator:


PROGRAM.

*&---------------------------------------------------------------------*
*& Program-global types and data objects
*&---------------------------------------------------------------------*
DATA:
  operator          TYPE c LENGTH 1,
  result            TYPE decfloat34,
  last_result       TYPE decfloat34,
  calculation_count TYPE i,
  additions         TYPE i,
  subtractions      TYPE i,
  multiplications   TYPE i,
  divisions         TYPE i,
  alv               TYPE REF TO cl_salv_table,
  operators         TYPE vrm_values.

"Calculation history
TYPES:
  BEGIN OF history,
    calculation_no TYPE i,
    number1        TYPE decfloat34,
    operator       TYPE c LENGTH 1,
    number2        TYPE decfloat34,
    result         TYPE decfloat34,
    calculation    TYPE string,
    date           TYPE sy-datum,
    time           TYPE sy-uzeit,
  END OF history.

DATA history TYPE STANDARD TABLE OF history WITH EMPTY KEY.

*&---------------------------------------------------------------------*
*& Selection screen
*&---------------------------------------------------------------------*

SELECTION-SCREEN BEGIN OF BLOCK calculator WITH FRAME TITLE title1.
  SELECTION-SCREEN BEGIN OF LINE.
    SELECTION-SCREEN COMMENT 1(20) cnum1.
    PARAMETERS number1 TYPE decfloat34.
  SELECTION-SCREEN END OF LINE.
  SELECTION-SCREEN SKIP.
  SELECTION-SCREEN BEGIN OF LINE.
    SELECTION-SCREEN COMMENT 1(20) cop.
    PARAMETERS op TYPE c LENGTH 1 AS LISTBOX VISIBLE LENGTH 20.
  SELECTION-SCREEN END OF LINE.
  SELECTION-SCREEN SKIP.
  SELECTION-SCREEN BEGIN OF LINE.
    SELECTION-SCREEN COMMENT 1(20) cnum2.
    PARAMETERS number2 TYPE decfloat34.
  SELECTION-SCREEN END OF LINE.
SELECTION-SCREEN END OF BLOCK calculator.
SELECTION-SCREEN BEGIN OF BLOCK options WITH FRAME TITLE title2.
  SELECTION-SCREEN BEGIN OF LINE.
    PARAMETERS use_last TYPE abap_boolean AS CHECKBOX DEFAULT abap_false.
    SELECTION-SCREEN COMMENT 4(55) cuselast.
  SELECTION-SCREEN END OF LINE.
  SELECTION-SCREEN BEGIN OF LINE.
    PARAMETERS use_alv TYPE abap_boolean AS CHECKBOX DEFAULT abap_false.
    SELECTION-SCREEN COMMENT 4(55) cusealv.
  SELECTION-SCREEN END OF LINE.
  SELECTION-SCREEN BEGIN OF LINE.
    PARAMETERS clearhis TYPE abap_boolean AS CHECKBOX DEFAULT abap_false.
    SELECTION-SCREEN COMMENT 4(55) cclrhis.
  SELECTION-SCREEN END OF LINE.
SELECTION-SCREEN END OF BLOCK options.

*&---------------------------------------------------------------------*
*& INITIALIZATION event block
*&---------------------------------------------------------------------*

INITIALIZATION.
  "Selection screen texts
  title1   = 'Calculator'.
  title2   = 'Options'.
  cnum1    = 'First number'.
  cop      = 'Operator'.
  cnum2    = 'Second number'.
  cuselast = 'Use last result as Number 1'.
  cusealv  = 'Display history as ALV grid'.
  cclrhis  = 'Clear calculation history'.

  operators = VALUE #(
      ( key = '+' text = '+' )
      ( key = '-' text = '-' )
      ( key = '*' text = '*' )
      ( key = '/' text = '/' )
    ).
  CALL FUNCTION 'VRM_SET_VALUES'
    EXPORTING
      id     = 'OP'
      values = operators.

*&---------------------------------------------------------------------*
*& AT SELECTION-SCREEN OUTPUT event block
*&---------------------------------------------------------------------*

AT SELECTION-SCREEN OUTPUT.

  "The example includes the functionality to store the last result
  "of a calculation and other information. This data is stored in the
  "ABAP memory so that they can be reused between runs of the report.
  "When the 'use_last' checkbox is selected, returning to the selection
  "screen initializes the operator and number 2, and number 1 is
  "initialized with the last result.
  IMPORT
    last_result       = last_result
    calculation_count = calculation_count
    additions         = additions
    subtractions      = subtractions
    multiplications   = multiplications
    divisions         = divisions
    history           = history
    FROM MEMORY ID 'DEMO_MEM_ID'.

  IF sy-subrc = 0.
    IF calculation_count > 0 AND use_last = abap_true.
      number1 = last_result.
      CLEAR: op, number2.
    ELSE.
      CLEAR: op, number1, number2.
    ENDIF.
  ENDIF.

*&---------------------------------------------------------------------*
*& AT SELECTION-SCREEN event block
*&---------------------------------------------------------------------*

AT SELECTION-SCREEN.

  IF op IS INITIAL.
    MESSAGE 'Please select an operator.' TYPE 'E'.
  ENDIF.

  IF op = '/' AND number2 = 0.
    MESSAGE 'Division by zero is not allowed.' TYPE 'E'.
  ENDIF.

*&---------------------------------------------------------------------*
*& START-OF-SELECTION event block
*&---------------------------------------------------------------------*

START-OF-SELECTION.

  PERFORM get_data.
  PERFORM clear_history.
  PERFORM calculate USING number1
                          number2
                    CHANGING result.
  PERFORM save_history USING number1
                             number2
                             result.
  PERFORM save_state.

  "The example includes the functionality to display the calculation
  "history in an ALV grid if the 'use_alv' checkbox is selected.
  "Otherwise, a classic list output is used to display the history
  "and statistics.
  IF use_alv = abap_true.
    PERFORM display_history_alv.
  ELSE.
    PERFORM display_result.
    PERFORM display_history.
    PERFORM display_statistics.
  ENDIF.

*&---------------------------------------------------------------------*
*& Subroutine get_data
*&---------------------------------------------------------------------*

FORM get_data.
  operator = op.
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine save_state
*&---------------------------------------------------------------------*

"Persists the complete calculator state (history and statistics)
"in the ABAP memory so that it is available with the next program runs.

FORM save_state.
  EXPORT
    last_result       = last_result
    calculation_count = calculation_count
    additions         = additions
    subtractions      = subtractions
    multiplications   = multiplications
    divisions         = divisions
    history           = history
    TO MEMORY ID 'DEMO_MEM_ID'.
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine clear_history
*&---------------------------------------------------------------------*

FORM clear_history.
  IF clearhis = abap_true.
    CLEAR: history,
          calculation_count,
          additions,
          subtractions,
          multiplications,
          divisions,
          last_result,
          alv.
    clearhis = abap_false.

    "Clearing the stored data
    FREE MEMORY ID 'DEMO_MEM_ID'.
  ENDIF.
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine calculate
*&---------------------------------------------------------------------*

"Perfoms the calculation based on the selected operator and input numbers.
FORM calculate
  USING    number1 TYPE decfloat34
           number2 TYPE decfloat34
  CHANGING result  TYPE decfloat34.
  CLEAR result.
  TRY.
      CASE operator.
        WHEN '+'.
          result = number1 + number2.
          additions += 1.
        WHEN '-'.
          result = number1 - number2.
          subtractions += 1.
        WHEN '*'.
          result = number1 * number2.
          multiplications += 1.
        WHEN '/'.
          result = number1 / number2.
          divisions += 1.
        WHEN OTHERS.
          MESSAGE 'Invalid operator.' TYPE 'E'.
      ENDCASE.
    CATCH cx_sy_arithmetic_error INTO DATA(error).
      MESSAGE error->get_text( ) TYPE 'E'.
  ENDTRY.
  last_result = result.
  calculation_count += 1.
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine save_history
*&---------------------------------------------------------------------*

FORM save_history
  USING number1 TYPE decfloat34
        number2 TYPE decfloat34
        result  TYPE decfloat34.

  DATA(calculation) = |{ number1 } { operator } { number2 } = { result }|.

  APPEND VALUE #( calculation_no = calculation_count
                  number1        = number1
                  operator       = operator
                  number2        = number2
                  result         = result
                  calculation    = calculation
                  date           = sy-datum
                  time           = sy-uzeit ) TO history.
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine display_result
*&---------------------------------------------------------------------*

"Used to display the result of the calculation in a classic list
FORM display_result.
  DATA(calculation) = |{ number1 } { operator } { number2 } = { result }|.

  "Input
  FORMAT COLOR COL_NORMAL.
  WRITE: / |  Number 1    : { number1 }|.
  WRITE: / |  Operator    : { operator }|.
  WRITE: / |  Number 2    : { number2 }|.
  FORMAT RESET.

  "Result
  WRITE: /.
  FORMAT COLOR COL_POSITIVE INTENSIFIED ON.
  WRITE: / |  RESULT      : { result }|.
  FORMAT RESET.

  IF use_last = abap_true.
    WRITE: /.
    WRITE: / '  The result will be available as Number 1 value when going back.'.
  ENDIF.
  WRITE: /.
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine display_history
*&---------------------------------------------------------------------*

"Used to display the history in a classic list output
FORM display_history.
  WRITE: /.
  FORMAT COLOR COL_HEADING INTENSIFIED.
  WRITE: / '  Calculation History'.
  WRITE: / '  ==================='.
  FORMAT RESET.
  WRITE: /.
  IF history IS INITIAL.
    FORMAT COLOR COL_NEGATIVE INTENSIFIED.
    WRITE: / '  No calculations available.'.
    FORMAT RESET.
    RETURN.
  ENDIF.

  "Table heading for the history
  WRITE:
      /(5) 'No.',
        8    'Date',
        20   'Time',
        32   'Calculation'.
  WRITE: / repeat( val = '-' occ = 80 ).

  "Displaying the history table content
  LOOP AT history INTO DATA(history_line).
    IF history_line-calculation_no = calculation_count.
      FORMAT COLOR COL_POSITIVE INTENSIFIED ON.
    ELSE.
      FORMAT COLOR COL_NORMAL.
    ENDIF.

    "Truncating the calculation if it would run past the screen.
    "The calculation starts at column 32, so the available width is
    "the list line size (sy-linsz) minus the leading columns. A
    "too-long calculation is cut and the last 3 characters are
    "replaced by 3 dots to indicate the truncation.
    DATA(available) = sy-linsz - 31.
    DATA(calculation) = history_line-calculation.
    IF available > 3 AND strlen( calculation ) > available.
      calculation = |{ substring( val = calculation
                                  len = available - 3 ) }...|.
    ENDIF.
    WRITE: /(5) history_line-calculation_no,
            8    history_line-date,
            20   history_line-time,
            32   calculation.
    FORMAT RESET.
  ENDLOOP.
  WRITE: / repeat( val = '-' occ = 80 ).
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine display_history_alv
*&---------------------------------------------------------------------*

"Used to display the history in an ALV grid output
FORM display_history_alv.
  IF history IS INITIAL.
    RETURN.
  ENDIF.
  PERFORM setup_alv.
  alv->display( ).
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine setup_alv
*&---------------------------------------------------------------------*

"Used to set up the ALV grid output for the history display.
"The ALV object is created and configured only once.
FORM setup_alv.
  IF alv IS BOUND.
    RETURN.
  ENDIF.
  TRY.
      cl_salv_table=>factory(
              IMPORTING
                r_salv_table = alv
              CHANGING
                t_table      = history
            ).

      "Using standard ALV functions
      DATA(functions) = alv->get_functions( ).
      functions->set_all( abap_true ).

      "Column setup
      DATA(columns) = alv->get_columns( ).
      DATA(column) = columns->get_column( 'CALCULATION_NO' ).
      column->set_short_text( 'No.' ).
      column->set_medium_text( 'Calculation' ).
      column->set_long_text(  'Calculation Number' ).

      column = columns->get_column( 'NUMBER1' ).
      column->set_short_text( 'Number 1' ).
      column->set_medium_text( 'First Number' ).
      column->set_long_text(  'First Number' ).

      column = columns->get_column( 'OPERATOR' ).
      column->set_short_text( 'Op.' ).
      column->set_medium_text( 'Operator' ).
      column->set_long_text(  'Arithmetic Operator' ).

      column = columns->get_column( 'NUMBER2' ).
      column->set_short_text( 'Number 2' ).
      column->set_medium_text( 'Second Number' ).
      column->set_long_text(  'Second Number' ).

      column = columns->get_column( 'RESULT' ).
      column->set_short_text( 'Result' ).
      column->set_medium_text( 'Result' ).
      column->set_long_text(  'Calculation Result' ).

      column = columns->get_column( 'CALCULATION' ).
      column->set_technical( abap_true ).

      column = columns->get_column( 'DATE' ).
      column->set_short_text( 'Date' ).
      column->set_medium_text( 'Date' ).
      column->set_long_text(  'Calculation Date' ).

      column = columns->get_column( 'TIME' ).
      column->set_short_text( 'Time' ).
      column->set_medium_text( 'Time' ).
      column->set_long_text(  'Calculation Time' ).

      "Optimize columns
      columns->set_optimize( abap_true ).

      "Sort descending by calculation number so that the latest
      "result is displayed first.
      DATA(sorts) = alv->get_sorts( ).
      sorts->add_sort( columnname = 'CALCULATION_NO'
                       sequence   = if_salv_c_sort=>sort_down ).

      "Display settings
      DATA(display_settings) = alv->get_display_settings( ).
      display_settings->set_list_header( |Calculation History - { calculation_count } calculations| ).
    CATCH cx_root INTO DATA(error).
      MESSAGE error->get_text( ) TYPE 'E'.
  ENDTRY.
ENDFORM.

*&---------------------------------------------------------------------*
*& Subroutine display_statistics
*&---------------------------------------------------------------------*

"Used to display the statistics in a classic list output
FORM display_statistics.
  WRITE: /.
  FORMAT COLOR COL_HEADING INTENSIFIED.
  WRITE: / '  Calculation Statistics'.
  WRITE: / '  ======================'.
  FORMAT RESET.
  WRITE: /.
  FORMAT COLOR COL_NORMAL.
  WRITE: / '  Total calculations :', |{ calculation_count }|.
  WRITE: / |  Additions          : { additions }  ( { COND decfloat34( WHEN calculation_count > 0
                                                                       THEN CONV decfloat34( additions * 100 ) / calculation_count
                                                                       ELSE 0 ) DECIMALS = 1 }% )|.
  WRITE: / |  Subtractions       : { subtractions }  ( { COND decfloat34( WHEN calculation_count > 0
                                                                          THEN CONV decfloat34( subtractions * 100 ) / calculation_count
                                                                          ELSE 0 ) DECIMALS = 1 }% )|.
  WRITE: / |  Multiplications    : { multiplications }  ( { COND decfloat34( WHEN calculation_count > 0
                                                                             THEN CONV decfloat34( multiplications * 100 ) / calculation_count
                                                                             ELSE 0 ) DECIMALS = 1 }% )|.
  WRITE: / |  Divisions          : { divisions }  ( { COND decfloat34( WHEN calculation_count > 0
                                                                       THEN CONV decfloat34( divisions * 100 ) / calculation_count
                                                                       ELSE 0 ) DECIMALS = 1 }% )|.
  FORMAT RESET.
  WRITE: /.
ENDFORM.

⬆️ back to top

Exiting Procedures and Returning Values with RETURN

Regarding the exiting of procedures, note the hint mentioned above. The use of RETURN is recommended.

RETURN terminates the current processing block. Usually, the statement is intended for leaving processing blocks early. In case of functional methods, i.e. methods that have one returning parameter, the RETURN statement can also be specified with an expression. In doing so, the following statement

res = some_expr.
RETURN.

can also be specified as follows:

RETURN some_expr.

In the following example, the expression result is passed to the returning parameter without naming it explicitly. As an expression, you can specify a constructor expression (and using type inference with # means using the type of the returning parameter).

CLASS zcl_demo_abap DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES: if_oo_adt_classrun.
  PROTECTED SECTION.
  PRIVATE SECTION.
    CLASS-METHODS multiply
      IMPORTING num1          TYPE i
                num2          TYPE i
      RETURNING VALUE(result) TYPE i.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    DATA(res1) = multiply( num1 = 2 num2 = 3 ).
    DATA(res2) = multiply( num1 = 10 num2 = 10 ).
    DATA(res3) = multiply( num1 = 99999999 num2 = 99999999 ).
    out->write( res1 ). "6
    out->write( res2 ). "100
    out->write( res3 ). "0
  ENDMETHOD.
  METHOD multiply.
    TRY.        
        "result = num1 * num2.
        RETURN num1 * num2.
      CATCH cx_sy_arithmetic_error.
        "The following statement is actually not needed in the example, but used nevertheless
        "to showcase the specification of a constructor expression after RETURN.
        RETURN VALUE #( ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

⬆️ back to top

Interrupting the Program Execution with WAIT UP TO Statements

Using WAIT UP TO statements, you can interrupt the program execution by a specified number of seconds.

"First retrieval of the current time stamp
DATA(ts1) = utclong_current( ).
...
WAIT UP TO 1 SECONDS.
...
WAIT UP TO 3 SECONDS.
...
"Second retrieval of the current time stamp after the WAIT statements
DATA(ts2) = utclong_current( ).
"Calculating the difference of the two time stamps
cl_abap_utclong=>diff( EXPORTING high     = ts2
                                 low      = ts1
                        IMPORTING seconds = DATA(seconds) ).

"The value of the 'seconds' data object holding the delta of the time stamps 
"should be greater than 4.
ASSERT seconds > 4.

⬆️ back to top

Exceptions and Runtime Errors

Exceptions and runtime errors affect the program flow. Find an overview in the Exceptions and Runtime Errors cheat sheet.

⬆️ back to top

Executable Example

zcl_demo_abap_prog_flow_logic

[!NOTE]