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.