This is a test message to test the length of the message box.
Login
ABAP Refactoring of legacy code
Created by Software-Heroes

ABAP - Refactoring of legacy code (1)

382

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.



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.


Included topics:
HANA migrationLegacy codeOld reportsRefactoring
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


ABAP - Refactoring of legacy code (2)

Category - ABAP

This is about the second part of the refactoring series. We'll show you the final steps to convert the old coding into a new form.

09/25/2020

ABAP Tools - Work with Eclipse (Refactoring)

Category - ABAP

Today's article is about working with Eclipse and how you can efficiently and quickly refactor your source code.

07/24/2020