Меню

Новый синтаксис ABAP. Часть 1

|

В статье рассматриваются изменения синтаксиса ABAP, появившиеся начиная с версии 7.4: инлайновые декларации, операторы-конструкторы, новые приёмы работы с таблицами, мэши, перечисления. Материал написан не как справочник, а как рассказ о взрослении языка. Автор показывает, что за каждой новой конструкцией стоит не просто удобство, а логика эволюции мышления разработчика: лаконичность становится формой ответственности.

1. Введение. Немного истории

ABAP создан в 1980 году для работы сначала с системой R/2 а потом с R/3. За долгие почти полвека своего развития он претерпел множество изменений. Каждое новое поколение разработчиков языка добавляло в него все новые и новые возможности. А поскольку SAP обещал совместимость старых версий с новыми, то изменения все накапливаются и накапливаются. И ABAP разрастается. И соответственно разрастается документация по языку. До такой степени, что знать все возможности языка представляется весьма маловероятным. ABAP впитал в себя концепции:

  • структурного программирования: никаких переходов по метке, но только последовательности, ветвления, циклы;
  • процедурного программирования: наличие возможностей создания и широкое использование процессинговых модуляризационных единиц (первоначально подпрограмм), к тому же инкапсулирующих декларации;
  • с версии 4.5 началось постепенное, даже очень постепенное внедрение объектно-ориентированного программирования, сначала в рамках локальных классов и интерфейсов, с версии 4.6 появились глобальные классы и интерфейсы, а в 6-х версиях появились классы исключений.

И вот с версий 7.4 началось постепенное движение ABAP от языка стэйтментов (не буду писать «операторов», оставлю это слова для того, что в ABAP называется «operator», в отличии от «statement») в сторону языка выражений. Вот некоторые из этих новых возможностях тут и обсудим. А именно поговорим об инлайновых декларациях, операторах конструкторах, новых возможностях работы с внутренними таблицами (индексных и ключевых табличных выражениях, мэшах).

2. Инлайновые декларации — DATA( ), FIELD-SYMBOL( )

Раньше в ABAP всякий объект данных должен был быть объявлен до своего первого использования. Теперь появилась возможность объявлять переменные и филд-символы ровно в том месте, где они в первый раз понадобились.

2.1. DATA( )

Раньше всё выглядело примерно так (см. Лист. 1):

DATA lv_count TYPE i.
SELECT COUNT(*) FROM mara INTO lv_count.

Лист. 1. Классическая декларация переменной и запрос

В новом синтаксисе (см. Лист. 2) всё уместилось в одну строку — декларация там, где происходит действие.

SELECT COUNT(*) FROM mara INTO @DATA(lv_count).

Лист. 2. Инлайновая декларация переменной

Такой подход уменьшает количество строк в коде. Декларация воспринимается как естественная часть действия: «счётчик вот здесь». Но это именно декларация. Если этот кусок кода повторить, то появится сообщение о синтаксической ошибке, ведь переменная lv_count уже объявлена. В длинной программе, если инлайновые декларации раскиданы по тысячам и десяткам тысяч строк текста, крайне трудно уследить, где, что и как объявлено. Лично я предпочитаю в длинных программах по-прежнему выносить все декларации в начало программы, а инлайновыми декларациями я позволяю себе пользоваться для определения локальных переменных в коротких методах и функциональных модулях. Впрочем, это дело вкуса, если иное не оговорено специально в регламенте проекта.

Кстати, обратите внимание на знак «@» перед конструкцией DATA(lv_count). Это тоже элемент нового синтаксиса. Элемент нового синтаксиса Open SQL (с версии 7.5 Open SQL переименован в ABAP SQL).

2.2. Маленькие хитрости DATA( ).

Инлайновую декларацию DATA можно использовать для приемника данных. Например, для приема выходного (exporting) или возвращаемого (returning) параметра. А можно и для получения результата выражения, но создаваемая этой декларацией переменная будет минимально достаточного типа, минимально достаточного для вычисления выражения, а не для получения точного результата. Забавный пример потери точности (Лист. 3, результат — Рис. 1):

DATA(first) = 2 + 2.
DATA(second) = '.2' + '.2'.
WRITE: / first color col_key.
WRITE: / second color col_key.

Лист. 3. Потеря точности при инлайновой декларации переменной

Рис. 1. Потеря точности. Результат выполнения Лист. 3

2.3. FIELD-SYMBOL( )

Похожим образом можно по месту декларировать филд-символ. Например, так (Лист. 4, результат — Рис. 2).

select ltext from tactt up to 3 rows into table @DATA(gt) where spras = 'E'.
loop at gt assigning FIELD-SYMBOL(<fs>).
write: / <fs> color col_key.
endloop.

Лист. 4. Инлайновое объявление FIELD-SYMBOL в цикле

Рис. 2. Результат выполнения Лист. 4

Стоит обратить внимание на орфографию. В отличии от декларативного стэймента окончание множественного числа «s» здесь не пишется.

3. Операторы-конструкторы

Операторы конструкторы создают безымянные объекты данных. В дальнейшем эти данные можно использовать, например, для определения значений именованных переменных в результате присвоения (ныне устаревший стэйтмент COMPUTE), в частности (но отнюдь не только) в инлайнового определенные данные, или для передачи в качестве параметра методу, без создания лишней переменной.

Общий синтаксис таков:

<Constructor_Operator> <Type>( < Constructor_Expression > ).

Где

  • Constructor_Operator — ключевое слово оператора-конструктора. Конкретные операторы-конструкторы мы рассмотрим ниже;
  • <Type> — тип создаваемого объекта данных, например имя типа, имя класса или символ диез «#», обозначающий, что результат будет иметь тип приемника (переменной или параметра, куда будет передаваться этот объект данных).
  • <Constructor_Expressions> — выражение конструктора, допустимых синтаксис которого, разумеется зависит от самого оператора конструктора.

3.1. VALUE

Создает и возвращает данные.

Чтобы создать структуру в качестве типа нужно указать тип структуры, а в качестве выражения конструктора присвоить компонентам структуры значения. Создать структуру со значениями полей можно, например так (Лист. 5. Результат — Рис. 3):

types ts_tactt type tactt.
data gs type ts_tactt.
gs = VALUE ts_tactt( spras = 'E' aCtVT = '@' ltext = 'Dog' ).
write: / gs-spras, / gs-actvt, / gs-ltext.

Лист. 5. Создание и заполнение структуры оператором-конструктором VALUE

Рис. 3. Результат выполнения Лист. 5

При создании внутренней таблицы в качестве выражения конструктора задаются в скобках выражения для каждой строки внутренней таблицы. Например, так (Лист. 6. Результат — Рис. 4):

types tt_tactt type standard table of tactt with default key.
data gt type tt_tactt.
gt = VALUE tt_tactt( ( spras = 'E' aCtVT = ' @' ltext = ' Dog' )
( spras = 'R' aCtVT = ' @' ltext = ' Собака' ) ).
loop at gt into DATA(gs).
write: / gs.
endloop.

Лист. 6. Создание и заполнение внутренней таблицы оператором-конструктором VALUE

Рис. 4. Результат выполнения Лист. 6

Как видно это позволяет обойтись одним стэйтментом, никакие APPEND или INSERT не понадобились.

3.2. NEW

Если в качестве типа указан тип данных, оператор конструктор NEW создает данные и возвращает ссылку на эти данные.

Так можно создать точно такую же внутреннюю таблицу, но получить не таблицу, а ссылку на нее (Лист. 7. Результат — Рис. 5):

types tt_tactt type standard table of tactt with default key.
data gr type ref to tt_tactt.
gr = NEW tt_tactt( ( spras = 'E' aCtVT = ' @' ltext = ' Dog' )
( spras = 'R' aCtVT = ' @' ltext = ' Собака' ) ).
break-point.

Лист. 7. Создание и заполнение внутренней таблицы оператором-конструктором NEW

Рис. 5. Результат выполнения Лист. 7

Если же в качестве типа указано имя класса, оператор конструктор, NEW создает объект этого класса. В качестве выражения конструктора нужно передать обязательные поля инстанционного конструктора. Вот, например, как можно создать селекционный экран с докинг-контейнером (Лист. 8, результат — Рис. 6):

parameters dummy.
at selection-screen output.
DATA(go_dock) = NEW cl_gui_docking_container( ratio = 50 ).

Лист. 8. Создание и докинг-контейнера на селекционном экране оператором-конструктором NEW

Рис. 6. Результат Лист. 8. Докинг-контейнер на селекционном экране, созданный оператором-конструктором NEW

Для еще одного примера создадим селекционный экран, содержащий в доккинг-контейнер, в докинг-контейнере грид, а в гриде — иголку со смертью Кощея — внутреннюю таблицу. При этом воспользуемся инлайновой декларацией DATA и операторами-конструкторами NEW и VALUE (Лист. 9, результат — Рис. 7):

types tt_tactt type standard table of tactt with default key.
parameters dummy.
at selection-screen output.
DATA(gt) = VALUE tt_tactt(
( spras = 'E' aCtVT = '@@' ltext = 'Dog' )
( spras = 'R' aCtVT = '@@' ltext = 'Собака' ) ).
DATA(go_grid) = NEW cl_gui_alv_grid(
i_parent = NEW cl_gui_docking_container( ratio = 50 ) ).
go_grid->set_table_for_first_display(
exporting i_structure_name = 'TACTT'
changing it_outtab = gt ).

Лист. 9. Создание докинг-контейнера с гридом на селекционном экране оператором-конструктором NEW

Рис. 7. Результат Лист. 9. Грид в докинг-контейнере на селекционном экране

3.3. REF

Создает ссылку на данные. Вполне эквивалентен старому стэйтменту GET REFERENCE. (Лист. 10).

data gr type ref to data.
get reference of 3 into gr.
gr = REF #( 3 ).

Лист. 10. Оператор-конструктор REF

3.4. CONV

В ABAP-е все время его существования действует неявное приведение типов данных. Можно попытаться в переменную одного типа положить значение переменной другого типа. И увидеть, что из этого произойдет. Ради приведения типов приходилось заводить переменные, которые больше ни для чего не нужны. Не знаю кому как, а мне давно не хватало явного приведения типов. И вот оно появилось. Маэстро, туш!

Оператор-конструктор CONV создает данные указанного типа. Вот пример приведения целочисленных данных (Лист. 11, результат — Рис. 8).

write: / CONV int1( 1 ) color col_key
, / CONV int2( 1 ) color col_key
, / CONV i( 1 ) color col_key
, / CONV int8( 1 ) color col_key, 'as from version 7.5'
, / CONV numc10( 1 ) color col_key
, / CONV char10( 1 ) color col_key.

Лист. 11. Оператор-конструктор CONV

Рис. 8. Результат Лист. 11. Приведение значения 1 к различным типам

Явное приведение типов особенно удобно когда параметры методов жестко типированы, типы параметров не совпадают, а нужно передавать результаты одних методов в другие.

3.5. CAST

Если CONV — осуществляет приведение данных, то CAST делает приведение ссылок. Аналогично старому оператору ?=.

Работает со ссылками на данные. Например, попробуем обращаться с системной датой, как с датой и как с временем (Лист. 12, результат — Рис. 9).

data : gra type ref to data
, grd type ref to d
, grt type ref to t
, gx type ref to cx_root.
.
gra = ref #( sy-datum ).
try.
grd = CAST d( gra ). "<=========== try as a date
write: / 'This is a date', grd->*.
catch cx_root into gx.
write: / gx->get_text( ).
endtry.
try.
grt = CAST t( gra ). "<=========== try as a time
write: / 'This is a date', grd->*.
catch cx_root into gx.
write: / gx->get_text( ).
endtry.

Лист. 12.Приведение типа ссылки на данные оператором-конструктором CAST

Рис. 9. Результат Лист. 12. Приведение типа ссылки на данные

Но это не особенно интересно, поскольку в ABAP-е не густо с родовыми ссылочными типами на данные. А вот с объектными ссылками все гораздо интереснее. Для примера напишем отчетец, который по введенному имени «словарского» типа предъявит средний экранный текст элемента данных (Лист. 13).

parameters p_tpname type string.
try.
DATA(gs_dfies) = CAST cl_abap_elemdescr(
cl_abap_typedescr=>describe_by_name( p_tpname )
)->get_ddic_field( ).
write: / 'medium screen text', gs_dfies-scrtext_m color col_key.
catch cx_root into DATA(gx).
write: / gx->get_text( ).
endtry.

Лист. 13. Приведение объектной ссылки оператором-конструктором CAST

И спросим его об именах ‘TACT-ACTVT’ (Рис. 10, Рис. 11) и ‘TACT’ (Рис. 12, Рис. 13).

Рис. 10. Ввод в программу Лист. 13. Элементарный тип

Рис. 11. Результат программы Лист. 13. Средний экранный текст элементарного типа данных

Рис. 12. Ввод в программу Лист. 13. Не элементарный тип. В данном случае — имя прозрачной таблицы словаря данных задает тип структуры отдельной записи

Рис. 13. Результат программа Лист. 13. Со структурированным типом данных нельзя обращаться, как с элементарным. Выведен текст исключения

Ах да. А причем тут приведение типов объектных ссылок? А дело в том, что метод describe_by_name возвращает ссылку на объект описания любого типа данных, а метод get_ddic_field, возвращающий структуру описания элементарного типа, на удивление относится только к объектам описания элементарных типов. В других классах описания типов этого метода просто нет. О чем нам и скажет текст исключения, если мы введем имя не элементарного типа, а, допустим, структуры.

3.6. EXACT

EXACT — возвращает точное значение выражения. Если происходит потеря точности, то возбуждается исключение CX_SY_CONVERSION_ROUNDING (Лист. 14, Рис. 14).

data
: gv1 type p decimals 1
, gv2 type p decimals 2
, gv3 type p decimals 3
, gv4 type p decimals 4
.
try.
gv4 = EXACT #( 1 / 4 ).
write: / gv4 color col_key.
gv3 = EXACT #( 1 / 4 ).
write: / gv3 color col_key.
gv2 = EXACT #( 1 / 4 ).
write: / gv2 color col_key.
gv1 = EXACT #( 1 / 4 ).
write: / gv1 color col_key.
catch CX_SY_CONVERSION_ROUNDING into data(gx).
write: / gx->get_text( ).
endtry.

Лист. 14. Точные вычисления оператором-конструктором EXACT

Рис. 14. Результат Лист. 14. Сообщение о потере точности

3.7. COND

COND — это оператор-конструктор селектор. Подобно стэйтменту IF…ELSEIF…ELSE…ENDIF условия присвоения значения задаются логическим выражением, следующим за фразой WHEN, присваиваемое значение задается выражением после фразы THEN.

Что при этом происходит можно усмотреть из примера (см. Лист. 15):

parameters ppp default 'A'.
start-of-selection.
data(qqq) = COND char20(
when ppp = 'A' then ppp && `+ABAP Akbar!`
when ppp = 'S' then ppp && `+SAP Forever!`
else `-Everybody runs SAP`
).
set blank lines on.
write / qqq color col_key.

Лист. 15. Оператор-конструктор COND

3.8. SWITCH

Это еще один оператор-конструктор селектор. Но в отличии от COND условия присвоения задаются равенством подобно конструкции CASE…WHEN…WHEN OTHERS…ENDCASE. В выражении конструктора первым указывается объект данных, значения которого сравниваются со значениями, объектов данных, указанных после ключевого слова WHEN. Присваиваемые значения задаются выражением после фразы THEN. Аналогично конструкции CASE…WHEN…WHEN OTHERS…ENDCASE логические выражения после фраз WHEN недопустимы, но допустимы перечисления значений с использованием ключевого слова OR. Поскольку ветвях WHEN проверяется только равенство значений, а логические выражения языка ABAP не вычисляются, есть основания предполагать, что это селектор должен работать быстрее.

Проиллюстритрируем примером (Лист. 16):

start-of-selection.
data(qqq) = SWITCH string( ppp
when 'A' then `ABAP Akbar!`
when 'S' or 'P' or 'Z' then `SAP Forever!`
else `Everybody runs SAP`
).
write / qqq.

Лист. 16. Оператор-конструктор SWITCH

Продолжение следует.

Об авторе

Василий Ковальский — эксперт по обучению ABAP. Преподавал ABAP-курсы в московском подразделении SAP с 1998 года до закрытия московского подразделения SAP. В настоящее время сотрудничает с ExpertRP, разработал и преподает линейку авторских ABAP-курсов, проводит мастер-классы и вебинары по ABAP. Связаться с Василием можно по адресу.