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

Вот такой компот, бывает что и на тысячи строк тянется эта простыня.
Почему так получается? Это скорее следствие скупости, лени, спешки плюс недостаток фантазии.
Работать – работает, больших проблем не доставляет. Открыл – всё видно. Можно даже копипастить подходы, использовать общие переменные.
Особая проблема с таким подходом возникает только в ситуации конкурентных правок. Но если такие ситуации всплывают раз в год, то проще перебороть и запинать, чем вылечить проблему в корне.
Хорошая идея – категорический запрет на решение разных задач в рамках одного 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.
Таким образом компот превращается просто в десяток-другой таких вызовов.
Что дальше?
Вот такая первая итерация. Первичная цель достигнута, но есть куда стремиться, есть над чем подумать.
Например:
- Вынести проверку предпосылки в дочерний класс
- Автоматический перебор дочерних классов / реализаций интерфейса
- Кто сказал интерфейсы?