Modularization
ABAP Developmentbeginner

Modularization Part 2: FORM Routines — Legacy but Still Important

Key takeaway

Read and maintain FORM routines in existing programs: PERFORM and FORM, USING and CHANGING, pass by value vs reference, TABLES parameters, global data side effects — and how to refactor them safely.

Explanation

📌 KEY POINTS
✅ FORM … ENDFORM defines a subroutine; PERFORM name USING … CHANGING … calls it.

✅ USING/CHANGING are pass-by-reference by default; VALUE( ) makes a copy.

✅ FORM routines are obsolete for new code — but you will maintain thousands of them (reports, user exits, print programs).

⚠️ FORMs often read and change global variables — the main source of side-effect bugs.

━━━━━━━━━━━━━━━━━━━━
▶ WHERE WE ARE

Part 1 introduced modularization units. FORM routines are the oldest processing unit — you must be able to read, debug and carefully change them.

━━━━━━━━━━━━━━━━━━━━
▶ 1. SYNTAX

PERFORM calc_free_seats USING gs_flight CHANGING gv_free.

FORM calc_free_seats USING us_flight TYPE sflight CHANGING cv_free TYPE i. … ENDFORM.

  • Parameters are matched by position.
  • Typed parameters (TYPE …) give syntax checks; untyped ones (old code) accept anything — risky.

━━━━━━━━━━━━━━━━━━━━
▶ 2. PASSING MECHANISMS

  • USING p — intended as input, but passed by reference: changing it inside the FORM changes the caller's variable.
  • USING VALUE(p) — copy; changes stay local.
  • CHANGING p — reference, intended for output.
  • CHANGING VALUE(p) — copy-back: value is written back only at normal ENDFORM.
  • TABLES itab — obsolete way to pass internal tables (with header line). Use USING/CHANGING with table types instead.

━━━━━━━━━━━━━━━━━━━━
▶ 3. LOCAL DATA

DATA inside a FORM is local (created at each call). STATICS keep their value between calls.

━━━━━━━━━━━━━━━━━━━━
▶ 4. EXTERNAL PERFORM

PERFORM form_name IN PROGRAM zother IF FOUND. — calls a FORM of another program. Common in user exits and SAPscript; fragile (no interface check).

━━━━━━━━━━━━━━━━━━━━
▶ 5. THE GLOBAL DATA PROBLEM

Many FORMs work with global tables directly instead of parameters. The call PERFORM process. reveals nothing about what it reads and changes. When maintaining: read the FORM fully, check where-used for globals, and add parameters when you touch it.

━━━━━━━━━━━━━━━━━━━━
▶ 6. REFACTORING STRATEGY

  • Don't rewrite everything at once.
  • When changing a FORM: add typed parameters, reduce global access.
  • Move logic into a local class method; let the FORM call the method (callers keep working).
  • Add a unit test for the extracted method.

━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP

Run the code example: see how USING without VALUE changes the caller's variable, and compare with the refactored method version.

✅ CHECKPOINT: you can maintain FORM-based code safely and modernize it gradually.

Code example

ABAP Code
REPORT zmod_part2. DATA: gs_flight TYPE sflight,      gv_free   TYPE i,      gv_count  TYPE i. CLASS lcl_seats DEFINITION.  PUBLIC SECTION.    CLASS-METHODS free_seats IMPORTING is_flight     TYPE sflight                             RETURNING VALUE(rv_free) TYPE i.ENDCLASS.CLASS lcl_seats IMPLEMENTATION.  METHOD free_seats.    rv_free = is_flight-seatsmax - is_flight-seatsocc.  ENDMETHOD.ENDCLASS. START-OF-SELECTION.  SELECT SINGLE * FROM sflight INTO gs_flight WHERE carrid = 'LH'. * ===== Typed FORM with USING / CHANGING =====  PERFORM calc_free_seats USING gs_flight CHANGING gv_free.  WRITE: / 'free seats:', gv_free. * ===== Trap: USING is by reference - the caller's variable changes! =====  gv_count = 10.  PERFORM trap USING gv_count.  WRITE: / 'gv_count after PERFORM trap:', gv_count.      " 11 !   gv_count = 10.  PERFORM safe USING gv_count.  WRITE: / 'gv_count after PERFORM safe:', gv_count.      " 10 * ===== Refactoring: FORM delegates to a method (callers unchanged) =====  PERFORM calc_free_seats_new USING gs_flight CHANGING gv_free. *---------------------------------------------------------------------*FORM calc_free_seats USING    us_flight TYPE sflight                     CHANGING cv_free   TYPE i.  cv_free = us_flight-seatsmax - us_flight-seatsocc.ENDFORM. FORM trap USING uv_count TYPE i.  uv_count = uv_count + 1.            " modifies the caller's variableENDFORM. FORM safe USING VALUE(uv_count) TYPE i.  uv_count = uv_count + 1.            " local copy onlyENDFORM. FORM calc_free_seats_new USING us_flight TYPE sflight CHANGING cv_free TYPE i.  cv_free = lcl_seats=>free_seats( us_flight ).          " logic now testableENDFORM.

Real project scenario

In a pricing user exit, a FORM received the net price via USING and "temporarily" multiplied it for a calculation. Because USING passes by reference, the caller's net price was changed too, and some orders were priced 10% too high until a user noticed a pattern.
The fix declared the parameter USING VALUE( ), and the team added a rule: when touching a FORM, type all parameters and use VALUE for inputs; logic is moved into methods where possible.

Common mistakes

  • Assuming USING parameters are read-only copies.
  • Untyped FORM parameters.
  • TABLES parameters with header lines.
  • FORMs that silently read/change global data.
  • Rewriting large FORM-based programs in one go.

Best practices

  • Type all FORM parameters.
  • Use VALUE( ) for inputs that must not change.
  • Avoid new FORMs; wrap logic in methods.
  • Reduce global access when touching a FORM.
  • Refactor incrementally with tests.

Interview angle

Questions: "Difference between USING and CHANGING?", "Pass by value vs reference in FORMs?", "Why are FORMs obsolete?"
Strong answer: both USING and CHANGING pass by reference unless VALUE( ) is used, so USING can change the caller's variable; CHANGING VALUE copies back at ENDFORM; TABLES is obsolete; FORMs lack encapsulation and encourage global data, so new code uses methods, but FORMs must be maintained in legacy code and exits — refactor gradually.

Practice this lesson

Quick check

1. In MV50AFZ1, a custom global variable set in one FORM exit caused wrong values in another delivery. Best practice?

2. How is a parameter passed with PERFORM calc USING lv_a CHANGING lv_b and FORM calc USING p_a CHANGING p_b handled by default?

3. What is the status of FORM routines for new ABAP development?

Interview questions

1. Report crashes because a FORM reads an uninitialised global variable. How do you prevent it?

Answer it in your own words first, then compare.

2. A delivery user exit in MV50AFZ1 contains several FORM routines sharing global variables, and a change caused wrong values in another FORM. How do you work safely here?

Answer it in your own words first, then compare.

Practice more with Interview AI →

Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.

More lessons in this topic

Interview questions on this topic

Related SAP topics