Привет, Хабр! Меня зовут Мария Иванищева, я работаю на ИТ-проектах Fix Price. Я расскажу о том, как мы автоматизировали процессы код-ревью в SAP.
Проблемы и риски
С ростом штата программистов производительность увеличивается далеко не всегда, ведь для этого нужно решить проблему с тем, что каждый программист пишет код по-своему. И чаще всего случается так, что пишет код один человек, а поддерживать его затем приходится другим. При этом написание кода может не отличаться качеством.
Применительно к SAP это означает еще большие проблемы, потому что SAP включает среды разработки, тестирования и продуктивный контур (будем называть его продуктив). И при переносе кода со среды разработки на тестирование и затем на продуктив могут возникнуть (и возникают) ошибки, которые приходится править, возвращая задачу разработчикам, или откатывать версию. Выбор, разумеется, не из лучших.
Неприятности могут возникнуть и на работающем коде. Яркий пример: при увеличении объема обрабатываемых данных значительно увеличивается время работы программы или же вовсе происходит падение. В результате возникает острая потребность в оптимизации кода.
Но и это еще не всё: третья проблема связана с незначительными (на первый взгляд) правками, которые были внесены когда-то давно. Казалось бы — программа работает и работает, но ведь всегда есть риск, что при дальнейших манипуляциях с кодом программистам придется разбираться, что именно было изменено. выяснение особенностей кода может затянуться. Поэтому единственное разумное решение во всех этих случаях — выработка стандартов, целью которых является универсальность написания кода.
Что было сделано в первую очередь
Важнейшим шагом к созданию нормальных процессов код-ревью стало создание Регламента разработки на ABAP :
универсальность кода было решено поддерживать благодаря неймингу — правилам именования объектов разработки;
структурирование кода осуществляется за счет разбивки кода на блоки программ и вынесения часто используемых процедур в отдельно запускаемые функции, что исключает дублирование.
Также перед выполнением работ по оптимизации у нас всегда составляется План оптимизации, который в обязательном порядке корректируют и согласуют синьоры.: составление этого документа помогает в дальнейшем избежать множества проблем, в том числе неправильного сбора данных или их потерю
Вся документация хранится в JIRA и Confluence, где указаны проекты, номера задач и все описания работ по ним. Каждый запрос имеет правильное наименование, что обеспечивает эффективную связь запроса в SAP с документацией в JIRA.
Автоматизация код-ревью
Вот мы и добрались до главного. Приведенная выше схема хороша в плане исключения ошибок: ревьюеры смотрят и комментируют код в соответствии с утвержденными стандартами, разработчики — правят ошибки. Но эта схема не оптимальна, поскольку процесс Code Review при такой организации оказывается достаточно долгим, и время на задачу разработки увеличивается.
Поскольку процессы код-ревью алгоритмизуемы, их можно упростить. Для этого мы разделили код-ревью на несколько этапов:
транспорт,
документация,
нейминг,
модульность,
оптимизация.
И что касается процессов транспорта и нейминга, то они поддались автоматизации и на текущий момент выполняются программой.
Алгоритм проверки объектов мы также разбили на несколько блоков:
Выбираются все объекты запроса.
Обходится всё дерево используемых объектов, собирается полный список Z-объектов и расширений стандарта.
Проверяется статус переносов промежуточных запросов каждого объекта в целевую систему. При этом решается задача контроля изменений кода под разные задачи. То есть ни один объект не пропускается и не берутся в перенос разработки по другим задачам, которые еще не прошли проверку.
Находится разница кода с продуктивом и проверяется на нейминг в соответствии с регламентом.
Недостающие или избыточные объекты собираются в общий список и выводятся на монитор с описанием ошибок.
Теперь переходим к самому «вкусному» — собственно технической реализации всего описанного выше.
Как это реализовано
Поскольку всё собрано в одном запросе, и перенос изменений связан с рисками его транспорта , то проверяется именно запрос. Разумеется, запрос и/или все подзадачи должны быть деблокированы, то есть по ним не должно вестись никаких работ.
Из конкретных требований: запрос должен содержать в первых символах номер заявки/задачи (через дефис) и перечень задач через запятую. Блоки списков задач по разным проектам разделяются точкой с запятой, а обязательное краткое описание запроса не должно включать точки с запятой (;), дефиса (-) и точки (.), поскольку эти знаки участвуют в синтаксисе самих запросов.
Вот примеры запросов:
HRS-1234 краткое описание
SMP-1234,2345,7654 еще одно краткое описание
SFP-7891, 1234; SD-7890, 1269, 1234 и снова краткое описание
Посмотрим на интерфейс проверки запросов на перенос:
Проверка объектов осуществляется через транзакцию Z_CR в системе разработки SAP BD1. Чек-боксы вынесены на экран выбора для проверки программистом. Так как транзакцией пользуются и консультанты, и администратор переносов, то эти пункты вариативны.
Далее мы определяем тип запроса: инструментальные средства или настройка. Перенос копий запрещен в продуктив и не обрабатывается программой проверки. В зависимости от этого собираем по нему данные. Затем определяем проект и номер задач по наименованию и получаем таблицу с проектами и номерам задач:
Номер задачи, к которой принадлежит запрос на перенос, определяется по наименованию запроса. Если по результатам проведенной автоматической проверки выведена пустая таблица, значит проверка пройдена успешно, и запрос не содержит ошибок:
В противном случае в таблице появится список обнаруженных ошибок запроса и сообщение о дальнейших действиях. В информационном сообщении выведено подробное указание, как поступать в зависимости от статуса ошибки, при этом ошибки указаны построчно для каждого объекта запроса:
Обработку результатов выполняет программист, запуская проверку после подготовки запроса. Далее найденные ошибки исправляются, после чего выполняется повторная проверка, и, при отсутствии ошибок (или их согласования руководителями групп), разработка передается на тестирование.
Также на скрине выше вы наверняка заметили, что ошибки помечены разным цветом:
красные требуют исправления и последующего согласования;
желтые требуют согласования руководителя проекта;
не помеченные цветом обозначают хардкод.
Теперь более подробно об обработке запросов.
Основной алгоритм обработки запросов через Z_CR в SAP BD1
В первую очередь определяем тип запроса и определяем по нему данные.
Hidden text
SELECT * INTO TABLE mt_e071
FROM e071
WHERE trkorr = mv_trkorr. - номер запроса
Удаляем такие объекты как: переводы, класс сообщений и сами сообщения, внутренние тексты программ, подзадачи запроса.
Hidden text
"Эти подсветим потом на выходе - версии проверять не надо
DELETE mt_e071 WHERE object = 'CDAT'. "Ведение кластера ракурса
DELETE mt_e071 WHERE object = 'INDX'. "Табличный индекс
DELETE mt_e071 WHERE object = 'TABU'. "Табличные данные
IF mt_e071_tkor IS INITIAL.
MESSAGE 'Запрос не деблокирован или указан несуществующий'(001) TYPE 'E'.
ENDIF.
Данные запроса настройки будут выведены с желтым флагом, и их анализ в дальнейшем выполняться не будет
Теперь можно приступать к запросу инструментальных средств.
Идем по таблице объектов запроса:
LOOP AT mt_e071 INTO ls_e071.
CLEAR: lt_env, lt_e071_fm.
CLEAR ls_dat.
ls_dat-epgmid = ls_e071-pgmid.
ls_dat-eobject = ls_e071-object.
ls_dat-eobj_name = ls_e071-obj_name.
Теперь ищем объекты внутри с помощью метода _search_objects_inside:
_search_objects_inside( EXPORTING iv_obj_type = ls_e071-object
iv_object_name = ls_e071-obj_name
IMPORTING et_env = lt_env ).
IF lines( lt_env ) = 0.
CONTINUE.
ENDIF.
Сравнение версий объектов с продуктивом:_check_versiya_object_prod
_check_versiya_object_prod( EXPORTING it_e071 = lt_e071_fm
is_dat = ls_dat ).
Проверка на расширение стандартных таблиц
SELECT * INTO TABLE lt_dd03l FROM dd03l WHERE tabname = ls_tab
AND fieldname LIKE '.INCL%'
AND precfield LIKE 'Z%'.
…
ls_dat-light = '@0A@'. "RED
ls_dat-comment = 'Расширение станд.табл отлично от продуктива'.
_search_objects_inside - сбор используемых объектов внутри
DATA: ls_environment_selection TYPE envi_types.
ls_environment_selection-prog = 'X'. "Программы
ls_environment_selection-fugr = 'X'. "Группы функций
ls_environment_selection-ldba = ' '. "Логические базы данных
ls_environment_selection-msag = ' '. "Классы сообщений
ls_environment_selection-tran = ' '. "Транзакции
ls_environment_selection-func = 'X'. "Функциональные модули
ls_environment_selection-dial = ' '. "Диалоговые модули
ls_environment_selection-tabl = 'X'. "Таблицы базы данных
ls_environment_selection-shlp = 'X'. "Средства поиска
ls_environment_selection-doma = 'X'. "Домены
ls_environment_selection-dtel = 'X'. "Элементы данных
ls_environment_selection-view = ' '. "Курсоры
ls_environment_selection-mcob = ' '. "Matchcode object
ls_environment_selection-mcid = ' '. "Matchcode ID
ls_environment_selection-para = ' '. "SET/GET параметры
ls_environment_selection-conv = ' '. "Conversion exit
ls_environment_selection-suso = ' '. "Объекты авторизации
ls_environment_selection-type = 'X'. "Группы типов
ls_environment_selection-ttyp = 'X'. "Типы таблиц
ls_environment_selection-stru = 'X'. "Структуры
ls_environment_selection-enqu = 'X'. "Объекты блокировок
ls_environment_selection-sqlt = ' '. "SQL tables
ls_environment_selection-clas = 'X'. "Классы
ls_environment_selection-intf = 'X'. "Интерфейсы
ls_environment_selection-udmo = ' '. "Data model
ls_environment_selection-ueno = ' '. "Entity type
ls_environment_selection-shi3 = ' '. "Area menu
ls_environment_selection-cntx = ' '. "Context
ls_environment_selection-ttab = 'X'. "Типы таблиц
“ или
“ls_environment_selection = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'.
CALL FUNCTION 'REPOSITORY_ENVIRONMENT_SET_RFC'
EXPORTING
obj_type = lv_obj_type "тип объекта(prog/clas/meth и др.)
environment_types = ls_environment_selection
object_name = ls_tn-obj_name "идентификатор объекта (название)
TABLES
environment = lt_env.
…
DELETE lt_env WHERE type = 'MSAG'. "класс сообщений
DELETE lt_env WHERE type = 'MESS'. "отдельное сообщение
DELETE lt_env WHERE type = 'DOCU'. "документация
DELETE lt_env WHERE type = 'CDAT'. "ведение кластера ракурса
"удалить submit изнутри
DELETE ct_env WHERE type = 'PROG' AND int_type = 'S'.
"Методы, типы, атрибуты, события, класса и т.д.
DELETE ct_env WHERE ( ( type = 'CLAS' OR type = 'INTF' )
AND object+0(1) <> 'Z' )
OR ( type+0(1) = 'O' AND encl_obj+0(1) <> 'Z' )
OR ( type = 'DGT' AND object+0(1) <> 'Z'). "тип из группы
типов ( se11)
В зависимости от типа объекта можно корректировать сбор проверяемых объектов и удалить стандартные объекты, объекты, которые используются в запросе.
Для объектов типа метод или функциональный метод сбор выше не подойдет.
Собираем
METHOD _object_for_meth
DATA: ls_methodid TYPE seocpdkey.
DATA: lt_split TYPE TABLE OF sobj_name
, ls_split TYPE sobj_name
, ls_tn TYPE ty_s_tn
.
SPLIT iv_obj_name AT space INTO TABLE lt_split.
DELETE lt_split WHERE table_line IS INITIAL.
READ TABLE lt_split INTO ls_methodid-clsname INDEX 1.
READ TABLE lt_split INTO ls_methodid-cpdname INDEX 2.
"название программы запуска метода ( в SE38 )
zclbs_service=>get_methnameprog(
EXPORTING
iv_clsname = ls_methodid-clsname " название класса
iv_cpdname = ls_methodid-cpdname " название метода
IMPORTING
ev_progname = ls_tn-obj_name " программа
).
*
IF ls_tn-obj_name IS NOT INITIAL.
ls_tn-obj_type = 'METH'.
ls_tn-flag_check = 'X'.
APPEND ls_tn TO et_tn.
ENDIF.
"Вытаскиваем Public
ls_tn-obj_name = ls_methodid-clsname . "cl_oo_classname_service=>get_pubsec_name( clsname = ls_methodid-clsname ).
ls_tn-obj_type = 'CPUB'.
ls_tn-flag_check = ''.
APPEND ls_tn TO et_tn.
"Вытаскиваем Private
ls_tn-obj_name = ls_methodid-clsname . "cl_oo_classname_service=>get_prisec_name( clsname = ls_methodid-clsname ).
ls_tn-obj_type = 'CPRI'.
ls_tn-flag_check = ''.
APPEND ls_tn TO et_tn.
"Вытаскиваем Protecte
ls_tn-obj_name = ls_methodid-clsname . "cl_oo_classname_service=>get_prosec_name( clsname = ls_methodid-clsname ).
ls_tn-obj_type = 'CPRO'.
ls_tn-flag_check = ''.
APPEND ls_tn TO et_tn.
ENDMETHOD. "_object_for_meth
METHOD _object_for_func.
"к тому что осталось надо добавить инклуды TOP UXX и все инклуды не фм
DATA: lt_tfdir TYPE TABLE OF tfdir
, ls_tfdir TYPE tfdir
, lv_fugr(40)
, lt_func TYPE TABLE OF char40
, lv_func(40)
, ls_tn TYPE ty_s_tn
.
"Добавим сам фм
ls_tn-obj_name = iv_obj_name.
ls_tn-obj_type = 'FUNC'.
ls_tn-flag_check = 'X'.
APPEND ls_tn TO et_tn.
"название фг
SELECT SINGLE pname INTO lv_fugr FROM tfdir
WHERE funcname = iv_obj_name.
SELECT * INTO TABLE lt_tfdir FROM tfdir
WHERE pname = lv_fugr.
lv_fugr = lv_fugr+3. "(SAPLZMMAA2 --> LZMMAA2 )
"собрали все фм, которые инклуды и в инклуде *UXX
LOOP AT lt_tfdir INTO ls_tfdir.
CLEAR lv_func.
lv_func = lv_fugr && 'U' && ls_tfdir-include.
APPEND lv_func TO lt_func.
ENDLOOP.
* lv_obj_type = iv_obj_type.
* lv_object_name = iv_object_name.
DATA: ls_environment_selection TYPE envi_types VALUE 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'.
DATA: lv_obj_type TYPE euobj-id
, lv_object_name TYPE tadir-obj_name
, lt_env TYPE senvi_tab
.
lv_object_name = lv_fugr+1.
lv_obj_type = 'FUGR'.
CALL FUNCTION 'REPOSITORY_ENVIRONMENT_SET_RFC'
EXPORTING
obj_type = lv_obj_type
environment_types = ls_environment_selection
object_name = lv_object_name "идентификатор объекта (название)
TABLES
environment = lt_env.
"удаляем все, что не LZ*
DELETE lt_env WHERE type <> 'INCL' AND object+0(2) <> 'LZ'.
LOOP AT lt_func INTO lv_func.
DELETE lt_env WHERE object = lv_func.
ENDLOOP.
DATA: ls_env LIKE LINE OF lt_env.
LOOP AT lt_env INTO ls_env.
ls_tn-obj_type = 'INCL'.
ls_tn-obj_name = ls_env-object.
ls_tn-flag_check = ''.
APPEND ls_tn TO et_tn.
ENDLOOP.
ENDMETHOD. "_object_for_func
Собранные данные для метода и фм отправляем в 'REPOSITORY_ENVIRONMENT_SET_RFC'
При этом получается, что мы собрали все методы и области класса метода и все инклуды и фм группы функций фм. Делаем мы это для того, чтобы обезопасить перенос. Так как при переносе метода, фм, ГФ, класса - мы можем вызвать перегенерацию программ на переносимой среде и вызвать дампы при изменении сигнатуры или кода.
_check_versiya_object_prod проверка версий объектов
CALL FUNCTION 'SVRS_MASSCOMPARE_ACT_OBJECTS'
EXPORTING
it_e071 = it_e071 "проверяемый объект
iv_rfcdest_b = mv_targ "целевая система
iv_filter_lang = ''
IMPORTING
et_compare_items = lt_compare_items "таблица ответа
EXCEPTIONS
rfc_error = 1
not_supported = 2
OTHERS = 3.
DELETE lt_compare_items WHERE equal <> ''. " версии объектов =
DELETE lt_compare_items WHERE object = 'TABT'. "технические данные таблиц
… отличия могут быть незначительные, такие как комментарий - их анализ будет проведен дополнительно
LOOP AT it_e071 INTO ls_e071_fm.
LOOP AT lt_compare_items INTO ls_items WHERE fragid = ls_e071_fm-pgmid
AND fragment = ls_e071_fm-object
AND fragname = ls_e071_fm-obj_name.
… добавляем сообщение в выходную таблицу
ls_dat-comment = 'Включите связ.объект в запрос. Разные версии'(003).
ls_dat-light = '@0A@'. " красный
APPEND ls_dat TO mt_dat.
_loop_dat обработка собранных объектов
дополнительно удалим, что собрали и что есть запросе
LOOP AT mt_dat ASSIGNING <ls_dat>.
READ TABLE mt_e071 TRANSPORTING NO FIELDS
WITH KEY object = <ls_dat>-object
obj_name = <ls_dat>-obj_name.
IF sy-subrc = 0.
CLEAR <ls_dat>.
ENDIF.
ENDLOOP.
Ряд обработок по ГФ - если в запросе вся гф - то фм из нее не надо выводить - они перенесутся все.
При проверках мы могли найти что версии разные, или есть изменения кода - которые отображает как отличия и ошибки, но на самом деле это или комментарии или код одинаков. Для этого проходим по ряду методов, которые сравнивают объекты на наличие реальных значимых изменений.
LOOP AT mt_dat ASSIGNING <ls_dat> WHERE object = 'REPS'
OR object = 'CPUB'
OR object = 'CPRI'
OR object = 'CPRO'.
IF _check_real_dif_reps( is_dat = <ls_dat> ) = ''.
CLEAR <ls_dat>.
ENDIF.
ENDLOOP.
“ по факту мы программно запускаем REMOTE сравнение
METHOD _check_real_dif_reps.
DATA: lt_rspar TYPE rsparams_tt,
ls_rspar TYPE rsparams.
DATA: lt_tb TYPE TABLE OF char300.
DATA: lt_result TYPE match_result_tab .
CLEAR: lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'OBJNAME'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = is_dat-obj_name.
APPEND ls_rspar TO lt_rspar.
ls_rspar-selname = 'OBJNAM2'.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'OBJTYP1'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = is_dat-object.
APPEND ls_rspar TO lt_rspar.
ls_rspar-selname = 'OBJTYP2'.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'LOG_DEST'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = 'АДРЕС ПРОДУКТИВА'.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'REM_SYST'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = 'BP1'.
APPEND ls_rspar TO lt_rspar.
SUBMIT rsvrsrs3 WITH SELECTION-TABLE lt_rspar EXPORTING LIST TO MEMORY AND RETURN .
DATA: lt_listobject TYPE TABLE OF abaplist.
CLEAR : lt_listobject
, lt_result
.
CALL FUNCTION 'LIST_FROM_MEMORY'
TABLES
listobject = lt_listobject
EXCEPTIONS
not_found = 1
OTHERS = 2.
CALL FUNCTION 'LIST_TO_ASCI'
TABLES
listasci = lt_tb
listobject = lt_listobject
EXCEPTIONS
empty_list = 1.
FIND ALL OCCURRENCES OF
'Различий в исходных текстах этих версий нет.'
IN TABLE lt_tb
RESULTS lt_result IN CHARACTER MODE.
IF sy-subrc = 0.
rv_ok = ''.
RETURN.
ELSE.
rv_ok = 'X'.
ENDIF.
IF rv_ok = 'X'.
* исходные тексты обеих версий идентичны
FIND ALL OCCURRENCES OF
'исходные тексты обеих версий идентичны'
IN TABLE lt_tb
RESULTS lt_result IN CHARACTER MODE.
IF sy-subrc = 0.
rv_ok = ''.
ELSE.
rv_ok = 'X'.
ENDIF.
ENDIF.
ENDMETHOD.
и домены также
LOOP AT mt_dat ASSIGNING <ls_dat> WHERE object = 'DOMA'
OR object = 'DOMD'
OR object = 'DTEL'
OR object = 'DTED'.
IF _check_real_dif_d( is_dat = <ls_dat> ) = ''.
CLEAR <ls_dat>.
ENDIF.
ENDLOOP.
“по доменам делаем как выше только сабмитим другой отчет
SUBMIT radvvde2 WITH SELECTION-TABLE lt_rspar EXPORTING LIST TO MEMORY AND RETURN.
“Для таблиц
LOOP AT mt_dat ASSIGNING <ls_dat> WHERE object = 'TABL'
OR object = 'TABD'.
IF _check_real_dif( is_dat = <ls_dat> ) = ''.
CLEAR <ls_dat>.
ENDIF.
ENDLOOP.
SUBMIT radvvtb2 WITH SELECTION-TABLE lt_rspar EXPORTING LIST TO MEMORY AND RETURN .
_find_not_transport_request Проверка не перенесенных запросов по другим задачам
METHOD _find_not_transport_request.
DATA: lt_e071 TYPE TABLE OF e071.
DATA: BEGIN OF ls_tab
, trkorr TYPE e071-trkorr
, as4date TYPE e070-as4date
, as4time TYPE e070-as4time
, as4text TYPE e07t-as4text
, END OF ls_tab
, lt_tab LIKE TABLE OF ls_tab
.
DATA: lt_task TYPE ty_t_task
, ls_task TYPE ty_s_task
.
DATA: ls_methodid TYPE seocpdkey.
DATA: lt_split TYPE TABLE OF sobj_name
, ls_split TYPE sobj_name
.
SELECT * INTO CORRESPONDING FIELDS OF TABLE lt_tab
FROM e071 INNER JOIN e070 ON e071~trkorr = e070~trkorr
INNER JOIN e07t ON e07t~trkorr = e070~trkorr
WHERE e071~obj_name = iv_object_name AND e070~trfunction = 'K' "инстр.стр/ T - перенос копий/ S - разработка корректура и т.д.
.
"расширяем запросы для FUNC - нужны запросы и с FUGR его
"ищем ГФ для FUNC
IF iv_object_type = 'FUNC'.
"название фг
DATA: lv_fugr TYPE pname.
SELECT SINGLE pname INTO lv_fugr FROM tfdir
WHERE funcname = iv_object_name.
lv_fugr = lv_fugr+4. "(SAPLZMMAA2 --> LZMMAA2 )
SELECT * APPENDING CORRESPONDING FIELDS OF TABLE lt_tab
FROM e071 INNER JOIN e070 ON e071~trkorr = e070~trkorr
INNER JOIN e07t ON e07t~trkorr = e070~trkorr
WHERE e071~obj_name = lv_fugr
AND e071~object = 'FUGR'
AND e070~trfunction = 'K' .
ENDIF.
IF iv_object_type = 'METH'.
CLEAR lt_split.
CLEAR ls_split.
SPLIT iv_object_name AT space INTO TABLE lt_split.
DELETE lt_split WHERE table_line IS INITIAL.
READ TABLE lt_split INTO ls_methodid-clsname INDEX 1.
READ TABLE lt_split INTO ls_methodid-cpdname INDEX 2.
SELECT * APPENDING CORRESPONDING FIELDS OF TABLE lt_tab
FROM e071 INNER JOIN e070 ON e071~trkorr = e070~trkorr
INNER JOIN e07t ON e07t~trkorr = e070~trkorr
WHERE e071~obj_name = ls_methodid-clsname
AND e071~object = 'CLAS'
AND e070~trfunction = 'K' .
ENDIF.
SORT lt_tab BY as4date DESCENDING "дата деблокирования ( по ней сортировка
при просм версионности
as4time DESCENDING .
DATA: lv_i TYPE i.
DATA: lv_err TYPE flag.
READ TABLE lt_tab INTO ls_tab WITH KEY trkorr = mv_trkorr.
lv_i = sy-tabix + 1.
LOOP AT lt_tab INTO ls_tab FROM lv_i. "с запроса нашего - ищем запросы ДО нашего ( ниже )
CLEAR lt_task.
_pars_task( EXPORTING iv_as4text = ls_tab-as4text
IMPORTING et_task = lt_task ).
IF mt_task = lt_task.
CONTINUE. "по нашей задаче нас не интересует - все должно быть тут
ENDIF.
CLEAR lv_err.
LOOP AT lt_task INTO ls_task.
READ TABLE mt_task TRANSPORTING NO FIELDS
WITH KEY proj = ls_task-proj
nomer = ls_task-nomer.
IF sy-subrc <> 0.
lv_err = 'X'.
EXIT. "выходим
ENDIF.
ENDLOOP.
IF sy-subrc = 0.
IF lv_err = ''.
CONTINUE.
ENDIF.
ENDIF.
DATA: ls_tr_text TYPE ty_s_tr_text.
"а вот если нет, начинаем проверять - донесен ли запрос
IF _check_transport( iv_trkorr = ls_tab-trkorr ) = 'X'.
ls_tr_text-trkorr = ls_tab-trkorr.
COLLECT ls_tr_text INTO et_tr_text.
ELSE.
EXIT.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD _pars_task.
DATA:
lv_text TYPE as4text,
lv_i TYPE i,
lv_proj(10),
lv_nomer(10),
ls_task TYPE ty_s_task,
lv_as4text TYPE as4text.
lv_as4text = iv_as4text.
CONDENSE lv_as4text NO-GAPS.
DO.
IF lv_as4text = ''.
EXIT.
ENDIF.
IF lv_as4text+0(1) CN '0123456789-,;' . "не число и не дефис
lv_proj = lv_proj && lv_as4text+0(1).
ENDIF.
IF lv_as4text+0(1) = '-'. "пропускаем
lv_as4text = lv_as4text+1.
CONTINUE.
ENDIF.
IF lv_as4text+0(1) CO '0123456789' . "число
lv_nomer = lv_nomer && lv_as4text+0(1).
IF lv_as4text+1(1) CN '0123456789,;' .. "не число
ls_task-nomer = lv_nomer.
ls_task-proj = lv_proj.
APPEND ls_task TO et_task.
CLEAR lv_nomer.
CLEAR lv_proj.
EXIT. "конец
ENDIF.
ENDIF.
IF lv_as4text+0(1) = ',' OR lv_as4text+0(1) = ';' .
ls_task-nomer = lv_nomer.
ls_task-proj = lv_proj.
APPEND ls_task TO et_task.
IF lv_as4text+0(1) = ','.
CLEAR lv_nomer.
ENDIF.
IF lv_as4text+0(1) = ';'.
CLEAR lv_nomer.
CLEAR lv_proj.
ENDIF.
ENDIF.
lv_as4text = lv_as4text+1.
ENDDO.
SORT et_task.
ENDMETHOD. "_pars_task
METHOD _check_transport.
DATA: lt_rspar TYPE rsparams_tt,
ls_rspar TYPE rsparams.
DATA: lt_tb TYPE TABLE OF char300.
DATA: ls_tb TYPE char300.
DATA: lt_result TYPE match_result_tab .
CLEAR: lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'PV_KORR'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = iv_trkorr.
APPEND ls_rspar TO lt_rspar.
SUBMIT rddprott WITH SELECTION-TABLE lt_rspar EXPORTING LIST TO MEMORY AND RETURN .
DATA: lt_listobject TYPE TABLE OF abaplist.
CLEAR : lt_listobject
, lt_result
.
CALL FUNCTION 'LIST_FROM_MEMORY'
TABLES
listobject = lt_listobject
EXCEPTIONS
not_found = 1
OTHERS = 2.
CALL FUNCTION 'LIST_TO_ASCI'
TABLES
listasci = lt_tb
listobject = lt_listobject
EXCEPTIONS
empty_list = 1.
DATA: lv_i TYPE i.
rv_err = 'X'.
READ TABLE lt_tb INTO ls_tb WITH KEY table_line = ' --- BP1 System BP1'.
IF sy-subrc <> 0.
READ TABLE lt_tb INTO ls_tb WITH KEY table_line = ' |-- BP1 System BP1'.
IF sy-subrc <> 0.
RETURN.
ELSE.
lv_i = sy-tabix.
rv_err = 'X'.
ENDIF.
ELSE.
lv_i = sy-tabix.
rv_err = 'X'.
ENDIF.
LOOP AT lt_tb INTO ls_tb FROM lv_i.
IF ls_tb CS '----- Импорт' AND ( ls_tb CS 'Успешно завершено'
OR ls_tb CS 'Заверш. с ошибкой'
OR ls_tb CS 'Заверш. с предупр.' ). "ошибка - тоже перенос
rv_err = ''.
RETURN.
ENDIF.
ENDLOOP.
ENDMETHOD. "_check_transport
CALL FUNCTION 'TMS_UIQ_IQD_READ_QUEUE' - считывание очереди переносов на продуктиве через DESTINATION
Проверка нейминга
Для анализа написанного кода в ABAP существует транзакция SCII
В ней можно проверять как целый запрос, так и отдельные объекты. SCII анализирует объекты полностью, перед нами же стоит задача настроить анализатор на дельту кода между системой разработки и продуктивом. Также необходимо оставить возможность настройки через стандартный интерфейс SCII. Изначально был реализован функциональный модуль, который возвращает нам результаты проверки через SCII конкретного запроса
Hidden text
CALL FUNCTION 'ZBS_FM_INSPECT_LIST'
EXPORTING
iv_sci_name = iv_sci_name - имя предварительно настроенного варианта из SCII
iv_sci_user = iv_sci_user - имя пользователя
iv_korr = iv_trkorr - номер запроса
IMPORTING
et_results = lt_scit_rest_buf
et_results_hd = lt_scit_resh
et_variant = lt_variant
EXCEPTIONS
no_object = 1
objs_already_exists = 2
no_default_checkvariant = 3
too_many_objects = 4
could_not_read_variant = 5
OTHERS = 6.
В результате мы получаем полную информацию о месте в коде, которое не прошло предварительно настроенную проверку:
Здесь:
SOBJTYPE — тип объекта;
SOBJNAME — название объекта;
LINE и COL — строка и столбец в тексте программы;
PARAM1 — указание на название переменной.
Функциональный модуль был написан через механизм работы транзакции SCII
Hidden text
DATA:
lo_objs_ref TYPE REF TO cl_ci_objectset,
lo_chkv_ref TYPE REF TO cl_ci_checkvariant,
lo_insp_ref TYPE REF TO cl_ci_inspection.
cl_ci_objectset=>get_ref(
EXPORTING
p_type = iv_type - 0KOR
p_korr = iv_korr
RECEIVING
p_ref = lo_objs_ref
EXCEPTIONS
invalid_request = 1
object_may_not_be_checked = 2
OTHERS = 3 ).
IF sy-subrc = 1.
RAISE objs_already_exists.
ENDIF.
"get local check variant
cl_ci_checkvariant=>get_ref(
EXPORTING
p_user = iv_sci_user
p_name = iv_sci_name
RECEIVING
p_ref = lo_chkv_ref
EXCEPTIONS
chkv_not_exists = 1
missing_parameter = 2
OTHERS = 3 ).
IF lo_chkv_ref IS INITIAL.
RAISE could_not_read_variant.
ENDIF.
lo_chkv_ref->get_info(
EXCEPTIONS
could_not_read_variant = 1
OTHERS = 2 ).
IF sy-subrc = 1.
RAISE could_not_read_variant.
ENDIF.
et_variant = lo_chkv_ref->variant.
cl_ci_inspection=>create(
EXPORTING
p_user = iv_sci_user
p_name = space
RECEIVING
p_ref = lo_insp_ref
EXCEPTIONS
insp_already_exists = 1
insp_not_exists = 2
OTHERS = 3 ).
IF sy-subrc <> 0.
RETURN.
ENDIF.
lo_insp_ref->set(
EXPORTING
p_chkv = lo_chkv_ref
p_objs = lo_objs_ref ).
CALL METHOD lo_insp_ref->run
EXPORTING
p_howtorun = 'D'
EXCEPTIONS
missing_information = 1
cancel_popup = 2
insp_already_run = 3
no_object = 4
too_many_objects = 5
could_not_read_variant = 6
locked = 7
objs_locked = 8
error_in_objs_build = 9
invalid_check_version = 10
OTHERS = 11.
CASE sy-subrc.
WHEN 4.
RAISE no_object.
WHEN 5.
RAISE too_many_objects.
WHEN OTHERS.
"
ENDCASE.
et_results = lo_insp_ref->scirestps. - на выходе получаем нужные нам данные со скриншота выше
et_results_hd = lo_insp_ref->sciresthd.
Теперь необходимо соотнести результаты полной проверки объектов из запроса с разницей версий между системой разработки и продуктивом. Для этого необходимо использовать механизм через который работает транзакция для сравнения версий — SE39. Изучив, как работает транзакция в дебаггере, можно заметить, что она использует следующие функциональные модули:
Hidden text
CALL FUNCTION 'SVRS_GET_REPS_FROM_OBJECT'
EXPORTING
destination = lv_logdest_lft - адрес системы разработок
object_name = lv_objname_lft - название программы для анализа
object_type = lv_objtype_lft - тип объекта, скорей всего PROG
versno = lv_version_lft - можно ничего не передавать, будет анализироваться текущая версия
iv_no_release_transformation = abap_true
TABLES
repos_tab = lt_abaptext_sec - текст программы из системы разработок
trdir_tab = lt_trdir_sec
vsmodilog = lt_smodilog_lft
EXCEPTIONS
no_version = 01.
CALL FUNCTION 'SVRS_GET_REPS_FROM_OBJECT'
EXPORTING
destination = lv_logdest_rgt - адрес продуктивной системы
object_name = lv_objname_rgt - название объекта для анализа
object_type = lv_objtype_rgt - тип объекта, скорей всего PROG
versno = lv_version_rgt - можно ничего не передавать, будет анализироваться текущая версия
iv_no_release_transformation = ''
TABLES
repos_tab = lt_abaptext_pri - текст программы из продуктивной системы
trdir_tab = lt_trdir_pri
vsmodilog = lt_smodilog_rgt
EXCEPTIONS
no_version = 01.
CALL FUNCTION 'SVRS_COMPUTE_DELTA_REPS'
EXPORTING
compare_mode = lv_comp_mode
ignore_case_differences = lv_ignore_case_differences
TABLES
texttab_old = lt_abaptext_sec
texttab_new = lt_abaptext_pri
trdirtab_old = lt_trdir_sec
trdirtab_new = lt_trdir_pri
trdir_delta = lt_trdir_delta
text_delta = lt_abaptext_delta. - получаем нужную нам для анализа дельту
Далее получаем таблицу изменений lt_linbuffer_dev. Замечу, что rp_write_first_block, rp_write_last_block, rp_write_next_block были скопированы и адаптированы из SE39
Hidden text
CLEAR: gt_abaptext_delta[],
gt_abaptext_sec[],
gt_abaptext_pri[],
gt_linbuffer[].
APPEND LINES OF lt_abaptext_delta TO gt_abaptext_delta[].
APPEND LINES OF lt_abaptext_sec TO gt_abaptext_sec[].
APPEND LINES OF lt_abaptext_pri TO gt_abaptext_pri[].
"""""""""""""""""""""""""""
"Копия со стандарта
"Получаем таблицу сравнения версий - gt_linbuffer
DESCRIBE TABLE gt_abaptext_delta LINES gv_dltlines.
IF lines( gt_abaptext_pri ) > lines( gt_abaptext_sec ).
gv_txtlines = lines( gt_abaptext_pri ).
ELSE.
gv_txtlines = lines( gt_abaptext_sec ).
ENDIF.
PERFORM rp_write_first_block USING lv_offs_hscroll.
DO.
IF gv_delta_ind > gv_dltlines.
PERFORM rp_write_last_block USING lv_offs_hscroll.
EXIT.
ENDIF.
PERFORM rp_write_next_block USING lv_offs_hscroll.
ENDDO.
"""""""""""""""""""""""""""
lt_linbuffer_dev[] = gt_linbuffer.
Изменения кода теперь лежат в таблице lt_linbuffer_dev. Остается соотнести содержимое таблиц lt_scit_rest_buf и lt_linbuffer_dev, чтобы получить результаты проверки SCII только по изменениям, внесенным в код в рамках текущего запроса. Соотносим их по номеру строки, после чего задачу можно считать выполненной.
Хардкод
Помимо проверки кода через стандартный анализатор, стояла задача избавиться от прямого указания конкретных параметров в коде. Изначально у нас уже есть номер запроса, поэтому получаем из него список объектов. Далее по каждому объекту получаем информацию из репозитория
Hidden text
SELECT SINGLE *
FROM tadir
INTO CORRESPONDING FIELDS OF TABLE ls_stadir
WHERE object = lv_objtype
AND obj_name = lv_objname.
Для работы с параметрами, от которых нужно избавиться, используется настроечная таблица. Из этой таблицы мы считываем значения и передаем их в lv_hardvalue. Подготавливаем таблицу lt_rspar для анализа объекта на вхождение значения CSVALUE:
Hidden text
CLEAR ls_rspar.
ls_rspar-selname = 'S_DEVC'.
ls_rspar-kind = 'S'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = ls_stadir-devclass.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'S_REST'.
ls_rspar-kind = 'S'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = ls_stadir-obj_name.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'P_EXCOMM'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = 'X'.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'P_STRG1'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = lv_hardvalue.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'P_PROG'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = 'X'.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'P_FUGR'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = 'X'.
APPEND ls_rspar TO lt_rspar.
CLEAR ls_rspar.
ls_rspar-selname = 'P_CINC'.
ls_rspar-kind = 'P'.
ls_rspar-sign = 'I'.
ls_rspar-option = 'EQ'.
ls_rspar-low = 'X'.
APPEND ls_rspar TO lt_rspar.
Далее получаем результат анализа таким образом:
Hidden text
DATA: lref_data TYPE REF TO data.
cl_salv_bs_runtime_info=>set( display = abap_false
metadata = abap_false
data = abap_true ).
SUBMIT afx_code_scanner WITH SELECTION-TABLE lt_rspar AND RETURN.
CLEAR lt_tab_lines[].
TRY.
cl_salv_bs_runtime_info=>get_data( IMPORTING t_data = lt_tab_lines[] ).
CATCH cx_salv_bs_sc_runtime_info.
RETURN.
ENDTRY.
cl_salv_bs_runtime_info=>clear_all( ).
Так мы получаем в lt_tab_lines нужную нам информацию.
Что дальше
Автоматизация проверки запросов уже позволила нам получить превосходный результат в плане улучшения качества кода. Мы смогли значительно снизить ошибки при переносе и сэкономить время, которое тратилось на проверку “руками”. Но нет предела совершенству, и мы уже ведем работы по расширению возможностей программы.
В перспективе планируется анализировать на хардкод только текущие изменения в коде с помощью механизма получения разницы версий, описанного в разделе Проверка нейминга. Сейчас разрабатываются функционал проверки на хардкод в дельте кода, идут работы с оптимизацией, проверки на оригинал системы, на использование объектов во время переноса и ряд других функций.