ABAP Best Practice

Последнее обновление от 04.07.2021

В данном разделе собраны лучшие практики, используемые в разработке на языке ABAP. Свод основан как на личном опыте, так и на множестве других источников. Рекомендации относятся как к специфичным особенностям языка ABAP, так и общим рекомендациям вне зависимости от языка программирования.

Зачем это все надо? Что касается правил кодирования:

  • Правила помогают поддерживать согласованность кодовой базы и позволяют разработчикам сосредоточиться на важных бизнесовых аспектах, а не спорить относительно форматирования кода 🙂
  • Благодаря согласованности кодовой базы новым разработчикам будет проще погружаться в уже существующие решения и вносить в них изменения.
  • Консультантам будет проще читать код, написанный в едином стиле кодирования.
  • Общепринятые правила можно использовать для автоматизации проверки кодовой базы в статических анализаторах (ATC или другом внешнем ПО).

Внимание! Материал еще дополняется, могут встречаться неточности в формулировках. Если у вас есть чем дополнить материал, воспользуйтесь комментариями. 

Содержание показать

Общие правила кодирования

Соблюдайте общепринятые принципы и правила хорошего тона

Речь идёт о принципах:

О правилах хорошего тона можно почитать в этой книге.

Пользуйтесь шаблонами проектирования

Паттерны (или шаблоны) проектирования описывают типичные способы решения часто встречающихся проблем при написании программ.

Вы можете вполне успешно работать, не зная ни одного паттерна. Но зная паттерны, Вы получаете ещё один инструмент в свой личный набор профессионала.

Шаблоны проектирования с примерами на ABAP.

Не используйте устаревшие операторы и API

Все мы знаем что язык ABAP совершенствуется от релиза к релизу и компания SAP старается сохранить обратную совместимость, из-за чего в коде могут встречаться такие операторы, которые уже давно отмечены как устаревшие (obsolete).

Не следует использовать устаревшие операторы языка при написании нового кода, т.к. рано или поздно они могут перестать сопровождаться и потребуется рефакторинг. Для ABAP в облаке было принято решение сразу отказаться от всех устаревших операторов. Если в вашем коде использованы подпрограммы, Вы не сможете просто перенести свой код в облако не адаптировав его.

Вы всегда можете найти перечень устаревших операторов в официальной документации к языку ABAP в разделе REFERENCE.

Что касается API, оно так же имеет свойство устаревать, например:

  • Вместо ФМ REUSE_ALV_GRID_DISPLAY используйте cl_salv_table=>factory или cl_gui_alv_grid
  • Вместо ФМ SO_NEW_DOCUMENT_ATT_SEND_API1 используйте cl_bcs
  • Вместо ФМ SAPGUI_PROGRESS_INDICATOR используйте cl_progress_indicator
  • Вместо ФМ POPUP_TO_DECIDE используйте ФМ POPUP_TO_CONFIRM
  • Вместо ФМ GUI_DOWNLOAD или ФМ GUI_UPLOAD используйте CL_GUI_FRONTEND_SERVICES
  • Вместо ФМ GUID_CREATE используйте CL_SYSTEM_UUID
  • Вместо ФМ WS_FILENAME_GET используйте CL_GUI_FRONTEND_SERVICES
  • Вместо ФМ F4_FILENAME используйте CL_GUI_FRONTEND_SERVICES

При старте новых разработок, оценивайте инструментарий что собираетесь использовать с учётом его сопровождения SAP-ом. К примеру, не следует использовать ABAP Object Services, SAP перестал развивать данный фреймворк.

Уменьшайте область видимости

Создавайте переменные, методы и атрибуты с минимально возможной областью видимости. Чем больше область видимости переменной / метода, тем более высокая связанность объектов в ваших разработках.

Уменьшайте число глобальных переменных, сокращайте число публичных методов, атрибутов.

Используйте современные конструкции и функции

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

Изменит непосредственно данные в переменной lv_data, однако более короткий вариант:

Будет лишён данного недостатка. Кроме того, мы можем в Inline стиле определять новые переменные — lv_upper_data в данном случае будет string переменной.

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

Еще примеры новых конструкций/выражений:

Хотя, не следует ими злоупотреблять. В следующем коде Вы вряд ли сразу поймёте что происходит:

Не используйте макросы

Пример:

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

Используйте ООП стиль вместо процедурного

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

Когда мы сравниваем объектно-ориентированный подход в ABAP с процедурным, то в первую очередь речь идёт о разнице в подходах при проектировании ПО на базе классов и групп функций.

Недостатки ГФ как правило вытекают из возможностей который даёт нам ООП подход (абстракция, инкапсуляция, наследование, полиморфизм):

  • Нет возможности создания нескольких инстанций. Из-за подобного ограничения реализация на ГФ становится куда более сложной, необходимо предусматривать кеширование чтения из БД, интерфейсы по массовому/единичному чтению/модификации объектов в БД и т.п.
  • Нет возможности наследования. Несмотря на тот факт, что наследование в ООП следует применять с осторожностью, оно в некоторых случаях позволяет существенно упростить развитие и сопровождение.
  • Надёжность инкапсуляции. Любой может получить доступ к внутренним переменным внутри ГФ через конструкцию ASSIGN, в классах доступ в внутреннему состоянию так же можно получить (например, через концепцию друзей), однако это должно быть специальным образом заложено при проектировании.
  • Глобальный уровень видимости функций. Функции не привязаны каким-то образом к ГФ, из-за чего их имена в теории могут пересекаться с функциями из других ГФ что в итоге будет приводить к использованию всевозможных префиксов/постфиксов. Нет возможности сделать private функции в рамках ГФ, хотя мы и можем отделять блоки в виде подпрограмм и вызывать их через PERFORM, доступ к ним так же можно получить извне.
  • Нет возможности использовать полиморфизм. Благодаря использованию интерфейсов в ABAP классах мы с легкостью можем переключать поведение между разными объектами, реализующими один и тот же интерфейс.

Использование процедурного стиля может быть оправдано только там, где технически невозможно применение ООП (экранная логика, замещения в FI реализованы через подпрограммы, RFC, Parallel processing через функции и т.п.), в таких случаях рекомендуется создавать обёртки над этими объектами в том же ООП стиле. Пример обёртки над параллельными вычислениями через pRFC/aRFC.

Организуйте правильное ведение пакетов

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

  • Главный пакет — все разработки, связанные с конкретным модулем в системе (обязательно указывается Application Component: FI, HR и т.п.), например: ZFI,
    • Субпакет — все разработки, связанные с конкретной областью в рамках пакета, например: ZFI_AA,
      • Субпакет — все разработки, связанные с конкретной разработкой в рамках области пакета верхнего уровня, например: ZFI_AA_LEDGER_EXTRACTS. В моей компании к имени пакета добавляется так же код разработки (в формате DNNNN), в том случае, когда эта разработка не является общей и не используется другими, в противном случае код разработки можно опускать.

С процедурой организации пакетов так же связана концепция ведения пакетов от SAP с ограничением использования объектов в них. Однако обычно проверка пакетов отключается на уровне базиса.

Используйте статическую проверку кода

Использование инструментов статического анализа кода позволяет существенно улучшить его качество, т.к. в транзакции SCI — инспекторе кода (или инструменте ATC) уже встроено большое количество проверок на наличие тех или иных ошибок в коде.

Можно настроить такие проверки как:

  • Правила наименования объектов в системе,
  • Проверки производительности,
  • Проверки на соответствие метрикам (количество строк в методе и т.п,).

На базе существующего функционала SCI можно создавать и свои собственные проверки для оценки соответствия кода принятому в компании уровню качества.

Дополнительные проверки SCI:

Особенностью работы ATC (SCI) является тот факт, что все найденные ошибки/предупреждения будут отображены, даже если автор последних изменений в объекте к ним не причастен.  Заставлять в таком случае править весь код иногда слишком накладно. У меня в компании реализован инструмент проведения аудита кода, с возможностью отсеивания объектов по запросам и ошибок с учётом изменения объектов в этих запросах.

Создайте систему централизованного ведения настроек

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

В таких случаях обычно используют:

  • Таблицу TVARV и транзакцию STVARV для ведения этих параметров
  • Ведение наборов: транзакции GS01-03.
  • Каталоги тестовых данных eCATT.
  • Свои Z разработки для ведения параметров.

Желательно определить общие для всех правила/инструменты ведения настроек, разработать ПО для чтения данных параметров и использовать его повсеместно.

У нас в компании для ведения настроек, объединённых в пакет (набор настроек) используется собственная разработка, которая кроме параметров и диапазонов значений позволяет вести еще и табличные значения произвольно заданной структуры.

В целом унификация решений в рамках системы/компании хорошая практика, тяжело поддерживать зоопарк решений направленных на одно и тоже (например: формирование Excel может быть выполнено с помощью множества разных решений: ZWWW, Ole, XSLT трансформаций, ZXLSX_Workbench и т.п., следует определить 1-2 решения и придерживаться только их).

Повышайте удобство использования ваших разработок

Информация в ваших разработках должна быть актуальной и понятной пользователям:

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

Декомпозируйте код

Соблюдайте принцип KISS. Существует множество расшифровок данного принципа:

  • Keep it simple, stupid.
  • Keep it small and simple.
  • Keep it sweet and simple.
  • Keep it simple and straightforward.
  • Keep it short and simple.
  • Keep it simple and smart.
  • Keep it strictly simple.

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

По возможности разбивайте программу на Include, например:

В больших отчётах это может облегчить навигацию.

Отделяйте логику представления от бизнес-логики

Мешанина из бизнес-логики и логики представления всегда создаёт проблемы восприятия. Часто это происходит из-за ошибочного дизайна классов, по сути, они решают сразу несколько задач: показать данные и обработать согласно бизнес-логике.

Всегда должно выполняться разделение ответственности, более того части бизнес-логики (модели) никак не должны зависеть от UI представления.

Для большего понимания данного правила изучите такие вещи как:

Форматирование кода

Видео на тему:

Используйте змеиный регистр (snake_case)

Создавайте свои объекты разработки с подчёркиванием между словами:

Данное правило используется практически всеми разработчиками ABAP, хотя я встречал и таких людей кто нарушал это правило.

Одна строка кода — один оператор

Читать и разбираться в коде, где несколько операторов располагаются в одну строку очень неприятно:

Не оставляйте более одной пустой строки

Пустые строки не несут какой-либо смысловой нагрузки, чем их меньше, тем легче читать код.

Разделять пустой строкой части одного выражения так же выглядит сомнительно:

Используйте структурную печать

Выполняйте структурную печать перед активацией кода. Отсутствие структурной печати существенно усложняет чтение кода. Использовать её рекомендуется с параметрами:

  • Включены отступы,
  • Перевод ключевых слов в верхний регистр: ключевое слово прописное.

Оптимизируйте код для чтения, не для написания

Разработчики тратят больше всего времени на чтение кода. Как следствие, Вы должны оптимизировать форматирование кода для чтения и отладки, а не для записи.

В качестве примера, предпочитайте следующее объявление переменных:

Вместо:

Старайтесь не использовать очень длинные строки кода

Для человеческого глаза более комфортно чтение коротких нежели длинных строк. Желательно не превышать длину в 120 символов.

Убирайте лишние пробелы

Вместо:

Используйте выравнивания только для одного объекта

Чтобы подчеркнуть принадлежность какой-то группе:

Или с новым оператором:

Не следует делать выравнивание для не связанных объектов (я в данном случае не вижу больших проблем с этим):

Закрывайте скобки в конце строки

Вместо более длинного варианта:

Так же не стоит оставлять строк кода содержащих только точку:

Вызовы с одним параметром не следует разносить на несколько строк

Вместо более длинного варианта:

Используйте перенос строк при вызовах с несколькими параметрами

Гораздо удобнее читать чем:

Делайте выравнивание для параметров

Так параметры легче воспринимать, чем если бы выравнивание отсутствовало:

Если строка слишком длинная используйте перенос

Отступы в inline определениях должны быть такими же как при вызове методов

Комментарии

Видео на тему:

Выражайте себя в коде, а не в комментариях

Вместо:

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

Комментарии не оправдывают плохие имена

Используйте хорошие имена, вместо комментирования плохих:

Используйте методы вместо комментирования сегментов вашего кода

Код становится в таком случае более сопровождаемым, количество зависимостей снижается, а читаемость повышается.

Пишите комментарии, чтобы объяснить почему, а не что делает ваш код

Никому не нужно чтобы код дублировался еще и на естественном языке:

Комментарий с «, а не с *

Комментарии через кавычки более читаемые (можно начинать с тем же отступом что и код), нежели через * (часто в таких комментариях забывают обновлять отступы):

Комментируйте до кода, а не после

Гораздо логичнее чем:

И лучше чем:

Удаляйте код, вместо его комментирования

Если встречается подобный код — удаляйте его. Всегда можно обратиться к истории изменений в системе контроля версий.

Не вставляйте сигнатуру метода в код

Сигнатуру можно увидеть в среде разработки, в SE80 достаточно нажать Ctrl+пробел рядом с именем метода и навести мышку на всплывающее уведомление:

Кроме того сигнатуры описанные в коде имеют свойство устаревать.

Так же не стоит вставлять комментирование условных блоков и окончания методов/подпрограмм:

Не дублируйте тексты сообщений в комментариях

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

Наименования объектов

Видео на тему:

Имена должны передавать намерения программиста

Следите за именами в своих программах и изменяйте их, если найдете более удачные варианты. Этим Вы упростите жизнь каждому, кто читает ваш код (в том числе и себе самому).
Имя переменной, класса, или функции должно отвечать на все главные вопросы. Оно должно сообщить, почему эта переменная (и т.д.) существует, что она делает и как используется. Если имя требует дополнительных комментариев, значит оно не передаёт намерений программиста.
Не сосредотачивайтесь на технических аспектах описываемых данных (например: имена таблиц в БД, типах данных и т.п.)
Плохой пример:

Хороший пример:

Избегайте дезинформации

Программисты должны избегать ложных ассоциаций, затемняющих смысл кода. Не используйте слова со скрытыми значениями, отличными от предполагаемого. Например: если в переменной лежит сумма по счёту, вместо lv_asum, лучше использовать lv_account_sum.

Еще пример: в программе есть метод создания некого бизнес объекта, логичное название для такого метода было бы create_some_object, но автор решил назвать метод init.

Используйте имена из пространства решения и задачи

Т.к. ваш код будут читать программисты, не стесняйтесь использовать термины из информатики, названия алгоритмов, шаблонов проектирования, математические термины и т.д. Имя AccountVisitor сообщит много полезной информации программисту, знакомому с шаблоном проектирования «Посетитель». Существует множество сугубо технических понятий, с которыми имеют дело программисты. Как правило, таким понятиям разумнее всего присваивать имена из пространства решения (технические).
Если для того что Вы используете не существует подходящего имени из технического пространства, используйте термины из пространства задачи: Account, Ledger и т.п.
Разделение концепций из пространства задачи и решения (технического) — часть работы хорошего программиста и проектировщика. В коде, главным образом ориентированным на концепции из пространства задачи, следует использовать имена из пространства задачи.

Избегайте остроумия

Если ваши имена будут излишне остроумными, то их смысл будет понятен только людям, разделяющим чувство юмора автора — и только если они помнят шутку. Пример: метод kill_them_all(), вместо delete_items().

Не используйте лишний контекст

Допустим у Вас имеется класс zcl_business_partner_address, нет необходимости в дублировании слова partner в атрибутах этого класса, вроде: mv_partner_zip_code или mv_partner_street. Всем и так понятно, что в контексте класса zcl_business_partner_address атрибут mv_zip_code будет иметь отношение к адресу.

Используйте множественную форму

В SAP решениях с давних времён существует практика наименования объектов в единственном числе,  к примеру таблица country — таблица стран. Для списков и перечней каких-либо объектов лучше использовать множественную форму — countries.

Используйте удобопроизносимые имена

Людям удобнее работать со словами. Значительная часть нашего мозга специализируется на концепции слов, а слова по определению удобопроизносимые. Было бы обидно не использовать ту часть мозга, которая развивалась для разговорной речи. Если имя неудобно произнести, то при любом его упоминании Вы будете выглядеть идиотом. Например: вместо имени класса DtRcrdDao, следовало бы использовать имя Customer.

Не используйте аббревиатуры

К сожалению имена классов в ABAP ограничены весьма серьезно относительно длины и иногда приходится использовать сокращения или аббревиатуры. Однако если у вас достаточно места, пишите имена полностью. Например: в имени класса CustContainer не совсем понятно что обозначает Cust — Custom, Customer, Customizing и т.д.

Однако есть из этого правила и устоявшиеся исключения вроде: i18n, l10n и g11n.

Используйте одинаковые аббревиатуры

Если все же сокращений не удаётся избежать, следует использовать одинаковые сокращения во всех объектах разработки. Например: таблица словаря cust_materials, где cust — customer, для переменной внутренней таблицы следует использовать аналогичное сокращение — lt_cust_materials, вместо lt_cu_materials, хотя в данном случае сокращения следовало бы избежать, т.к. имя переменной позволяет раскрыть её до конца.

Используйте существительные для классов и глаголы для методов

Для классов и интерфейсов используйте имена существительные:

Для методов или функций используйте глаголы:

Названия методов не должны быть общими вроде:

Названия должны отражать предназначение метода:

Для булевых методов как правило принято использовать префиксы is_ или has_:

Не следует называть классы общими словами вроде: Utility, Common, WebsiteBO, JimmysObjects и т.п.

Избегайте загрязняющие слова типа: data, object, info

Опускайте подобные слова при создании переменных:

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

Выберете одно слово для каждой концепции

Методы чтения данных:

В данном случае чтение данных обозначается словом read, однако можно было придумать что-то вроде:

В итоге имеем разные термины обозначающие одно и то же, что затрудняет восприятие.

Создайте свод правил по наименованию объектов

В рамках компании, команды, логично использование общих правил по наименованию объектов в системе. Единые правила наименования локальных/глобальных объектов в моей компании объединены в так называемый регламент разработки, которому следуют как внутренние исполнители, так и внешние подрядчики.

Так как язык ABAP имеет ограничения на имена переменных, использования префиксов (венгерской нотации) для типов данных считаю оправданным, хотя многие со мной могут не согласится и скажут об излишнем кодировании. Если посмотреть на код, поставляемый SAP, префиксы используются повсеместно.

    • zcl_ для классов
    • zif_ для интерфейсов
    • zif_ для интерфейсов
    • Параметры методов/функций:
      • Тип параметра:
        • i — importing
        • r — returning
        • c — changing
        • e — exporting
      • Тип переменной:
        • t — табличный
        • v — простой тип
        • o — ссылка на экземпляр класса
        • r — ссылка на ссылочную переменную REF TO DATA
        • s — структура

Данный перечень можно продолжать очень долго, т.к. в системе существует большое количество разных объектов, как глобальных (словарные, классы, интерфейсы), так и локальных (атрибуты классов, локальные типы, константы в классах). Главное что стоит отметить это то, что все разработчики в рамках команд/проекта/компании должны писать в одном стиле, дабы чтение кода не вызывало головную боль.

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

Объявление переменных

Старайтесь не использовать неявные определения

По возможности старайтесь не использовать объявления данных, через TABLES, NODES.

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

Используйте Inline объявление переменных

Если это возможно, предпочитайте inline определения переменных:

вместо объявления их в начале функции, метода:

Если inline объявление невозможно, определение переменных всегда должно быть вначале подпрограммы, функции, метода.

Используйте inline определения вне условных конструкций

Данный код будет работать, т.к. ABAP компилятор обрабатывает Inline объявления так, как если бы они были определены явным образом в начале метода. Однако данный код может оказаться крайне запутанным для чтения другим разработчиком.

В данном случае либо не стоит использовать переменную в условии, где она не определяется, либо стоит перенести объявление наверх:

Указывайте тип данных явным образом

Современные операторы, такие как VALUE, COND и прочие позволяют существенно сократить объемы кода, однако следует внимательно использовать их, дабы результат не вызывал сюрпризов:

В данном примере в результате условия будет *. Связано это с тем что тип явным образом не задан, система по умолчанию определяет тип после первого THEN, space это по сути поле CHAR длинной 1, а при попытке присвоения к CHAR1 мы получаем * из-за невозможности её заполнить целиком значением 777.  Правила присвоения описаны тут.

Исправить можно путём явного указания типа:

Рассмотрим еще пример с REDUCE:

В указанном выше примере Вы возможно подумаете что сумма будет посчитана по полю forucam и при этом корректно, однако это не так. Сумма действительно будет посчитана, но все цифры после запятой будут отброшены, так как в качестве типа данных для lv_val система приняла i. По правилам, если тип данных не указан для переменной, система пытается вывести его из INITIAL значения, в данном случае 0.

Таким образом, даже если указать корректный тип в качестве результата, результат все равно окажется без цифр после запятой:

Корректно будет использовать оператор с явным указанием типа для lv_val:

Используйте LIKE LINE OF

Это позволит вам делать меньше правок, если тип внутренней таблицы вдруг поменяется:

Присвоения, расчёты и другие способы доступа к данным

Избегайте преобразований при присвоениях

Язык ABAP позволяет выполнять присвоения между объектами (переменными) с разными типами данных. При подобных операциях присваиваемое значение, после конвертации на основе заранее определённых правил преобразования, должно иметь соответствующее представление в диапазоне значений той переменной, к которой мы выполняем присвоение. Если правило для преобразования типов не определено или значение выходит за рамки возможных значений, возникает исключение.

Преобразования выполняются не только при прямых присвоениях, но так же и в арифметических или логических выражениях.

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

Не стоит так же злоупотреблять правилами преобразования типов, например:

В данном случае нужно быть очень хорошо осведомлённым относительно правил, чтобы догадаться о значении 42 в переменной num_text.

Не забывайте про исключения

Если невозможно предсказать выйдет ли значение присваиваемой переменной за диапазон возможных значений, будет ли корректно обработано правило преобразования и т.п., необходимо предусмотреть обработку исключительных ситуаций, либо заранее предусмотреть последствия:

Числовые типы

Правильно выбирайте числовые типы

От выбора числового типа зависит скорость и точность вычислений, так например тип F (Float) следует использовать только тогда, когда точность при округлениях не является критичной, т.к. тип вычисления float не гарантирует корректных с бизнес точки зрения округлений.

Помните о типах вычислений и приоритете их выбора

Тип вычисления выражений в ABAP соответствует числовым типам и определяется согласно следующему приоритету:

  1. Если в выражении есть хотя бы одна переменная decfloat16 (decfloat34), выражение будет считаться согласно типу вычисления decfloat34,
  2. Если в выражении есть хотя бы одна переменная типом f, либо используется возведение в степень **, выражение будет считаться согласно типу вычисления Float (с возможными ошибками при округлении)
  3. Если в выражении есть хотя бы одна переменная с типом p, выражение будет считаться согласно типу вычисления p,
  4. Если в выражении есть хотя бы одна переменная с типом int8, выражение будет считаться согласно типу вычисления in8,
  5. Если в выражении есть хотя бы одна переменная с типом i (s, b), выражение будет считаться согласно типу вычисления i.

Более подробно читайте в документации.

Строки

Используйте ‵ вместо ‘ при определении литералов

При объявлении через ‵ мы объявляем литерал строкового типа (string), через ‘ мы объявляем литерал символьного типа (c).

Воздержитесь от использования ‘, поскольку это добавляет избыточное преобразование типов и сбивает с толку читателя, имеет ли он дело с CHAR или STRING:

Еще одна ловушка может скрываться при отсечении конечных пробелов при конкатенации строковых переменных:

В переменной result1 будет WorldWord, в переменной result2 будет World Word.

Или следующий пример:

В переменной text будет Word Word, т.к. в ABAP нет пустого текстового литерала », он будет заменён пробелом.

В следующем примере мы никогда не попадём в отладчик, т.к. пробел во втором случае будет заменён пустым значением:

Используйте строковые шаблоны при создании строк

Строковые шаблоны лучше подчёркивают что является переменной, а что текстом, особенно при использовании более одной переменной.

При конкатенации это не так очевидно:

Оптимизируйте конкатенацию строк

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

Но только тогда, когда:

  • Целевая переменная встречается справа от оператора присвоения лишь однажды и находится первой в выражении конкатенации
  • Для целевой переменной находящейся справа не выполняются преобразования через опции форматирования строкового шаблона
  • В правой части не используются иные выражения кроме конкатенации (не вызываются методы, не используются встроенные функции, оператор преобразования CONV и т.п.).

В следующем примере конкатенация будет выполняться с учётом оптимизации:

Однако внутренняя оптимизация не будет срабатывать если конкатенация выполняется следующими способами:

В следующем примере вызов функции ipow при конкатенации вызовет проблемы с производительностью:

Обойти эту проблему можно воспользовавшись временными переменными, при этом не нарушая выше обозначенных правил оптимизации:

Все это так же актуально и для новых операторов:

VS:

Логические типы

В языке ABAP нет встроенного логического типа данных, как в других языках, однако мы все же используем его аналог на базе констант определённых в пуле типов abap:

  • abap_true
  • abap_bool
  • abap_false

Используйте логический тип с умом

Мы часто сталкиваемся со случаями, когда логические значения кажутся естественным выбором:

До тех пор пока бизнес-логика не изменится и не появится более двух возможных значений статуса, тут нам уже потребуются перечисляемый тип:

Из-за чего в последствии вырастут ужасные решения вроде:

Для логического типа используйте тип abap_bool

Не используйте тип char1, хотя он и совпадает с технической точки зрения, но в таком случае мы явно не говорим об использовании логического типа, читаемость ухудшается.

В некоторых случаях, например в Dynpro нельзя использовать abap_bool, т.к. он определен в пуле типов abap, в данном случае используйте boole_d или xfield, Вы можете определить свой элемент данных на тех же доменах, если нужно своё собственное описание элемента данных.

Имена логических переменных должны отвечать на вопросы

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

Более правильными именами будут:

Используйте ABAP_TRUE и ABAP_FALSE для сравнения

Не используйте для логических сравнений space, ‘X’ или ‘ ‘, так мы перестаём явно указывать на использование именно логического типа:

Так же избегайте сравнение IS INITIAL:

Используйте XSDBOOL для установки логических типов

Вариант с IF ELSE будет гораздо длиннее:

Использование boolc и boolx в данном случае не подходят, т.к. они возвращают тип string, а не char1. В следующем примере ответ будет NO:

Связано это с правилами преобразования типов при сравнении. При сравнении char с string конечные пробелы при преобразовании из типа char к string будут проигнорированы.

Функцию xsdbool можно заменить на условный оператор COND:

Константы

Используйте константы вместо хардкода

Гораздо понятнее чем:

Группируйте константы

Как правило, объявление глобальных констант осуществляется в интерфейсах. Можно создать один общий интерфейс констант где они будут разбросаны как попало:

Но лучше логически их объединять на уровне интерфейса/класса:

В рамках одного интерфейса как правило создаются группы констант (см. cs_sort_criterion):

Если требуется перечисляемый тип и версия языка >=7.51, вместо именованных групп констант лучше использовать встроенный в язык синтаксис объявления перечисляемых типов. Тогда не придётся самостоятельно заниматься валидацией значений как внутри создаваемых классов, так и со стороны клиента при использовании.

Следуйте правилам наименования для констант

На приведённом выше примере константы используются чтобы не хардкодить цифры, что совершенно не оправдано. Наименования этих констант так же ни о чём не говорят — один день, один километр или что-то еще, следует использовать более описательные имена из доменной области где эти константы используются.

Еще пример:

Следовало бы заменить на более описательное имя из которого сразу станет понятно зачем нужна эта константа:

Не используйте константы иконок в качестве статусов

Наверняка вам приходилось видеть нечто подобное:

Либо:

Константы из пула ICON_ не передают семантики связанной с местом их использования, из-за чего понять что такое icon_breakpoint становится весьма сложно. Вместо их использования следует завести отдельные константы:

Если Вам потребуется графическое представление того или иного статуса, заведите внутри класса констант метод получающий это представление и сделайте для его отображения отдельное поле. Более подробно на эту тему.

Можно сделать упрощение и в качестве значений для статусов из интерфейса/класса констант использовать значения констант из пула иконок:

Внутренние таблицы

Не используйте таблицы с заголовками

Таблицы объявленные с header line (заголовком) являются устаревшей конструкцией, по возможности не используйте их.

Используйте правильный тип доступа к табличным данным в циклах

  • Используйте структуру (INTO DATA, копирование данных строки) если строка таблицы узкая и не требуется её модификация (Копирование данных существенно замедляет работу программы на больших объемах данных). Данное правило описано в ABAP Guidelines, я бы не рекомендовал использовать данный способ доступа вообще.
  • Используйте ссылочные поля (field-symbols) если строка широкая или содержит вложенные структуры, объекты (глубокая), или же требуется её модификация.
  • Используйте ссылки (REFERENCE INTO) если строка широкая или содержит вложенные структуры, объекты и требуется передавать куда-то ссылку на неё.

Модифицируйте таблицу в циклах только по одной записи

У нас есть возможность использовать в циклах такие операторы как CLEAR, FREE, =, SELECT INTO TABLE, прямо к той же таблице по которой мы проходим в текущем цикле. Однако их использование может приводить к ошибкам времени выполнения или непредвиденному поведению программы.

Используйте правильный тип внутренней таблицы

  • HASHED таблицы как правило используются для больших таблиц, которые считываются за один раз, редко изменяются и чтение происходит по уникальному ключу. Пересчёт хеша весьма дорогостоящая процедура, соответственно использование hashed таблиц для часто меняющихся данных нецелесообразно.
  • SORTED таблицы как правило используются для больших таблиц, в которых допускается частое изменение данных, чтение по которым осуществляется как по уникальному ключу, так и по его части.
  • STANDARD таблицы используются там где не важен порядок сортировки и скорость чтения из таблицы, там где накладные расходы по ведению индексирования будут превышать расходы по чтению/изменению данных.

Использование дополнительных ключей позволяет использовать преимущества того или иного типа таблицы, однако с такими типами таблиц следует быть более осторожным, т.к. как правило разработчик может просто не заметить дополнительно объявленного ключа в стандартной таблице, что может привести к дампам во время работы с таблицей.

Более подробно по выбору типа внутренней таблицы, читайте в официальной документации.

Не используйте внутренние таблицы с ключом по умолчанию (DEFAULT KEY)

Определение ключа по умолчанию зачастую ведёт к появлению непонятных ошибок, особенно при изменении структуры внутренней таблицы.

Пример:

В данном примере таблица будет отсортирована по полям carrid, connid т.к. ключ по умолчанию будет определён на основе всех полей символьного типа — clike. Код будет работать правильно, до тех пор, пока кто-то не изменит структуру, добавив очередное символьное поле посередине:

Сортировка поменяется, т.к. ключ по умолчанию будет уже из трёх полей.

Если внутренняя таблица не нуждается в ключе по умолчанию, используйте EMPTY KEY. В таком случае PRIMARY KEY будет пустым и сортировка без явного указания полей не будет выполняться совсем:

Определяйте порядок сортировки явным образом

Если использовать оператор SORT без указания полей по которым будет выполнена сортировка, сортировка произойдёт относительно PRIMARY KEY внутренней таблицы. PRIMARY KEY сам по себе в некоторых случая определяется неявным образом, соответственно сложность понимания логики работы оператора SORT возрастает.

Всегда указывайте перечень полей в операторе SORT, желательно указывая еще и порядок сортировки (ASCENDING/DESCENDING).

Не используйте оператор COLLECT

При использовании оператора COLLECT мы не можем сказать каким образом отработает подсуммировка пока не поймём состав структуры внутренней таблицы и определения PRIMARY KEY, что делает понимание логики кода крайне затруднительным.

Если ваша система достаточно современна, используйте GROUP BY в цикле по таблице, где явным образом будут определены поля по которым она будет выполнена или по крайней мере определяйте ключ PRIMARY KEY явным образом:

Более подробно относительно GROUP BY.

Используйте INSERT INTO TABLE вместо APPEND TO

Данная конструкция работает со всеми типами внутренних таблиц и упрощает рефакторинг при изменении типа таблицы и используемого основного ключа. Используйте оператор APPEND TO только в случаях когда Вы явно хотите показать что работаете со стандартной внутренней таблицей как с массивом и строка добавляется в его конец.

Используйте LINE_EXISTS вместо READ TABLE или LOOP AT с выходом из цикла

Конструкция:

Гораздо приятнее для чтения чем:

Или:

Используйте READ TABLE вместо LOOP AT

Если нужно считать запись, используйте оператор READ TABLE:

Это гораздо короче чем циклы:

Или:

В современных системах можно использовать оператор VALUE:

Используйте LOOP AT WHERE вместо вложенных IF

Гораздо короче чем:

Не делайте лишних чтений

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

Работа с БД

Старайтесь не использовать NativeSQL без необходимости

NativeSQL необходимо использовать только если в этом есть явная необходимость — ABAP SQL (OpenSQL) не обладает тем же функционалом (Например: использование HANA артефактов/функций и т.п, недоступных в AMDP, оптимизация запроса на уровне текущей СУБД), в остальных случаях воздержитесь от его использования.

Преимущества ABAP SQL над NativeSQL:

  • Встроенная проверка синтаксиса
  • Независимость от БД (общий синтаксис)
  • Интеграция в язык ABAP

В целом следует придерживаться следующих правил по использованию NativeSQL в SAP системах:

  • ABAP программы должны использовать ABAP SQL для работы с объектами БД;
  • Если ABAP SQL не может быть использован, следует использовать AMDP для работы с NativeSQL;
  • Если AMDP не может быть использован, следует использовать интерфейсы NativeSQL встроенные в ABAP — ADBC или EXEC SQL;
  • Не следует использовать NativeSQL для доступа к управляемым из ABAP-а объектам. Если вы в HANA Native разработке будете обращаться к функциям сгенерированным на основании ABAP CDS или к AMDP, у вас могут возникнуть проблемы после перегенерации этих объектов системой. Их имена или интерфейсы могут поменяться.

При использовании FOR ALL ENTRIES всегда проверяйте драйвер таблицу на пустоту

Если Вы этого не сделаете, то все условия в SELECT операторе будут проигнорированы. Если такое поведение допускается сделайте об этом комментарий, чтобы другие разработчики не подумали что это ошибка.

Соблюдайте форматирование запроса

Операторы Open SQL должны быть отформатированы так, чтобы зарезервированные слова INTO, FROM, WHERE, ORDER BY и т.д. располагались с новой строки с отступом.

Гораздо приятнее читать чем:

Используйте нормальные псевдонимы

Часто вижу такой код:

Воспринимать подобные запросы крайне сложно, особенно когда речь идёт о более чем трёх таблицах в запросе. Иногда псевдонимы лепятся просто чтобы были, а не из-за технической необходимости, как в например в таблице spfli as s1.

Использовать псевдонимы надо только тогда, когда они нужны и имена им нужно придумывать логически обоснованные, пример адаптированного запроса:

Используйте современный синтаксис SQL

Для систем на базе ABAP 7.4 и старше необходимо использовать новый синтаксис SQL (под новым я имею в виду тот случай, когда Вы используете @ для abap переменных, именно @ говорит компилятору о необходимости применения нового синтаксиса и его парсера).

В данном примере мы делаем простую выборку из таблицы полётов, причем значение в поле seatsmax должно быть меньше значения в поле seatsocc. Если мы захотим сравнить значение с некоторым значением из abap переменной, мы можем написать такой запрос:

Обратите внимание что используется уже — вместо ~, данный запрос возможен, если объявлены следующие переменные:

Выглядит все это ужасно и крайне запутанно. Используя новый синтаксис, мы явно говорим компилятору где используется ABAP переменная, следующий код не будет скомпилирован, т.к. в новом синтаксисе для всех ABAP переменных необходимо их экранирование символом @:

В современном ABAP SQL с каждым новым релизом появляются все новые способы оптимизации SQL запросов, так например, вместо FOR ALL ENTRIES и нескольких последовательных запросов можно воспользоваться общими табличными выражениями (Common table expressions или CTE), что существенно может повысить производительность ваших запросов. Оконные функции так же позволяют существенно сократить затраты и не выполнять лишних действий на сервере приложений.

Избегайте обновления БД прямым способом

Все обновления должны происходить через модули обновления. Прямые обновления могут приводить к неконсистентности данных. Прямые обновления допускаются в крайних случаях: проблемы с производительностью, расширение стандарта с уже реализованными прямыми обновлениями и т.п.

Подробнее о работе с обновлением БД.

AMDP классы только для AMDP процедур/функций

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

Не используйте INNER JOIN для текстовых таблиц

В случае если перевод по какой-то причине не был сделан, такой запрос может привести к ошибочным результатам. Используйте OUTER JOIN для текстовых таблиц.

Следуйте золотым правилам работы с БД

Тема производительности при работе с БД очень обширная, сильно зависит от используемой СУБД и достойная отдельной книги, впрочем основные правила которые следует соблюдать при работе с БД будут затронуты.

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

Нагружайте или не нагружайте СУБД

В зависимости от используемой СУБД и железа на котором она работает, всегда следует анализировать где лучше производить вычисления на уровне СУБД сервера или на уровне сервера приложений.

Так для систем на базе HANA справедливо выносить максимальное число вычислений на СУБД (Code-to-Data / Code push down), т.к. HANA оптимизирована для выполнения параллельных вычислений, агрегаций, сортировок и прочих операций.

Сокращайте издержки при поиске записей

Сокращения можно добиться прежде всего указанием WHERE условия с учётом ключевых полей или полей из индекса к таблице (с соблюдением условий необходимых для поиска по индексу)

    • Старайтесь не использовать LIKE с %
    • Не пропускайте поля в индексе:

    • Не используйте отрицательных условий если это возможно:

    • Заменяйте вложенные OR условия на IN:

    • Старайтесь не использовать диапазоны со значениями CP, BT в условиях

Уменьшайте количество обращений к базе данных

  • По возможности используйте операторы которые работают сразу с множеством строк в базе данных.
    • SELECT INTO TABLE \ APPENDING TABLE вместо SELECT SINGLE в циклах:

    • INSERT FROM TABLE/DELETE FROM dbtab WHERE вместо INSERT/DELETE отдельных строк в цикле

  • Используйте JOIN для выборки данных из нескольких таблиц:
    • Старайтесь заменить FAE на выборку с JOIN.
    • Избегайте вложенных SELECT операторов:

    • Избегайте объединения внутренних таблиц на уровне сервера приложений, если их можно сформировать одним запросом с JOIN.
  • Используйте буферизацию. Средства трассировки позволяют увидеть число идентичных запросов, что сразу может указывать на проблемы в коде.
  • При использовании FAE учитывайте его особенности: удаляйте дубликаты в драйвер таблице, проверяйте драйвер таблицу на не пустоту, иначе все WHERE условия будут отброшены.
  • Используйте вложенные запросы. Вместо:

Лучше сделать следующим образом:

Уменьшайте объемы передаваемых данных

Необходимо стараться уменьшать объемы передаваемых данных с СУБД на сервер приложений и обратно, главное здесь:

  • При формировании запроса с FAE убирайте дубликаты
  • Уменьшение количества выбираемых из базы данных строк:
    • Ограничивать запросы условиями при выборке в WHERE дополнении, не делать отсеивание на сервере приложений:
      • через CHECK или IF условиях в SELECT.. ENDSELECT операторе:

      • DELETE к полученным данным после выполненной выборки:

    • Выполнять агрегацию на уровне СУБД:

  • Ограничение относительно выбираемых полей:
    • Выбирайте только те поля, которые действительно нужны, старайтесь не использовать SELECT *.
    • Старайтесь объединять запросы к БД, не делать выборки сначала по полям F1, F2, а затем с теми же условиями по полям F3, F4:

    • При изменении данных в БД, изменяйте только то что действительно нужно изменить: используйте оператор UPDATE … SET.

Используйте одну концепцию хранения дат в БД

При необходимости сохранять даты и время внутри БД вы можете пойти разными путями:

  • Хранить TIMESTAMP в полном или урезанном варианте, в UTC или нет,
  • Хранить дату и время используя стандартные словарные типы DATS и TIMS.

Не стоит использовать одновременно оба подхода, так как вам дополнительно придётся задумываться о способе конвертации из одного формата в другой при сопоставлении данных.

Условные конструкции

Старайтесь использовать положительные условия

Отрицательные условия читаются всегда крайне тяжело:

При конвертации к положительному условию, не используйте конструкцию IF ELSE с пустым положительным условием:

Откажитесь от символьных сравнительных операторов

Если есть выбор между буквенным и графическим оператором сравнения, то следует всегда выбирать графический (например: «=» вместо «EQ», «>» вместо «GT» и т.п.).

Используйте IS NOT вместо NOT IS

Гораздо легче читать чем:

Используйте декомпозицию для сложных условий

Условия могут стать легче если разложить их на элементарные части:

Вместо:

Инкапсулируйте условные конструкции

Часто приходится видеть одни и те же сложные условия в разных частях программы, правильнее вынести их в отдельный метод:

Даже если условие не дублируется, отдельный метод всегда будет выразительнее.

Не оставляйте пустые условные конструкции

Данный пример гораздо нагляднее чем:

Убирайте ненужные скобки

Вместо более сложного для чтения варианта:

Не дублируйте код в условных конструкциях

Вместо:

Не используйте длинные последовательности кода в CASE операторе

Если код в CASE операторе для условий начинает разрастаться, вынесете его в отдельный метод, тем самым повысив читаемость кода.

Используйте CASE вместо ELSE IF для нескольких альтернативных условий

Гораздо понятнее и удобнее в сопровождении чем вариант с множеством ELSE:

Держите глубину вложенности на низком уровне

Вложенные условные конструкции сложно понимать и далее сопровождать. Как вариант решения, выносить условия в отдельные методы или создавать вспомогательные логические переменные, что позволит сократить уровень вложенности.

В приведённом примере первое условие можно так же упростить:

Вместо:

Полиморфизм вместо условного оператора

У вас есть условный оператор который в зависимости от атрибута объекта или результата выполнения метода выполняет те или иные действия, например:

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

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

Регулярные выражения

Не используйте регулярные выражения понапрасну

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

Сборка регулярных выражений

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

Dynpro

Не пишите бизнес-логику в экранных модулях или событиях отчётов

Бизнес-логика должна быть сосредоточена в методах:

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

Тексты для параметров экрана выбора обязательны

Даже если эти параметры являются техническими и скрыты от пользователя через NO-DISPLAY (например: какой-нибудь флаг, заполняемый при вызове программы извне, чтобы она работала без диалогов), тексты хоть как-то документируют и поясняют их назначение.

Интернационализация и локализация

Не используйте текстовый хардкод

В данном случае Вы можете поместить текст в тексты программы/класса, объявив его следующим образом:

При необходимости его можно будет перевести на другие языки.

Не используйте конструкцию text-nnn

Часто можно увидеть такой код:

text-001, в данном случае говорит о необходимости взять текст из текстовых данных класса или программы с идентификатором 001, при этом чтобы во время чтения было понятно что обозначает text-001, автор сделал комментарий справа.

Лучше использовать конструкцию:

Суть та же, но при проваливании в текст и обратно среда проверяет соответствие и будет предупреждать о несоответствии. Комментарий теперь не требуется, соответственно он не может устареть и не будет вводить в заблуждение если по факту текст будет другим.

Не используйте текстовые константы

Их нельзя будет перевести, соответственно не пользуйтесь ими.

Используйте текстовые таблицы для хранения текстов в БД

Не сохраняйте тексты в таблице с данными, используйте текстовые таблицы для хранения текстов.

У текстовых таблиц есть следующие преимущества:

  • Они поддерживают возможность перевода (SE63)
  • Для одного и того же объекта можно определить несколько текстов
  • Тексты автоматически подставляются в средствах поиска к значениям
  • Тексты автоматически добавляются к ракурсам ведения

Используйте один язык в рамках команды разработки/проекта

Некоторые любители английского языка зачастую пишут комментарии на английском, хотя проект целиком создан под российского клиента и не планируется к доработке иностранцами.

В рамках команды/проекта используйте только один язык, чтобы не вносить лишнюю путаницу.

Для объектов разработки используйте только английский язык

Никакого русского транслита в наименованиях объектов разработки быть не должно:

Используйте длинные тексты из SO10

Вместо объединения нескольких текстов в программе в более длинные, лучше использовать тексты, которые ведутся через транзакцию SO10.

Классы

Старайтесь минимизировать использование статических классов/методов

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

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

Конечно есть ситуации в которых использование статических методов может быть оправдано (хотя и есть явные противники этого):

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

Видео на тему:

Предпочитайте композицию наследованию

Избегайте построение больших иерархий классов с наследованием, вместо этого старайтесь пересмотреть ваш дизайн, возможно вам подойдёт композиция.

При использовании наследования мы должны соблюдать ряд правил, например принцип Барбары Лисков. Соблюсти данный принцип (который говорит, что все, что справедливо для базового класса справедливо и для его наследника) не всегда возможно.  Наследование так же усложняет процесс рефакторинга, т.к. мы будем вынуждены вносить изменения во всю иерархию классов.

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

Хороший пример (с утками) того, до чего может довести наследование приведён в книге Head First. Паттерны проектирования.

Подробнее об композиции и наследовании.

Старайтесь избегать дружественных классов

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

Используйте интерфейсные ссылки

В случае если класс реализует тот или иной интерфейс, доступ к методам обычно осуществляется путём указания имени интерфейса:

В данном примере класс Person реализует интерфейс business_partner. Подобный синтаксис позволяет избежать конфликта имён в случае реализации нескольких интерфейсов с одинаковыми именами методов. Если интерфейс один, можно прописать alias для метода и таким образом проблемы с длинными именами у нас не возникнет. Однако если вы понимаете, что доступ вам необходим исключительно к поведению реализуемому в рамках интерфейса, лучше использовать интерфейсные ссылки:

Предпочитайте глобальные классы локальным

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

Локальные классы безусловно могут быть использованы, но лишь как вспомогательные, делегирующие выполнение всей бизнес-логики на глобальные классы. Например: в каждом отчёте есть вспомогательный класс lcl_report или lcl_application, который включает в себя методы по обработке событий отчёта (F4, start_of_selection и т.п.). Локальные классы должны быть максимально простыми. Так же зачастую локальные классы в разных разработках содержат горы дублирующегося кода, что нарушает основополагающий принцип DRY.

Создавайте классы с минимальным интерфейсом и числом атрибутов

Классы с сотнями методов или атрибутов ужасны в понимании и сопровождении. Если интерфейс класса слишком раздут, как правило это говорит о плохом проектировании и о наличии у класса множества обязанностей (нарушении SRP). Если Вы создаёте подобный класс, задумайтесь, скорее всего вам необходимо разделить его обязанности на несколько классов.

Определяйте конструктор в Public области видимости

Для глобальных классов конструктор инстанций объектов всегда должен быть объявлен как Public, то кто может создавать инстанцию определяется на уровне дополнения в CLASS … DEFENITION: CREATE PRIVATE/PROTECTED/PUBLIC. Если не следовать данному правилу могут возникать синтаксические ошибки.

Используйте NEW вместо CREATE OBJECT

Вместо более длинного варианта:

Исключение из данного правило, динамическое определение типа при создании объекта:

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

Для классов следует выставлять пометку FINAL в том случае, если он не предназначен для дальнейшего наследования, тем самым мы явно говорим о том, что наследование не подразумевалось при дизайне класса и может привести к проблемам.

Методы

Видео на тему:

Общие правила

Не создавайте длинных методов

Согласно Роберту Мартину длинна метода не должна превышать 10 строк кода. Однако более менее реальная метрика — метод должен помещаться на экран монитора. В ABAP Guidelines говорится о не более чем 150 выражениях. В SCI имеется проверка (в разделе метрик), позволяющая увидеть проблемные места:

Используйте статичные методы вместо конструктора с необязательными параметрами

Так как в ABAP нет перегрузки методов, данное поведение можно смоделировать с помощью необязательных параметров:

Однако создание такого конструктора или метода противоречит принципу единственной обязанности.

Данное правило является частным случаем следующего.

Разделяйте методы, вместо использования необязательных параметров

В данном примере мы разделили один метод на два без использования необязательных параметров:

Метод должен выполнять конкретную задачу и не нарушать принципа единственной обязанности.

В целом применение необязательных параметров всегда запутывает того кто читает код:

  • Какие из необязательных параметров необходимы и в каких случаях?
  • Какие комбинации при их заполнении необходимо учитывать?
  • Могут ли одни исключать другие?

Для ответа на эти и другие вопросы может потребоваться очень много времени.

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

Логические переменные как правило сигнализируют то, что метод выполняет более одной обязанности:

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

Разделение метода позволит упростить восприятие кода:

Публичные методы должны быть частью интерфейса

Публичные методы экземпляра всегда должны быть частью интерфейса. Это разъединяет зависимости и упрощает их проверку в модульных (unit) тестах.

Если метод не будет иметь никаких альтернативных реализаций и заменяться mock объектами в модульных тестах, использование определение через интерфейс можно опустить.

Метод должен выполнять только одну вещь и делать это хорошо

У метода должно быть ровно одно предназначение, такие методы легки в понимании и сопровождении.

Признаки хорошего метода:

  • Имеет мало входных параметров
  • Отсутствуют параметры логического типа
  • Имеют один выходной параметр
  • Не более 20 выражений внутри метода
  • Из его кода нельзя извлечь и создать другой метод
  • Код метода не поддаётся мысленному разделению на логические блоки (например: выборка данных — несколько запросов к БД, обработка данных, формирование результата. В данном случае должны быть отдельные методы по выборке, обработке и формированию результата).

Если метод содержит такие слова как And, Or, If, вероятно он выполняет более чем одну вещь и требует рефакторинга.

Метод должен выполнять бизнес-логику или обработку ошибок, но не все вместе

Предыдущее правило «Метод должен выполнять только одну вещь и делать это хорошо» можно так же распространить и на обработку ошибок внутри метода, как правило разработчики объединяют бизнес-логику и проверку входных параметров, хотя это две разные задачи:

В данном случае обработку входных параметров можно вынести в отдельный метод:

Либо можно перенести бизнес-логику в отдельный метод (хотя это уже будет нарушением следующего правила):

Выдавайте исключение как можно раньше

Производите проверки и вызов исключений как можно раньше:

Последующие проверки гораздо сложнее интерпретировать:

Не используйте CHECK вместо RETURN

Старайтесь не использовать оператор CHECK внутри методов для выхода из него так как это усложняет понимание кода, особенно если в нём встречаются циклы. В зависимости от того, где был использован оператор CHECK, внутри циклов или вне их, его поведение будет отличаться. Для выхода из метода используйте RETURN.

Оператор CHECK может быть использован, но только вначале метода в качестве проверки входных параметров:

Не вызывайте статические методы через инстанцию

Вместо вызова статического метода через инстанцию:

Следует использовать вызов через класс:

Параметры

Старайтесь не использовать предпочтительный параметр

Дополнение PREFERRED PARAMETER позволяет опустить его объявление при вызове метода, если есть несколько необязательных параметров:

Ввод данного параметра усложняет понимание кода, т.к. до тех пор, пока мы не перейдём в объявление метода, мы не узнаем какой из них является предпочтительным. Минимизация параметров в целом у метода, особенно необязательных автоматически избавляет нас от необходимости использования дополнения PREFERRED PARAMETER.

Стремитесь сократить число Importing параметров

В идеальном случае их не должно быть более трёх:

Гораздо понятнее чем:

Слишком большое число входных параметров усложняют реализацию метода, т.к. растёт число возможных комбинаций для его вызова. Большое число параметров являются признаком того, что метод выполняет более чем одну обязанность (нарушение принципа SOC).

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

Только один параметр RETURN, EXPORT, или CHANGE в методах

Хороший метод выполняет одну конкретную задачу, и это должно отражаться так же и в параметрах, который данный метод возвращает. Если выходные параметры вашего метода не образуют логическую сущность, ваш метод выполняет сразу несколько задач.

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

Вместо:

В качестве антипаттерна в данном случае можно упомянуть множественные EXPORTING параметры и необходимость их проверки через IS SUPPLIED внутри таких методов. Лучше явным образом отразить в интерфейсе метода что вам необходимо в качестве возврата, чем делать это неявным образом через IS SUPPLIED.

Используйте RETURNING вместо EXPORING

В случаях когда это возможно замените EXPORTING параметр на RETURNING, это сделает ваш код более коротким:

Вместо более длинного варианта:

Кроме того Вы избежите необходимости обнулять EXPORTING переменную внутри метода, как Вы знаете EXPORTING параметры не очищаются при передаче их в методы и могут привести к непредвиденным ошибкам.

Из данного правила есть исключение когда идёт речь о действительно больших таблицах, передача по значению RETURNING может замедлить работу программы, в случае с EXPORTING передача будет идти по ссылке.

Всегда очищайте или перезаписывайте EXPORING параметры

Так как при передаче EXPORTING параметров они не очищаются системой, для избежания непредвиденных ошибок необходимо всегда делать это самостоятельно:

В инспекторе кода есть соответствующая проверка, которая укажет вам об отсутствии записи в EXPORTING параметр.

Конечно, если значение в EXPORING параметр передаётся по значению, нет никакого смысла в очистке переменной, в данном случае это скорее антипаттерн:

Остерегайтесь использования одной переменной на вход и выход

В данном примере разработчик метода сделал правильным и очистил переменную возврата, однако он не мог предположить, что EXPORTING и IMPORING параметры будут использованы для одной и той же переменной, что привело к некорректному результату, так как по умолчанию значение передаётся по ссылке. Ошибку можно было бы избежать, если передавать параметры по значению, но согласитесь, мало кто об этом задумывается.

Вызовы методов

Оставляйте параметры рядом с вызовом

Вместо подобного вызова методов с несколькими параметрами:

Старайтесь оставлять параметр рядом с вызовом:

Выглядит это гораздо более читаемо. Видео на тему, с 10 по 23 минуты.

Используйте функциональный вызов методов вместо процедурного

Функциональный вызов:

Гораздо короче процедурного:

Если используется динамический вызов, тут уже не обойтись без процедурного стиля вызова:

Не используйте RECEIVING

Вместо более длинного варианта:

Пропускайте дополнение EXPORTING там где это возможно

Вместо более длинного варианта:

Пропускайте имя параметра если он один

Вместо более длинного варианта:

Если имя метода не раскрывает логики его работы, и Вы не можете выполнить рефакторинг, имя параметра следует оставить, если он раскрывает логику метода:

Опускайте ссылку на самого себя при вызове методов класса

Ссылка на самого себя me-> неявно выставляется системой при вызове методов класса внутри этого класса:

Вместо более длинного варианта:

Обработка исключений

Сообщения

Пользуйтесь сообщениями из класса сообщений

Оператор MESSAGE можно использовать напрямую с текстом:

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

Создавайте сообщения так, чтобы их можно было найти

В данном примере мы не сможем отыскать места вызова сообщений через поиск в классе сообщений (Where-used list). Можно сделать недопустимое условие вроде:

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

В сообщениях используйте &1-&4 вместо &

Порядок заполняемых слов может отличаться относительно языка и переводчику будет невозможно заменить порядок, если будут использованы анонимные заполнители.

Исключения

Используйте классы исключений вместо кодов возврата и классических исключений

Вместо:

Преимущества исключений на базе классов очевидны:

  • Вызывающий код не должен сосредотачиваться на обработке кодов возврата, в исключениях Вы можете просто пропустить обработку исключения, доверив это коду на более высоком уровне.
  • Исключения это не просто код, но и подробная информация об ошибке, с текстовым описанием, атрибутами и т.д.
  • Среда разработки будет напоминать вам о необходимости обработки исключения (если речь не идёт об исключениях наследованных от CX_DYNAMIC_CHECK (но его использование это скорее антипаттерн).
  • У исключений на базе классов есть возможность выстраивания цепочки исключений, что позволяет делать подробные выводы о произошедшей ситуации.
  • Возобновляемые исключения для ситуаций какой-либо массовой обработки, когда одно исключение не должно в целом влиять на весь процесс.

Если Вы работает с кодом, в котором используются исключения старого типа или коды возвратов, не продолжайте их использование в своём коде, обработайте и вызовите исключение на базе классов:

Используйте исключения только для неожиданных ситуаций

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

Исключения должны быть использованы для случаев, которых Вы не ожидаете и которые отражают ошибки:

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

Используйте свои супер классы исключений

Создавайте супер классы для каждого типа исключений в ваших разработках, вместо прямого наследования относительно базовых классов исключений. Данное правило позволит вам отлавливать не все, а конкретно ваши исключения, так же Вы можете добавить дополнительную логику, например: особую обработку текстов и т.п.

Используйте одно исключение в интерфейсе метода

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

Лучшее решение для распознавания различных ситуаций с ошибками — использование одного типа исключений, но с добавлением подклассов, которые позволяют — но не требуют — реагировать на отдельные ситуации с ошибками:

В случае большого числа возможных ошибок или нежелания делать отдельные наследники, можно воспользоваться кодами ошибок:

Используйте правильный тип исключений

В книге ABAP to the Future автор приводит замечательную схему выбора подходящего базового класса для ваших исключений:

Учитывая поведение данных классов, я полностью согласен с этим алгоритмом.

Оборачивайте исключения из внешних компонентов

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

Создавайте цепочки исключений, в качестве примера:

  • Метод А пытается найти запись некоторого ресурса в Z таблице, не находит и выдаёт исключение CX_NO_ENTRY_IN_TABLE.
  • Метод Б создаёт некоторый заказ и вызывая метод А отлавливает исключение, порождает собственное ZCX_ORDER_CREATION_ERROR, причем его pervious будет пойманное CX_NO_ENTRY_IN_TABLE.
  • Метод С вызываемый из интеграционного слоя и вызывая метод Б отлавливает исключение, порождает собственное ZCX_INTEGRATION_ERROR, причём его previous будет пойманное ZCX_ORDER_CREATION_ERROR.

Затем можно провести рекурсивный анализ цепочки вроде такого:

Системные переменные

Не изменяйте системные переменные

В большинстве случаев это не приводит ни к чему хорошему, допускается в исключительных ситуациях, например, если требуется заполнить структуру sy* (sy-msgid, sy-msgno и др.) в сообщениях и не хочется объявлять переменную:

Не используйте устаревшие и помеченные для внутреннего использования системные переменные

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

Не используйте системные переменные для передачи в параметры

Так как параметр был передан по ссылке, а не по значению, и перезаписан на второй итерации цикла в do_something в выводе будет: 1, 1, 2.

Правильнее будет завести отдельную переменную:

Не определяйте поведение программы относительно системных переменных с хардкодом

Вместо захардкоженных условий относительно системных переменных – sy-sysid, sy-uname, sy-mandt, sy-host, заведите соответствующие настройки, с которыми будете их сравнивать, таким образом Вы сможете уберечься от возможных проблем при смене манданта, пользователей и т.п.

Прочее

Не используйте хардкод в именах идентификаторов MEMORY ID операторов EXPORT/IMPORT

Использование хардкода в данном случае затруднит вам поиск мест где встречается EXPORT/IMPORT для конкретного идентификатора. В качестве имени MEMORY ID необходимо использовать константы глобальных классов/интерфейсов.

Обработка всех возможных исключений ФМ обязательна

При вызове ФМ не забывайте указывать все допустимые варианты исключений, даже если Вы их не планируете обрабатывать. Это позволит в отладке увидеть значение sy-subrc не погружаясь в модуль. Кроме того всегда указывайте OTHERS, SAP редко меняет интерфейсы ФМ, но и большинства ФМ отсутствует пометка о деблокировании (отсутствии последующих изменений). Напомню, что если в ФМ будет добавлено новое исключение и при его вызове не будет указан OTHERS или новое исключение, в программе вызова произойдёт дамп.

Не используйте локально объявленные структуры в ALV

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

Например: заполнение текстов столбцов в ALV программно, когда это не основано на каких-то динамических условиях это антипаттерн, т.к. соответствующие тексты могли быть объявлены на уровне элемента данных в словаре поля словарной структуры. В некоторых разработках которые я встречал, такое заполнение занимало сотни строк кода 😒

Избегайте конструкции ASSIGN(‘(PROGRAM)VARIABLE’) TO

Конструкция в основном используется для получения данных из области памяти стандартной программы (если она находится в рамках текущего стека вызовов) где-нибудь в EXIT-ах. Опасность данной конструкции заключается в возможном последующем изменении программы и удаления из неё получаемой переменной. В качестве альтернативного вариант можно рассмотреть расширение программы и передачу через EXPORT/IMPORT .. MEMORY ID. При обновлении системы вам в любом случае придётся делать проверку расширений, ошибка будет явно получена. Если же делать через ASSIGN Вы можете не заметить потенциальных проблем.

Не копируйте стандартные программы

Это приводит к огромным мучениям при сопровождении и обновлении системы, используйте расширения явные или неявные.

Не используйте динамические конструкции языка без обработки исключений

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

При вызове RFC модулей определяйте системные исключения

При вызовах RFC модулей из других систем, указывайте все стандартные для RFC исключения вместе с дополнением MESSAGE, это облегчит отладку и понимания возможных ошибок в будущем.

Создавайте BAdI вызовы внутри Customer Exit-ов

Customer Exit, в отличие от BAdI, всегда имеют одну реализацию, код в рамках include-программы exit-а постепенно начинает разрастаться и приводит к тому, что сопровождать его становится все сложнее. Чтобы этого не происходило сделайте на каждый используемый Customer Exit свой Z-BAdI. Аналогичным образом можно поступать и с Enhancement Framework расширениями.

Источники

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

  • Книга Чистая архитектура. Искусство разработки программного обеспечения, автор: Роберт Мартин. Роберт Мартин дает прямые и лаконичные ответы на ключевые вопросы архитектуры и дизайна.
  • Книга Чистый код: создание, анализ и рефакторинг, автор: Роберт Мартин. Книга состоит из трех частей. Сначала вы познакомитесь с принципами, паттернами и приемами написания чистого кода. Затем приступите к практическим сценариям с нарастающей сложностью — упражнениям по чистке кода или преобразованию проблемного кода в менее проблемный. И только после этого перейдете к самому важному — концентрированному выражению сути этой книги — набору эвристических правил и «запахов кода». Именно эта база знаний описывает путь мышления в процессе чтения, написания и чистки кода.
  • Книга Совершенный код, автор Стив Макконнел. Более 10 лет первое издание этой книги считалось одним из лучших практических руководств по программированию. Сейчас эта книга полностью обновлена с учетом современных тенденций и технологий и дополнена сотнями новых примеров, иллюстрирующих искусство и науку программирования. Опираясь на академические исследования, с одной стороны, и практический опыт коммерческих разработок ПО — с другой, автор синтезировал из самых эффективных методик и наиболее эффективных принципов ясное прагматичное руководство. Каков бы ни был ваш профессиональный уровень, с какими бы средствами разработками вы ни работали, какова бы ни была сложность вашего проекта, в этой книге вы найдете нужную информацию, она заставит вас размышлять и поможет создать совершенный код. Изложенные в книге методики и стратегии помогут вам:проектировать с минимальной сложностью и максимальной продуктивностью извлекать выгоду из групповой разработки применять методики защитного программирования, позволяющие избежать ошибок совершенствовать свой код применять методики конструирования, наиболее подходящие для вашего проекта быстро и эффективно производить отладку своевременно и быстро обнаруживать критические проблемы проекта обеспечивать качество на всех стадиях проекта.
  • Книга Принципы, паттерны и методики гибкой разработки на языке C# Основанная на богатом опыте известного специалиста, Роберта Мартина, книга охватывает как теорию, так и все аспекты практического применения гибкой разработки. Во вступительных главах излагаются основные принципы, а далее они демонстрируются в действии. Применяя объектно-ориентированный подход, авторы рассматривают конкретные паттерны, применяемые к проектированию приложений, описывают методы рефакторинга и способы эффективного использования различных видов UML-диаграмм.
  • Книга Рефакторинг: улучшение проекта существующего кода, автор Мартин Фаулер. В данной книге известный эксперт в области объектных технологий Мартин Фаулер открывает перед сообществом разработчиков новые горизонты, рассказывая о практиках, применяемых экспертами, и демонстрируя, какие значительные преимущества от их применения может получить любой разработчик.
  • Clean ABAP, гайдлайн основанный на идеях книги «Чистый код», https://github.com/SAP/styleguides/blob/master/clean-abap/CleanABAP.md#clean-abap
  • DSAG ABAP Best practices
  • A list of common principles of clean ABAP development. https://github.com/ilyakaznacheev/abap-best-practice
  • Improving the Quality of ABAP Code
  • ABAP Programming Guidelines.

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

  1. Спасибо большое за статью, оказалась очень полезна для меня. Дай Вам Бог здоровья!)

    1. Пожалуйста! Все верно, указанный источник является одним из тех, которые послужили основой всего что здесь описано. Так же тут присутствует часть материалов из официальной документации, часть материалов из книги Роберта Мартина — «Чистый код» (на которой основан гайд из гита), что-то из Стива Макконнелла и его «Совершенного кода», что-то из лекций SAP и блогов Хорста Келлера.

      Основная идея в том, чтобы сформировать единый и как можно более подробный материал на данную тему.

      Поэтому заметка будет постоянно обновляться 😉

  2. Ну некоторые моменты я бы назвал спорными
    например несколько пустых строк. бывает что метод разделен на несколько логических частей, и 2 пустые строки очень даже подходят, чтобы отделить эти чести

    1. Если метод разделен на несколько логических частей значит метод следует разделить на несколько методов, иначе это уже нарушение single responsibility (SOLID)

      1. Разделение на несколько методов в классе это не single responsibility (SOLID).
        single responsibility — инкапсуляция ответственности на уровне класса. разделение на методы не является принципом S.
        Это к тому, что S — не аргумент для разделения, но правда в том, что это нужно делать:
        — разделение на несколько методов улучшает читабельность класса
        — логика изолирована в рамках метода
        — при рефакторинге его будет проще выделить в отдельный класс

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

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