Иногда так получается, что в одном глобальном расширении внутри одного большого куска ABAP-кода скапливается много маленьких кусочков, делающие совершенно разные вещи для совершенно разных людей/модулей/пакетов.
Вот для примера типичный код из события OpenFI BTE 1120 (замещения перед сохранением бухгалтерской проводки):
Вот такой компот, бывает что и на тысячи строк тянется эта простыня.
Почему так получается? Это скорее следствие скупости, лени, спешки плюс недостаток фантазии.
Работать – работает, больших проблем не доставляет. Открыл – всё видно. Можно даже копипастить подходы, использовать общие переменные.
Особая проблема с таким подходом возникает только в ситуации конкурентных правок. Но если такие ситуации всплывают раз в год, то проще перебороть и запинать, чем вылечить проблему в корне.
Хорошая идея – категорический запрет на решение разных задач в рамках одного ABAP фрагмента (инклюда, функции, метода). Совсем уж общего решения предложить нельзя, так как есть нюансы, да и многое дело вкуса.
Приступим к первой итерации.
Общий обёрточный класс
Чтобы сделать прототип и запрятать управление *SUBST*(особые танцы данного юзер-экзита) под капот, создадим общий обёрточный класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
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 следует переопределять в дочерних процессах.
Реализация дочерних классов
Класс номер один:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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. |
Класс номер два:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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. |
И осталось подёргать эти классы из общего метода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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. |
Таким образом компот превращается просто в десяток-другой таких вызовов.
Что дальше?
Вот такая первая итерация. Первичная цель достигнута, но есть куда стремиться, есть над чем подумать.
Например:
- Вынести проверку предпосылки в дочерний класс
- Автоматический перебор дочерних классов / реализаций интерфейса
- Кто сказал интерфейсы?
Можно сделать Z-BAdi c фильтром
Почему родительский класс не абстрактный? На лицо явный пример, где он должен использоваться
Чем этот велосипед проще и понятнее BAdi?
В любом случае все эти вызовы в одном инклюде нужно убирать, например, в конструкцию INCLUDE … IF FOUND
Большое спасибо за фидбек.
Конечно, базовый класс следует сделать абстрактным (upd), но для драфта/концепта это не существенно, так же как и оформление констант и кое-что ещё.
Насчет INCLUDE у меня есть некотрые предубеждения. Например, у них были засады с проверками при компиляции. А сейчас такие веяния времени, что избавляться от предубеждений к INCLUDE уже поздновато, на мой субъективный взгляд. Юнит-тесты на них не напишешь и т.д.
А насчет BAdI всё правильно, но самую малость преждевременно.
Введение новых уровней абстракций и сущностей должно быть оправдано.
Интерфейс + перебор интерфейсов даёт нам как раз BAdI. Там и сортировка есть заодно. Но преобразование типов всё равно необходимо куда-то запрятать.
В концепте всего две имплементации и по моему плану BAdI должен всплыть не раньше третьей имплементации. Вот только фильтр в BAdI будет не очень полезен в моём конкретном случае, ибо условия плавают всё время.
Простой пример разделения — с помощью ФМ с таким же интерфейсом.