Field notes · twenty years in SE80

ABAP, written like it’s 2026.
Not 1998.

Opinions, idioms, and the standard library SAP forgot to ship. For working ABAP developers who think the language stopped being ugly years ago — and want code that proves it.

orders.prog.abap SAP_BASIS 7.02 vibes
DATA: lt_active TYPE STANDARD TABLE OF ty_order,
      ls_active TYPE ty_order,
      lv_total  TYPE p DECIMALS 2.

LOOP AT lt_orders INTO DATA(ls).
  IF ls-status = 'A'.
    CLEAR ls_active.
    ls_active-id   = ls-id.
    ls_active-name = ls-name.
    ls_active-amt  = ls-amt.
    APPEND ls_active TO lt_active.
    lv_total = lv_total + ls-amt.
  ENDIF.
ENDLOOP.

* now do something with lt_active / lv_total ...
orders.prog.abap SAP_BASIS 7.40+
DATA(lt_active) = VALUE ty_orders(
  FOR <o> IN lt_orders WHERE ( status = 'A' )
  ( id = <o>-id  name = <o>-name  amt = <o>-amt ) ).

DATA(lv_total) = REDUCE p(
  INIT t = 0
  FOR <a> IN lt_active
  NEXT t = t + <a>-amt ).

" two expressions. zero scratch variables.
" the type is local to the call site.
01 The manifesto

Six things every ABAP developer should have stopped doing by now.

None of this is hot. All of it has been shipping in your system since SAP_BASIS 7.40 — which, by the way, is almost a decade old. Move on.

01.

Stop writing LOOP / APPEND / ENDLOOP for things that are obviously a list comprehension.

If your loop body is a copy-then-append, it’s a VALUE FOR. If it’s an accumulator, it’s a REDUCE. If it’s a filter, it’s a FILTER. The compiler can see what you mean — let it.

02.

SY-SUBRC is not error handling. It’s a status code.

Every dump deserves a ZCX_*. Every exception class deserves a T100 message and a cause chain. “Error during processing” is a confession, not a message.

03.

Inline declarations aren’t a fashion choice. They cut noise.

A DATA(lv_x) at the call site says: this variable exists for one job and one job only. Five lines of DATA: at the top of a method say: I’m hiding the type system from you.

04.

Functional method calls beat CALL METHOD ... RECEIVING every time.

If your getter takes three lines to invoke, you’ve already lost. A returning method composes. A RECEIVING call doesn’t. Chain them. Nest them. Pass them as parameters.

05.

The “standard library” in ABAP is whatever you wrote at your last project.

So write it once and write it well. A solid JSON serializer, a clean BAL logger, a tasteful SALV wrapper, an honest OSQL helper. Carry it with you. Stop reinventing your own bug.

06.

Testability is not optional in ABAP. It’s especially not optional in ABAP.

The transports are scary. The downtime windows are scarier. ABAP Unit exists. Mock the BAPI, inject the DB layer, write the green bar — or accept that production is your QA.

02 Modern idioms

A small gallery of before-and-after.

The exact same logic, written twice. The version on the right doesn’t need comments, doesn’t need scratch variables, and doesn’t need a 1998 explanation.

01   Building a filtered, mapped list

From LOOP / APPEND with a temp work area — to a single VALUE FOR ... WHERE expression.
Before
DATA: lt_active TYPE ty_user_tab,
      ls_active TYPE ty_user.

LOOP AT lt_users INTO DATA(ls_user).
  IF ls_user-status = 'A'.
    CLEAR ls_active.
    ls_active-id   = ls_user-id.
    ls_active-name = ls_user-name.
    APPEND ls_active TO lt_active.
  ENDIF.
ENDLOOP.
After
DATA(lt_active) = VALUE ty_user_tab(
  FOR <u> IN lt_users
  WHERE ( status = 'A' )
  ( id = <u>-id  name = <u>-name ) ).

02   Conditional assignment

Three-branch IF/ELSEIF/ELSE reduces to one expression — the type is inferred and the result is immutable in spirit.
Before
DATA lv_band TYPE string.

IF lv_amount > 10000.
  lv_band = 'PLATINUM'.
ELSEIF lv_amount > 1000.
  lv_band = 'GOLD'.
ELSE.
  lv_band = 'STANDARD'.
ENDIF.
After
DATA(lv_band) = COND string(
  WHEN lv_amount > 10000 THEN 'PLATINUM'
  WHEN lv_amount > 1000  THEN 'GOLD'
  ELSE                       'STANDARD' ).

03   Summing / accumulating

A classic REDUCE. The accumulator is named. The loop is a single expression. The total is DATA(...) — not a variable you forgot to clear.
Before
DATA lv_total TYPE p DECIMALS 2.

LOOP AT lt_lines INTO DATA(ls_line).
  lv_total = lv_total + ls_line-amount.
ENDLOOP.
After
DATA(lv_total) = REDUCE p(
  INIT t = 0
  FOR <l> IN lt_lines
  NEXT t = t + <l>-amount ).

04   Lookups against an internal table

If you have a sorted or hashed key, stop writing READ TABLE ... WITH KEY ... IF SY-SUBRC = 0. The functional read is one line.
Before
DATA ls_match TYPE ty_user.

READ TABLE lt_users INTO ls_match
  WITH KEY id = 'K123'.

IF sy-subrc = 0.
  " use ls_match-name ...
ENDIF.
After
TRY.
    DATA(lv_name) =
      lt_users[ id = 'K123' ]-name.
    " use lv_name ...
  CATCH cx_sy_itab_line_not_found.
    " caller decides what 'missing' means
ENDTRY.
03 Exception classes, the only way

Every dump deserves a ZCX_*.

The T100 message gives the operator a fighting chance. The cause chain gives you a fighting chance. The factory method gives the call site one-line clarity. Three things, no excuses.

The template you copy-paste once and never write again.

One IF_T100_DYN_MSG-based exception class. One raise( ) factory method. One get_text( ) override that reads the T100 message text. Then forget about it. Every domain in your project subclasses this, sets its own message class, and ships.

  • Always carry a previous cause — chains beat stack traces.
  • Always use a T100 long text — ops staff need the what to do, not the what went wrong.
  • Never catch CX_ROOT. Catch what you can recover from. Let the rest bubble.
zcx_app_error.clas.abap global exception
CLASS zcx_app_error DEFINITION
  PUBLIC INHERITING FROM cx_static_check.

  PUBLIC SECTION.
    INTERFACES if_t100_dyn_msg.

    CLASS-METHODS raise
      IMPORTING iv_msg     TYPE string
                !previous   TYPE REF TO cx_root OPTIONAL
      RAISING   zcx_app_error.

    METHODS get_text REDEFINITION.
ENDCLASS.
04 The standard library SAP forgot

A working ABAP dev’s personal toolbox.

Every senior ABAPer has these classes in their pocket. Names vary, package prefixes vary, one or two will be hot takes — but every entry on this list should ship with SAP_BASIS and every entry on this list doesn’t. So we write them. And we carry them.

zcl_cross_logger

BAL logging without the BAL pain

One add( ), one save( ). T100 messages, free text, structured payloads — all in. No more BAL_LOG_MSG_ADD ceremony.

zcl_cross_json

JSON that handles deep structures

Serialize and deserialize anything — nested tables, references, mixed types. Field-name conventions configurable. No more “why is my list of objects a single string?”.

zcl_cross_osql

Open SQL helpers that compose

Dynamic WHERE builders, IN-list chunkers that respect the 1000-element limit, parameter sanitisers. SELECT what you mean, not what 1990 thinks you meant.

zcl_cross_rtti

RTTI wrapped so it’s readable

Get the type, copy the structure shape, walk the components — without the ?= casts and the CL_ABAP_TYPEDESCR=>DESCRIBE_BY_* incantations.

zcl_cross_salv

ALV in three lines

A factory( ), a display( ), sensible defaults for columns, sorting, totals. The other 99% of ALV ceremony stays where it belongs — somewhere else.

zcl_cross_bdc

BDC that can replay itself

Record once, replay forever. Field-symbol-driven action streams. SM35 as the fallback, not the home base.

zcl_cross_mail

E-mails without the SOST archeology

HTML body, inline images, attachments, distribution lists. One builder, one send( ), one transport-safe template path.

zcl_cross_scan

Code scanning as a method call

SCAN ABAP-SOURCE wrapped so you can actually use it. Find every place that touches a field, every CLASS-DATA, every commented-out hack. Lint your own code.

zcl_acdc_task

A task framework that survives prod

Async, parallel, restartable units of work. Each task carries its own log, its own state, and its own retry semantics. Daughter jobs, dialog runs, batch — one interface.

05 One more thing

ABAP isn’t going anywhere. Neither are we.

This site is a field notebook, not a tutorial. It gets longer when something annoys me enough to write it down. If you want to argue, that’s the right reflex — arguing is how ABAP gets better.

Re-read the manifesto ↑