Declarations inline : Code ABAP plus compact et lisible

Catégorie
ABAP
Publié
Auteur
Johannes

Les declarations inline sont disponibles depuis ABAP 7.40 et revolutionnent la facon dont nous declarons les variables et les symboles de champ. Au lieu d’ecrire des instructions DATA et FIELD-SYMBOLS separees, vous pouvez declarer des variables directement la ou vous en avez besoin.

Que sont les declarations inline ?

Les declarations inline permettent de declarer des variables et des symboles de champ directement dans l’instruction qui leur attribue une valeur. Le compilateur deduit automatiquement le type.

SyntaxeDescriptionDisponible depuis
DATA(var)Variable avec inference de typeABAP 7.40
FIELD-SYMBOL(<fs>)Symbole de champ avec inference de typeABAP 7.40
FINAL(var)Variable immuableABAP 7.57

Avantages des declarations inline

  • Moins de lignes de code : Pas d’instructions DATA separees necessaires
  • Typage automatique : Le compilateur connait le bon type
  • Meilleure lisibilite : Declaration et utilisation au meme endroit
  • Sources d’erreurs reduites : Pas d’incompatibilites de type entre declaration et utilisation

Declaration inline DATA()

Bases

La forme la plus simple de declaration inline utilise DATA() :

CLASS zcl_inline_basics DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_basics IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Declaration classique (ancienne)
DATA lv_text_old TYPE string.
lv_text_old = 'Bonjour le monde'.
" Declaration inline (nouvelle)
DATA(lv_text_new) = 'Bonjour le monde'.
" Le type est deduit automatiquement
DATA(lv_number) = 42. " Type : i (integer)
DATA(lv_decimal) = '3.14'. " Type : string (Attention !)
DATA(lv_packed) = CONV decfloat34( '3.14' ). " Type : decfloat34
out->write( |Text : { lv_text_new }| ).
out->write( |Number : { lv_number }| ).
out->write( |Decimal : { lv_packed }| ).
" Retour de methode directement dans une variable
DATA(lv_timestamp) = cl_abap_context_info=>get_system_date( ).
out->write( |Date : { lv_timestamp }| ).
ENDMETHOD.
ENDCLASS.

Comprendre l’inference de type

Le compilateur deduit le type a partir de l’expression de droite :

" Litteraux string -> csequence (type char)
DATA(lv_char) = 'ABC'. " Type : char3
" String Template -> string
DATA(lv_string) = |ABC|. " Type : string
" Litteraux numeriques
DATA(lv_int) = 100. " Type : i
DATA(lv_float) = '1.5'. " Type : string (PAS p ou f !)
" Conversion explicite pour les types corrects
DATA(lv_amount) = CONV netwr( '1234.56' ). " Type : netwr
DATA(lv_quantity) = CONV menge( 10 ). " Type : menge

Declaration inline FIELD-SYMBOL()

Les symboles de champ peuvent egalement etre declares inline :

CLASS zcl_inline_fieldsymbols DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_fieldsymbols IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" Creer une table interne
DATA(lt_flights) = VALUE ty_t_flight(
( carrid = 'LH' connid = '400' price = 500 )
( carrid = 'LH' connid = '401' price = 600 )
( carrid = 'AA' connid = '100' price = 800 )
).
" LOOP classique avec symbole de champ (ancien)
" FIELD-SYMBOLS <fs_flight_old> TYPE ty_s_flight.
" LOOP AT lt_flights ASSIGNING <fs_flight_old>.
" ENDLOOP.
" Declaration inline dans le LOOP (nouveau)
LOOP AT lt_flights ASSIGNING FIELD-SYMBOL(<fs_flight>).
" Acces en ecriture direct possible
<fs_flight>-price = <fs_flight>-price * '1.1'.
out->write( |{ <fs_flight>-carrid } { <fs_flight>-connid }: { <fs_flight>-price }| ).
ENDLOOP.
" FIELD-SYMBOL inline avec READ TABLE
READ TABLE lt_flights ASSIGNING FIELD-SYMBOL(<fs_found>)
WITH KEY carrid = 'AA'.
IF sy-subrc = 0.
out->write( |Trouve : { <fs_found>-carrid } { <fs_found>-connid }| ).
ENDIF.
ENDMETHOD.
ENDCLASS.

Declaration inline dans LOOP

L’utilisation la plus courante des declarations inline est dans les constructions LOOP :

CLASS zcl_inline_loop DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_order,
order_id TYPE sysuuid_x16,
customer_id TYPE i,
amount TYPE p DECIMALS 2,
status TYPE c LENGTH 1,
END OF ty_s_order.
TYPES ty_t_orders TYPE STANDARD TABLE OF ty_s_order WITH EMPTY KEY.
ENDCLASS.
CLASS zcl_inline_loop IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_orders) = VALUE ty_t_orders(
( customer_id = 1 amount = '100.00' status = 'N' )
( customer_id = 1 amount = '250.00' status = 'P' )
( customer_id = 2 amount = '175.50' status = 'C' )
).
" LOOP avec INTO pour copie (lecture)
LOOP AT lt_orders INTO DATA(ls_order).
out->write( |Commande pour client { ls_order-customer_id }: { ls_order-amount }| ).
ENDLOOP.
" LOOP avec ASSIGNING pour reference (ecriture)
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<fs_order>)
WHERE status = 'N'.
<fs_order>-status = 'P'. " Mise a jour directe
out->write( |Statut modifie pour montant { <fs_order>-amount }| ).
ENDLOOP.
" LOOP avec INDEX pour le numero de ligne
LOOP AT lt_orders INTO DATA(ls_ord) INDEX INTO DATA(lv_index).
out->write( |Ligne { lv_index }: { ls_ord-amount }| ).
ENDLOOP.
" LOOP AT GROUP BY pour le regroupement
LOOP AT lt_orders INTO DATA(ls_customer_order)
GROUP BY ( customer_id = ls_customer_order-customer_id )
INTO DATA(lt_group).
DATA(lv_total) = REDUCE p DECIMALS 2(
INIT sum = CONV p DECIMALS 2( 0 )
FOR <order> IN GROUP lt_group
NEXT sum = sum + <order>-amount
).
out->write( |Client { lt_group-customer_id } Total : { lv_total }| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Declaration inline dans READ TABLE

READ TABLE beneficie particulierement des declarations inline :

CLASS zcl_inline_read_table DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES: BEGIN OF ty_s_product,
product_id TYPE c LENGTH 10,
name TYPE string,
price TYPE p DECIMALS 2,
stock TYPE i,
END OF ty_s_product.
TYPES ty_t_products TYPE SORTED TABLE OF ty_s_product
WITH UNIQUE KEY product_id.
ENDCLASS.
CLASS zcl_inline_read_table IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_products) = VALUE ty_t_products(
( product_id = 'PROD001' name = 'Laptop' price = '999.99' stock = 50 )
( product_id = 'PROD002' name = 'Souris' price = '29.99' stock = 200 )
( product_id = 'PROD003' name = 'Clavier' price = '79.99' stock = 100 )
).
" READ TABLE avec INTO et declaration inline
READ TABLE lt_products INTO DATA(ls_product)
WITH TABLE KEY product_id = 'PROD001'.
IF sy-subrc = 0.
out->write( |Produit : { ls_product-name } - { ls_product-price } EUR| ).
ENDIF.
" READ TABLE avec ASSIGNING et declaration inline
READ TABLE lt_products ASSIGNING FIELD-SYMBOL(<fs_product>)
WITH TABLE KEY product_id = 'PROD002'.
IF sy-subrc = 0.
<fs_product>-stock = <fs_product>-stock - 1. " Reduire le stock
out->write( |Nouveau stock pour { <fs_product>-name }: { <fs_product>-stock }| ).
ENDIF.
" READ TABLE avec REFERENCE INTO
READ TABLE lt_products REFERENCE INTO DATA(lr_product)
WITH TABLE KEY product_id = 'PROD003'.
IF sy-subrc = 0.
out->write( |Reference : { lr_product->name }| ).
ENDIF.
" Expression de table avec DEFAULT (sans sy-subrc)
DATA(ls_found) = VALUE #(
lt_products[ product_id = 'PROD001' ]
DEFAULT VALUE #( name = 'Non trouve' )
).
out->write( |Trouve : { ls_found-name }| ).
" Optionnel : Verifier Line Exists
IF line_exists( lt_products[ product_id = 'PROD999' ] ).
out->write( 'Produit existe' ).
ELSE.
out->write( 'Produit non trouve' ).
ENDIF.
ENDMETHOD.
ENDCLASS.

Declaration inline dans SELECT

Les declarations inline dans les instructions SELECT rendent le code particulierement compact :

CLASS zcl_inline_select DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_select IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" SELECT dans une table interne avec declaration inline
SELECT carrid, connid, fldate, price
FROM sflight
WHERE carrid = 'LH"
INTO TABLE @DATA(lt_flights).
IF sy-subrc = 0.
out->write( |{ lines( lt_flights ) } vols trouves| ).
ENDIF.
" SELECT SINGLE avec declaration inline
SELECT SINGLE carrid, connid, cityfrom, cityto
FROM spfli
WHERE carrid = 'LH' AND connid = '400"
INTO @DATA(ls_connection).
IF sy-subrc = 0.
out->write( |Route : { ls_connection-cityfrom } -> { ls_connection-cityto }| ).
ENDIF.
" SELECT avec LOOP et declaration inline
SELECT carrid, carrname
FROM scarr
INTO @DATA(ls_carrier).
out->write( |Compagnie : { ls_carrier-carrid } - { ls_carrier-carrname }| ).
ENDSELECT.
" Aggregation avec declaration inline
SELECT carrid, SUM( price ) AS total_revenue
FROM sflight
GROUP BY carrid
INTO TABLE @DATA(lt_revenue).
LOOP AT lt_revenue INTO DATA(ls_rev).
out->write( |{ ls_rev-carrid }: { ls_rev-total_revenue }| ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

FINAL() pour les variables immuables

Depuis ABAP 7.57, il existe FINAL() pour les constantes avec inference de type :

CLASS zcl_inline_final DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_final IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" FINAL pour les valeurs immuables
FINAL(lc_max_items) = 100.
FINAL(lc_api_endpoint) = |https://api.example.com/v1|.
" Erreur de compilation si on essaie de modifier une variable FINAL :
" lc_max_items = 200. " <- Cela genererait une erreur !
out->write( |Max Items : { lc_max_items }| ).
out->write( |API : { lc_api_endpoint }| ).
" FINAL dans LOOP (chaque iteration est nouvelle, donc autorise)
DATA(lt_values) = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ).
LOOP AT lt_values INTO FINAL(lv_value).
out->write( |Valeur : { lv_value }| ).
" lv_value = 'X'. " <- Erreur : variable FINAL non modifiable
ENDLOOP.
" FINAL avec retours de methode
FINAL(lv_today) = cl_abap_context_info=>get_system_date( ).
out->write( |Aujourd'hui : { lv_today }| ).
ENDMETHOD.
ENDCLASS.

Bonnes pratiques et lisibilite

Quand utiliser les declarations inline

SituationRecommandation
LOOP, READ TABLE, SELECTToujours utiliser inline
Retours de methodeInline si le type est clair
Types complexesPreferer la declaration explicite
Variables reutiliseesDeclaration explicite au debut

Eviter les anti-patterns

CLASS zcl_inline_best_practices DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_inline_best_practices IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
" MAUVAIS : Declaration multiple dans le meme scope
" DATA(lv_result) = calculate_a( ).
" DATA(lv_result) = calculate_b( ). " Erreur de compilation !
" BON : Une variable, affectations multiples
DATA lv_result TYPE i.
lv_result = 10.
lv_result = 20.
" MAUVAIS : Type pas clair avec des litteraux
DATA(lv_unclear) = '123'. " Est-ce un string ou un nombre ?
" BON : Conversion explicite
DATA(lv_number) = CONV i( '123' ).
" MAUVAIS : Lignes trop longues avec inline
" DATA(ls_very_long) = VALUE ty_s_complex( field1 = 'a' field2 = 'b' ... ).
" BON : Diviser pour les structures complexes
DATA(ls_order) = VALUE ty_s_order(
order_id = cl_system_uuid=>create_uuid_x16_static( )
customer_id = 1
amount = '100.00"
status = 'N"
).
out->write( |Result : { lv_result }| ).
out->write( |Number : { lv_number }| ).
ENDMETHOD.
ENDCLASS.

Inference de type vs types explicites

" L'inference de type est ideale pour :
" - Variables locales ephemeres
" - Valeurs de retour de methode
" - Resultats LOOP/READ TABLE
" Les types explicites sont meilleurs pour :
" - Parametres d'interface
" - Variables membres de classe
" - Quand le type doit etre documente
" - Champs de devise/quantite typiques ABAP
" Exemple : Type explicite pour champ devise
DATA lv_amount TYPE netwr.
lv_amount = '1234.56'.
" vs. Inline (le type doit quand meme correspondre)
DATA(lv_amount_inline) = CONV netwr( '1234.56' ).

Conclusion

Les declarations inline rendent le code ABAP plus compact et lisible. Les points cles :

  • DATA() pour les variables normales avec inference de type
  • FIELD-SYMBOL() pour les symboles de champ dans LOOP et READ TABLE
  • FINAL() pour les valeurs immuables (a partir de 7.57)
  • Utilisez CONV pour la conversion de type explicite avec des litteraux
  • Utilisez les declarations inline dans LOOP, READ TABLE et SELECT
  • Pour les variables complexes ou reutilisees : declaration classique en debut de methode

Articles connexes