ABAP – Bonnes pratiques – Separation of Concerns

Bonjour à tous,

Cet article se veut le premier d’une série sur les bonnes pratiques de développement ABAP telles que définies par les gens de SAP eux-mêmes. Après avoir longuement recherché de telles bonnes pratiques, j’ai fini par les trouver, et je souhaite les partager ici, reformatées avec mes mots.

Avant-propos…
Certaines des bonnes pratiques qui seront mentionnées ici ne sont pas forcément particulières au langage ABAP, mais s’appliquent bien sûr à tous les langages de programmation, avec plus ou moins d’adaptation. Ce sera le cas pour le présent article, par exemple.

Séparation des préoccupations…
Nous commencerons cette série en traitant de la séparation des préoccupations, qui sonne, il faut le dire, beaucoup mieux en anglais : separation of concerns (SoC).


La séparation des préoccupations est un principe utilisé en développement pour diviser une application en unités individuelles, dont les fonctions présentent un minimum d’intrications. Pour ce faire, on utilisera la modularisation, l’encapsulation et l’ordonnancement des couches logicielles.
L’architecture classique des systèmes SAP est une architecture en trois tiers. Et bien que cela soit idéal pour mettre en place le principe de SoC, cela n’avait jamais été mis en place (dans les programmes standard, j’entends). Les programmes applicatifs étaient historiquement gérés en un seul bloc monolithique, sans gestion de couches. Ce type de programmation n’est plus pertinent dans le monde du développement actuel !

Règle de base…
La bonne pratique SAP suggère d’utiliser ce principe de SoC le plus rigoureusement possible. Il est particulièrement important de séparer la logique de la couche applicative de celle de la couche de présentation, de la couche persistante et celle de la logique de communication avec des systèmes externes (quatre couches donc).

Détaillons un peu…
Le principe SoC identifie les parties d’une application ayant trait à un objectif donné, et les encapsule dans des unités individuelles. Ces dernières communiquent uniquement entre elles via des interfaces. Grâce à cela, le logiciel est découpé en composants maintenables, et le résultat est une application :

  • plus stable
  • plus compréhensible
  • plus réutilisable
  • plus maintenable
  • et plus facilement testable.

Mauvais exemple…
L’exemple de code présenté plus bas correspond au modèle de programmation propagé par SAP lui-même depuis un bon nombre d’années. Cependant, ils modernisent leur modèle de développement et souhaitent à présent que tous les développeurs ABAP suivent leurs nouvelles bonnes pratiques.
L’exemple ci-dessous est donc typique de la façon dont les différentes préoccupations (concerns) sont mélangées les unes aux autres dans une seule unité de programme. Les déclarations et l’implémentation des fonctions sont mélangées ; l’accès aux données persistantes, le traitement des données, leur présentation et les déclarations associées apparaissent tous dans une seule unité.

REPORT z_non_soc_report.

PARAMETERS p_carrid TYPE spfli-carrid.

DATA gt_spfli_tab TYPE STANDARD TABLE OF spfli.
DATA go_alv       TYPE REF TO cl_salv_table.
DATA gx_alv_exc   TYPE REF TO cx_salv_msg.

SELECT *
INTO TABLE gt_spfli_tab
FROM spfli
WHERE carrid = p_carrid.

IF sy-subrc = 0.
  SORT gt_spfli_tab BY cityfrom cityto.

  TRY.
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table = go_alv
        CHANGING
          t_table = gt_spfli_tab
      ).

      alv->display( ).

    CATCH cx_salv_msg INTO alv_exc.

      MESSAGE alv_exc TYPE 'I' DISPLAY LIKE 'E'.
  ENDTRY.
ENDIF.

 

Alors bien sûr, il ne faut pas forcément que toutes les préoccupations soient systématiquement séparées. Dans un programme aussi court que celui-ci, cela serait chronophage. Cependant, les applications réelles sont généralement de (très) longs programmes ABAP dans lesquels tous ces préoccupations sont gérées en même temps.
Le peu de modularisation utilisée se réduit à réutiliser des unités fonctionnelles, mais ne se penche jamais, ou rarement, sur les couches logicielles. En outre, c’est souvent de grands volumes de données qui sont traités et utilisés dans les différentes procédures, ce qui a pour conséquence que toutes les parties du programme sont dépendantes entre elles et ne peuvent être testées individuellement.

Bon exemple procédural…
Dans le code source qui suit, on reprend les mêmes fonctions que le précédent, en implémentant le principe de SoC en mode procédural.
Toutes les préoccupations sont regroupées dans des procédures séparées qui sont affectées aux couches. C’est un peu trop pour un programme aussi simple, mais c’est pour l’exemple.

REPORT z_soc_report.

SELECTION-SCREEN BEGIN OF SCREEN 100.
PARAMETERS p_carrid TYPE spfli-carrid.
SELECTION-SCREEN END OF SCREEN 100.

TYPES spfli_tab TYPE STANDARD TABLE OF spfli.

DATA gv_carrid TYPE spfli-carrid.
DATA gt_table  TYPE spfli_tab.
DATA gv_arc    TYPE sy-subrc.

START-OF-SELECTION.

 PERFORM get_carrid
   CHANGING
     gv_carrid.
 PERFORM get_table
   USING
     gv_carrid
  CHANGING
    gt_table
    gv_arc.

IF gv_arc = 0.
 PERFORM sort_table
   CHANGING
     gt_table.
 PERFORM display_table
   USING
     gt_table.
ENDIF.

* Presentation layer
FORM get_carrid
  CHANGING
    value(cv_carrid) TYPE spfli-carrid.
 CALL SELECTION-SCREEN 100.
 IF sy-subrc = 0.
   cv_carrid = p_carrid.
 ENDIF.
ENDFORM.

FORM display_table
  USING
    it_table TYPE spfli_tab.
  DATA lo_alv     TYPE REF TO cl_salv_table.
  DATA lx_alv_exc TYPE REF TO cx_salv_msg.

  TRY.
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table = lo_alv
        CHANGING
          t_table = it_table
      ).
     lo_alv->display( ).

    CATCH cx_salv_msg INTO lx_alv_exc.
      MESSAGE alv_exc TYPE 'I' DISPLAY LIKE 'E'.
 ENDTRY.
ENDFORM.

* Application layer
FORM sort_table
  CHANGING
  ct_table TYPE spfli_tab.
  SORT table BY cityfrom cityto.
ENDFORM.

* Persistency layer
FORM get_table
  USING
    iv_carrid TYPE spfli-carrid
  CHANGING
    ct_table  TYPE spfli_tab
    cv_arc    TYPE sy-subrc.
  SELECT *
  INTO TABLE table
  FROM spfli
  WHERE carrid = iv_carrid.
  cv_arc = sy-subrc.
ENDFORM.

 

Bon exemple orienté objet…
Le code précédent est mieux que le premier. Cependant, et ce sera l’objet d’un article de cette série sur les bonnes pratiques, SAP recommande de ne plus développer qu’en ABAP objet. C’est pourquoi je vous propose un dernier exemple, isofonctionnel.

REPORT z_soc_class_report.

SELECTION-SCREEN BEGIN OF SCREEN 100.
PARAMETERS p_carrid TYPE spfli-carrid.
SELECTION-SCREEN END OF SCREEN 100.

TYPES ty_spfli_tab TYPE STANDARD TABLE OF spfli.

CLASS lcl_presentation_server DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      get_carrid
        RETURNING
          VALUE(rv_carrid) TYPE spfli-carrid,
      display_table 
        IMPORTING
          VALUE(it_table) TYPE spfli_tab.
ENDCLASS.

CLASS lcl_lcl_presentation_server IMPLEMENTATION.
  METHOD get_carrid.
    CALL SELECTION-SCREEN 100.
    IF sy-subrc = 0.
      rv_carrid = p_carrid.
    ENDIF.
  ENDMETHOD.

  METHOD display_table.
    DATA lo_alv     TYPE REF TO cl_salv_table.
    DATA lx_alv_exc TYPE REF TO cx_salv_msg.

    TRY.
        cl_salv_table=>factory(
          IMPORTING
            r_salv_table = lo_alv
          CHANGING
            t_table      = it_table
        ).
        alv->display( ).

      CATCH cx_salv_msg INTO alv_exc.
        MESSAGE alv_exc TYPE 'I' DISPLAY LIKE 'E'.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

CLASS application_server DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      sort_table
        CHANGING
          ct_table TYPE spfli_tab.
ENDCLASS.

CLASS application_server IMPLEMENTATION.
  METHOD sort_table.
    SORT ct_table BY cityfrom cityto.
  ENDMETHOD.
ENDCLASS.

CLASS persistency_server DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      get_table
        IMPORTING
          iv_carrid TYPE spfli-carrid
        EXPORTING
          et_table  TYPE spfli_tab
          ev_arc    TYPE sy-subrc.
ENDCLASS.

CLASS persistency_server IMPLEMENTATION.
  METHOD get_table.
    SELECT *
    INTO TABLE et_table
    FROM spfli
    WHERE carrid = iv_carrid.
    ev_arc = sy-subrc.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_report DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS main.
ENDCLASS.

CLASS lcl_report IMPLEMENTATION.
  METHOD main.
    DATA lv_carrid TYPE spfli-carrid.
    DATA lt_table  TYPE spfli_tab.
    DATA lv_arc    TYPE sy-subrc.

    lv_carrid = lcl_presentation_server=>get_carrid( ).
    lcl_persistency_server=>get_table(
      EXPORTING
        iv_carrid = lv_carrid
      IMPORTING
        et_table  = lt_table
        ev_arc    = lv_arc
    ).
    IF lv_arc = 0.
      lcl_application_server=>sort_table(
        CHANGING
          ct_table = lt_table
      ).

      lcl_presentation_server=>display_table( lt_table ).
    ENDIF.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  lcl_report=>main( ).

 

À première vue, cela paraît un peu excessif pour une si petite fonctionnalité. Mais il faut bien garder en tête que plus l’application est grande et réaliste, plus petite est la proportion de surcharge générée par l’encapsulation des préoccupations dans des classes. si les options de réutilisabilité des objets ABAP sont utilisées correctement, il est même possible de réduire la quantité de code source.
Enfin, les étapes individuelles sont à présent enveloppées dans des classes, c’est-à-dire de vraies unités de programme (contrairement au second code source). Dans la pratique, cette séparation n’est pas opérée dans un seul programme mais dans des classes globales, ce qui permet une meilleure réutilisation.

Et nous arrivons au terme de la présentation du principe de SoC tel que l’on peut le concevoir en ABAP. J’espère avoir été suffisamment clair. La partie commentaire est ouverte, n’hésitez pas à vous exprimer sur le sujet.

Le prochain article de cette série traitera du principe KISS et sera plus court.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *