ABAP - Refactoring of legacy code (1)
Do you want to completely revise an old report or should you transfer it to a new system because you are currently doing a HANA migration? No problem, we'll show you a few steps how to do it.
Table of contents
In this two-part series, we want to introduce you to 12 steps that will help you clean up and improve existing coding. If you're just migrating an old report to a new system, this little guide will give you an approximate direction.
Start
You have decided to migrate an old report or you want to clean it up completely, there are different methods for this. In this article we show you the 4 phases a report can go through and which should help you. Each phase consists of 3 rough steps that should give you a direction.
For this purpose a small report as an example, which we will now revise step by step and thus bring you closer to the individual phases.
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: Analysis
The analysis phase is all about viewing the existing code and preparing the copy, as well as cleaning up. Here the basis for the refactoring should be created and the necessary steps should be reviewed. There are the following steps in this phase:
Copy object
We assume that we will transfer the report to a new system. Therefore, in the first step, the report and all dependent objects are copied to the new system. No changes should be made to the existing code in this step and the report should be able to be activated normally at the end.
Check structure
If you don't know the report, this step is about sifting through the components and getting an overview. This should determine the approximate workload and which special features should be observed.
- How well is the report structured (methods, forms)?
- Is many data at global level?
- Are screens used and do you have to pay attention to PBO/PAI?
Clean up
The first real step is to start cleaning up the source code. Everything that is commented out, as well as change notes, can be deleted as they are not needed in the new report. Pressing Pretty-Printer once shouldn't be wrong either.
After the first phase, the source code should look something like this:
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: Structure
The second phase deals with the division of the code into corresponding includes, as well as the structure of the class and the shifting of the coding. No major code changes are made in this phase.
Create includes
In this step, the code is broken down according to the components and divided into individual includes so that the overview is maintained even with a lot of source code. The includes are named like the main program, but have an addition that represents the function:
- Main program - all program-specific events
- TOP include (e.g. _TOP) - all global data definitions, field symbols, tables, class announcements are located here
- Selection screen (e.g. _SEL) - all selection screens and sections should be moved to a separate include
- Forms and modules (e.g. _F01) - here are the last forms or modules if you still have a screen processing in the report
- Classes (e.g. _C01) - a separate include should be used for each class, these can be numbered consecutively
Create class
Now it is a matter of structuring the class and the data. For this purpose, the class is created and all events that were previously created in the report. A corresponding counterpart is now created for each form and each method, these can certainly also be set to private. The definition of the class could then look like this:
CLASS lcl_report DEFINITION FINAL.
PUBLIC SECTION.
METHODS:
initialization,
main,
on_bukrs.
PRIVATE SECTION.
METHODS:
output.
ENDCLASS.
Move code
In the next step we move the corresponding code into the individual methods and transfer the global variables/types to the global part of the class. Here you should make sure that field symbols cannot be moved, as they cannot be defined in the global area of the class. In our case, we cannot move the type definition and the field symbol for the time being. We will fix this later.
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( ).
After this phase, a lot has changed, especially in the structure of the report, but not everything is perfect, as we want it to be. There should still be no errors in the report and activation after the phase should not be a problem, otherwise you would have to look here for what does not yet work completely. Also don't forget to instantiate and use the new class in your report events.
The following points may help:
- Are global variables still required for the selection screen?
- Are types still used for field symbols?
- Have you swapped all of the form calls for the method calls?
Conclusion
In the first two phases, the main focus was on understanding the source code, cleaning it up and converting it into a new structure that we can now work with. The report is now ready for the next steps in refactoring. In the next article we go through the last two phases and bring the report into the form we want.