Очень жаль, хоть и ожидаемо.
Ковид, его побочки, война и инфляция – четыре всадника нашего очередного апокалипсиса.
(далее…)против Энтропии
Очень жаль, хоть и ожидаемо.
Ковид, его побочки, война и инфляция – четыре всадника нашего очередного апокалипсиса.
(далее…)Ну допустим, я показал ему текст некоторой программы:
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 118 119 120 121 122 123 124 125 |
REPORT y_bc001_text_search. TABLES: tadir. SELECT-OPTIONS: s_devc FOR tadir-devclass DEFAULT 'Z*' OPTION CP. SELECT-OPTIONS: s_objec FOR tadir-object. SELECT-OPTIONS: s_objnm FOR tadir-obj_name DEFAULT 'Z*' OPTION CP. PARAMETERS: p_text TYPE string. START-OF-SELECTION. SELECT tadir~pgmid, tadir~object, tadir~obj_name, trdir~subc, tadir~devclass FROM tadir LEFT OUTER JOIN trdir ON tadir~object = 'PROG' AND tadir~obj_name = trdir~name INTO TABLE @DATA(lt_objects) WHERE tadir~devclass IN @s_devc AND tadir~object IN @s_objec AND tadir~obj_name IN @s_objnm AND ( tadir~object = 'PROG' OR tadir~object = 'FUGR' OR tadir~object = 'CLAS' ). LOOP AT lt_objects ASSIGNING FIELD-SYMBOL(<object>). IF <object>-object = 'PROG' AND ( <object>-subc = '1' OR <object>-subc = 'M' ). DATA: lt_tpool TYPE STANDARD TABLE OF textpool WITH DEFAULT KEY. CALL FUNCTION 'RS_TEXTPOOL_READ' EXPORTING objectname = <object>-obj_name action = 'READ' * AUTHORITY_CHECK = ' ' language = sy-langu TABLES tpool = lt_tpool EXCEPTIONS object_not_found = 1 permission_failure = 2 invalid_program_type = 3 error_occured = 4 action_cancelled = 5 OTHERS = 6. IF sy-subrc <> 0. * Implement suitable error handling here ENDIF. LOOP AT lt_tpool ASSIGNING FIELD-SYMBOL(<tpool>). IF <tpool>-id = 'I' AND <tpool>-entry CP p_text. WRITE: <object>-obj_name, <tpool>-key, <tpool>-entry. ENDIF. ENDLOOP. ELSEIF <object>-object = 'FUGR'. DATA: lv_fugr TYPE rs38m-programm. lv_fugr = 'SAPL' && <object>-obj_name. CALL FUNCTION 'RS_TEXTPOOL_READ' EXPORTING objectname = lv_fugr action = 'READ' * AUTHORITY_CHECK = ' ' language = sy-langu TABLES tpool = lt_tpool EXCEPTIONS object_not_found = 1 permission_failure = 2 invalid_program_type = 3 error_occured = 4 action_cancelled = 5 OTHERS = 6. IF sy-subrc <> 0. * Implement suitable error handling here ENDIF. LOOP AT lt_tpool ASSIGNING <tpool>. IF <tpool>-id = 'I' AND <tpool>-entry CP p_text. WRITE: <object>-obj_name, <tpool>-key, <tpool>-entry. ENDIF. ENDLOOP. ELSEIF <object>-object = 'CLAS'. DATA: lv_clas TYPE rs38m-programm. lv_clas = <object>-obj_name. DATA(lv_eq_count) = 30 - strlen( lv_clas ). DO lv_eq_count TIMES. lv_clas = lv_clas && '='. ENDDO. lv_clas = lv_clas && 'CP'. CALL FUNCTION 'RS_TEXTPOOL_READ' EXPORTING objectname = lv_clas action = 'READ' * AUTHORITY_CHECK = ' ' language = sy-langu TABLES tpool = lt_tpool EXCEPTIONS object_not_found = 1 permission_failure = 2 invalid_program_type = 3 error_occured = 4 action_cancelled = 5 OTHERS = 6. IF sy-subrc <> 0. * Implement suitable error handling here ENDIF. LOOP AT lt_tpool ASSIGNING <tpool>. IF <tpool>-id = 'I' AND <tpool>-entry CP p_text. WRITE: <object>-obj_name, <tpool>-key, <tpool>-entry. ENDIF. ENDLOOP. ENDIF. ENDLOOP. END-OF-SELECTION. WRITE: / 'Search completed:', p_text. |
Так вот, сервис может пояснить за код, лучше чем я:
(далее…)Есть в ABAP кое-какие языковые конструкции, отсутствующие в топовых языках, по крайней мере на базовом уровне, например MOVE-CORRESPONDING.
VARYING – это одна из таких конструкций. Я изредка использовал её в базовом варианте:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
DATA: BEGIN OF ls_varstr, cod01 TYPE char20, cod02 TYPE char20, cod03 TYPE char20, cod04 TYPE char20, END OF ls_varstr. ls_varstr = VALUE #( cod01 = '001' cod02 = '002' cod03 = '003' cod04 = '004' ). DATA: lv_code TYPE char20. DO 4 TIMES VARYING lv_code FROM ls_varstr-cod01 NEXT ls_varstr-cod02. WRITE: / lv_code. ENDDO. |
Результат предсказуем:
Но как иногда оказывается, у этой конструкции есть две дополнительных глубины.
Во-первых, можно крутить два поля одновременно:
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 |
DATA: BEGIN OF ls_varstr, cod01 TYPE char20, val01 TYPE char100, cod02 TYPE char20, val02 TYPE char100, cod03 TYPE char20, val03 TYPE char100, cod04 TYPE char20, val04 TYPE char100, END OF ls_varstr. ls_varstr = VALUE #( cod01 = '001' val01 = 'First' cod02 = '002' val02 = 'Second' cod03 = '003' val03 = 'Third' cod04 = '004' val04 = 'Fofth' ). DATA: lv_code TYPE char20. DATA: lv_value TYPE char100. DO 4 TIMES VARYING lv_code FROM ls_varstr-cod01 NEXT ls_varstr-cod02 VARYING lv_value FROM ls_varstr-val01 NEXT ls_varstr-val02. WRITE: / lv_code, '=', lv_value . ENDDO. |
И оказывается так тоже можно, результат не удивит:
А во-вторых, поля в последовательности могут называться как угодно.
Мне раньше казалось, что нумерация в поле очень важна: VAL01, VAL02. Магия! А вот нет. Так тоже можно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
DATA: BEGIN OF ls_varstr, first TYPE char20, second TYPE char20, i-do-not-know TYPE char20, wtf TYPE char20, END OF ls_varstr. ls_varstr = VALUE #( first = '001' second = '002' i-do-not-know = '003' wtf = '004' ). DATA: lv_code TYPE char20. DO 4 TIMES VARYING lv_code FROM ls_varstr-first NEXT ls_varstr-second. WRITE: / lv_code. ENDDO. |
Если чуть подумать, то станет всё понятнее, как оно было заложено и почему. Могу проиллюстрировать на менее интуитивно понятном примере:
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 |
DATA: BEGIN OF ls_varstr, cod01 TYPE char20, val01 TYPE char100, cod02 TYPE char20, val02 TYPE char100, cod03 TYPE char20, val03 TYPE char100, cod04 TYPE char20, val04 TYPE char100, other TYPE text60, fields TYPE text60, END OF ls_varstr. ls_varstr = VALUE #( cod01 = '001' val01 = 'First' cod02 = '002' val02 = 'Second' cod03 = '003' val03 = 'Third' cod04 = '004' val04 = 'Fofth' other = 'Other~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' fields = 'Fields~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' ). DATA: lv_code TYPE char20. DATA: lv_value TYPE char100. DO 5 TIMES VARYING lv_code FROM ls_varstr-cod01 NEXT ls_varstr-cod02 VARYING lv_value FROM ls_varstr-val01 NEXT ls_varstr-val02. WRITE: / lv_code, '=', lv_value . ENDDO. |
Обратите внимание на DO 5 TIMES. Ха-ха? Знаете что произойдёт?
(далее…)Не все знают, но не обязательно писать Z-программы. Подсказываю: можно вместо этого писать и Y-программы во избежание чего-бы-то-ни-было.
Пробежала статья сегодня: Hello, SAP, Where Are My Global Classes?
Автор жалуется что классы как бы глобальные, а всё равно система всё больше и больше становится кладбищем велосипедов. Ничего найти нельзя, проще придумать очередной велосипед.
И тут же у меня возник резонанс: пробежал баг с неправильным расчётом и там важно было учесть количество дней между двумя датами.
В предыдущей редакции предыдущим автором использовал следующий подход:
1 2 3 4 |
CALL FUNCTION 'DAYS_BETWEEN_TWO_DATES' IMPORTING e_tage = lv_days. lv_days = v_days + 1. |
Хорошо, вот только теперь говорят, что дни надо считать только рабочие.
Вот и снова мы попадаем на кладбище велосипедов, хочешь RKE_SELECT_FACTDAYS_FOR_PERIOD, а можно и DURATION_DETERMINE, но наверняка способов ещё немало. И найти их проще через гугл, чем непосредственно в системе.
В мире SAP эта платформа MOOC хорошо известна, такой узкопрофильный аналог Coursera. Как они сейчас пишут на своём баннере: “5 millions course enrollments”. Это если сложить все курсы, можно проверить, но это тема другого разговора.
Например: из недавнего был очень хороший курс Building Apps with the ABAP RESTful Application Programming Model. 25000 зачисленных это много, средний курс на платформе собирает примерно 5000-10000 зачисленных. Это в целом немного, а если с Coursera сравнивать – то эти цифры унизительно малы, впрочем миллионы регистраций на Coursera есть на курсах широкой специализации (типа Python), а тут у нас язык даже не из первой двадцатки TIOBE. В этом рейтинге ABAP сейчас находится на 35-ом месте с долей 0.35%. Совпадение чисел – действительно совпадение.
Кстати, давайте посчитаем если у Python 11,03% против 0,35% у ABAP, то это означает 30-кратную разницу. Значит 25000 регистраций ABAP сравнимо было бы с 750`000 регистраций у Python. Очень достойный результат.
Большое отличие платформы OpenSAP – курсы полностью бесплатные только в момент запуска. По завершении курса бесплатная карета превращается в бесплатную тыкву, примерно такую же как Аудит у Coursera. То есть: нет тестов и сертификата, можно только “смотреть видосики в интернете”.
Для меня режим “аудит” не подходит, так как отсутствие кнута(тестов) и пряника(сертификата) существенно снижает мотивацию и концентрацию на материале.
Есть один интересный курс на Writing Testable Code for ABAP, я его пропустил в своё время. А за реактивацию (превращение тыквы обратно в карету) они обычно просят $59, с редкой 50% скидкой по праздникам. В прошлом году в честь сами-знаете-чего-когда-все-стали-сидеть-дома OpenSAP открывали обратно за бесплатно многие свежие курсы, но этот конкретный курс под акцию не попал.
Если вам жалко столько денег, то есть метод получить один курс бесплатно на выбор.
Для этого надо пройти курс SAP’s Integrated Intelligent Suite, вполне годный обзорный курс. Этот курс надо завершить и запостить об этом запись в твиттере с правильными хештегами. Для этого даже завёл себе аккаунт в твиттере: я способен и на такие непотребства, если это сэкономит мне целых $59. И вот примерно через месяц пришло письмо счастья с кодом для реактивации, который я знаю куда применить.
Следите за обновлениями на OpenSAP, ловите интересные курсы, пока они бесплатные.
Например: буквально завтра уже запускается курс Efficient DevOps with SAP. Налетай!
Краткое содержание:
Эффект старой плёнки.
Есть в SAP HCM стандартная транзакция PAR1 (Гибкие данные сотрудников). Но при внедрении у пользователей были хотелки, которые в стандарте невозможны. Скопировали транзакцию в Z-область. Допилили её так, как хотелось пользователям. Добавили инфотипы, ещё кое-что по мелочи. Дело нехитрое.
Затемнение.
Прошло десять лет.
В обычной жизни мы не паримся с памятью в ABAP, сборщика мусора как бы нет, глобальной памяти нет.
В самом простом случае у нас есть то, что называется транзакцией (не путать с транзакцией базы данных), что можно определить как “приложение” на нашем собственно говоря сервере приложений. Транзакция всегда ассоциирована с определённой именованной программой.
И вот пока приложение работает, оно потребляет память в виде переменных, экземпляров классов, массивов (также известных как “внутренние таблицы”). И если посмотреть на стек вызовов, то он упирается во входную точку начала этой программы. Ничего ABAPовского за пределами этой точки не существует. Одно приложение не может получить доступ к данным другого приложения, только через какие-то обходные манёвры, например: через постоянное хранилище (СУБД), через взаимные блокировки и прочие костыли. Как только приложение завершает работу, вся его память высвобождается. А приложения долго не работают, потому что они уже в некоторой степени атомарны (то есть как бы уже не монолит, но ещё не микросервисы).
Есть уже случае чуть посложнее, например: есть веб-сервис, слушающий входящие соединения, или одно “приложение” вызывает другое “приложение” через SUBMIT REPORT . Но подход вы уже уловили, это и называется SAP LUW, то есть Logical Unit of Work. Получается, что стек вызовов и контекст существуют неразрывно только внутри него.
И как обычно бывает, изредка появляется необходимость перепрыгнуть заборы.
Один из известных костылей -оператор IMPORT/EXPORT MEMORY ID, который позволяет перебрасывать переменные (структуры, массивы) между приложениями, но только если они существуют в одной пользовательской сессии. Например Приложение-А вызывает Приложение-Б, Приложение-Б генерирует данные, закидывает их в ячейку памяти X, после чего завершает работу, управление возвращается в Приложение-А, которое считывает данные из ячейки памяти X, профит!
Разговор был бы не так интересен, если бы мы остановились на этом. Но перепрыгнув забор, мы обнаруживаем ещё один забор!
А если мы хотим перебросить данные между разными пользовательскими сессиями?
Для примера кейс будет таким: есть чистый слушающий HTTP-сервис, в котором мы хотим организовать что-то вроде сессии.
HTTP-сессии в ABAP могут быть как stateless, так и stateful. По сути их отличие в том, что этот пресловутый SAP LUW растягивается на всю пользовательскую сессию (stateful) или только на один http-вызов (stateless). В stateful-режиме сервис работает со своими причудами, например: обновлённый код сервиса не подхватывается на лету, чтобы изменения в сервисе вступили в силу нужно завершить сессию. Так же и в обычном ABAPe: пока запущена программа со всеми диалоговыми шагами обновлённый код не подхватывается – необходимо перезайти в транзакцию. Получается в рамках HTTP необходимо сделать log-off, что очень редко случается целенаправленно в жизненных реалиях, гораздо чаще сессия умирает по таймауту.
И вот кривая дорожка сомнительных архитектурных решений привела нас к небольшому челенджу – организовать хранение данных сессии при stateless-сессии HTTP.
Окей, вызов принят. Представим типичный сценарий:
Обыкновенным ABAPом красные места можно тупо реализовать через БД или другой как-мы говорим-у нас-в-деревне “персистент сторэдж”, мы же это умеем. Но мы же дошли досюда не за простыми решениями.
Существует возможность создать экземпляр объекта и положить его в какую-то суперглобальную область памяти, существующую вечно внутри сервера приложений. Соответственно, в любой момент можно попробовать взять этот объект во временное пользование (на чтение или на изменение). И это будет тот-же-самый-объект со всеми его атрибутами. Он будет существовать до перезагрузки сервера или до его ручного уничтожения через кнопку в админке (SHMM). Вот вкратце история. А под катом немного подробностей.
При разработке часто бывает необходимость вести некоторые настройки или конфигурации.
На первом уровне это хардкод внутри программы:
Хардкод вполне оправдан, если:
Вторым уровнем может возникнуть классическая настроечная таблица + сгенерированное ведение в транзакции SM30. Впрочем тут тоже можно сделать шаг вправо и влево:
Такие настройки уже вполне можно передать в руки настройщиков и пользователей.
Доходит иногда до смешного, даже в стандарте можно наблюдать целые настроечные таблицы в виде одной колонки (“Активировать”), соответственно в таблице только одна строка со значеним – флажок стоит или нет.
Третьим уровнем могут часто возникать разные велосипеды для организации небольших настроек.
Часто можно заметить, что в системе есть много разных стандартных узкоспециализированных хранилищ, которые можно использовать в собственных корыстных целях, например:
Но некоторые приходят к мысли, что следует разработать некоторое универсальное хранилище настроек (Z-разработка), вроде большой таблицы в стиле ключ-значение или даже нечто похожего на реестр Windows, навскидку:
Добро пожаловать под кат, представляю вам свой велосипед, там есть пара занятных моментов. Почему свой? Да потому что у остальных есть фатальный недостаток, очевидно. (далее…)
Устав от простыней в стиле:
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 |
LOOP AT SCREEN. IF screen-name = 'ZSS-PERID'. screen-input = 0. ENDIF. IF zss-pernr IS NOT INITIAL. IF screen-name = 'ZSS-PERNR' OR screen-name = 'ZSS-LNAME' OR screen-name = 'ZSS-FNAME'. screen-input = 0. ENDIF. ENDIF. IF screen-name = 'ZSS-STELL'. screen-input = 0. ENDIF. ... IF screen-name = 'ZSS-TAXNM'. IF ... . screen-required = 0. ELSE. screen-required = 1. ENDIF. ENDIF. MODIFY SCREEN. ENDLOOP. ... LOOP AT SCREEN. IF screen-group1 = 'M1'. IF ... screen-input = 0. ELSE. screen-input = 1. ENDIF. MODIFY SCREEN. ENDIF. ENDLOOP. ... |
я решил обкатать новый подход.
Первым делом декларативное определение, например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
mt_profile_readonly = VALUE #( ( mask = '*' action = zcl_screen=>readonly ) ). mt_profile_employee = VALUE #( ( mask = '*' action = zcl_screen=>readonly ) ( mask = '*QKTGR*' action = zcl_screen=>obligatory ) ( mask = '*POSID*' action = zcl_screen=>input ) ). mt_profile_contractor = VALUE #( ( mask = '*' action = zcl_screen=>readonly ) ( mask = '*QKTGR*' action = zcl_screen=>input ) ( mask = '*TAXNM*' action = zcl_screen=>obligatory ) ( mask = '*TDATE*' action = zcl_screen=>input ) ). mt_profile_visitor = VALUE #( ( mask = '*' action = zcl_screen=>input ) ( mask = '*PERID*' action = zcl_screen=>invisible ) ( mask = '*PERNR*' action = zcl_screen=>invisible ) ( mask = '*STELL*' action = zcl_screen=>invisible ) ). |
Применение на практике сводится к следующему:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
MODULE screen_3000 OUTPUT. go_person_controller->process_screen( ). ENDMODULE. ... METHOD process_screen. IF m_mode = mc_display. zcl_screen=>apply_profile( mt_profile_readonly ). ELSE. IF m_data-pernr IS NOT INITIAL. IF is_employee( EXPORTING iv_pernr = m_data-perid ). zcl_screen=>apply_profile( mt_profile_employee ). ELSE. zcl_screen=>apply_profile( mt_profile_contractor ). ENDIF. ELSE. zcl_screen=>apply_profile( mt_profile_visitor ). ENDIF. ENDIF. ENDMETHOD. |
Пробую пока покатать-проверить, насколько такой подход применим в условиях, приближенным к боевым. Нужно больше мест применения, чтобы определить какой подход даст лучшую читаемость, компактность и проверяемость. И потихотньку можно добавить мяса.