Динамическое программирование в ABAP

На днях довелось прослушать курс BC402 в рамках программы «Вечерний ABAP», хочется выразить благодарность компании SAP за такую возможность, а также отметить профессионализм преподавателя, в роли которого выступал Василий Ковальский. Сам курс посвящен обзору довольно обширных тем, которые, так или иначе, пригодятся всем ABAP программистам в их повседневной деятельности. Одной из рассматриваемых тем данного курса была возможность динамического программирования в ABAP, о которой и хотелось бы поговорить далее.

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

В ABAP под динамическим программированием могут пониматься следующие вещи:

  • Динамические дополнения к операторам, когда вместо констант, подставляются переменные: CALL FUNCTION gv_function, go_some=>(gv_method), SORT lt_tab BY (gv_column, SELECT (lv_fieldlist) FROM (gv_table) … WHERE (lv_where) и т.д.
  • Генерация программ с помощью специальных операторов: INSERT REPORT, GENERATE SUBROUTINE POOL.
  • Динамика с использованием field-symbols.
  • Динамика с использованием ссылочных переменных, в т.ч. с помощью runtime type services.

В данной статье будут рассмотрены последние 2 способа, информацию по остальным можно получить из официальной справки, а также из курса BC402.

Для динамического программирования необходимо понять, что такое ссылочные переменные и указатели на поля (field symbols) и в чем их отличия.

Для разработчиков, только начинающих изучение ABAP термин field-symbols часто может вызывать путаницу. Те из них, кто работал с языками C/C++, зачастую путают их с типом указателя (Pointer). Но field-symbols не являются указателями на область памяти, они лишь являются указателями на переменную или объект данных которые являются видимыми в текущем блоке кода (можно использовать термин alias).

На следующем рисунке хорошо видны основные отличия.

p_1

У нас есть три объекта:

  1. lv_char — переменная которая имеет символьный тип, хранится в блоке памяти 103 и имеет значение «С».
  2. <fs_char> — field-symbol являющаяся псевдонимом для lv_char.  Если изменить одну переменную, это изменение так же отразится на значении другой и наоборот.
  3. lr_pointer – ссылочная переменная, которая содержит указатель на адрес в памяти — 103, где хранится значение переменной lv_char. Изменение значения переменной lv_char никак не отражается на переменной lr_pointer т.к. она хранит лишь адрес на блок памяти. Однако можно с помощью операции разыменования изменить переменную lv_char через lr_pointer. Об этом будет написано далее.

Обобщенные типы

 

В дополнение к стандартным типам данных, в ABAP существует так же ряд обобщенных типов, использование которых возможно только в случае: формальных параметров методов (функций, процедур), field-symbols и ссылочных переменных. Часто используя динамическое программирование, приходится иметь дело с заранее не известными типами данных, для этого нужно знать каким образом их можно представить в виде обобщенных типов.

Перечень таких типов определен ниже:

Тип Описание
any Любой тип данных
any table Любая внутренняя таблица
clike Обобщенный символьный тип (cdntstring, а так же плоские структуры, состоящие из элементов символьных типов)
csequence Текстовая последовательность (cstring)
data Любой тип данных (аналогично any в случае объявления TYPE data, если объявлять TYPE REF TO DATA, будут подразумеваться ссылки на данные, но не объектные ссылки). Данный тип может быть использован в ссылочных переменных (рассмотрено ниже).
decfloat Числовой тип с плавающей запятой, один из следующих: decfloat16decfloat34.
hashed table Любая хеш таблица
index table Любая стандартная или сортированная внутренняя таблица.
numeric Числовой тип (i (bs), pdecfloat16decfloat34f)
object Любой объектный тип
simple Любой элементарный тип данных включая плоские структуры состоящие из символьных элементов.
sorted table Любая сортированная таблица
standard table Любая стандартная таблица
table Аналогично предыдущему
xsequence Байтовая последовательность (xxstring)

Предположим мы пишем некую процедуру, в параметрах которой хотели бы видеть любую сортированную или стандартную таблицу, сделать это можно с помощью обобщенного типа index table:

Попробовав активировать данный код, мы увидим следующее предупреждение:

p_2

Таким образом передать хеш-таблицу у нас не выйдет.

Использование полей field-symbols

 

Объявление полей field-symbol похоже на объявление обычных переменных, только вместо ключевого слова DATA, используется ключевые слова FIELD-SYMBOLS, символы <> являются частью синтаксиса и обязательны при объявлении. Данный тип может быть объявлен как локально, так и глобально в программе, функциональном модуле или реализации метода, но не может использоваться в интерфейсах методов и функций или атрибутах класса.

Одним из преимуществ field-symbols над обычными объектами данных является то, что их можно определять родовыми типами: any, data, csequence, simple, any table и т.д. Таким образом, к ним можно присвоить все что угодно.

По умолчанию для field-symbols нет присвоения к какой-либо переменной и если мы обратимся к не присвоенному field-symbols, программа сразу же упадет в дамп c динамической ошибкой — GETWA_NOT_ASSIGNED, которая является не обрабатываемой.

Однако из этого правило есть исключение, если объявить глобально field-symbol без указания типа, тип будет автоматически определен как символьный, а поле будет автоматически присвоено к константе space. Таким образом следующий код не вызовет динамической ошибки:

Проверить же присвоение можно с помощью оператора IS ASSIGNED или IS NOT ASSIGNED. Присвоение происходит с помощью оператора ASSIGN, пример:

В данном примере будет выведен текст, при этом field-symbol имеет обобщенный тип, что позволяет нам присваивать любую символьную переменную ABAP без каких-либо возражений компилятора.

В данном примере показано статическое присвоение, т.е. присвоение при котором мы знаем, на какую переменную мы хотим получить ссылку. Оператор ASSIGN позволяет использовать динамическое дополнение:

Допускается так же присвоение field-symbols к field-symbols:

Обратите внимание, что с помощью оператора «=» присвоение не может быть скопировано, т.е. такой вариант будет ошибочным, в случае если первое поле не было присвоено ранее:

Оператор «=» в данном контексте означает копирование данных из области, на которую ссылается вторая переменная в область, на которую ссылается первая.

Кроме прямого присвоения с помощью оператора ASSIGN, присвоение может быть выполнено в операторах обработки внутренних таблиц. Пример:

При присвоении field-symbol из операторов обработки внутренних таблиц, мы получаем указатель на строку обрабатываемой таблицы, считанную через READ TABLE или текущую по итерации из цикла по таблице.

При выходе из цикла по таблице, если таблица не пустая, поле field-symbol будет ссылаться на последнюю обработанную в рамках цикла запись.

Применяя field-symbols при обработке больших таблиц (особенно в циклах), мы можем тем самым получить существенную оптимизацию в работе программы, т.к. если бы мы использовали для получения данных из таблицы переменную структурного типа, системе пришлось бы каждый раз копировать данные из таблицы в переменную.

Если глобальное поле field-symbols будет присвоено в локальной процедуре к локальной переменной, то на выходе из неё присвоение пропадёт. Таким образом можно сделать вывод о том, что присвоение доступно ровно до того момента, как и сама переменная на которую сделано это присвоение.

В том случае если поле field-symbol присваивается переменной с определенной структурой, и мы явно указываем какой тип у field-symbol, получение доступа к её компонентам аналогично работе с обычной структурой.

Когда структура для field-symbol является заранее неопределенной (обобщенного типа any), значение компонента можно получить, используя оператор – ASSIGN COMPONENT:

В момент присвоения, с помощью дополнения CASTING, возможна адаптация значений, если типы данных не совпадают, но при этом они совместимы (нельзя присвоить таблицу обычной переменной и т.п.). Используя данный оператор, следует всегда обращать внимание на длину присваиваемых данных, иначе можно потерять часть данных. Пример:

Если добавить в структуру еще одно после, оно уже не будет перенесено в <fs_int>.

Начиная с версии ABAP 7.40 field-symbols на переменные можно объявлять непосредственно в месте, где они будут использованы:

В качестве примера динамического программирования с помощью field-symbols рассмотрим программу способную вывести в отчёте любую простую таблицу, состоящую из элементарных типов:

Результат:

p_3

Ссылочные переменные

 

Как говорилось ранее, ссылочные переменные указывают не на конкретную переменную, а на область памяти и содержат внутри себя указатель. При этом объект в памяти может быть, как явно связан с какой-либо переменной, так и создан анонимно и привязан только к ссылочной переменной.

Объявление ссылочных переменных выглядит следующим образом:

Единственным обобщенным типом для ссылочных переменных может быть тип – data.

Как было сказано выше, ссылочные переменные содержат внутри себя адрес в памяти, до тех пор, пока существует объект данных, для которого она выделена. Чтобы получить адрес относительно переменной, необходимо воспользоваться оператором GET REFERENCE:

Что касается проверки присвоения, то её необходимо выполнять с помощью ключевого слова IS BOUND. IS INITIAL в данном контексте может привести к неправильной оценке:

Так как значение ссылочной переменной является адресом в памяти, где хранятся данные, то для получения самого значения из памяти или его изменения, необходимо выполнить специальную процедуру (de-referencing) разыменования, для этого существует оператор ->*. Пример:

Между ссылочными переменными допускается копирование (через MOVE или «=»), при этом копируется не область памяти или значение, а адрес на область памяти.

В приведенном примере обе ссылочные переменные ссылаются на область памяти для переменной gv_data, в которой лежит значение «NEXT VALUE».

В случае, когда мы используем не типизированную ссылочную переменную, для копирования её значения можно использовать знакомые нам уже поля field-symbols, на которые можно «натянуть» все что угодно:

В том случае, когда ссылочная переменная ссылается на структуру, доступ к её компонентам осуществляется следующим образом: «->ИмяКомпонента»:

С помощью оператора CREATE DATA мы можем создать анонимный объект в памяти, привязав его к ссылочной переменной, время жизни такого объекта равносильно времени жизни ссылочной переменной:

У оператора CREATE DATA есть возможность указать тип динамически:

В версии ABAP 7.40 появился удобный оператор для получения ссылочных переменных вместо GET REFERENCE:

Runtime type services

 

Ранее рассмотренные примеры показывали, каким образом можно использовать field-symbols и ссылочные переменные, однако в итоге мы работали с типами данных, которые так или иначе уже где-то описаны. А что если к нам на вход пришел тип данных, созданный во время выполнения? Для получения его описания мы можем воспользоваться инструментом, который называется RTTS. По сути это определенная иерархия (набор) классов, вызывая методы которых мы можем получить полное описание типа данных. Кроме описательной возможности, данный инструмент позволяет динамически создавать типы данных во время выполнения программы.

Иерархия классов RTTS выглядит следующим образом:

p_4

В верху иерархии стоит класс CL_ABAP_TYPEDESCR который имеет абстрактный тип, т.о. вы не сможете создать его инстанцию, однако важность этого класса в другом, он содержит фабричные методы которые позволят вам получить инстанцию нижестоящих классов в зависимости от переданных параметров и методов. К примеру можно получить описание относительно имени типа данных:

Кроме получения по имени, описание можно так же получать по объекту данных (переменной), по ссылке на объект данных, по объектной ссылке.

Так же данный класс содержит ряд вспомогательных методов, например, проверка является ли тип типом из словаря и т.п.

Создавая ранее анонимную ссылочную переменную с указанием типа через дополнение TYPE можно было обратить внимание, что данное дополнение может быть расширено на еще одно – HANDLE.  Данная конструкция используется при передаче описания типа через RTTS класс, пример:

RTTS позволяет создавать произвольные типы динамически во время выполнения, простой пример:

Более интересный пример был рассмотрен мной ранее, когда речь шла о динамическом создании таблицы.

В качестве заключения

 

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

14 комментариев

  1. По поводу динамического программирования, вернее, динамического создания таблицы.
    Была у меня задача создать отчет по остаткам средств на всех счетах компании в разных странах и разных банках. Есс-но, счетов может быть сколько угодно и их количество может меняться.
    Мое такое динамическое решение (create_dynamic_table) было забраковано «верхушкой» отдела и было приказано создать словарную структуру из 300 столбцов («этого должно хватить»), ессно, руками. Спор дошел почти до драки. Вот думаю, это у меня проблемы с логикой или начальственное самодурство непобедимо?

    1. К сожалению начальственное самодурство сложно победить. Был случай когда из-за не знания того что такое rfc начальство было сложно убедить в его использовании, хотя это вполне себе стандарт коммуникации между sap системами 😉

      1. молодой задор + желание доказать, что все всё делают неправильно

        1. FI-щики хотели видеть таблицу, где столбцами бы были банки/счета, а в строках: цифры на начало конец, периодов и движения. В зависимости от открытия счетов в разных банках/странах количество столбцов могло меняться

    2. Геморрой это Ваша заливная рыба. Дядя Вася из соседней команды, который уже 20 лет программирует в АБАП, никогда с таким решением не разберётся. А ведь именно ему кинут его на стол для суппорта.

      А самая главная ошибка: никогда не спрашивай советов, как нужно что-то сделать у не-программиста (= не-специалиста).

      Можно только проиграть. Время и респект в комманде.

  2. Не проверять sy-subrc после ASSIGN — зло!

    Вообще после 7.4 field-symbols можно спокойно забыть как староое — за исключением в assign component of.

      1. Хотя бы потому это чужеродный элемент для ABAP OO, созданный в древние времена и который к тому же ужасно портит визуальный ряд кода.

        Из-за возможности использовать field-symbol без точного указания типа, программисты злоупотребляют ими, от чего страдает качество кода. Да, он шустрее работы work area при работе с широкими и глубокими таблицами (примерно более 7 строк), но эти преимущества есть reference тоже.

        Кстати: спрашивать и cl_type_Descr ( и его друзей типа tabledescr ) — очень дорогое удовольствие, даже не смотря на то, что эти классы поддерживают кэширование результатов.

        1. Согласен на счет сложности восприятия.

          И все таки, скорость иногда критична. Пример сравнения можно посмотреть тут zevolving.com/2014/03/use-of-reference-variable-vs-workarea-vs-field-symbols/.

          1. Ага, видел, хороший тест. SAP грозился разогнать работу с reference до скорости field-symbol … будем ждать 🙂

            Спасибо за статью… Интересно, да ещё и на русском 🙂

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *