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.
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 ...
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.
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.
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.
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.
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.
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.
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.
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.
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.
LOOP / APPEND with a temp work area — to a single VALUE FOR ... WHERE expression.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.
DATA(lt_active) = VALUE ty_user_tab( FOR <u> IN lt_users WHERE ( status = 'A' ) ( id = <u>-id name = <u>-name ) ).
IF/ELSEIF/ELSE reduces to one expression — the type is inferred and the result is immutable in spirit.DATA lv_band TYPE string. IF lv_amount > 10000. lv_band = 'PLATINUM'. ELSEIF lv_amount > 1000. lv_band = 'GOLD'. ELSE. lv_band = 'STANDARD'. ENDIF.
DATA(lv_band) = COND string( WHEN lv_amount > 10000 THEN 'PLATINUM' WHEN lv_amount > 1000 THEN 'GOLD' ELSE 'STANDARD' ).
REDUCE. The accumulator is named. The loop is a single expression. The total is DATA(...) — not a variable you forgot to clear.DATA lv_total TYPE p DECIMALS 2. LOOP AT lt_lines INTO DATA(ls_line). lv_total = lv_total + ls_line-amount. ENDLOOP.
DATA(lv_total) = REDUCE p( INIT t = 0 FOR <l> IN lt_lines NEXT t = t + <l>-amount ).
READ TABLE ... WITH KEY ... IF SY-SUBRC = 0. The functional read is one line.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.
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.
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.
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.
previous cause — chains beat stack traces.CX_ROOT. Catch what you can recover from. Let the rest bubble.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.
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.
One add( ), one save( ). T100 messages, free text, structured payloads — all in. No more BAL_LOG_MSG_ADD ceremony.
Serialize and deserialize anything — nested tables, references, mixed types. Field-name conventions configurable. No more “why is my list of objects a single string?”.
Dynamic WHERE builders, IN-list chunkers that respect the 1000-element limit, parameter sanitisers. SELECT what you mean, not what 1990 thinks you meant.
Get the type, copy the structure shape, walk the components — without the ?= casts and the CL_ABAP_TYPEDESCR=>DESCRIBE_BY_* incantations.
A factory( ), a display( ), sensible defaults for columns, sorting, totals. The other 99% of ALV ceremony stays where it belongs — somewhere else.
Record once, replay forever. Field-symbol-driven action streams. SM35 as the fallback, not the home base.
HTML body, inline images, attachments, distribution lists. One builder, one send( ), one transport-safe template path.
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.
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.
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 ↑