ABAP - Refactoring alter Reports (1)
Du willst einen alten Report mal komplett Überarbeiten oder sollst diesen auf ein neues System bringen, da ihr gerade eine HANA Migration macht? Kein Problem, wir zeigen dir ein paar Schritte, wie dir das gelingt.
Inhaltsverzeichnis
In dieser zweiteiligen Reihe wollen wir dir 12 Schritte näher bringen, die dir helfen sollen bestehendes Coding aufzuräumen und zu verbessern. Wenn du gerade bei der Migration einen alten Reports auf ein neues System bist, wird dir dieser kleine Guide eine ungefähre Richtung geben.
Start
Du hast dich dazu entschieden einen alten Report zu migrieren oder willst ihn einmal komplett aufräumen, dafür gibt es verschiedene Methoden. In diesem Aritkel zeigen wir dir die 4 Phasen, die ein Report durchlaufen kann und die dir helfen sollen. Jede Phase besteht dabei aus 3 groben Schritten die dir eine Richtung geben sollen.
Dazu ein kleiner Report als Beispiel, den wir nun Schritt für Schritt überarbeiten werden und dir so die einzelnen Pfasen etas näher bringen möchten.
REPORT ztest_determine_bookings.
* INSERT START - BS20110506
TYPES:
BEGIN OF ts_booking,
bukrs TYPE bkpf-bukrs,
belnr TYPE bkpf-belnr,
gjahr TYPE bkpf-gjahr,
cpudt TYPE bkpf-cpudt,
xblnr TYPE bkpf-xblnr,
* DELETED START - BS20110506
* aedat type bkpf-aedat,
* awkey type bkpf-awkey,
* DELETED END - BS20110506
buzei TYPE bseg-buzei,
wrbtr TYPE bseg-wrbtr,
waers TYPE bkpf-waers,
kostl TYPE bseg-kostl, "BS20110315
prctr TYPE bseg-prctr, "BS20110315
butxt TYPE t001-butxt,
END OF ts_booking.
* INSERT END - BS20110506
TYPES: tt_booking TYPE STANDARD TABLE OF ts_booking.
DATA:
gt_data TYPE STANDARD TABLE OF t001,
gs_data TYPE t001.
* INSERT START - BS20110506
DATA: gs_booking TYPE ts_booking.
* INSERT END - BS20110506
DATA:
gt_booking TYPE tt_booking,
gd_date TYPE d. "BS20110315
DATA go_my_table TYPE REF TO cl_salv_table.
field-SYMBOLS:
<gs_booking> type ts_booking.
SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME TITLE TEXT-t01.
PARAMETERS:
p_bukrs TYPE bkpf-bukrs,
p_belnr TYPE bkpf-belnr,
p_gjahr TYPE bkpf-gjahr.
SELECT-OPTIONS:
* s_bukrs for gs_data-bukrs,
s_date FOR gd_date.
SELECTION-SCREEN END OF BLOCK b01.
INITIALIZATION.
DATA ls_date LIKE LINE OF s_date.
* data ls_bukrs like line of s_bukrs. "BS20110410
ls_date-sign = 'I'.
ls_date-option = 'BT'.
ls_date-low = '20200101'.
ls_date-high = sy-datum.
APPEND ls_date TO s_date.
AT SELECTION-SCREEN ON p_bukrs.
IF p_bukrs IS NOT INITIAL.
SELECT SINGLE * FROM t001 INTO gs_data WHERE bukrs = p_bukrs.
IF sy-subrc <> 0.
MESSAGE 'Company Code is not valid!' TYPE 'E'.
ENDIF.
ENDIF.
START-OF-SELECTION.
DATA:
ls_bkpf TYPE bkpf,
ls_bseg TYPE bseg.
IF p_bukrs IS INITIAL OR p_belnr IS INITIAL OR p_gjahr IS INITIAL.
MESSAGE 'Required fields not filled!' TYPE 'E'.
ELSE.
SELECT SINGLE * FROM t001 INTO gs_data WHERE bukrs = p_bukrs.
IF sy-subrc <> 0.
MESSAGE 'Company Code is not valid!' TYPE 'E'.
ELSE.
SELECT * FROM bkpf INTO ls_bkpf
WHERE bukrs = p_bukrs
AND belnr = p_belnr
AND gjahr = p_gjahr.
SELECT * FROM bseg INTO ls_bseg
WHERE bukrs = p_bukrs
AND belnr = p_belnr
AND gjahr = p_gjahr.
CLEAR gs_booking.
MOVE-CORRESPONDING ls_bkpf TO gs_booking.
gs_booking-buzei = ls_bseg-buzei.
gs_booking-wrbtr = ls_bseg-wrbtr.
gs_booking-kostl = ls_bseg-kostl.
gs_booking-prctr = ls_bseg-prctr.
APPEND gs_booking TO gt_booking.
ENDSELECT.
ENDSELECT.
* select single * from t001 into gs_data where bukrs = p_bukrs. "BS20110410
SELECT * FROM t001 INTO TABLE gt_data.
loop at gt_booking ASSIGNING <gs_booking>.
READ TABLE gt_data INTO gs_data
WITH KEY bukrs = <gs_booking>-bukrs.
IF sy-subrc = 0.
<gs_booking>-butxt = gs_data-butxt.
ENDIF.
ENDLOOP.
PERFORM output.
ENDIF.
ENDIF.
FORM output.
* DELETED START - BS20110506
* LOOP AT gt_booking INTO gs_booking.
* write: /, gs_booking-bukrs, gs_booking-belnr, gs_booking-gjahr,
* gs_booking-buzei, gs_booking-wrbtr, gs_booking-kostl.
* ENDLOOP.
* DELETED END - BS20110506
* INSERT START - BS20110506
cl_salv_table=>factory(
IMPORTING
r_salv_table = go_my_table
CHANGING
* t_table = gt_t001 "BS20110315
t_table = gt_booking "BS20110315
).
go_my_table->display( ).
* INSERT END - BS20110506
ENDFORM.
Phase: Analyse
In der Analysephase geht es vor allem um die Sichtung des bestehenden Codes und die Vorbereitung der Kopie, sowie das Aufräumen. Hier soll die Grundlage für das Refactoring geschaffen werden und noch einmal die nötigen Schritte überblickt werden. Dabei gibt es dir folgenden Schritte in dieser Phase:
Objekt kopieren
Wir gehen davon aus, dass wir den Report in ein neues System überführen. Deshalb wir im ersten Schritt der Report und alle abhängigen Objekte auf das neue System kopiert. Es sollten in diesem Schritt noch keine Änderungen am bestehenden Code durchgeführt werden und der Report sollte sich zum Abschluss normal aktivieren lassen.
Aufbau prüfen
Wenn du den Report nicht kennst, geht es in diesem Schritt um die Sichtung der Bestandteile und sich eine Übersicht zu schaffen. Damit sollte der ungefähre Arbeitsaufwand herauskommen und auf welche Besonderheiten geachtet werden sollte.
- Wie gut ist der Report strukturiert (Methoden, Forms)?
- Sind viele Daten global?
- Werden Dynpros verwendet und muss auf PBO/PAI geachtet werden?
Aufräumen
Im ersten echten Schritt beginnen wir mit dem Aufräumen des Quellcodes. Alles was auskommentiert ist, sowie Änderungsvermerke, können gelöscht werden, da sie im neuen Report auch nicht benötigt werden. Einmal Pretty-Printer drücken sollte auch nicht verkehrt sein.
Nach der ersten Phase sollte dann der Quellcode in etwa so aussehen:
REPORT ztest_determine_bookings.
TYPES:
BEGIN OF ts_booking,
bukrs TYPE bkpf-bukrs,
belnr TYPE bkpf-belnr,
gjahr TYPE bkpf-gjahr,
cpudt TYPE bkpf-cpudt,
xblnr TYPE bkpf-xblnr,
buzei TYPE bseg-buzei,
wrbtr TYPE bseg-wrbtr,
waers TYPE bkpf-waers,
kostl TYPE bseg-kostl,
prctr TYPE bseg-prctr,
butxt TYPE t001-butxt,
END OF ts_booking.
TYPES: tt_booking TYPE STANDARD TABLE OF ts_booking.
DATA:
gt_data TYPE STANDARD TABLE OF t001,
gs_data TYPE t001.
DATA: gs_booking TYPE ts_booking.
DATA:
gt_booking TYPE tt_booking,
gd_date TYPE d.
DATA go_my_table TYPE REF TO cl_salv_table.
FIELD-SYMBOLS:
<gs_booking> TYPE ts_booking.
SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME TITLE TEXT-t01.
PARAMETERS:
p_bukrs TYPE bkpf-bukrs,
p_belnr TYPE bkpf-belnr,
p_gjahr TYPE bkpf-gjahr.
SELECT-OPTIONS:
s_date FOR gd_date.
SELECTION-SCREEN END OF BLOCK b01.
INITIALIZATION.
DATA ls_date LIKE LINE OF s_date.
ls_date-sign = 'I'.
ls_date-option = 'BT'.
ls_date-low = '20200101'.
ls_date-high = sy-datum.
APPEND ls_date TO s_date.
AT SELECTION-SCREEN ON p_bukrs.
IF p_bukrs IS NOT INITIAL.
SELECT SINGLE * FROM t001 INTO gs_data WHERE bukrs = p_bukrs.
IF sy-subrc <> 0.
MESSAGE 'Company Code is not valid!' TYPE 'E'.
ENDIF.
ENDIF.
START-OF-SELECTION.
DATA:
ls_bkpf TYPE bkpf,
ls_bseg TYPE bseg.
IF p_bukrs IS INITIAL OR p_belnr IS INITIAL OR p_gjahr IS INITIAL.
MESSAGE 'Required fields not filled!' TYPE 'E'.
ELSE.
SELECT SINGLE * FROM t001 INTO gs_data WHERE bukrs = p_bukrs.
IF sy-subrc <> 0.
MESSAGE 'Company Code is not valid!' TYPE 'E'.
ELSE.
SELECT * FROM bkpf INTO ls_bkpf
WHERE bukrs = p_bukrs
AND belnr = p_belnr
AND gjahr = p_gjahr.
SELECT * FROM bseg INTO ls_bseg
WHERE bukrs = p_bukrs
AND belnr = p_belnr
AND gjahr = p_gjahr.
CLEAR gs_booking.
MOVE-CORRESPONDING ls_bkpf TO gs_booking.
gs_booking-buzei = ls_bseg-buzei.
gs_booking-wrbtr = ls_bseg-wrbtr.
gs_booking-kostl = ls_bseg-kostl.
gs_booking-prctr = ls_bseg-prctr.
APPEND gs_booking TO gt_booking.
ENDSELECT.
ENDSELECT.
SELECT * FROM t001 INTO TABLE gt_data.
LOOP AT gt_booking ASSIGNING <gs_booking>.
READ TABLE gt_data INTO gs_data
WITH KEY bukrs = <gs_booking>-bukrs.
IF sy-subrc = 0.
<gs_booking>-butxt = gs_data-butxt.
ENDIF.
ENDLOOP.
PERFORM output.
ENDIF.
ENDIF.
FORM output.
cl_salv_table=>factory(
IMPORTING
r_salv_table = go_my_table
CHANGING
t_table = gt_booking
).
go_my_table->display( ).
ENDFORM.
Phase: Strukturierung
In der zweiten Phase geht es um die Aufteilung des Codes in entsprechende Includes, sowie den Aufbau der Klasse und das verschieben des Codings. In dieser Phase werden noch keine größeren Dinge am Code geändert.
Includes erstellen
In diesem Schritt wird der Code entsprechend der Bestandteile zerlegt und in einzelne Includes aufgeteilt, damit auch bei viel Quellcode die Übersicht gewahrt bleibt. Die Includes werden wie das Hauptprogramm benannt, bekommen aber einen Zusatz, der die Funktion darstellt:
- Hauptprogramm - alle programmspezifischen Events
- TOP-Include (z.B. _TOP) - hier befinden sich alle globalen Datendefinitionen, Feldsymbole, Tabellen, Klassenbekanntgaben
- Selektionsbild (z.B. _SEL) - alle Selektionsbilder und Abschnitte sollten in ein eigenes Include verschoben werden
- Forms und Module (z.B. _F01) - hier befinden sich die letzten Forms oder Module, wenn du noch eine Dynproverarbeitung im Report hast
- Klassen (z.B. _C01) - pro Klasse sollte ein eigenes Include genommen werden, diese können durchnummeriert werden
Klasse anlegen
Nun geht es um die Strukturierung der Klasse und der Daten. Dazu wird die Klasse angelegt und alle Events die bisher im Report angelegt wurden. Für jedes Form und jede Methode wird nun ein entsprechendes Gegenstück angelegt, diese dürfen durchaus auch schon auf Private gesetzt werden. Die Definition der Klasse könnte dann entsprechend aussehen:
CLASS lcl_report DEFINITION FINAL.
PUBLIC SECTION.
METHODS:
initialization,
main,
on_bukrs.
PRIVATE SECTION.
METHODS:
output.
ENDCLASS.
Code verschieben
Im nächsten Schritt verschieben wir den entsprechenden Code in die einzelnen Methoden und übernehmen die globalen Variablen/Typen in den globalen Teil der Klasse. Hier solltest du darauf achten, dass Feldsymbole nicht verschoben werden können, da diese nicht im globalen Bereich der Klasse definiert werden können. In unserem Fall können wir also die Typdefinition und das Feldsymbol erst einmal nicht übernehmen.
REPORT ztest_determine_bookings.
TYPES:
BEGIN OF ts_booking,
bukrs TYPE bkpf-bukrs,
belnr TYPE bkpf-belnr,
gjahr TYPE bkpf-gjahr,
cpudt TYPE bkpf-cpudt,
xblnr TYPE bkpf-xblnr,
buzei TYPE bseg-buzei,
wrbtr TYPE bseg-wrbtr,
waers TYPE bkpf-waers,
kostl TYPE bseg-kostl,
prctr TYPE bseg-prctr,
butxt TYPE t001-butxt,
END OF ts_booking.
TYPES: tt_booking TYPE STANDARD TABLE OF ts_booking.
DATA:
gd_date TYPE d.
FIELD-SYMBOLS:
<gs_booking> TYPE ts_booking.
SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME TITLE TEXT-t01.
PARAMETERS:
p_bukrs TYPE bkpf-bukrs,
p_belnr TYPE bkpf-belnr,
p_gjahr TYPE bkpf-gjahr.
SELECT-OPTIONS:
s_date FOR gd_date.
SELECTION-SCREEN END OF BLOCK b01.
CLASS lcl_report DEFINITION FINAL.
PUBLIC SECTION.
METHODS:
initialization,
main,
on_bukrs.
PRIVATE SECTION.
DATA:
gt_data TYPE STANDARD TABLE OF t001,
gs_data TYPE t001,
gt_booking TYPE tt_booking,
gs_booking TYPE ts_booking,
go_my_table TYPE REF TO cl_salv_table.
METHODS:
output.
ENDCLASS.
CLASS lcl_report IMPLEMENTATION.
METHOD initialization.
DATA ls_date LIKE LINE OF s_date.
ls_date-sign = 'I'.
ls_date-option = 'BT'.
ls_date-low = '20200101'.
ls_date-high = sy-datum.
APPEND ls_date TO s_date.
ENDMETHOD.
METHOD on_bukrs.
IF p_bukrs IS NOT INITIAL.
SELECT SINGLE * FROM t001 INTO gs_data WHERE bukrs = p_bukrs.
IF sy-subrc <> 0.
MESSAGE 'Company Code is not valid!' TYPE 'E'.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD main.
DATA:
ls_bkpf TYPE bkpf,
ls_bseg TYPE bseg.
IF p_bukrs IS INITIAL OR p_belnr IS INITIAL OR p_gjahr IS INITIAL.
MESSAGE 'Required fields not filled!' TYPE 'E'.
ELSE.
SELECT SINGLE * FROM t001 INTO gs_data WHERE bukrs = p_bukrs.
IF sy-subrc <> 0.
MESSAGE 'Company Code is not valid!' TYPE 'E'.
ELSE.
SELECT * FROM bkpf INTO ls_bkpf
WHERE bukrs = p_bukrs
AND belnr = p_belnr
AND gjahr = p_gjahr.
SELECT * FROM bseg INTO ls_bseg
WHERE bukrs = p_bukrs
AND belnr = p_belnr
AND gjahr = p_gjahr.
CLEAR gs_booking.
MOVE-CORRESPONDING ls_bkpf TO gs_booking.
gs_booking-buzei = ls_bseg-buzei.
gs_booking-wrbtr = ls_bseg-wrbtr.
gs_booking-kostl = ls_bseg-kostl.
gs_booking-prctr = ls_bseg-prctr.
APPEND gs_booking TO gt_booking.
ENDSELECT.
ENDSELECT.
SELECT * FROM t001 INTO TABLE gt_data.
LOOP AT gt_booking ASSIGNING <gs_booking>.
READ TABLE gt_data INTO gs_data
WITH KEY bukrs = <gs_booking>-bukrs.
IF sy-subrc = 0.
<gs_booking>-butxt = gs_data-butxt.
ENDIF.
ENDLOOP.
output( ).
ENDIF.
ENDIF.
ENDMETHOD.
METHOD output.
cl_salv_table=>factory(
IMPORTING
r_salv_table = go_my_table
CHANGING
t_table = gt_booking
).
go_my_table->display( ).
ENDMETHOD.
ENDCLASS.
INITIALIZATION.
DATA(go_report) = NEW lcl_report( ).
go_report->initialization( ).
AT SELECTION-SCREEN ON p_bukrs.
go_report->on_bukrs( ).
START-OF-SELECTION.
go_report->main( ).
Nach Abschluss dieser Phase hat sich vor allem in der Struktur des Reports viel geändert, doch noch nicht alles ist perfekt und so wie wir es wollen. Es sollten weiterhin keine Fehler im Report zu finden sein und die Aktivierung nach der Phase sollte kein Problem sein, ansonsten müsstest du hier suchen, was noch nicht ganz funktioniert. Auch nicht vergessen die neue Klasse zu instanziieren und zu verwenden.
Dabei können vielleicht die folgenden Punkte helfen:
- Werden globale Variablen noch für das Selektionsbild benötigt?
- Werden Typen noch für Feldsymbole verwendet?
- Hast du alle Form Aufrufe durch die Methodenaufrufe ausgetauscht?
Fazit
In den ersten beiden Phasen ging es vor allem um das Verstehen des Quellcodes, das Aufräumen und das Überführen in eine neue Struktur, mit der wir nun arbeiten können. Der Report ist damit für die nächsten Schritte des Refactoring vorbereitet. Im nächsten Artikel durchlaufen wir dann die beiden letzten Phasen und bringen den Report in unsere gewünschte Form.