ZDOC_2700

Как варить компот

Иногда так получается, что в одном глобальном расширении внутри одного большого куска ABAP-кода скапливается много маленьких кусочков, делающие совершенно разные вещи для совершенно разных людей/модулей/пакетов.

Вот для примера типичный код из события OpenFI BTE 1120 (замещения перед сохранением бухгалтерской проводки):
User-exit source sample
Вот такой компот, бывает что и на тысячи строк тянется эта простыня.

Почему так получается? Это скорее следствие скупости, лени, спешки плюс недостаток фантазии.

Работать – работает, больших проблем не доставляет. Открыл – всё видно. Можно даже копипастить подходы, использовать общие переменные.

Особая проблема с таким подходом возникает только в ситуации конкурентных правок. Но если такие ситуации всплывают раз в год, то проще перебороть и запинать, чем вылечить проблему в корне.

Хорошая идея – категорический запрет на решение разных задач в рамках  одного ABAP фрагмента (инклюда, функции, метода). Совсем уж общего решения предложить нельзя, так как есть нюансы, да и многое дело вкуса.

Приступим к первой итерации.

Общий обёрточный класс

Чтобы сделать прототип и запрятать управление *SUBST*(особые танцы данного юзер-экзита) под капот, создадим общий обёрточный класс:

class ZCL_BTE_1120_BASE definition
  public
  abstract
  create public .

public section.

  methods RUN
    changing
      !T_BKPF type BKPF_T
      !T_BSEG type BSEG_T
      !T_BKPFSUB type BKPF_SUBST_T
      !T_BSEGSUB type BSEG_SUBST_T .
protected section.

  data MS_BKPF type BKPF .
  data MT_BSEG type BSEG_T .

  methods PROCESS .
private section.

  data MS_BKPF_OLD type BKPF .
  data MS_BKPF_NEW type BKPF .
  data MT_BSEG_OLD type BSEG_T .
  data MT_BSEG_NEW type BSEG_T .
ENDCLASS.



CLASS ZCL_BTE_1120_BASE IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_BTE_1120_BASE->PROCESS
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method PROCESS.
  "do nothing, method need redefinition
endmethod.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_BTE_1120_BASE->RUN
* +-------------------------------------------------------------------------------------------------+
* | [<-->] T_BKPF                         TYPE        BKPF_T
* | [<-->] T_BSEG                         TYPE        BSEG_T
* | [<-->] T_BKPFSUB                      TYPE        BKPF_SUBST_T
* | [<-->] T_BSEGSUB                      TYPE        BSEG_SUBST_T
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD run.

  READ TABLE t_bkpf INDEX 1 INTO ms_bkpf_old.
  mt_bseg_old[] = t_bseg[].

  ms_bkpf   = ms_bkpf_old.
  mt_bseg[] = mt_bseg_old[].


  process( ).


  ms_bkpf_new   = ms_bkpf.
  mt_bseg_new[] = mt_bseg[].

  CLEAR t_bkpf.
  APPEND ms_bkpf_new TO t_bkpf.
  t_bseg[] = mt_bseg_new[].


  DATA: lo_bseg_struct    TYPE REF TO cl_abap_structdescr.
  DATA: lo_bsegsub_struct TYPE REF TO cl_abap_structdescr.
  DATA: lo_bkpf_struct    TYPE REF TO cl_abap_structdescr.
  DATA: lo_bkpfsub_struct TYPE REF TO cl_abap_structdescr.
  lo_bseg_struct  ?= cl_abap_structdescr=>describe_by_data( t_bseg[ 1 ] ).
  lo_bkpf_struct  ?= cl_abap_structdescr=>describe_by_data( t_bkpf[ 1 ] ).
  lo_bsegsub_struct  ?= cl_abap_structdescr=>describe_by_data( t_bsegsub[ 1 ] ).
  lo_bkpfsub_struct  ?= cl_abap_structdescr=>describe_by_data( t_bkpfsub[ 1 ] ).

  READ TABLE t_bkpfsub INDEX 1 ASSIGNING FIELD-SYMBOL(<bkpfsub>).

  LOOP AT lo_bkpf_struct->components INTO DATA(ls_bkpf_struct).
    ASSIGN COMPONENT ls_bkpf_struct-name OF STRUCTURE ms_bkpf_old TO FIELD-SYMBOL(<bkpf_fld_old>).
    ASSIGN COMPONENT ls_bkpf_struct-name OF STRUCTURE ms_bkpf_new TO FIELD-SYMBOL(<bkpf_fld_new>).

    IF <bkpf_fld_new> NE <bkpf_fld_old>.
      ASSIGN COMPONENT ls_bkpf_struct-name OF STRUCTURE <bkpfsub> TO FIELD-SYMBOL(<bkpfsub_field>).
      IF sy-subrc = 0.
        <bkpfsub_field> = <bkpf_fld_new>.
      ELSE.
        "[WARN] Changed unchangable field?
      ENDIF.
    ENDIF.
  ENDLOOP.


  LOOP AT t_bsegsub ASSIGNING FIELD-SYMBOL(<bsegsub>).
    READ TABLE mt_bseg_old INDEX sy-tabix ASSIGNING FIELD-SYMBOL(<bseg_old>).
    READ TABLE mt_bseg_new INDEX sy-tabix ASSIGNING FIELD-SYMBOL(<bseg_new>).

    LOOP AT lo_bseg_struct->components INTO DATA(ls_bseg_struct).
      ASSIGN COMPONENT ls_bseg_struct-name OF STRUCTURE <bseg_old> TO FIELD-SYMBOL(<bseg_fld_old>).
      ASSIGN COMPONENT ls_bseg_struct-name OF STRUCTURE <bseg_new> TO FIELD-SYMBOL(<bseg_fld_new>).

      IF <bseg_fld_new> NE <bseg_fld_old>.
        ASSIGN COMPONENT ls_bseg_struct-name OF STRUCTURE <bsegsub> TO FIELD-SYMBOL(<bsegsub_field>).
        IF sy-subrc = 0.
          <bsegsub_field> = <bseg_fld_new>.
        ELSE.
          "[WARN] Changed unchangable field?
        ENDIF.
      ENDIF.
    ENDLOOP.

  ENDLOOP.

ENDMETHOD.
ENDCLASS.

Метод RUN предназначен для вызова снаружи, выполняет обёрточные функции

Метод PROCESS следует переопределять в дочерних процессах.

Реализация дочерних классов

Класс номер один:

class ZCL_BTE_1120_HRPAY_KZAZ definition
  public
  inheriting from ZCL_BTE_1120_BASE
  final
  create public .

public section.
protected section.

  methods PROCESS
    redefinition .
private section.
ENDCLASS.



CLASS ZCL_BTE_1120_HRPAY_KZAZ IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_BTE_1120_HRPAY_KZAZ->PROCESS
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD process.

  LOOP AT mt_bseg ASSIGNING FIELD-SYMBOL(<bseg>).
    ...<bseg>-xnegp = abap_false...

    IF <bseg>-hkont = 'T0000015' AND <bseg>-shkzg = 'H' AND ms_bkpf-blart = 'KZ'.
      ...<bseg>-xnegp = abap_true...
    ENDIF.
  ENDLOOP.

ENDMETHOD.
ENDCLASS.

Класс номер два:

class ZCL_BTE_1120_HRPAY_KM definition
  public
  inheriting from ZCL_BTE_1120_BASE
  final
  create public .

public section.
protected section.

  methods PROCESS
    redefinition .
private section.
ENDCLASS.



CLASS ZCL_BTE_1120_HRPAY_KM IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_BTE_1120_HRPAY_KM->PROCESS
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD process.

  LOOP AT mt_bseg ASSIGNING FIELD-SYMBOL(<bseg>).
    IF ( <bseg>-hkont = '0032440000' OR
         <bseg>-hkont = '0032460000' ).
      "some code 
    ENDIF.

    IF ( <bseg>-hkont = '0032410000' OR
         <bseg>-hkont = '0031220000' ).
      IF <bseg>-shkzg = 'H'.
         "some code 
      ENDIF.
    ENDIF.
  ENDLOOP.

ENDMETHOD.
ENDCLASS.

И осталось подёргать эти классы из общего метода:

  IF ls_bkpf-awtyp = 'HRPAY' AND ( ls_bkpf-blart = 'KZ' OR ls_bkpf-blart = 'AZ' ).

    DATA(lo_hrpay_kzaz) = NEW zcl_bte_1120_hrpay_kzaz( ).
    lo_hrpay_kzaz->run(
      CHANGING
        t_bkpf    = t_bkpf[]
        t_bseg    = t_bseg[]
        t_bkpfsub = t_bkpfsub[]
        t_bsegsub = t_bsegsub[] ).

  ENDIF.


  IF ls_bkpf-awtyp = 'HRPAY' AND ls_bkpf-blart = 'KM'.

    DATA(lo_hrpay_km) = NEW zcl_bte_1120_hrpay_km( ).
    lo_hrpay_km->run(
      CHANGING
        t_bkpf    = t_bkpf[]
        t_bseg    = t_bseg[]
        t_bkpfsub = t_bkpfsub[]
        t_bsegsub = t_bsegsub[] ).

  ENDIF.

Таким образом компот превращается просто в десяток-другой таких вызовов.

Что дальше?

Вот такая первая итерация. Первичная цель достигнута, но есть куда стремиться, есть над чем подумать.

Например:

  • Вынести проверку предпосылки в дочерний класс
  • Автоматический перебор дочерних классов / реализаций интерфейса
  • Кто сказал интерфейсы?