На днях довелось прослушать курс 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).
На следующем рисунке хорошо видны основные отличия.
У нас есть три объекта:
- lv_char — переменная которая имеет символьный тип, хранится в блоке памяти 103 и имеет значение «С».
- <fs_char> — field-symbol являющаяся псевдонимом для lv_char. Если изменить одну переменную, это изменение так же отразится на значении другой и наоборот.
- lr_pointer – ссылочная переменная, которая содержит указатель на адрес в памяти — 103, где хранится значение переменной lv_char. Изменение значения переменной lv_char никак не отражается на переменной lr_pointer т.к. она хранит лишь адрес на блок памяти. Однако можно с помощью операции разыменования изменить переменную lv_char через lr_pointer. Об этом будет написано далее.
Обобщенные типы
В дополнение к стандартным типам данных, в ABAP существует так же ряд обобщенных типов, использование которых возможно только в случае: формальных параметров методов (функций, процедур), field-symbols и ссылочных переменных. Часто используя динамическое программирование, приходится иметь дело с заранее не известными типами данных, для этого нужно знать каким образом их можно представить в виде обобщенных типов.
Перечень таких типов определен ниже:
Тип | Описание |
any | Любой тип данных |
any table | Любая внутренняя таблица |
clike | Обобщенный символьный тип (c, d, n, t, string, а так же плоские структуры, состоящие из элементов символьных типов) |
csequence | Текстовая последовательность (c, string) |
data | Любой тип данных (аналогично any в случае объявления TYPE data, если объявлять TYPE REF TO DATA, будут подразумеваться ссылки на данные, но не объектные ссылки). Данный тип может быть использован в ссылочных переменных (рассмотрено ниже). |
decfloat | Числовой тип с плавающей запятой, один из следующих: decfloat16, decfloat34. |
hashed table | Любая хеш таблица |
index table | Любая стандартная или сортированная внутренняя таблица. |
numeric | Числовой тип (i (b, s), p, decfloat16, decfloat34, f) |
object | Любой объектный тип |
simple | Любой элементарный тип данных включая плоские структуры состоящие из символьных элементов. |
sorted table | Любая сортированная таблица |
standard table | Любая стандартная таблица |
table | Аналогично предыдущему |
xsequence | Байтовая последовательность (x, xstring) |
Предположим мы пишем некую процедуру, в параметрах которой хотели бы видеть любую сортированную или стандартную таблицу, сделать это можно с помощью обобщенного типа index table:
1 2 3 4 5 6 7 8 9 10 |
DATA: lt_stan TYPE STANDARD TABLE OF i, lt_sort TYPE SORTED TABLE OF i WITH NON-UNIQUE DEFAULT KEY, lt_hash TYPE HASHED TABLE OF i WITH UNIQUE DEFAULT KEY. PERFORM some_proc USING lt_stan. PERFORM some_proc USING lt_sort. PERFORM some_proc USING lt_hash. FORM some_proc USING it_some_tab TYPE index table. ENDFORM. |
Попробовав активировать данный код, мы увидим следующее предупреждение:
Таким образом передать хеш-таблицу у нас не выйдет.
Использование полей field-symbols
Объявление полей field-symbol похоже на объявление обычных переменных, только вместо ключевого слова DATA, используется ключевые слова FIELD-SYMBOLS, символы <> являются частью синтаксиса и обязательны при объявлении. Данный тип может быть объявлен как локально, так и глобально в программе, функциональном модуле или реализации метода, но не может использоваться в интерфейсах методов и функций или атрибутах класса.
Одним из преимуществ field-symbols над обычными объектами данных является то, что их можно определять родовыми типами: any, data, csequence, simple, any table и т.д. Таким образом, к ним можно присвоить все что угодно.
1 |
FIELD-SYMBOLS <fs_any_string> TYPE CSEQUENCE. |
По умолчанию для field-symbols нет присвоения к какой-либо переменной и если мы обратимся к не присвоенному field-symbols, программа сразу же упадет в дамп c динамической ошибкой — GETWA_NOT_ASSIGNED, которая является не обрабатываемой.
Однако из этого правило есть исключение, если объявить глобально field-symbol без указания типа, тип будет автоматически определен как символьный, а поле будет автоматически присвоено к константе space. Таким образом следующий код не вызовет динамической ошибки:
1 2 3 4 5 |
REPORT ZTEST. FIELD-SYMBOLS: <gv_trick>. WRITE <gv_trick>. |
Проверить же присвоение можно с помощью оператора IS ASSIGNED или IS NOT ASSIGNED. Присвоение происходит с помощью оператора ASSIGN, пример:
1 2 3 4 5 6 7 8 9 10 11 |
FIELD-SYMBOLS <gfs_any_string> TYPE CSEQUENCE. DATA: gv_string TYPE string VALUE 'TEST'. ASSIGN gv_string TO <gfs_any_string>. IF <gfs_any_string> IS ASSIGNED. WRITE <gfs_any_string>. ELSE. WRITE 'Ссылка на переменную не присвоена!!'. ENDIF. |
В данном примере будет выведен текст, при этом field-symbol имеет обобщенный тип, что позволяет нам присваивать любую символьную переменную ABAP без каких-либо возражений компилятора.
В данном примере показано статическое присвоение, т.е. присвоение при котором мы знаем, на какую переменную мы хотим получить ссылку. Оператор ASSIGN позволяет использовать динамическое дополнение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
PARAMETERS: p_vari TYPE string. START-OF-SELECTION. FIELD-SYMBOLS <gfs_any_string> TYPE CSEQUENCE. DATA: gv_string TYPE string VALUE 'TEST'. ASSIGN (p_vari) TO <gfs_any_string>. IF <gfs_any_string> IS ASSIGNED. WRITE <gfs_any_string>. ELSE. WRITE 'Ссылка на переменную не присвоена!!'. ENDIF. |
Допускается так же присвоение field-symbols к field-symbols:
1 |
ASSIGN <gfs_any_string_1> TO <gv_any_string_2>. |
Обратите внимание, что с помощью оператора «=» присвоение не может быть скопировано, т.е. такой вариант будет ошибочным, в случае если первое поле не было присвоено ранее:
1 |
<gfs_any_string_1> = <gfs_any_string_2>. |
Оператор «=» в данном контексте означает копирование данных из области, на которую ссылается вторая переменная в область, на которую ссылается первая.
Кроме прямого присвоения с помощью оператора ASSIGN, присвоение может быть выполнено в операторах обработки внутренних таблиц. Пример:
1 2 3 |
READ TABLE gt_tab INDEX 1 ASSIGNING <gfs_any_struct>. LOOP AT gt_tab ASSIGNING <gfs_any_struct>. ENDLOOP. |
При присвоении field-symbol из операторов обработки внутренних таблиц, мы получаем указатель на строку обрабатываемой таблицы, считанную через READ TABLE или текущую по итерации из цикла по таблице.
При выходе из цикла по таблице, если таблица не пустая, поле field-symbol будет ссылаться на последнюю обработанную в рамках цикла запись.
Применяя field-symbols при обработке больших таблиц (особенно в циклах), мы можем тем самым получить существенную оптимизацию в работе программы, т.к. если бы мы использовали для получения данных из таблицы переменную структурного типа, системе пришлось бы каждый раз копировать данные из таблицы в переменную.
Если глобальное поле field-symbols будет присвоено в локальной процедуре к локальной переменной, то на выходе из неё присвоение пропадёт. Таким образом можно сделать вывод о том, что присвоение доступно ровно до того момента, как и сама переменная на которую сделано это присвоение.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
FIELD-SYMBOLS: <gv_variable> TYPE any. START-OF-SELECTION. PERFORM assign_variable. IF <gv_variable> IS NOT ASSIGNED. WRITE: / 'Не присвоена'. ENDIF. FORM assign_variable. DATA: lv_variable TYPE i. ASSIGN lv_variable TO <gv_variable>. IF <gv_variable> IS ASSIGNED. WRITE: 'Присвоена'. ENDIF. ENDFORM. |
В том случае если поле field-symbol присваивается переменной с определенной структурой, и мы явно указываем какой тип у field-symbol, получение доступа к её компонентам аналогично работе с обычной структурой.
1 2 3 4 5 6 7 8 9 10 11 |
DATA: ls_flight TYPE sflight. FIELD-SYMBOLS: <lfs_flight> LIKE ls_flight. ls_flight-connid = '182S'. ls_flight-seatsocc = 60. ASSIGN ls_flight TO <lfs_flight>. WRITE: 'На рейс', <lfs_flight>-connid, 'забронировано мест:', <lfs_flight>-seatsocc. |
Когда структура для field-symbol является заранее неопределенной (обобщенного типа any), значение компонента можно получить, используя оператор – ASSIGN COMPONENT:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
DATA: ls_flight TYPE sflight. FIELD-SYMBOLS: <lfs_flight> TYPE ANY, <connid> TYPE sflight-connid, <seatsocc> TYPE sflight-seatsocc. ls_flight-connid = '182S'. ls_flight-seatsocc = 60. ASSIGN ls_flight TO <lfs_flight>. ASSIGN COMPONENT 'CONNID' OF STRUCTURE ls_flight TO <connid>. ASSIGN COMPONENT 'SEATSOCC' OF STRUCTURE ls_flight TO <seatsocc>. WRITE: 'На рейс',<connid>, 'забронировано мест:', <seatsocc>. |
В момент присвоения, с помощью дополнения CASTING, возможна адаптация значений, если типы данных не совпадают, но при этом они совместимы (нельзя присвоить таблицу обычной переменной и т.п.). Используя данный оператор, следует всегда обращать внимание на длину присваиваемых данных, иначе можно потерять часть данных. Пример:
1 2 3 4 5 6 7 8 9 10 11 12 |
DATA: BEGIN OF gs_test, char1 VALUE '1', char2 VALUE '2', END OF gs_test. TYPES: ty_num_2 TYPE n LENGTH 2. FIELD-SYMBOLS: <fs_int> TYPE ty_num_2. ASSIGN gs_test TO <fs_int> CASTING. WRITE <fs_int>. |
Если добавить в структуру еще одно после, оно уже не будет перенесено в <fs_int>.
Начиная с версии ABAP 7.40 field-symbols на переменные можно объявлять непосредственно в месте, где они будут использованы:
1 2 3 4 5 6 7 8 9 10 |
DATA: lt_tab TYPE STANDARD TABLE OF i WITH DEFAULT KEY. lt_tab = VALUE #( ( 1 ) ( 2 ) ( 3 ) ). READ TABLE lt_tab INDEX 1 ASSIGNING FIELD-SYMBOL(<ls_one>). WRITE <ls_one>. LOOP AT lt_tab FROM 2 ASSIGNING FIELD-SYMBOL(<ls_two>). WRITE <ls_two>. ENDLOOP. |
В качестве примера динамического программирования с помощью field-symbols рассмотрим программу способную вывести в отчёте любую простую таблицу, состоящую из элементарных типов:
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 |
DATA: lt_spfli TYPE STANDARD TABLE OF spfli. START-OF-SELECTION. SELECT * FROM spfli UP TO 100 ROWS INTO CORRESPONDING FIELDS OF TABLE lt_spfli. PERFORM write_table USING lt_spfli. FORM write_table USING it_table TYPE ANY TABLE. FIELD-SYMBOLS: <lt_table> TYPE ANY TABLE, <ls_line> TYPE any, <lv_val> TYPE any. ASSIGN it_table TO <lt_table>. LOOP AT <lt_table> ASSIGNING <ls_line>. WRITE: /. DO. ASSIGN COMPONENT sy-index OF STRUCTURE <ls_line> TO <lv_val>. IF sy-subrc NE 0. EXIT. ENDIF. IF sy-index EQ 1. WRITE: <lv_val>. ELSE. WRITE: `,`, <lv_val>. ENDIF. ENDDO. ENDLOOP. ENDFORM. |
Результат:
Ссылочные переменные
Как говорилось ранее, ссылочные переменные указывают не на конкретную переменную, а на область памяти и содержат внутри себя указатель. При этом объект в памяти может быть, как явно связан с какой-либо переменной, так и создан анонимно и привязан только к ссылочной переменной.
Объявление ссылочных переменных выглядит следующим образом:
1 |
DATA: gr_data TYPE REF TO data. |
Единственным обобщенным типом для ссылочных переменных может быть тип – data.
Как было сказано выше, ссылочные переменные содержат внутри себя адрес в памяти, до тех пор, пока существует объект данных, для которого она выделена. Чтобы получить адрес относительно переменной, необходимо воспользоваться оператором GET REFERENCE:
1 |
GET REFERENCE OF gv_data INTO gr_data. |
Что касается проверки присвоения, то её необходимо выполнять с помощью ключевого слова IS BOUND. IS INITIAL в данном контексте может привести к неправильной оценке:
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 |
DATA: gr_dref TYPE REF TO data. FIELD-SYMBOLS: <lfs_counter> TYPE any. START-OF-SELECTION. IF gr_dref IS INITIAL. WRITE: / 'Ссылка пустая'. ENDIF. PERFORM some_procedure. IF gr_dref IS INITIAL. WRITE: / 'Ссылка пустая'. ELSE. WRITE: / 'Ссылка не пустая'. IF gr_dref IS BOUND. WRITE: / 'Объект для ссылки существует'. ELSE. WRITE: / 'Объект для ссылки не существует'. ENDIF. ENDIF. FORM some_procedure. DATA: lv_counter TYPE i VALUE 5. " Получим указатель на локальную переменную GET REFERENCE OF lv_counter INTO gr_dref. " После выхода из подпрограммы указатель все еще будет иметь некий адрес, " но по нему уже не сможем получить значение переменной ENDFORM. "some_procedure |
Так как значение ссылочной переменной является адресом в памяти, где хранятся данные, то для получения самого значения из памяти или его изменения, необходимо выполнить специальную процедуру (de-referencing) разыменования, для этого существует оператор ->*. Пример:
1 2 3 4 5 6 7 8 |
DATA: gr_data TYPE REF TO string, gv_data TYPE string VALUE 'TEST'. GET REFERENCE OF gv_data INTO gr_data. gr_data->* = 'NEW VALUE'. WRITE gr_data->*. |
Между ссылочными переменными допускается копирование (через MOVE или «=»), при этом копируется не область памяти или значение, а адрес на область памяти.
1 2 3 4 5 6 7 8 9 10 11 12 |
DATA: gr_data TYPE REF TO string, gv_data TYPE string VALUE 'TEST', gr_new_data TYPE REF TO string. GET REFERENCE OF gv_data INTO gr_data. gr_data->* = 'NEW VALUE'. gr_new_data = gr_data. gr_new_data->* = 'NEXT VALUE'. WRITE gr_data->*. WRITE: / gr_new_data->*. |
В приведенном примере обе ссылочные переменные ссылаются на область памяти для переменной gv_data, в которой лежит значение «NEXT VALUE».
В случае, когда мы используем не типизированную ссылочную переменную, для копирования её значения можно использовать знакомые нам уже поля field-symbols, на которые можно «натянуть» все что угодно:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
DATA: gr_data TYPE REF TO DATA, gv_int_a TYPE i, gv_int_b TYPE i. FIELD-SYMBOLS: <gfs_int_b> TYPE ANY. gv_int_a = 5. GET REFERENCE OF gv_int_a INTO gr_data. ASSIGN gr_data->* TO <gfs_int_b> CASTING TYPE i. gv_int_b = <gfs_int_b>. WRITE: gv_int_b. |
В том случае, когда ссылочная переменная ссылается на структуру, доступ к её компонентам осуществляется следующим образом: «->ИмяКомпонента»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
TYPES: BEGIN OF ty_struct, char1, char2, END OF ty_struct. DATA: gs_struct TYPE ty_struct. DATA: gr_struct TYPE REF TO ty_struct. gs_struct-char1 = 'A'. gs_struct-char2 = 'B'. GET REFERENCE OF gs_struct INTO gr_struct. WRITE: / gr_struct->char1, gr_struct->char2. |
С помощью оператора CREATE DATA мы можем создать анонимный объект в памяти, привязав его к ссылочной переменной, время жизни такого объекта равносильно времени жизни ссылочной переменной:
1 2 3 4 5 |
DATA: lr_string TYPE REF TO string. CREATE DATA lr_string. lr_string->* = 'Test'. |
У оператора CREATE DATA есть возможность указать тип динамически:
1 2 3 4 5 6 7 8 9 10 11 12 |
DATA: lr_int TYPE REF TO DATA, lv_type TYPE string VALUE 'i'. FIELD-SYMBOLS: <lv_int> TYPE any. CREATE DATA lr_int TYPE (lv_type). ASSIGN lr_int->* TO <lv_int>. <lv_int> = 10. WRITE <lv_int>. |
В версии ABAP 7.40 появился удобный оператор для получения ссылочных переменных вместо GET REFERENCE:
1 2 3 4 5 |
DATA: lt_tab TYPE STANDARD TABLE OF i WITH DEFAULT KEY. lt_tab = VALUE #( ( 1 ) ( 2 ) ( 3 ) ). DATA(lr_tab) = REF #( lt_tab ). |
Runtime type services
Ранее рассмотренные примеры показывали, каким образом можно использовать field-symbols и ссылочные переменные, однако в итоге мы работали с типами данных, которые так или иначе уже где-то описаны. А что если к нам на вход пришел тип данных, созданный во время выполнения? Для получения его описания мы можем воспользоваться инструментом, который называется RTTS. По сути это определенная иерархия (набор) классов, вызывая методы которых мы можем получить полное описание типа данных. Кроме описательной возможности, данный инструмент позволяет динамически создавать типы данных во время выполнения программы.
Иерархия классов RTTS выглядит следующим образом:
В верху иерархии стоит класс CL_ABAP_TYPEDESCR который имеет абстрактный тип, т.о. вы не сможете создать его инстанцию, однако важность этого класса в другом, он содержит фабричные методы которые позволят вам получить инстанцию нижестоящих классов в зависимости от переданных параметров и методов. К примеру можно получить описание относительно имени типа данных:
1 2 3 |
DATA: lo_str_descr TYPE REF TO cl_abap_structdescr. lo_str_descr ?= cl_abap_typedescr=>describe_by_name( 'SPFLI' ). |
Кроме получения по имени, описание можно так же получать по объекту данных (переменной), по ссылке на объект данных, по объектной ссылке.
Так же данный класс содержит ряд вспомогательных методов, например, проверка является ли тип типом из словаря и т.п.
Создавая ранее анонимную ссылочную переменную с указанием типа через дополнение TYPE можно было обратить внимание, что данное дополнение может быть расширено на еще одно – HANDLE. Данная конструкция используется при передаче описания типа через RTTS класс, пример:
1 2 3 4 5 6 7 8 9 10 11 12 |
DATA: lo_str_descr TYPE REF TO cl_abap_structdescr, lr_struct TYPE REF TO data. FIELD-SYMBOLS: <ls_spfli> TYPE spfli. lo_str_descr ?= cl_abap_typedescr=>describe_by_name( 'SPFLI' ). " Создадим объект данных относительно описания из словаря CREATE DATA lr_struct TYPE HANDLE lo_str_descr. ASSIGN lr_struct->* TO <ls_spfli>. <ls_spfli>-carrid = '11'. WRITE <ls_spfli>-carrid. |
RTTS позволяет создавать произвольные типы динамически во время выполнения, простой пример:
1 2 3 4 5 6 7 8 |
DATA: lr_ref TYPE REF TO DATA, lo_type TYPE REF TO cl_abap_elemdescr. " Получаем описание типа С длинной 40 символов lo_type = cl_abap_elemdescr=>get_c( 40 ). " Создаем объект данных по описательному объекту CREATE DATA lr_ref TYPE HANDLE lo_type. |
Более интересный пример был рассмотрен мной ранее, когда речь шла о динамическом создании таблицы.
В качестве заключения
Несмотря на свою привлекательность, динамическое программирование имеет и существенный минус, который следует учитывать при проектировании ПО – сложность сопровождения. Бизнес требования, как правило, не стоят на месте и всегда появляются некоторые исключения из общей логики, внедрение подобных исключений в общую логику может сопровождаться с трудностями их определения, кроме того в итоге получается код содержащий не изящный метод с общей логикой, а совокупность затычек для разных случаев.
Эх, попадись мне эта статейка, когда я начинал абапить…
В начале у всех с этой темой путаница 😉
По поводу динамического программирования, вернее, динамического создания таблицы.
Была у меня задача создать отчет по остаткам средств на всех счетах компании в разных странах и разных банках. Есс-но, счетов может быть сколько угодно и их количество может меняться.
Мое такое динамическое решение (create_dynamic_table) было забраковано «верхушкой» отдела и было приказано создать словарную структуру из 300 столбцов («этого должно хватить»), ессно, руками. Спор дошел почти до драки. Вот думаю, это у меня проблемы с логикой или начальственное самодурство непобедимо?
К сожалению начальственное самодурство сложно победить. Был случай когда из-за не знания того что такое rfc начальство было сложно убедить в его использовании, хотя это вполне себе стандарт коммуникации между sap системами 😉
молодой задор + желание доказать, что все всё делают неправильно
скажите, я один подумал, что двух столбцов хватит(счет и сумма)
Если страна + банк ключи 🙂 Но мы наверное не так поняли задачу)
FI-щики хотели видеть таблицу, где столбцами бы были банки/счета, а в строках: цифры на начало конец, периодов и движения. В зависимости от открытия счетов в разных банках/странах количество столбцов могло меняться
Геморрой это Ваша заливная рыба. Дядя Вася из соседней команды, который уже 20 лет программирует в АБАП, никогда с таким решением не разберётся. А ведь именно ему кинут его на стол для суппорта.
А самая главная ошибка: никогда не спрашивай советов, как нужно что-то сделать у не-программиста (= не-специалиста).
Можно только проиграть. Время и респект в комманде.
Не проверять sy-subrc после ASSIGN — зло!
Вообще после 7.4 field-symbols можно спокойно забыть как староое — за исключением в assign component of.
Как забыть?) Почему?)
Хотя бы потому это чужеродный элемент для ABAP OO, созданный в древние времена и который к тому же ужасно портит визуальный ряд кода.
Из-за возможности использовать field-symbol без точного указания типа, программисты злоупотребляют ими, от чего страдает качество кода. Да, он шустрее работы work area при работе с широкими и глубокими таблицами (примерно более 7 строк), но эти преимущества есть reference тоже.
Кстати: спрашивать и cl_type_Descr ( и его друзей типа tabledescr ) — очень дорогое удовольствие, даже не смотря на то, что эти классы поддерживают кэширование результатов.
Согласен на счет сложности восприятия.
И все таки, скорость иногда критична. Пример сравнения можно посмотреть тут zevolving.com/2014/03/use-of-reference-variable-vs-workarea-vs-field-symbols/.
Ага, видел, хороший тест. SAP грозился разогнать работу с reference до скорости field-symbol … будем ждать 🙂
Спасибо за статью… Интересно, да ещё и на русском 🙂