Skip to content

Latest commit

 

History

History
5017 lines (3734 loc) · 308 KB

CleanABAP_ru.md

File metadata and controls

5017 lines (3734 loc) · 308 KB

Переведено с оригинала на английском от 18.03.2022. Последняя версия на английском.

Чистый ABAP

Русский  ·  English  ·  中文  ·  Français  ·  Deutsch  ·  日本語  ·  Español  ·  한국어

Это руководство является адаптацией книги Robert C. Martin's Clean Code для ABAP.

Шпаргалка - это версия, оптимизированная для печати.

Содержание

Инструкция

Чистый ABAP > Содержание > Эта секция

Как начать работу с Чистым кодом

Чистый ABAP > Содержание > Инструкция > Эта секция

Если Чистый код для вас что-то новое, вам следует сначала прочитать книгу Robert C. Martin's Clean Code. Начать с плавного пошагового погружения в тему в целом вам может помочь книга Clean Code Developer initiative.

Мы рекомендуем вам начать с простых для понимания и общепринятых вещей, например: Булевы значения, Условия, and Если.

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

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

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

Как рефакторить устаревший код

Чистый ABAP > Содержание > Инструкция > Эта секция

Разделы Булевы значения, Условия, Если и Методы будут вам наиболее полезны, если вы работаете над устаревшим проектом с большим количеством кода, который вы не можете или не хотите изменять. Описанные в этом разделе рекомендации могут быть применены к новому коду без каких-либо конфликтов.

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

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

  • Смешивание REF TO и FIELD-SYMBOL при выполнении цикла.
  • Смешивание NEW и CREATE OBJECT при вызове CONSTRUCTOR.
  • Смешивание RETURNING и EXPORTING в сигнатуре методов возвращающих/экспортирующих только один параметр.

Мы получили хорошие результаты используя четырехэтапный план рефакторинга:

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

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

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

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

Как выполнять автоматическую проверку

Чистый ABAP > Содержание > Инструкция > Эта секция

code pal for ABAP предоставляет полный набор автоматических проверок для Чистого ABAP.

ABAP Test Cockpit, Code Inspector, Расширенная проверка программы, и Checkman предоставляют некоторые проверки, которые могут помочь вам обнаружить определенные проблемы.

abapOpenChecks, коллекция проверок с открытым исходным кодом для Code Inspector, также охватывает некоторые из описанных антипаттернов.

abaplint это повторная реализация ABAP-парсера с открытым исходным кодом. Он работает без системы SAP и предназначен для использования в коде, сериализованном с помощью abapGit. Инструмент предлагает множество интеграций (GitHub Actions, Jenkins, текстовые редакторы...), проверяет отсутствие в коде некоторых антипаттернов, а также может использоваться для проверки соглашений о форматировании и коде.

Как относиться к другим руководствам

Чистый ABAP > Содержание > Инструкция > Эта секция

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

Некоторые факты взяты из Руководство по программированию на ABAP, с которым это руководство в основном совместимо; отклонения указаны и всегда соответствуют духу более чистого кода.

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

С момента публикации Clean ABAP стал справочным руководством для многих внутренних команд разработчиков SAP, включая несколько сотен кодеров, работающих над S/4HANA.

Как выражать несогласие

Чистый ABAP > Содержание > Инструкция > Эта секция

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

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

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

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

Имена

Чистый ABAP > Содержание > Эта секция

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

Чистый ABAP > Содержание > Имена > Эта секция

Используйте имена, которые передают содержание и смысл вещей.

CONSTANTS max_wait_time_in_seconds TYPE i ...
DATA customizing_entries TYPE STANDARD TABLE ...
METHODS read_user_preferences ...
CLASS /clean/user_preference_reader ...

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

" anti-pattern
CONSTANTS sysubrc_04 TYPE sysubrc ...
DATA iso3166tab TYPE STANDARD TABLE ...
METHODS read_t005 ...
CLASS /dirty/t005_reader ...

Не пытайтесь исправить плохие имена с помощью комментариев.

Подробнее в Chapter 2: Meaningful Names: Use Intention-Revealing Names Robert C. Martin's Clean Code.

Предпочитайте термины из предметной области решения и предметной области проблемы

Чистый ABAP > Содержание > Имена > Эта секция

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

Слои, обеспечивающие бизнес-функциональность лучше всего называть в соответствии с проблемной областью. Особенно это относится к компонентам, разработанным с помощью предметно-ориентированного проектирования, например API и бизнес-объекты.

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

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

Подробнее в Chapter 2: Meaningful Names: Use Solution Domain Names and [...]: Use Problem Domain Names Robert C. Martin's Clean Code.

Используйте множественное число

Чистый ABAP > Содержание > Имена > Эта секция

В SAP существует унаследованная практика называть таблицы объектов в единственном числе, например country (страна) для "таблицы стран". Распространенной тенденцией во внешнем мире является использование множественного числа для списков вещей. Поэтому мы рекомендуем вместо этого использовать countries (страны).

Этот совет в первую очередь относится к переменным и свойствам. Для объектов разработки могут быть уместны другие шаблоны, например, широко используемое соглашение называть таблицы базы данных ("прозрачные таблицы") в единственном числе.

Подробнее в Chapter 2: Meaningful Names: Use Intention-Revealing Names Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Имена > Эта секция

Мы много думаем и говорим об объектах. Поэтому используйте имена, которые каждый может произнести. Например, используйте discovery_object_types, а не что-то загадочное вроде dobjt.

Подробнее в Chapter 2: Meaningful Names: Use Pronounceable Names Robert C. Martin's Clean Code

Избегайте сокращений

Чистый ABAP > Содержание > Имена > Эта секция

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

Если вам действительно нужно что-то сократить, начните с не важных слов.

Сокращение названий вещей на первый взгляд может показаться эффективным, но очень быстро становится двусмысленным. Например, "cust" в cust означает "customizing", "customer", или "custom"? Все три термина распространены в приложениях SAP.

Подробнее в Chapter 2: Meaningful Names: Make Meaningful Distinctions Robert C. Martin's Clean Code.

Используйте одни и те же сокращения везде

Чистый ABAP > Содержание > Имена > Эта секция

Люди будут искать ключевые слова, чтобы найти соответствующий код. Упростите им задачу, используя одинаковое сокращение для одного и того же. Например, всегда сокращайте "detection object type" до "dobjt" вместо сокращений "dot", "dotype", "detobjtype" и тому подобных.

Подробнее в Chapter 2: Meaningful Names: Use Searchable Names Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Имена > Эта секция

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

CLASS /clean/account
CLASS /clean/user_preferences
INTERFACE /clean/customizing_reader

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

METHODS withdraw
METHODS add_message
METHODS read_entries

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

IF is_empty( table ).

Мы рекомендуем называть функции так же как методы:

FUNCTION /clean/read_alerts

Избегайте неинформативных слов, таких как "data", "info", "object"

Чистый ABAP > Содержание > Имена > Эта секция

Опустите неинформативные слова

account  " instead of account_data
alert    " instead of alert_object

или замените их чем-то конкретным, что добавит смысла

user_preferences          " instead of user_info
response_time_in_seconds  " instead of response_time_variable

Подробнее в Chapter 2: Meaningful Names: Make Meaningful Distinctions Robert C. Martin's Clean Code

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

Чистый ABAP > Содержание > Имена > Эта секция

METHODS read_this.
METHODS read_that.
METHODS read_those.

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

" anti-pattern
METHODS read_this.
METHODS retrieve_that.
METHODS query_those.

Подробнее в Chapter 2: Meaningful Names: Pick One Word per Concept Robert C. Martin's Clean Code

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

Чистый ABAP > Содержание > Имена > Эта секция

Не используйте названия шаблонов проектирования программного обеспечения для классов и интерфейсов, если вы действительно не имеете их в виду. Например, не называйте свой класс file_factory, если он действительно не реализует шаблон проектирования Фабрика. К наиболее распространенным шаблонам относятся: singleton, factory, facade, composite, decorator, iterator, observer, и strategy.

Подробнее в Chapter 2: Meaningful Names: Avoid Disinformation Robert C. Martin's Clean Code

Избегайте схем кодирования имен, особенно венгерскую нотацию и префиксы

Чистый ABAP > Содержание > Имена > Эта секция

Мы рекомендуем вам избавиться от всех префиксов при написании кода.

METHOD add_two_numbers.
  result = a + b.
ENDMETHOD.

вместо излишне длинного

METHOD add_two_numbers.
  rv_result = iv_a + iv_b.
ENDMETHOD.

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

Избегайте перекрытия встроенных функций

Чистый ABAP > Содержание > Имена > Эта секция

Внутри класса встроенная функция всегда перекрыта методами класса, если они имеют одно и то же имя, независимо от количества и типа аргументов в функции. Также, функция перекрыта независимо от количества и типа параметров метода. Встроенными функциями являются, например, condense( ), lines( ), line_exists( ), strlen( ) и т. д.

"anti-pattern
METHODS lines RETURNING VALUE(result) TYPE i.    
METHODS line_exists RETURNING VALUE(result) TYPE i.  
"anti-pattern 
CLASS-METHODS condense RETURNING VALUE(result) TYPE i.   
CLASS-METHODS strlen RETURNING VALUE(result) TYPE i.  

Подробнее в Built-In Functions - Obscuring with Methods.

Язык

Чистый ABAP > Содержание > Эта секция

Помните о наследии

Чистый ABAP > Содержание > Язык > Эта секция

Если вы ведете разработку в системах, поддерживающих только более старые версии ABAP, следуйте советам этого руководства с осторожностью. Во многих приведенных ниже рекомендациях используются относительно новый синтаксис и конструкции, которые могут не поддерживаться в более старых версиях ABAP. Проверьте рекомендации, которым вы хотите следовать в самой старой версии, которую вы должны поддерживать. Не следует просто отбрасывать Чистый код в целом - подавляющее большинство правил (например, присвоение имен, комментирование) будут работать в any версии ABAP.

Помните о производительности

Чистый ABAP > Содержание > Язык > Эта секция

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

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

Некоторые дополнительные соображения частично взяты из главы 2 Martin Fowler's Refactoring:

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

Таким образом, стремление сделать весь код сверхэффективным - это не лучший способ использования ресурсов. Мы не предлагаем игнорировать производительность, но лучше больше сосредоточиться на чистом и хорошо структурированном коде во время начальной разработки и использовать ABAP Profiler для определения критических областей для оптимизации.

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

Предпочитайте объектно-ориентированное программирование процедурному

Чистый ABAP > Содержание > Язык > Эта секция

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

FUNCTION check_business_partner [...].
  DATA(validator) = NEW /clean/biz_partner_validator( ).
  result = validator->validate( business_partners ).
ENDFUNCTION.

Группы функций против Классов подробно описывает различия.

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

Чистый ABAP > Содержание > Язык > Эта секция

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

DATA(variable) = 'A'.
" MOVE 'A' TO variable.

DATA(uppercase) = to_upper( lowercase ).

index += 1.         " >= NW 7.54
index = index + 1.  " < NW 7.54
" ADD 1 TO index.

DATA(object) = NEW /clean/my_class( ).
" CREATE OBJECT object TYPE /dirty/my_class.

result = VALUE #( FOR row IN input ( row-text ) ).
" LOOP AT input INTO DATA(row).
"  INSERT row-text INTO TABLE result.
" ENDLOOP.

DATA(line) = value_pairs[ name = 'A' ]. " entry must exist
DATA(line) = VALUE #( value_pairs[ name = 'A' ] OPTIONAL ). " entry can be missing
" READ TABLE value_pairs INTO DATA(line) WITH KEY name = 'A'.

DATA(exists) = xsdbool( line_exists( value_pairs[ name = 'A' ] ) ).
IF line_exists( value_pairs[ name = 'A' ] ).
" READ TABLE value_pairs TRANSPORTING NO FIELDS WITH KEY name = 'A'.
" DATA(exists) = xsdbool( sy-subrc = 0 ).

Многие из подробно описанных правил, приведенных ниже, являются лишь повторением этого общего совета.

Избегайте устаревших языковых элементов

Чистый ABAP > Содержание > Язык > Эта секция

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

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

SELECT *
  FROM spfli
  WHERE carrid = @carrid AND
        connid = @connid
  INTO TABLE @itab.

по сравнению с устаревшей неэкранированной формой

SELECT *
  FROM spfli
  WHERE carrid = carrid AND
        connid = connid
  INTO TABLE itab.

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

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

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

Документация SAP NetWeaver содержит постоянный раздел, в котором перечислены устаревшие языковые элементы, например NW 7.50, NW 7.51, NW 7.52, NW 7.53.

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

Чистый ABAP > Содержание > Язык > Эта секция

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

Константы

Чистый ABAP > Содержание > Эта секция

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

Чистый ABAP > Содержание > Константы > Эта секция

IF abap_type = cl_abap_typedescr=>typekind_date.

понятнее чем

" anti-pattern
IF abap_type = 'D'.

Подробнее в Chapter 17: Smells and Heuristics: G25: Replace Magic Numbers with Named Constants Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Константы > Эта секция

CLASS /clean/message_severity DEFINITION PUBLIC ABSTRACT FINAL.
  PUBLIC SECTION.
    CONSTANTS:
      warning TYPE symsgty VALUE 'W',
      error   TYPE symsgty VALUE 'E'.
ENDCLASS.

или

CLASS /clean/message_severity DEFINITION PUBLIC CREATE PRIVATE FINAL.
  PUBLIC SECTION.
    CLASS-DATA:
      warning TYPE REF TO /clean/message_severity READ-ONLY,
      error   TYPE REF TO /clean/message_severity READ-ONLY.
  " ...
ENDCLASS.

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

" anti-pattern
INTERFACE /dirty/common_constants.
  CONSTANTS:
    warning      TYPE symsgty VALUE 'W',
    transitional TYPE i       VALUE 1,
    error        TYPE symsgty VALUE 'E',
    persisted    TYPE i       VALUE 2.
ENDINTERFACE.

Раздел Перечисления описывает общие шаблоны перечислений и рассказывает об их преимуществах и недостатках.

Подробнее в Chapter 17: Smells and Heuristics: J3: Constants versus Enums Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Константы > Эта секция

Если вы собираете константы произвольным образом, например, в интерфейсе, сгруппируйте их:

CONSTANTS:
  BEGIN OF message_severity,
    warning TYPE symsgty VALUE 'W',
    error   TYPE symsgty VALUE 'E',
  END OF message_severity,
  BEGIN OF message_lifespan,
    transitional TYPE i VALUE 1,
    persisted    TYPE i VALUE 2,
  END OF message_lifespan.

Это делает отношение более ясным, чем:

" Anti-pattern
CONSTANTS:
  warning      TYPE symsgty VALUE 'W',
  transitional TYPE i       VALUE 1,
  error        TYPE symsgty VALUE 'E',
  persisted    TYPE i       VALUE 2,

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

DO.
  ASSIGN COMPONENT sy-index OF STRUCTURE message_severity TO FIELD-SYMBOL(<constant>).
  IF sy-subrc IS INITIAL.
    IF input = <constant>.
      DATA(is_valid) = abap_true.
      RETURN.
    ENDIF.
  ELSE.
    RETURN.
  ENDIF.
ENDDO.

Подробнее в Chapter 17: Smells and Heuristics: G27: Structure over Convention Robert C. Martin's Clean Code.

Переменные

Чистый ABAP > Содержание > Эта секция

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

Чистый ABAP > Содержание > Переменные > Эта секция

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

METHOD do_something.
  DATA(name) = 'something'.
  DATA(reader) = /clean/reader=>get_instance_for( name ).
  result = reader->read_it( ).
ENDMETHOD.

чем объявление переменных с отдельной секцией DATA в начале метода

" anti-pattern
METHOD do_something.
  DATA:
    name   TYPE seoclsname,
    reader TYPE REF TO /dirty/reader.
  name = 'something'.
  reader = /dirty/reader=>get_instance_for( name ).
  result = reader->read_it( ).
ENDMETHOD.

Подробнее в Chapter 5: Formatting: Vertical Distance: Variable Declarations Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Переменные > Эта секция

" anti-pattern
IF has_entries = abap_true.
  DATA(value) = 1.
ELSE.
  value = 2.
ENDIF.

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

DATA value TYPE i.
IF has_entries = abap_true.
  value = 1.
ELSE.
  value = 2.
ENDIF.

Подробнее в Chapter 5: Formatting: Vertical Distance: Variable Declarations Robert C. Martin's Clean Code.

Не сцепливайте предварительные объявления

Чистый ABAP > Содержание > Переменные > Эта секция

DATA name TYPE seoclsname.
DATA reader TYPE REF TO reader.

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

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

" anti-pattern
DATA:
  name   TYPE seoclsname,
  reader TYPE REF TO reader.

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

Предпочитайте REF TO вместо FIELD-SYMBOL

Чистый ABAP > Содержание > Переменные > Эта секция

Этой секции бросили вызов. FIELD-SYMBOLы вроде бы, значительно быстрее при переборе внутренних таблиц, так что рекомендация использовать REF TO для этих случаев может ухудшить производительность.

LOOP AT components REFERENCE INTO DATA(component).

вместо аналогичного

" anti-pattern
LOOP AT components ASSIGNING FIELD-SYMBOL(<component>).

за исключением случаев, когда вам нужны именно символы поля

ASSIGN generic->* TO FIELD-SYMBOL(<generic>).
ASSIGN COMPONENT name OF STRUCTURE structure TO FIELD-SYMBOL(<component>).
ASSIGN (class_name)=>(static_member) TO FIELD-SYMBOL(<member>).

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

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

  • В объектно-ориентированном ABAP ссылки встречаются повсюду, и их невозможно избежать, так как любой объект является REF TO <class-name>. При этом, символы полей строго требуются только в немногих особых случаях, связанных с динамической типизацией. Таким образом, ссылки формируют естественное предпочтение в любой объектно-ориентированной программе.

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

Подробнее читайте в статье Accessing Data Objects Dynamically in the ABAP Programming Guidelines.

Таблицы

Чистый ABAP > Содержание > Эта секция

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

Чистый ABAP > Содержание > Таблицы > Эта секция

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

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

  • Используйте STANDARD таблицы для небольших таблиц, где индексация создает больше накладных расходов, чем пользы, и "массивов", где вас либо вообще не волнует порядок строк, либо вы хотите обрабатывать их точно в том порядке, в котором они были добавлены. Кроме того, используйте их если требуется другой доступ к таблице, например, индексированный доступ и отсортированный доступ через "SORT" и "BINARY SEARCH".

Это всего лишь приблизительные рекомендации. Подробнее в статье Selection of Table Category in the ABAP Language Help.

Избегайте DEFAULT KEY

Чистый ABAP > Содержание > Таблицы > Эта секция

" anti-pattern
DATA itab TYPE STANDARD TABLE OF row_type WITH DEFAULT KEY.

Ключи по умолчанию часто добавляются только для того, чтобы заставить работать новые функциональные операторы. Сами ключи по сути, обычно лишние и тратят ресурсы впустую. Они могут даже привести к непонятным ошибкам, поскольку игнорируют числовые типы данных. Например, операторы SORT и DELETE ADJACENT без явного указания списка полей будут использовать первичный ключ внутренней таблицы. Если при этом используется DEFAULT KEY это может привести к неожиданным результатам. Например, если используются числовые поля в ключе в сочетании с READ TABLE... BINARY и т. д.

Либо укажите ключевые компоненты явно

DATA itab2 TYPE STANDARD TABLE OF row_type WITH NON-UNIQUE KEY comp1 comp2.

или используйте EMPTY KEY если вам вообще не нужен ключ.

DATA itab1 TYPE STANDARD TABLE OF row_type WITH EMPTY KEY.

Следуя Horst Keller's blog on Internal Tables with Empty Key

Внимание: SORT по внутренним таблицам с EMPTY KEY (без явных полей сортировки) вообще не сортирует, но будут выданы синтаксические предупреждения в случае, если отсутствие ключа может быть определено статически.

Предпочитайте INSERT INTO TABLE вместо APPEND TO

Чистый ABAP > Содержание > Таблицы > Эта секция

INSERT VALUE #( ... ) INTO TABLE itab.

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

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

Предпочитайте LINE_EXISTS вместо READ TABLE или LOOP AT

Чистый ABAP > Содержание > Таблицы > Эта секция

IF line_exists( my_table[ key = 'A' ] ).

выражает намерение яснее и короче, чем

" anti-pattern
READ TABLE my_table TRANSPORTING NO FIELDS WITH KEY key = 'A'.
IF sy-subrc = 0.

или даже

" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.
  line_exists = abap_true.
  EXIT.
ENDLOOP.

Предпочитайте READ TABLE вместо LOOP AT

Чистый ABAP > Содержание > Таблицы > Эта секция

READ TABLE my_table REFERENCE INTO DATA(line) WITH KEY key = 'A'.

выражает намерение яснее и короче, чем

" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.
  EXIT.
ENDLOOP.

или даже

" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line).
  IF line->key = 'A'.
    EXIT.
  ENDIF.
ENDLOOP.

Предпочитайте LOOP AT WHERE вместо вложенных IF

Чистый ABAP > Содержание > Таблицы > Эта секция

LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.

выражает намерение яснее и короче, чем

LOOP AT my_table REFERENCE INTO DATA(line).
  IF line->key = 'A'.
    EXIT.
  ENDIF.
ENDLOOP.

Избегайте ненужного чтения таблиц

Чистый ABAP > Содержание > Таблицы > Эта секция

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

TRY.
    DATA(row) = my_table[ key = input ].
  CATCH cx_sy_itab_line_not_found.
    RAISE EXCEPTION NEW /clean/my_data_not_found( ).
ENDTRY.

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

" anti-pattern
IF NOT line_exists( my_table[ key = input ] ).
  RAISE EXCEPTION NEW /clean/my_data_not_found( ).
ENDIF.
DATA(row) = my_table[ key = input ].

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

Строки

Чистый ABAP > Содержание > Эта секция

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

Чистый ABAP > Содержание > Строки > Эта секция

CONSTANTS some_constant TYPE string VALUE `ABC`.
DATA(some_string) = `ABC`.  " --> TYPE string

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

" anti-pattern
DATA some_string TYPE string.
some_string = 'ABC'.

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

" anti-pattern
DATA(some_string) = |ABC|.

Используйте | чтобы собрать текст

Чистый ABAP > Содержание > Строки > Эта секция

DATA(message) = |Received HTTP code { status_code } with message { text }|.

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

" anti-pattern
DATA(message) = `Received an unexpected HTTP ` && status_code && ` with message ` && text.

Булевы значения

Чистый ABAP > Содержание > Эта секция

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

Чистый ABAP > Содержание > Булевы значения > Эта секция

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

" anti-pattern
is_archived = abap_true.

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

archiving_status = /clean/archivation_status=>archiving_in_process.

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

assert_true( xsdbool( document->is_archived( ) = abap_true AND
                      document->is_partially_archived( ) = abap_true ) ).

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

Подробнее в 1

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

Чистый ABAP > Содержание > Булевы значения > Эта секция

DATA has_entries TYPE abap_bool.

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

Также избегайте других булевых типов, поскольку они часто имеют странные побочные эффекты. Например, boolean поддерживает третье значение "undefined", что приводит к трудноуловимым ошибкам программирования.

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

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

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

Чистый ABAP > Содержание > Булевы значения > Эта секция

has_entries = abap_true.
IF has_entries = abap_false.

Не используйте эквивалентные символы 'X' и ' ' или space. Из-за них сложнее понять, что это логическое выражение:

" anti-pattern
has_entries = 'X'.
IF has_entries = space.

Избегайте сравнений с INITIAL. Это заставляет читателей вспоминать, что значение abap_bool по умолчанию равно abap_false:

" anti-pattern
IF has_entries IS NOT INITIAL.

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

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

Чистый ABAP > Содержание > Булевы значения > Эта секция

DATA(has_entries) = xsdbool( line IS NOT INITIAL ).

Эквивалент IF-THEN-ELSE намного длиннее:

" anti-pattern
IF line IS INITIAL.
  has_entries = abap_false.
ELSE.
  has_entries = abap_true.
ENDIF.

xsdbool — подходит нам больше, потому что он напрямую создает char1, который лучше соответствует нашему логическому типу abap_bool. Эквивалентные функции boolc и boolx создают разные типы и требуют лишнего неявного преобразования типов.

Мы согласны с тем, что название xsdbool неудачно и вводит в заблуждение. В конце концов, нас совсем не интересуют части "Определения схемы XML", которые предлагает префикс "xsd".

Возможной альтернативой xsdbool является тернарная форма COND. Ее синтаксис интуитивно понятен, но немного длиннее, потому что он без необходимости повторяет сегмент THEN abap_true и требует знания неявного значения по умолчанию abap_false. Именно поэтому мы предлагаем его только в качестве вторичного решения.

DATA(has_entries) = COND abap_bool( WHEN line IS NOT INITIAL THEN abap_true ).

Условия

Чистый ABAP > Содержание > Эта секция

Постарайтесь сделать условия положительными

Чистый ABAP > Содержание > Условия > Эта секция

IF has_entries = abap_true.

Для сравнения посмотрите, насколько трудно понять одно и то же утверждение, всего лишь изменив его на противоположное:

" anti-pattern
IF has_no_entries = abap_false.

"Постарайтесь" в названии раздела означает, что вы не должны доводить это до такой степени когда в итоге получите что-то вроде пустых ветвей IF:

" anti-pattern
IF has_entries = abap_true.
ELSE.
  " only do something in the ELSE block, IF remains empty
ENDIF.

Подробнее в Chapter 17: Smells and Heuristics: G29: Avoid Negative Conditionals Robert C. Martin's Clean Code.

Предпочитайте IS NOT вместо NOT IS

Чистый ABAP > Содержание > Условия > Эта секция

IF variable IS NOT INITIAL.
IF variable NP 'TODO*'.
IF variable <> 42.

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

" anti-pattern
IF NOT variable IS INITIAL.
IF NOT variable CP 'TODO*'.
IF NOT variable = 42.

Более специфичный вариант Постарайтесь сделать условия положительными. Так, как описано в разделе Alternative Language Constructs в руководстве по программированию на ABAP.

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

Чистый ABAP > Содержание > Условия > Эта секция

Предикативный (или, другими словами, указывающий на свойство чего-либо) вызов для булевых методов

IF [ NOT ] condition_is_fulfilled( ).

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

" anti-pattern
IF condition_is_fulfilled( ) = abap_true / abap_false.

Имейте в виду, что предикативный вызов метода ... meth( ) ... это просто краткая форма ... meth( ) IS NOT INITIAL ..., смотрите Predicative Method Call в документации по ключевым словам ABAP. Вот почему короткая форма должна использоваться только для методов, возвращающих типы, где начальное значение это "ложь", а не "истина".

Подумайте о декомпозиции сложных условий

Чистый ABAP > Содержание > Условия > Эта секция

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

DATA(example_provided) = xsdbool( example_a IS NOT INITIAL OR
                                  example_b IS NOT INITIAL ).

DATA(one_example_fits) = xsdbool( applies( example_a ) = abap_true OR
                                  applies( example_b ) = abap_true OR
                                  fits( example_b ) = abap_true ).

IF example_provided = abap_true AND
   one_example_fits = abap_true.

вместо того, чтобы оставить их вместе:

" anti-pattern
IF ( example_a IS NOT INITIAL OR
     example_b IS NOT INITIAL ) AND
   ( applies( example_a ) = abap_true OR
     applies( example_b ) = abap_true OR
     fits( example_b ) = abap_true ).

Используйте ABAP Development Tools quick fixes для быстрого извлечения условий и создания переменных, как показано выше.

Подумайте об извлечении сложных условий

Чистый ABAP > Содержание > Условия > Эта секция

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

IF is_provided( example ).

METHOD is_provided.
  DATA(is_filled) = xsdbool( example IS NOT INITIAL ).
  DATA(is_working) = xsdbool( applies( example ) = abap_true OR
                              fits( example ) = abap_true ).
  result = xsdbool( is_filled = abap_true AND
                    is_working = abap_true ).
ENDMETHOD.

Если

Чистый ABAP > Содержание > Эта секция

Не допускайте пустых ветвей IF

Чистый ABAP > Содержание > Если > Эта секция

IF has_entries = abap_false.
  " do some magic
ENDIF.

короче и понятнее, чем

" anti-pattern
IF has_entries = abap_true.
ELSE.
  " do some magic
ENDIF.

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

Чистый ABAP > Содержание > Если > Эта секция

CASE type.
  WHEN type-some_type.
    " ...
  WHEN type-some_other_type.
    " ...
  WHEN OTHERS.
    RAISE EXCEPTION NEW /clean/unknown_type_failure( ).
ENDCASE.

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

" anti-pattern
IF type = type-some_type.
  " ...
ELSEIF type = type-some_other_type.
  " ...
ELSE.
  RAISE EXCEPTION NEW /dirty/unknown_type_failure( ).
ENDIF.

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

Чистый ABAP > Содержание > Если > Эта секция

" anti-pattern
IF <this>.
  IF <that>.
  ENDIF.
ELSE.
  IF <other>.
  ELSE.
    IF <something>.
    ENDIF.
  ENDIF.
ENDIF.

Вложенные IFы очень быстро становятся трудными для понимания и требуют экспоненциального количества тестов для полного покрытия.

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

Другие случаи могут быть упрощены путем объединения IFов, например

IF <this> AND <that>.

вместо без необходимости вложенных

" anti-pattern
IF <this>.
  IF <that>.

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

Чистый ABAP > Содержание > Эта секция

Предпочитайте более простые методы регулярным выражениям

Чистый ABAP > Содержание > Регулярные выражения > Эта секция

IF input IS NOT INITIAL.
" IF matches( val = input  regex = '.+' ).

WHILE contains( val = input  sub = 'abc' ).
" WHILE contains( val = input  regex = 'abc' ).

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

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

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

Чистый ABAP > Содержание > Регулярные выражения > Эта секция

CALL FUNCTION 'SEO_CLIF_CHECK_NAME'
  EXPORTING
    cls_name = class_name
  EXCEPTIONS
    ...

вместо того, чтобы заново изобретать вещи

" anti-pattern
DATA(is_valid) = matches( val     = class_name
                          pattern = '[A-Z][A-Z0-9_]{0,29}' ).

Кажется, существует естественная склонность закрывать глаза на принцип «Не повторяйтесь» (DRY) когда речь идет о регулярных выражениях, см. раздел Chapter 17: Smells and Heuristics: General: G5: Duplication в Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Регулярные выражения > Эта секция

CONSTANTS class_name TYPE string VALUE `CL\_.*`.
CONSTANTS interface_name TYPE string VALUE `IF\_.*`.
DATA(object_name) = |{ class_name }\|{ interface_name }|.

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

Классы

Чистый ABAP > Содержание > Эта секция

Классы: Объектная ориентация

Чистый ABAP > Содержание > Классы > Эта секция

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

Чистый ABAP > Содержание > Классы > Классы: Объектная ориентация > Эта секция

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

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

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

CLASS /clean/string_utils DEFINITION [...].
  CLASS-METHODS trim
   IMPORTING
     string        TYPE string
   RETURNING
     VALUE(result) TYPE string.
ENDCLASS.

METHOD retrieve.
  DATA(trimmed_name) = /clean/string_utils=>trim( name ).
  result = read( trimmed_name ).
ENDMETHOD.

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

Чистый ABAP > Содержание > Классы > Классы: Объектная ориентация > Эта секция

Избегайте построения иерархий классов с наследованием. Вместо этого отдавайте предпочтение композиции.

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

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

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

В подразделе Интерфейсы против абстрактных классов сравниваются некоторые детали.

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

Чистый ABAP > Содержание > Классы > Классы: Объектная ориентация > Эта секция

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

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

CLASS /clean/xml_converter DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS convert
      IMPORTING
        file_content  TYPE xstring
      RETURNING
        VALUE(result) TYPE /clean/some_inbound_message.
ENDCLASS.

CLASS /clean/xml_converter IMPLEMENTATION.
  METHOD convert.
    cl_proxy_xml_transform=>xml_xstring_to_abap(
      EXPORTING
        xml       = file_content
        ext_xml   = abap_true
        svar_name = 'ROOT_NODE'
      IMPORTING
        abap_data = result ).
   ENDMETHOD.
ENDCLASS.

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

CLASS /clean/log DEFINITION PUBLIC CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS add_message IMPORTING message TYPE /clean/message.
  PRIVATE SECTION.
    DATA messages TYPE /clean/message_table.
ENDCLASS.

CLASS /clean/log IMPLEMENTATION.
  METHOD add_message.
    INSERT message INTO TABLE messages.
  ENDMETHOD.
ENDCLASS.

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

Область видимости

Чистый ABAP > Содержание > Классы > Эта секция

Глобальный по умолчанию, локальный только при необходимости

Чистый ABAP > Содержание > Классы > Область видимости > Эта секция

Работайте с глобальными классами. Используйте локальные классы только там, где это уместно.

Глобальные классы — это те, которые видны в словаре данных. Локальные классы находятся внутри includ'а другого объекта и видны только этому другому объекту.

Локальные классы подходят

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

  • для извлечения сложного приватного алгоритма, например, чтобы отделить состоящий из нескольких методов алгоритм сортировки-агрегирования, нацеленный на решение конкретной задачи, от остального кода в вашем классе;

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

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

Поскольку ABAP выполняет блокировку на уровне includ'а, люди не смогут одновременно работать, например над разными локальными классами, находящимися в одном includ'е (что было бы возможно, если бы они были отдельными глобальными классами).

Пересмотрите свое использование локальных классов, если

  • ваш локальный include содержит десятки классов и тысячи строк кода;
  • вы думаете о глобальных классах как о "пакетах", которые содержат другие классы;
  • ваш глобальный класс вырождается в пустую оболочку;
  • вы найдете дублирующийся код, повторяющийся в отдельных локальных includ'ах;
  • ваши разработчики начинают блокировать друг друга и не могут работать параллельно;
  • ваш список работ для выполения становится огромным, потому что ваши команды больше не понимают локальные includ'ы друг друга.

FINAL если не предназначен для наследования

Чистый ABAP > Содержание > Классы > Область видимости > Эта секция

Если класс явно не предназначен для наследования, сделайте его FINAL.

При проектировании взаимодействия классов в первую очередь вы должны предпочитать композицию наследованию. Выбор в пользу наследования не должен быть легкомысленным, поскольку он предполагает размышления о модификаторах доступа, таких как PROTECTED или PRIVATE, а также о принципе подстановки Барбары Лисков, к тому же, многие внутренние части дизайна будут "заморожены". Поэтому, если вы разрабатывали свои классы без учета этих вещей, вы должны предотвратить непреднамеренное наследование, сделав свой класс FINAL.

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

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

PRIVATE по умолчанию, PROTECTED только если нужно

Чистый ABAP > Содержание > Классы > Область видимости > Эта секция

Делайте атрибуты, методы и другие члены класса PRIVATE по умолчанию.

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

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

Рассмотрите возможность использования неизменяемого объекта, вместо добавления геттера

Чистый ABAP > Содержание > Классы > Область видимости > Эта секция

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

CLASS /clean/some_data_container DEFINITION.
  PUBLIC SECTION.
    METHODS constructor
      IMPORTING
        a TYPE i
        b TYPE c
        c TYPE d.
    DATA a TYPE i READ-ONLY.
    DATA b TYPE c READ-ONLY.
    DATA c TYPE d READ-ONLY.
ENDCLASS.

вместо

CLASS /dirty/some_data_container DEFINITION.
  PUBLIC SECTION.
    METHODS get_a ...
    METHODS get_b ...
    METHODS get_c ...
  PRIVATE SECTION.
    DATA a TYPE i.
    DATA b TYPE c.
    DATA c TYPE d.
ENDCLASS.

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

Используйте READ-ONLY с осторожностью

Чистый ABAP > Содержание > Классы > Область видимости > Эта секция

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

Хотя ABAP и предлагает дополнение READ-ONLY для объявлении данных, мы рекомендуем использовать его с осторожностью.

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

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

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

Конструкторы

Чистый ABAP > Содержание > Классы > Эта секция

Предпочитайте NEW вместо CREATE OBJECT

Чистый ABAP > Содержание > Классы > Конструкторы > Эта секция

DATA object TYPE REF TO /clean/some_number_range.
object = NEW #( '/CLEAN/CXTGEN' )
...
DATA(object) = NEW /clean/some_number_range( '/CLEAN/CXTGEN' ).
...
DATA(object) = CAST /clean/number_range( NEW /clean/some_number_range( '/CLEAN/CXTGEN' ) ).

вместо излишне длинного

" anti-pattern
DATA object TYPE REF TO /dirty/some_number_range.
CREATE OBJECT object
  EXPORTING
    number_range = '/DIRTY/CXTGEN'.

конечно, за исключением случаев, когда вам нужно указать тип динамически

CREATE OBJECT number_range TYPE (dynamic_type)
  EXPORTING
    number_range = '/CLEAN/CXTGEN'.

Если ваш глобальный класс CREATE PRIVATE, сделайте его CONSTRUCTOR публичным

Чистый ABAP > Содержание > Классы > Конструкторы > Эта секция

CLASS /clean/some_api DEFINITION PUBLIC FINAL CREATE PRIVATE.
  PUBLIC SECTION.
    METHODS constructor.

Мы согласны с тем, что это противоречит самому себе. Однако, согласно статье Instance Constructor of the ABAP Help, указание CONSTRUCTOR в PUBLIC SECTION требуется, чтобы гарантировать правильную компиляцию и проверку синтаксиса.

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

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

Чистый ABAP > Содержание > Классы > Конструкторы > Эта секция

CLASS-METHODS describe_by_data IMPORTING data TYPE any [...]
CLASS-METHODS describe_by_name IMPORTING name TYPE any [...]
CLASS-METHODS describe_by_object_ref IMPORTING object_ref TYPE REF TO object [...]
CLASS-METHODS describe_by_data_ref IMPORTING data_ref TYPE REF TO data [...]

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

" anti-pattern
METHODS constructor
  IMPORTING
    data       TYPE any OPTIONAL
    name       TYPE any OPTIONAL
    object_ref TYPE REF TO object OPTIONAL
    data_ref   TYPE REF TO data OPTIONAL
  [...]

Раздел Разделите методы вместо добавления OPTIONAL параметров объясняет причину этой рекомендации.

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

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

Чистый ABAP > Содержание > Классы > Конструкторы > Эта секция

Хорошими словами для начала имени методов создания чело-либо: new_, create_, и construct_. Люди интуитивно связывают их с построением объектов. Они также хорошо сочетаются с такими глагольными словосочетаниями, как new_from_template, create_as_copy, или create_by_name.

CLASS-METHODS new_describe_by_data IMPORTING p_data TYPE any [...]
CLASS-METHODS new_describe_by_name IMPORTING p_name TYPE any [...]
CLASS-METHODS new_describe_by_object_ref IMPORTING p_object_ref TYPE REF TO object [...]
CLASS-METHODS new_describe_by_data_ref IMPORTING p_data_ref TYPE REF TO data [...]

вместо чего-то бессмысленного, как

" anti-pattern
CLASS-METHODS create_1 IMPORTING p_data TYPE any [...]
CLASS-METHODS create_2 IMPORTING p_name TYPE any [...]
CLASS-METHODS create_3 IMPORTING p_object_ref TYPE REF TO object [...]
CLASS-METHODS create_4 IMPORTING p_data_ref TYPE REF TO data [...]

Создавайте синглтоны только там, где несколько экземпляров не имеют смысла

Чистый ABAP > Содержание > Классы > Конструкторы > Эта секция

METHOD new.
  IF singleton IS NOT BOUND.
    singleton = NEW /clean/my_class( ).
  ENDIF.
  result = singleton.
ENDMETHOD.

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

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

Методы

Чистый ABAP > Содержание > Эта секция

Эти правила применяются к методам в классах и к функциональным модулям.

Вызовы

Чистый ABAP > Содержание > Методы > Эта секция

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

Чистый ABAP > Содержание > Методы > Вызовы > Эта секция

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

cl_my_class=>static_method( ).

Вместо того, чтобы вызывать его через переменную экземпляра cl_my_class

" anti-pattern
lo_my_instance->static_method( ).

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

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

METHOD static_method.
  another_static_method( ).
  yet_another( ).
ENDMETHOD.

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

CLASS cl_my_class IMPLEMENTATION.

  METHOD instance_method.
    cl_my_class=>a_static_method( ).
    another_instance_method( ).
  ENDMETHOD.

  ...

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

Чистый ABAP > Содержание > Методы > Вызовы > Эта секция

modify->update( node           = /clean/my_bo_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

вместо слишком длинного

" anti-pattern
CALL METHOD modify->update
  EXPORTING
    node           = /dirty/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields.

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

CALL METHOD modify->(method_name)
  EXPORTING
    node           = /clean/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields.

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

Не указывайте RECEIVING

Чистый ABAP > Содержание > Методы > Вызовы > Эта секция

DATA(sum) = aggregate_values( values ).

вместо излишне длинного

" anti-pattern
aggregate_values(
  EXPORTING
    values = values
  RECEIVING
    result = DATA(sum) ).

Не указывайте необязательное ключевое слово EXPORTING

Чистый ABAP > Содержание > Методы > Вызовы > Эта секция

modify->update( node           = /clean/my_bo_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

вместо излишне длинного

" anti-pattern
modify->update(
  EXPORTING
    node           = /dirty/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields ).

Не указывайте имя параметра при вызовах с одним параметром

Чистый ABAP > Содержание > Методы > Вызовы > Эта секция

DATA(unique_list) = remove_duplicates( list ).

вместо излишне длинного

" anti-pattern
DATA(unique_list) = remove_duplicates( list = list ).

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

car->drive( speed = 50 ).
update( asynchronous = abap_true ).

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

Чистый ABAP > Содержание > Методы > Вызовы > Эта секция

Поскольку ссылка на себя me-> неявно устанавливается системой, опустите ее при вызове атрибута или метода экземпляра.

DATA(sum) = aggregate_values( values ).

вместо излишне длинного

" anti-pattern
DATA(sum) = aggregate_values( me->values ).
" anti-pattern
DATA(sum) = me->aggregate_values( values ).

разумеется, me-> можно опустить только если у вас нет конфликта области видимости между локальной переменной или параметром импорта и атрибутом экземпляра

me->logger = logger.

Методы: Объектная ориентация

Чистый ABAP > Содержание > Методы > Эта секция

Предпочитайте экземпляр статическим методам

Чистый ABAP > Содержание > Методы > Методы: Объектная ориентация > Эта секция

По умолчанию методы должны быть членами экземпляра. Методы экземпляра лучше отражают "объектность" класса. Их легче замокать в модульных тестах.

METHODS publish.

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

CLASS-METHODS create_instance
  RETURNING
    VALUE(result) TYPE REF TO /clean/blog_post.

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

Чистый ABAP > Содержание > Методы > Методы: Объектная ориентация > Эта секция

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

METHOD /clean/blog_post~publish.

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

Раздел Интерфейсы против абстрактных классов описывает, почему это относится и к классам, которые переопределяют унаследованные методы.

Количество параметров

Чистый ABAP > Содержание > Методы > Эта секция

Стремитесь к нескольким IMPORTING параметрам, лучше всего меньше трех

Чистый ABAP > Содержание > Методы > Количество параметров > Эта секция

FUNCTION seo_class_copy
  IMPORTING
    clskey      TYPE seoclskey
    new_clskey  TYPE seoclskey
    config      TYPE class_copy_config
  EXPORTING
    ...

будет намного понятнее, чем

" anti-pattern
FUNCTION seo_class_copy
  IMPORTING
    clskey                 TYPE seoclskey
    new_clskey             TYPE seoclskey
    access_permission      TYPE seox_boolean DEFAULT seox_true
    VALUE(save)            TYPE seox_boolean DEFAULT seox_true
    VALUE(suppress_corr)   TYPE seox_boolean DEFAULT seox_false
    VALUE(suppress_dialog) TYPE seox_boolean DEFAULT seox_false
    VALUE(authority_check) TYPE seox_boolean DEFAULT seox_true
    lifecycle_manager      TYPE REF TO if_adt_lifecycle_manager OPTIONAL
    lock_handle            TYPE REF TO if_adt_lock_handle OPTIONAL
    VALUE(suppress_commit) TYPE seox_boolean DEFAULT seox_false
  EXPORTING
    ...

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

Вы можете уменьшить количество параметров, объединив их по смыслу в структуры или объекты.

Разделите методы вместо добавления OPTIONAL параметров

Чистый ABAP > Содержание > Методы > Количество параметров > Эта секция

METHODS do_one_thing IMPORTING what_i_need TYPE string.
METHODS do_another_thing IMPORTING something_else TYPE i.

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

" anti-pattern
METHODS do_one_or_the_other
  IMPORTING
    what_i_need    TYPE string OPTIONAL
    something_else TYPE i OPTIONAL.

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

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

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

Используйте PREFERRED PARAMETER с осторожностью

Чистый ABAP > Содержание > Методы > Количество параметров > Эта секция

С опцией PREFERRED PARAMETER сложнее сказать, какие параметры фактически заполнены, и еще сложнее понять код. Минимизируя количество параметров, особенно необязательных, вы автоматически уменьшаете потребность в использовании PREFERRED PARAMETER.

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

Чистый ABAP > Содержание > Методы > Количество параметров > Эта секция

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

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

TYPES:
  BEGIN OF check_result,
    result      TYPE result_type,
    failed_keys TYPE /bobf/t_frw_key,
    messages    TYPE /bobf/t_frw_message,
  END OF check_result.

METHODS check_business_partners
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE check_result.

вместо

" anti-pattern
METHODS check_business_partners
  IMPORTING
    business_partners TYPE business_partners
  EXPORTING
    result            TYPE result_type
    failed_keys       TYPE /bobf/t_frw_key
    messages          TYPE /bobf/t_frw_message.

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

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

TYPES:
  BEGIN OF check_result,
    result      TYPE result_type,
    failed_keys TYPE /bobf/t_frw_key,
    messages    TYPE /bobf/t_frw_message,
  END OF check_result.

METHODS check
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE result_type.

METHODS check_and_report
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE check_result.

Типы параметров

Чистый ABAP > Содержание > Методы > Эта секция

Предпочитайте RETURNING вместо EXPORTING

Чистый ABAP > Содержание > Методы > Типы параметров > Эта секция

METHODS square
  IMPORTING
    number        TYPE i
  RETURNING
    VALUE(result) TYPE i.

DATA(result) = square( 42 ).

Вместо излишне длинного

" anti-pattern
METHODS square
  IMPORTING
    number TYPE i
  EXPORTING
    result TYPE i.

square(
  EXPORTING
    number = 42
  IMPORTING
    result = DATA(result) ).

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

RETURNING больших таблиц это обычно нормально

Чистый ABAP > Содержание > Методы > Типы параметров > Эта секция

Несмотря на то, что документация ABAP и руководства по производительности говорят об обратном, мы редко сталкиваемся со случаями, когда передача большой или глубоко вложенной таблицы в VALUE параметре действительно вызывает проблемы с производительностью. Поэтому мы рекомендуем использовать RETURNING почти всегда:

METHODS get_large_table
  RETURNING
    VALUE(result) TYPE /clean/some_table_type.

METHOD get_large_table.
  result = me->large_table.
ENDMETHOD.

DATA(my_table) = get_large_table( ).

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

" anti-pattern
METHODS get_large_table
  EXPORTING
    result TYPE /dirty/some_table_type.

METHOD get_large_table.
  result = me->large_table.
ENDMETHOD.

get_large_table( IMPORTING result = DATA(my_table) ).

Этот раздел противоречит Руководству по программированию ABAP и элементам управления Code Inspector, которые предлагают экспортировать большие таблицы по ссылке, чтобы избежать снижения производительности. Несмотря на серьезные неоднократные попытки, нам не удавалось воспроизвести дефицит производительности или памяти, и мы получили уведомление об оптимизации ядра системы, которая в целом улучшает производительность RETURNING.

Используйте либо RETURNING, либо EXPORTING, либо CHANGING, но не комбинацию

Чистый ABAP > Содержание > Методы > Типы параметров > Эта секция

METHODS copy_class
  IMPORTING
    old_name      TYPE seoclsname
    new name      TYPE secolsname
  RETURNING
    VALUE(result) TYPE copy_result
  RAISING
    /clean/class_copy_failure.

вместо сбивающей с толку смеси

" anti-pattern
METHODS copy_class
  ...
  RETURNING
    VALUE(result)      TYPE vseoclass
  EXPORTING
    error_occurred     TYPE abap_bool
  CHANGING
    correction_request TYPE trkorr
    package            TYPE devclass.

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

Приемлемым исключением из этого правила могут быть строители (builders), которые используют свои входные данные при построении своих выходных данных:

METHODS build_tree
  CHANGING
    tokens        TYPE tokens
  RETURNING
    VALUE(result) TYPE REF TO tree.

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

METHODS build_tree
  IMPORTING
    tokens        TYPE REF TO token_stack
  RETURNING
    VALUE(result) TYPE REF TO tree.

Используйте CHANGING с осторожностью, там, где это подходит

Чистый ABAP > Содержание > Методы > Типы параметров > Эта секция

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

METHODS update_references
  IMPORTING
    new_reference TYPE /bobf/conf_key
  CHANGING
    bo_nodes      TYPE root_nodes.

METHOD update_references.
  LOOP AT bo_nodes REFERENCE INTO DATA(bo_node).
    bo_node->reference = new_reference.
  ENDLOOP.
ENDMETHOD.

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

Разделите метод вместо использования булева входного параметра

Чистый ABAP > Содержание > Методы > Типы параметров > Эта секция

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

" anti-pattern
METHODS update
  IMPORTING
    do_save TYPE abap_bool.

Кроме того, вызовы методов с одним и, обычно, безымянным логическим параметром, как правило, скрывают предназначение этого параметра.

" anti-pattern
update( abap_true ).  " what does 'true' mean? synchronous? simulate? commit?

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

update_without_saving( ).
update_and_save( ).

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

METHODS set_is_deleted
  IMPORTING
    new_value TYPE abap_bool.

Подробнее в 1 2 3

Имена параметров

Чистый ABAP > Содержание > Методы > Эта секция

Подумайте о том, чтобы назвать RETURNING параметр RESULT

Чистый ABAP > Содержание > Методы > Имена параметров > Эта секция

Обычно, если имена методов подобраны правильно, то параметр RETURNING не нуждается в собственном имени. Его имя будет не более чем повторением имени метода или чего-то столь же очевидного.

Повторение имени атрибута может даже привести к конфликтам, которые придется разрешать добавлением me->.

" anti-pattern
METHODS get_name
  RETURNING
    VALUE(name) TYPE string.

METHOD get_name.
  name = me->name.
ENDMETHOD.

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

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

Инициализация параметров

Чистый ABAP > Содержание > Методы > Эта секция

Очистите или перезапишите EXPORTING ссылочные параметры

Чистый ABAP > Содержание > Методы > Инициализация параметров > Эта секция

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

METHODS square
  EXPORTING
    result TYPE i.

" clear
METHOD square.
  CLEAR result.
  " ...
ENDMETHOD.

" overwrite
METHOD square.
  result = cl_abap_math=>square( 2 ).
ENDMETHOD.

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

Будьте осторожны с идентичным вводом и выводом

Чистый ABAP > Содержание > Методы > Инициализация параметров > Эта секция

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

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

" anti-pattern
DATA value TYPE i.

square_dirty(
  EXPORTING
    number = value
  IMPORTING
    result = value ).

METHOD square_dirty.
  CLEAR result.
  result = number * number.
ENDMETHOD.

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

Не очищайте VALUE параметры

Чистый ABAP > Содержание > Методы > Инициализация параметров > [Эта секция](#не очищайте-value-параметры)

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

METHODS square
  EXPORTING
    VALUE(result) TYPE i.

METHOD square.
  " no need to CLEAR result
ENDMETHOD.

RETURNING параметры всегда являются VALUE параметрами, поэтому вам никогда не придется их очищать:

METHODS square
  RETURNING
    VALUE(result) TYPE i.

METHOD square.
  " no need to CLEAR result
ENDMETHOD.

Тело метода

Чистый ABAP > Содержание > Методы > Эта секция

Делай что-то одно, делай это хорошо, делай только это

Чистый ABAP > Содержание > Методы > Тело метода > Эта секция

Метод должен делать что-то одно и только одно. Он должен делать это очень хорошо.

Метод, скорее всего, делает одну вещь, если

Сосредоточьтесь либо на благополучном исходе либо на обработке ошибок, но не на том и другом одновременно

Чистый ABAP > Содержание > Методы > Тело метода > Эта секция

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

" anti-pattern
METHOD append_xs.
  IF input > 0.
    DATA(remainder) = input.
    WHILE remainder > 0.
      result = result && `X`.
      remainder = remainder - 1.
    ENDWHILE.
  ELSEIF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSE.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

Может быть разложено на

METHOD append_xs.
  validate( input ).
  DATA(remainder) = input.
  WHILE remainder > 0.
    result = result && `X`.
    remainder = remainder - 1.
  ENDWHILE.
ENDMETHOD.

METHOD validate.
  IF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSEIF input < 0.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

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

METHOD append_xs.
  IF input > 0.
    result = append_xs_without_check( input ).
  ELSEIF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSE.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

METHOD append_xs_without_check.
  DATA(remainder) = input.
  WHILE remainder > 0.
    result = result && `X`.
    remainder = remainder - 1.
  ENDWHILE.
ENDMETHOD.

Спуститесь на один уровень абстракции

Чистый ABAP > Содержание > Методы > Тело метода > Эта секция

Операторы в методе должны быть на один уровень абстракции ниже самого метода. Соответственно, все эти операторы должны быть одного уровня абстракции.

METHOD create_and_publish.
  post = create_post( user_input ).
  post->publish( ).
ENDMETHOD.

вместо запутанных смесей низкоуровневых операций (trim, to_upper,...) и высокоуровневых (publish,...) например

" anti-pattern
METHOD create_and_publish.
  post = NEW blog_post( ).
  DATA(user_name) = trim( to_upper( sy-uname ) ).
  post->set_author( user_name ).
  post->publish( ).
ENDMETHOD.

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

Сохраняйте методы небольшими

Чистый ABAP > Содержание > Методы > Тело метода > Эта секция

Методы должны содержать менее 20 операторов, оптимально от 3 до 5 операторов.

METHOD read_and_parse_version_filters.
  DATA(active_model_version) = read_random_version_under( model_guid ).
  DATA(filter_json) = read_model_version_filters( active_model_version-guid ).
  result = parse_model_version_filters( filter_json ).
ENDMETHOD.

Одного, следующего объявления DATA достаточно, чтобы показать, что метод делает гораздо больше, чем что-то одно:

" anti-pattern
DATA:
  class           TYPE vseoclass,
  attributes      TYPE seoo_attributes_r,
  methods         TYPE seoo_methods_r,
  events          TYPE seoo_events_r,
  types           TYPE seoo_types_r,
  aliases         TYPE seoo_aliases_r,
  implementings   TYPE seor_implementings_r,
  inheritance     TYPE vseoextend,
  friendships     TYPE seof_friendships_r,
  typepusages     TYPE seot_typepusages_r,
  clsdeferrds     TYPE seot_clsdeferrds_r,
  intdeferrds     TYPE seot_intdeferrds_r,
  attribute       TYPE vseoattrib,
  method          TYPE vseomethod,
  event           TYPE vseoevent,
  type            TYPE vseotype,
  alias           TYPE seoaliases,
  implementing    TYPE vseoimplem,
  friendship      TYPE seofriends,
  typepusage      TYPE vseotypep,
  clsdeferrd      TYPE vseocdefer,
  intdeferrd      TYPE vseoidefer,
  new_clskey_save TYPE seoclskey.

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

METHOD decide_what_to_do.
  CASE temperature.
    WHEN burning.
      result = air_conditioning.
    WHEN hot.
      result = ice_cream.
    WHEN moderate.
      result = chill.
    WHEN cold.
      result = skiing.
    WHEN freezing.
      result = hot_cocoa.
  ENDCASE.
ENDMETHOD.

Тем не менее, все же имеет смысл проверить, не скрывает ли такой длинный код более элегантный паттерн:

METHOD decide_what_to_do.
  result = VALUE #( spare_time_activities[ temperature = temperature ] OPTIONAL ).
ENDMETHOD.

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

Поток управления

Чистый ABAP > Содержание > Методы > Эта секция

Быстрый провал

Чистый ABAP > Содержание > Методы > Поток управления > Эта секция

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

METHOD do_something.
  IF input IS INITIAL.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
  DATA(massive_object) = build_expensive_object_from( input ).
  result = massive_object->do_some_fancy_calculation( ).
ENDMETHOD.

Более поздние проверки труднее увидеть и понять, и к этому моменту вы можете потратить ресурсы впустую.

" anti-pattern
METHOD do_something.
  DATA(massive_object) = build_expensive_object_from( input ).
  IF massive_object IS NOT BOUND. " happens if input is initial
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
  result = massive_object->do_some_fancy_calculation( ).
ENDMETHOD.

CHECK против RETURN

Чистый ABAP > Содержание > Методы > Поток управления > Эта секция

Нет единого мнения о том, следует ли использовать CHECK или RETURN для выхода из метода, когда ввод не соответствует ожидаемому.

Хотя CHECK определенно обеспечивает более короткий синтаксис,

METHOD read_customizing.
  CHECK keys IS NOT INITIAL.
  " do whatever needs doing
ENDMETHOD.

имя оператора не говорит о том, что произойдет, если условие не выполнится. Поэтому, длинная форма в целом более понятна:

METHOD read_customizing.
  IF keys IS INITIAL.
    RETURN.
  ENDIF.
  " do whatever needs doing
ENDMETHOD.

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

METHOD read_customizing.
  IF keys IS NOT INITIAL.
    " do whatever needs doing
  ENDIF.
ENDMETHOD.

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

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

Избегайте CHECK в других местах

Чистый ABAP > Содержание > Методы > Поток управления > Эта секция

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

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

Основано на разделе Выход из процедур в Руководстве по программированию на ABAP. Обратите внимание, что это противоречит справке о ключевом слове CHECK в циклах.

Обработка ошибок

Чистый ABAP > Содержание > Эта секция

Сообщения

Чистый ABAP > Содержание > Обработка ошибок > Эта секция

Сделайте сообщения легко находимыми

Чистый ABAP > Содержание > Обработка ошибок > Сообщения > Эта секция

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

MESSAGE e001(ad) INTO DATA(message).

Если переменная message не нужна, добавьте прагму ##NEEDED:

MESSAGE e001(ad) INTO DATA(message) ##NEEDED.

Избегайте следующего:

" anti-pattern
IF 1 = 2. MESSAGE e001(ad). ENDIF.

Это анти-паттерн, поскольку:

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

Коды возврата

Чистый ABAP > Содержание > Обработка ошибок > Эта секция

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

Чистый ABAP > Содержание > Обработка ошибок > Коды возврата > Эта секция

METHOD try_this_and_that.
  RAISE EXCEPTION NEW cx_failed( ).
ENDMETHOD.

вместо

" anti-pattern
METHOD try_this_and_that.
  error_occurred = abap_true.
ENDMETHOD.

Исключения имеют множество преимуществ перед кодами возврата:

  • Исключения сохраняют сигнатуры ваших методов чистыми: вы можете вернуть результат метода как параметр RETURNING и по-прежнему генерировать исключения. Коды возврата загрязняют ваши подписи дополнительными параметрами для обработки ошибок.

  • Вызывающей программе не нужно немедленно реагировать на исключения. Можно просто придерживаться правильных вариантов использования своего кода. Обработчик исключений CATCH может находиться в самом конце своего метода или полностью вне его.

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

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

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

Чистый ABAP > Содержание > Обработка ошибок > Коды возврата > Эта секция

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

DATA:
  current_date TYPE string,
  response     TYPE bapiret2.

CALL FUNCTION 'BAPI_GET_CURRENT_DATE'
  IMPORTING
    current_date = current_date
  CHANGING
    response     = response.

IF response-type = 'E'.
  RAISE EXCEPTION NEW /clean/some_error( ).
ENDIF.

Исключения

Чистый ABAP > Содержание > Обработка ошибок > Эта секция

Исключения для ошибок, а не для обычных случаев

Чистый ABAP > Содержание > Обработка ошибок > Исключения > Эта секция

" anti-pattern
METHODS entry_exists_in_db
  IMPORTING
    key TYPE char10
  RAISING
    cx_not_found_exception.

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

METHODS entry_exists_in_db
  IMPORTING
    key           TYPE char10
  RETURNING
    VALUE(result) TYPE abap_bool.

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

METHODS assert_user_input_is_valid
  IMPORTING
    user_input TYPE string
  RAISING
    cx_bad_user_input.

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

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

Чистый ABAP > Содержание > Обработка ошибок > Исключения > Эта секция

TRY.
    get_component_types( ).
  CATCH cx_has_deep_components_error.
ENDTRY.

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

" anti-pattern
get_component_types(
  EXCEPTIONS
    has_deep_components = 1
    OTHERS              = 2 ).

Бросание

Чистый ABAP > Содержание > Обработка ошибок > Эта секция

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

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

CLASS cx_fra_static_check DEFINITION ABSTRACT INHERITING FROM cx_static_check.
CLASS cx_fra_no_check DEFINITION ABSTRACT INHERITING FROM cx_no_check.

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

Бросайте один тип исключения

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

METHODS generate
  RAISING
    cx_generation_error.

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

" anti-pattern
METHODS generate
  RAISING
    cx_abap_generation
    cx_hdbr_access_error
    cx_model_read_error.

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

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

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

CLASS cx_bad_generation_variable DEFINITION INHERITING FROM cx_generation_error.
CLASS cx_bad_code_composer_template DEFINITION INHERITING FROM cx_generation_error.

METHODS generate RAISING cx_generation_error.

TRY.
    generator->generate( ).
  CATCH cx_bad_generation_variable.
    log_failure( ).
  CATCH cx_bad_code_composer_template INTO DATA(bad_template_exception).
    show_error_to_user( bad_template_exception ).
  CATCH cx_generation_error INTO DATA(other_exception).
    RAISE EXCEPTION NEW cx_application_error( previous =  other_exception ).
ENDTRY.

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

CLASS cx_generation_error DEFINITION ...
  PUBLIC SECTION.
    TYPES error_code_type TYPE i.
    CONSTANTS:
      BEGIN OF error_code_enum,
        bad_generation_variable    TYPE error_code_type VALUE 1,
        bad_code_composer_template TYPE error_code_type VALUE 2,
        ...
      END OF error_code_enum.
    DATA error_code TYPE error_code_type.

TRY.
    generator->generate( ).
  CATCH cx_generation_error INTO DATA(exception).
    CASE exception->error_code.
      WHEN cx_generation_error=>error_code_enum-bad_generation_variable.
      WHEN cx_generation_error=>error_code_enum-bad_code_composer_variable.
      ...
    ENDCASE.
ENDTRY.

Бросайте CX_STATIC_CHECK для управляемых исключений

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

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

CLASS cx_file_not_found DEFINITION INHERITING FROM cx_static_check.

METHODS read_file
  IMPORTING
    file_name_enterd_by_user TYPE string
  RAISING
    cx_file_not_found.

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

Это соответствует Руководству по программированию на ABAP но противоречит Robert C. Martin's Clean Code, который рекомендует предпочесть непроверяемые исключения;
Раздел Исключения объясняет, почему.

Бросайте CX_NO_CHECK для обычно безнадежных ситуаций

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

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

CLASS cx_out_of_memory DEFINITION INHERITING FROM cx_no_check.

METHODS create_guid
  RETURNING
    VALUE(result) TYPE /bobf/conf_key.

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

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

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

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

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

DATA value TYPE decfloat.
value = '7.13'.
cl_abap_math=>get_db_length_decs(
  EXPORTING
    in     = value
  IMPORTING
    length = DATA(length) ).

Например, посмотрите на метод get_db_length_decs класса cl_abap_math, который сообщает вам количество цифр и десятичных разрядов в десятичном числе с плавающей запятой. Этот метод вызывает динамическое исключение cx_parameter_invalid_type, если входной параметр не соответствует десятичному числу с плавающей запятой. Этот метод обычно вызывается для статистически типизированной переменной, поэтому разработчик знает, когда может возникнуть это исключение. В этом случае динамическое исключение позволит вызывающей стороне опустить избыточное предложение CATCH.

Дамп для полностью неисправимых ситуаций

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

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

RAISE SHORTDUMP TYPE cx_sy_create_object_error.  " >= NW 7.53
MESSAGE x666(general).                           " < NW 7.53

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

Предпочитайте RAISE EXCEPTION NEW вместо RAISE EXCEPTION TYPE

Чистый ABAP > Содержание > Обработка ошибок > Бросание > Эта секция

Примечание. Доступно начиная с NW 7.52.

RAISE EXCEPTION NEW cx_generation_error( previous = exception ).

в целом короче, чем излишне длиннее

RAISE EXCEPTION TYPE cx_generation_error
  EXPORTING
    previous = exception.

Однако, если вы часто используете опцию MESSAGE, рекомендуется придерживаться варианта с TYPE:

RAISE EXCEPTION TYPE cx_generation_error
  MESSAGE e136(messages)
  EXPORTING
    previous = exception.

Отлавливание

Чистый ABAP > Содержание > Обработка ошибок > Эта секция

Оберните внешние исключения вместо того, чтобы позволять им вторгаться в ваш код

Чистый ABAP > Содержание > Обработка ошибок > Отлавливание > Эта секция

METHODS generate RAISING cx_generation_failure.

METHOD generate.
  TRY.
      generator->generate( ).
    CATCH cx_amdp_generation_failure INTO DATA(exception).
      RAISE EXCEPTION NEW cx_generation_failure( previous = exception ).
  ENDTRY.
ENDMETHOD.

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

" anti-pattern
METHODS generate RAISING cx_sy_gateway_failure.

METHOD generate.
  generator->generate( ).
ENDMETHOD.

Комментарии

Чистый ABAP > Содержание > Эта секция

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

Чистый ABAP > Содержание > Комментарии > Эта секция

METHOD correct_day_to_last_in_month.
  WHILE is_invalid( date ).
    reduce_day_by_one( CHANGING date = date ).
  ENDWHILE.
ENDMETHOD.

METHOD is_invalid.
  DATA zero_if_invalid TYPE i.
  zero_if_invalid = date.
  result = xsdbool( zero_if_invalid = 0 ).
ENDMETHOD.

METHOD reduce_day_by_one.
  date+6(2) = date+6(2) - 1.
ENDMETHOD.

вместо

" anti-pattern
" correct e.g. 29.02. in non-leap years as well as result of a date calculation would be
" something like e.g. the 31.06. that example has to be corrected to 30.06.
METHOD fix_day_overflow.
  DO 3 TIMES.
    " 31 - 28 = 3 => this correction is required not more than 3 times
    lv_dummy = cv_date.
    " lv_dummy is 0 if the date value is a not existing date - ABAP specific implementation
    IF ( lv_dummy EQ 0 ).
      cv_date+6(2) = cv_date+6(2) - 1. " subtract 1 day from the given date
    ELSE.
      " date exists => no correction required
      EXIT.
    ENDIF.
  ENDDO.
ENDMETHOD.

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

Этот пример был оспорен с точки зрения производительности. Были утверждения, что сокращение методов слишком сильно ухудшает производительность. Примерные измерения показывают, что код после рефакторинга работает в 2,13 раза медленнее исходного, грязного варианта. Чистому варианту требуется 9,6 мкс, чтобы исправить ввод 31-02-2018, грязному варианту — всего 4,5 мкс. Это может вызвать проблемы, если метод будет запускаться очень часто в высокопроизводительном приложении. Для обычной проверки пользовательского ввода это должно быть приемлемо. Обратитесь к разделу Помните о производительности, если вы испытываете проблемы с производительностью сдедуя Чистому коду.

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

Чистый ABAP > Содержание > Комментарии > Эта секция

DATA(input_has_entries) = has_entries( input ).

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

" anti-pattern
" checks whether the table input contains entries
DATA(result) = check_table( input ).

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

Чистый ABAP > Содержание > Комментарии > Эта секция

DATA(statement) = build_statement( ).
DATA(data) = execute_statement( statement ).

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

" anti-pattern
" -----------------
" Build statement
" -----------------
DATA statement TYPE string.
statement = |SELECT * FROM d_document_roots|.

" -----------------
" Execute statement
" -----------------
DATA(result_set) = adbc->execute_sql_query( statement ).
result_set->next_package( IMPORTING data = data ).

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

Чистый ABAP > Содержание > Комментарии > Эта секция

" can't fail, existence of >= 1 row asserted above
DATA(first_line) = table[ 1 ].

Никому не нужно повторение кода на естественном языке

" anti-pattern
" select alert root from database by key
SELECT * FROM d_alert_root WHERE key = key.

Описание проекта должно быть в проектной документации, а не в коде

Чистый ABAP > Содержание > Комментарии > Эта секция

" anti-pattern
" This class serves a double purpose. First, it does one thing. Then, it does another thing.
" It does so by executing a lot of code that is distributed over the local helper classes.
" To understand what's going on, let us at first ponder the nature of the universe as such.
" Have a look at this and that to get the details.

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

Комментируйте используя ", а не *

Чистый ABAP > Содержание > Комментарии > Эта секция

Комментарии в двойных кавычках форматируются при выполнении структурной печати и получают отступ в соответствии с инструкциями, к которым они относятся.

METHOD do_it.
  IF input IS NOT INITIAL.
    " delegate pattern
    output = calculate_result( input ).
  ENDIF.
ENDMETHOD.

Комментарии со звездочкой, как правило, имеют странные отступы.

" anti-pattern
METHOD do_it.
  IF input IS NOT INITIAL.
* delegate pattern
    output = calculate_result( input ).
  ENDIF.
ENDMETHOD.

Размещайте комментарии перед утверждением, к которому они относятся

Чистый ABAP > Содержание > Комментарии > Эта секция

" delegate pattern
output = calculate_result( input ).

Понятнее, чем

" anti-pattern
output = calculate_result( input ).
" delegate pattern

И менее инвазивен, чем

output = calculate_result( input ).  " delegate pattern

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

Чистый ABAP > Содержание > Комментарии > Эта секция

" anti-pattern
* output = calculate_result( input ).

Если вы найдете что-то похожее, удалите это. Этот закомментированный код явно не нужен, потому что ваше приложение работает и без него и все тесты проходят. При необходимости, удаленный код можно воспроизвести из истории версий. Если вам нужно сохранить фрагмент кода навсегда, скопируйте его в файл или объект $TMP или HOME.

Используйте FIXME, TODO, и XXX и добавьте свой ID

Чистый ABAP > Содержание > Комментарии > Эта секция

METHOD do_something.
  " XXX FH delete this method - it does nothing
ENDMETHOD.
  • FIXME указывает на ошибки, которые слишком малы или слишком велики для внутренних инцидентов.
  • TODO это места, где вы хотите что-то доделать в ближайшем (!) будущем.
  • XXX отмечает код, который работает, но может быть улучшен

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

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

Чистый ABAP > Содержание > Комментарии > Эта секция

Комментарии к сигнатуре метода никому не нужны.

" anti-pattern
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method CALIBRATION_KPIS=>CALCULATE_KPI
* +-------------------------------------------------------------------------------------------------+
* | [--->] STRATEGY_ID                 TYPE        STRATEGY_ID
* | [--->] THRESHOLD                   TYPE        STRATEGY_THRESHOLD
* | [--->] DETECTION_OBJECT_SCORE      TYPE        T_HIT_RESULT
* | [<---] KPI                         TYPE        T_SIMULATED_KPI
* +--------------------------------------------------------------------------------------</SIGNATURE>

Десятилетия назад, когда вы не могли посмотреть сигнатуру метода при проверке его кода или при работе с распечатками, состоящими из десятков страниц, эти комментарии могли иметь смысл. Но все современные ABAP IDE (SE24, SE80, ADT) легко показывают сигнатуру метода, так что эти комментарии стали не более чем шумом.

В редакторе на основе формуляров SE24/SE80, нажмите кнопку Сигнатура. В ABAP Development Tools, отметьте название метода и нажмите F2 или добавьте view ABAP Element Info в свою перспективу.

Точно так же комментарии после закрывающих ключевых слов тоже излишни. Эти комментарии могли быть полезны несколько десятилетий назад, когда программы и функции, а также вложенные в них IFы cостояли из сотен строк кода. Но наш современный стиль написания кода создает достаточно короткие методы, чтобы было легко увидеть к какому открывающему оператору относится ENDIF или ENDMETHOD:

" anti-pattern
METHOD get_kpi_calc.
  IF has_entries = abap_false.
    result = 42.
  ENDIF.  " IF has_entries = abap_false
ENDMETHOD.   " get_kpi_calc

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

Чистый ABAP > Содержание > Комментарии > Эта секция

" anti-pattern
" alert category not filled
MESSAGE e003 INTO dummy.

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

Современные IDE предоставляют простые способы просмотра текста сообщения, например, в ABAP Development Tools для этого можно отметить идентификатор сообщения и нажать Shift+F2.

Если вы хотите сделать это более явно, можно вынести сообщение в отдельный метод.

METHOD create_alert_not_found_message.
  MESSAGE e003 INTO dummy.
ENDMETHOD.

ABAP Doc только для публичных APIs

Чистый ABAP > Содержание > Комментарии > Эта секция

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

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

Подробнее в Chapter 4: Good Comments: Javadocs in Public APIs and Chapter 4: Bad Comments: Javadocs in Nonpublic Code Robert C. Martin's Clean Code.

Предпочитайте прагмы псевдокомментариям

Чистый ABAP > Содержание > Комментарии > Эта секция

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

" pattern
MESSAGE e001(ad) INTO DATA(message) ##NEEDED.

" anti-pattern
MESSAGE e001(ad) INTO DATA(message). "#EC NEEDED

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

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

Чистый ABAP > Содержание > Эта секция

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

Будьте последовательны

Чистый ABAP > Содержание > Форматирование > Эта секция

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

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

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

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

Чистый ABAP > Содержание > Форматирование > Эта секция

Разработчики проводят большую часть своего времени за чтением кода. На самом деле написание кода занимает гораздо меньшую часть рабочего времени.

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

Например, предпочитайте это

DATA:
  a TYPE b,
  c TYPE d,
  e TYPE f.

такому хаку,

" anti-pattern
DATA:
  a TYPE b
  ,c TYPE d
  ,e TYPE f.

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

Чистый ABAP > Содержание > Форматирование > Эта секция

Используйте структурную печать — Shift+F1 в SE80, SE24 и ADT перед активацией объекта.

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

Подробнее в Chapter 5: Formatting: Team Rules Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Форматирование > Эта секция

Всегда используйте настройки вашей команды. Укажите их в Меню > Утилиты > Параметры настройки ... > ABAP-редак. > Структурная печать.

Установите Отступ и Преобразование простых/строчных букв > Ключ. слово прописное как общепринятый в вашей команде.

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

Подробнее в Chapter 5: Formatting: Team Rules Robert C. Martin's Clean Code.

Не более одного оператора в строке

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA do_this TYPE i.
do_this = input + 3.

Даже если что-то введет вас в заблуждение и заставить ошибочно полагать, что это читабельно:

" anti-pattern
DATA do_this TYPE i. do_this = input + 3.

Придерживайтесь разумной длины строки

Чистый ABAP > Содержание > Форматирование > Эта секция

Придерживайтесь максимальной длины строки в 120 символов.

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

Ограничение в 80 или даже 72 символа, существовавшее в старых тарминалах, является слишком строгим. Часто рекомендуется длина в 100 символов, и это приемлемый выбор, но кажется, что ограничение в 120 символов выглядит немного лучше для ABAP, возможно, из-за общей многословности языка.

Напоминаем, что в ADT вы можете установить ограничение до 120 символов, после чего это при просмотре кода будет отображаться вертикальная линия. Вы можете настроить это в Menu > Window > Preferences > General > Editors > Text Editors.

Уплотните ваш код

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA(result) = calculate( items ).

вместо добавления ненужных пробелов

" anti-pattern
DATA(result)        =      calculate(    items =   items )   .

Добавьте только одну пустую строку для разделения разных вещей, не более

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA(result) = do_something( ).

DATA(else) = calculate_this( result ).

чтобы подчеркнуть, что эти два утверждения делают разные вещи. Однако нет причин для

" anti-pattern
DATA(result) = do_something( ).



DATA(else) = calculate_this( result ).

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

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

Чистый ABAP > Содержание > Форматирование > Эта секция

METHOD do_something.
  do_this( ).
  then_that( ).
ENDMETHOD.

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

" anti-pattern
METHOD do_something.

  do_this( ).

  then_that( ).

ENDMETHOD.

Пустые строки имеют смысл только в том случае, если у вас есть операторы, занимающие несколько строк.

METHOD do_something.

  do_this( ).

  then_that(
    EXPORTING
      variable = 'A'
    IMPORTING
      result   = result ).

ENDMETHOD.

Выравнивайте присвоения для одного и того же объекта, но не для разных

Чистый ABAP > Содержание > Форматирование > Эта секция

Чтобы подчеркнуть, что эти вещи каким-то образом связаны друг с другом

structure-type = 'A'.
structure-id   = '4711'.

или еще лучше

structure = VALUE #( type = 'A'
                     id   = '4711' ).

Но, если это не связанные вещи, оставьте такую форму записи:

customizing_reader = fra_cust_obj_model_reader=>s_get_instance( ).
hdb_access = fra_hdbr_access=>s_get_instance( ).

Подробнее в Chapter 5: Formatting: Horizontal Alignment Robert C. Martin's Clean Code.

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

Чистый ABAP > Содержание > Форматирование > Эта секция

modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

вместо излишне длинного

" anti-pattern
modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields
).

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

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA(unique_list) = remove_duplicates( list ).
remove_duplicates( CHANGING list = list ).

вместо излишне длинного

" anti-pattern
DATA(unique_list) = remove_duplicates(
                           list ).
DATA(unique_list) = remove_duplicates(
                         CHANGING
                           list = list ).

Указывайте параметры начиная со строки вызова

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA(sum) = add_two_numbers( value_1 = 5
                             value_2 = 6 ).

Если ваши строки становятся слишком длинными, вы можете перенести параметры на следующую строку:

DATA(sum) = add_two_numbers(
                value_1 = round_up( input DIV 7 ) * 42 + round_down( 19 * step_size )
                value_2 = VALUE #( ( `Calculation failed with a very weird result` ) ) ).

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

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA(sum) = add_two_numbers(
                value_1 = 5
                value_2 = 6 ).

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

DATA(sum) = add_two_numbers(
    value_1 = 5
    value_2 = 6 ).

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

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

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA(sum) = add_two_numbers( value_1 = 5
                             value_2 = 6 ).

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

" anti-pattern
DATA(sum) = add_two_numbers( value_1 = 5 value_2 = 6 ).

Выровняйте параметры

Чистый ABAP > Содержание > Форматирование > [Эта секция](#выровняйте параметры)

modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

Из-за рваных краев трудно увидеть, где заканчивается параметр и начинается его значение:

" anti-pattern
modify->update( node = if_fra_alert_c=>node-item
                key = item->key
                data = item
                changed_fields = changed_fields ).

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

Перенесите вызов на новую строку, если она станет слишком длинной

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA(some_super_long_param_name) =
  if_some_annoying_interface~add_two_numbers_in_a_long_name(
      value_1 = 5
      value_2 = 6 ).

Добавьте отступы и табуляцию

Чистый ABAP > Содержание > Форматирование > Эта секция

Используйте 2 пробела для ключевых слов и 4 пробела для параметров:

DATA(sum) = add_two_numbers(
              EXPORTING
                value_1 = 5
                value_2 = 6
              CHANGING
                errors  = errors ).

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

DATA(sum) = add_two_numbers(
                value_1 = 5
                value_2 = 6 ).

Можно использовать Tab для отступа. Ничего страшного, если это добавит на один пробел больше, чем необходимо. (Это происходит тогда, когда левая часть DATA(sum) = имеет нечетное количество символов.)

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

Чистый ABAP > Содержание > Форматирование > Эта секция

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

DATA(result) = merge_structures( a = VALUE #( field_1 = 'X'
                                              field_2 = 'A' )
                                 b = NEW /clean/structure_type( field_3 = 'C'
                                                                field_4 = 'D' ) ).

Не выравнивайте указания типов

Чистый ABAP > Содержание > Форматирование > Эта секция

DATA name TYPE seoclsname.
DATA reader TYPE REF TO /clean/reader.

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

" anti-pattern
DATA name   TYPE seoclsname.
DATA reader TYPE REF TO /clean/reader.

Не объединяйте присвоения

Чистый ABAP > Содержание > Форматирование > Эта секция

var2 = var3.
var1 = var3.
var1 = xsdbool( var2 = var3 ).

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

" anti-pattern
var1 = var2 = var3.

Тестирование

Чистый ABAP > Содержание > Эта секция

Принципы

Чистый ABAP > Содержание > Тестирование > Эта секция

Пишите тестируемый код

Чистый ABAP > Содержание > Тестирование > Принципы > Эта секция

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

Если для этого требуется рефакторинг, проведите его. Сделайте это перед тем, как начнете добавлять другие функции.

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

Позвольте другим создавать моки

Чистый ABAP > Содержание > Тестирование > Принципы > Эта секция

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

Правила удобочитаемости

Чистый ABAP > Содержание > Тестирование > Принципы > Эта секция

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

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

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

Не делайте копии и не пишите тестовые отчеты

Чистый ABAP > Содержание > Тестирование > Принципы > Эта секция

Не начинайте работу над задачей из бэклога, создавая копию объекта разработки в $TMP и играя с ним. Другие люди не заметят эти объекты и, следовательно, не будут знать о состоянии вашей работы. Вы можете потратить впустую много времени, сначала создавая рабочую копию. Затем вы можете забыть удалить эту копию, загрязняя вашу систему и зависимости. (Не верите? Перейдите в свою систему разработки прямо сейчас и проверьте свой $TMP).

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

Тестируйте публичные, а не приватные части

Чистый ABAP > Содержание > Тестирование > Принципы > Эта секция

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

Срочная необходимость протестировать приватные или защищенные методы может быть ранним сигналом о некоторых недостатках проектирования. Задайте себе вопрос:

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

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

  • Ваши интерфейсы слишком сложны и требуют слишком много данных, которые не важны или которые нельзя легко замокать?

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

Чистый ABAP > Содержание > Тестирование > Принципы > Эта секция

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

Не придумывайте тесты с фиктивными утверждениями или без них только для того, чтобы достичь большего покрытия кода. Лучше оставлять вещи непроверенными, чтобы ясно показать, что вы не можете подвергнуть их рефакторингу без риска. Вы можете иметь менее 100% охвата и при этом иметь идеальные тесты. Есть случаи (например, IFы в конструкторе для вставки тестовых симуляций), которые на практике делают невозможным достижение 100%. Хорошие тесты, как правило, охватывают одну и ту же инструкцию несколько раз для разных ветвей и условий. Поэтому, теоретически покрытие кода у них превышает 100%.

Тестовые классы

Чистый ABAP > Содержание > Тестирование > Эта секция

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

Чистый ABAP > Содержание > Тестирование > Тестовые классы > Эта секция

Назовите свой локальный тестовый класс как часть "когда" вашего тестового сценария.

CLASS ltc_<public method name> DEFINITION FOR TESTING ... ."

или назовите его "данная" ситуация.

CLASS ltc_<common setup semantics> DEFINITION FOR TESTING ... .
" anti-patterns
CLASS ltc_fra_online_detection_api DEFINITION FOR TESTING ... . " We know that's the class under test - why repeat it?
CLASS ltc_test DEFINITION FOR TESTING ....                      " Of course it's a test, what else should it be?

Поместите тесты в локальные классы

Чистый ABAP > Содержание > Тестирование > Тестовые классы > Эта секция

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

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

Если возможно, используйте test relations, чтобы задокументировать, какие объекты охватываются тестом. В приведенном ниже примере, тестовый класс hiring_test можно запустить в классе recruting или candidate с помощью сочетания клавиш Shift-Crtl-F12 (Windows) или Cmd-Shift-F12 (macOS).

"! @testing recruting
"! @testing candidate
class hiring_test definition
  for testing risk level dangerous duration medium
  abstract.
  ...
endclass.

Поместите вспомогательные методы во вспомогательные классы

Чистый ABAP > Содержание > Тестирование > Тестовые классы > Эта секция

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

" inheritance example

CLASS lth_unit_tests DEFINITION ABSTRACT.

  PROTECTED SECTION.
    CLASS-METHODS assert_activity_entity
      IMPORTING
        actual_activity_entity TYPE REF TO zcl_activity_entity
        expected_activity_entity TYPE REF TO zcl_activity_entity.
    ...
ENDCLASS.

CLASS lth_unit_tests IMPLEMENTATION.

  METHOD assert_activity_entity.
    ...
  ENDMETHOD.

ENDCLASS.

CLASS ltc_unit_tests DEFINITION INHERITING FROM lth_unit_tests FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.
  ...
ENDCLASS.

Как выполнять тестовые классы

Чистый ABAP > Содержание > Тестирование > Эта секция

В ABAP Development Tools,
нажмите Ctrl+Shift+F10 чтобы запустить все тесты класса.
Нажмите Ctrl+Shift+F11 чтобы включить измерение покрытия.
Нажмите Ctrl+Shift+F12 чтобы также запустить тесты в других классах, которые поддерживаются как тестовые отношения.

На macOS, используйте Cmd вместо Ctrl.

Тестируемый код

Чистый ABAP > Содержание > Тестирование > Эта секция

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

Чистый ABAP > Содержание > Тестирование > Тестируемый код > Эта секция

Дайте переменной, которая представляет тестируемый код, осмысленное имя:

DATA blog_post TYPE REF TO ...

Не повторяйте просто имя класса со всеми его пространствами имен и префиксами:

" anti-pattern
DATA clean_fra_blog_post TYPE REF TO ...

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

DATA empty_blog_post TYPE REF TO ...
DATA simple_blog_post TYPE REF TO ...
DATA very_long_blog_post TYPE REF TO ...

Если вы испытываете проблемы с поиском осмысленного имени, используйте cut по умолчанию. Аббревиатура расшифровывается как "тестируемый код" (code under test).

DATA cut TYPE REF TO ...

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

Тестируйте интерфейсы, а не реализации

Чистый ABAP > Содержание > Тестирование > Тестируемый код > Эта секция

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

DATA code_under_test TYPE REF TO some_interface.

а не класс

" anti-pattern
DATA code_under_test TYPE REF TO some_class.

Поместите вызов тестируемого кода в отдельный метод

Чистый ABAP > Содержание > Тестирование > Тестируемый код > Эта секция

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

METHODS map_xml_to_itab
  IMPORTING
    xml_string TYPE string
    config     TYPE /clean/xml2itab_config DEFAULT default_config
    format     TYPE /clean/xml2itab_format DEFAULT default_format.

METHOD map_xml_to_itab.
  result = cut->map_xml_to_itab( xml_string = xml_string
                                 config     = config
                                 format     = format ).
ENDMETHOD.

DATA(itab) = map_xml_to_itab( '<xml></xml>' ).

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

" anti-pattern
DATA(itab) = cut->map_xml_to_itab( xml_string = '<xml></xml>'
                                   config     = VALUE #( 'some meaningless stuff' )
                                   format     = VALUE #( 'more meaningless stuff' ) ).

Инъекция

Чистый ABAP > Содержание > Тестирование > Эта секция

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

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

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

METHODS constructor
  IMPORTING
    customizing_reader TYPE REF TO if_fra_cust_obj_model_reader.

METHOD constructor.
  me->customizing_reader = customizing_reader.
ENDMETHOD.

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

" anti-pattern
METHODS set_customizing_reader
  IMPORTING
    customizing_reader TYPE REF TO if_fra_cust_obj_model_reader.

METHOD do_something.
  object->set_customizing_reader( a ).
  object->set_customizing_reader( b ). " would you expect that somebody does this?
ENDMETHOD.

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

" anti-pattern
METHOD setup.
  cut = NEW fra_my_class( ). " <- builds a productive customizing_reader first - what will it break with that?
  cut->customizing_reader ?= cl_abap_testdouble=>create( 'if_fra_cust_obj_model_reader' ).
ENDMETHOD.

METHOD constructor.
  customizing_reader = fra_cust_obj_model_reader=>s_get_instance( ).
  customizing_reader->fill_buffer( ). " <- won't be called on your test double, so no chance to test this
ENDMETHOD.

Рассмотрите возможность использования инструмента ABAP test double

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

DATA(customizing_reader) = CAST /clean/customizing_reader( cl_abap_testdouble=>create( '/clean/default_custom_reader' ) ).
cl_abap_testdouble=>configure_call( customizing_reader )->returning( sub_claim_customizing ).
customizing_reader->read( 'SOME_ID' ).

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

" anti-pattern
CLASS /dirty/default_custom_reader DEFINITION FOR TESTING CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES /dirty/customizing_reader.
    DATA customizing TYPE /dirty/customizing_table.
ENDCLASS.

CLASS /dirty/default_custom_reader IMPLEMENTATION.
  METHOD /dirty/customizing_reader~read.
    result = customizing.
  ENDMETHOD.
ENDCLASS.

METHOD test_something.
  DATA(customizing_reader) = NEW /dirty/customizing_reader( ).
  customizing_reader->customizing = sub_claim_customizing.
ENDMETHOD.

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

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

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

  • Используйте службу CL_OSQL_REPLACE для тестирования сложных операторов OpenSQL, перенаправляя их в корзину тестовых данных, которую можно заполнить тестовыми данными, не мешая остальной системе.

  • Используйте CDS test framework для тестирования ваших CDS представлений.

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

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

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

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

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

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

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

CLASS /clean/unit_tests DEFINITION.
  PRIVATE SECTION.
    DATA cut TYPE REF TO /clean/interface_under_test.
    METHODS setup.
ENDCLASS.

CLASS /clean/class_under_test DEFINITION LOCAL FRIENDS unit_tests.

CLASS unit_tests IMPLEMENTATION.
  METHOD setup.
    DATA(mock) = cl_abap_testdouble=>create( '/clean/some_mock' ).
    " /clean/class_under_test is CREATE PRIVATE
     " so this only works because of the LOCAL FRIENDS
    cut = NEW /clean/class_under_test( mock ).
  ENDMETHOD.
ENDCLASS.

Не злоупотребляйте LOCAL FRIENDS для вторжения в проверенный код

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

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

" anti-pattern
CLASS /dirty/class_under_test DEFINITION LOCAL FRIENDS unit_tests.
CLASS unit_tests IMPLEMENTATION.
  METHOD returns_right_result.
    cut->some_private_member = 'AUNIT_DUMMY'.
  ENDMETHOD.
ENDCLASS.

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

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

" anti-pattern
IF me->in_test_mode = abap_true.

Не создавайте подклассы чтобы замокать методы

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

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

" anti-pattern
CLASS unit_tests DEFINITION INHERITING FROM /dirty/real_class FOR TESTING [...].
  PROTECTED SECTION.
    METHODS needs_to_be_mocked REDEFINITION.

Чтобы тестировать устаревший код, все же лучше использовать тестовые швы. Они такие же хрупкие, но все же самые чистые решения, поскольку они не изменяют продуктивное поведение класса, в отличие от включения наследования путем удаления флага FINAL или изменения области действия метода с PRIVATE на PROTECTED.

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

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

Не мокайте то, что вам не нужно

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

cut = NEW /clean/class_under_test( db_reader = db_reader
                                   config    = VALUE #( )
                                   writer    = VALUE #( ) ).

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

" anti-pattern
cut = NEW /dirty/class_under_test( db_reader = db_reader
                                   config    = config
                                   writer    = writer ).

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

Не создавайте тестовые фреймворки

Чистый ABAP > Содержание > Тестирование > Инъекция > Эта секция

Модульные тесты — в отличие от интеграционных тестов — должны быть data-in-data-out, при этом все тестовые данные должны определяться на лету по мере необходимости.

cl_abap_testdouble=>configure_call( test_double )->returning( data ).

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

" anti-pattern

test_double->set_test_case( 1 ).

CASE me->test_case.
  WHEN 1.
  WHEN 2.
ENDCASE.

Тестовые методы

Чистый ABAP > Содержание > Тестирование > Эта секция

Названия тестовых методов должны отражать то, что дано и что ожидается

Чистый ABAP > Содержание > Тестирование > Тестовые методы > Эта секция

Хорошие имена отражают что "дано" и что будет "тогда" (желаемая целевая ситуация теста) .

METHOD reads_existing_entry.
METHOD throws_on_invalid_key.
METHOD detects_invalid_input.

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

" anti-patterns

" What's expected, success or failure?
METHOD get_conversion_exits.

" It's a test method, what else should it do but "test"?
METHOD test_loop.

" So it's parameterized, but what is its aim?
METHOD parameterized_test.

" What's "_wo_w" supposed to mean and will you still remember that in a year from now?
METHOD get_attributes_wo_w.

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

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

Используйте формат дано-когда-тогда

Чистый ABAP > Содержание > Тестирование > Тестовые методы > Эта секция

Организуйте свой тестовый код в соответствии с парадигмой "данные-когда-тогда": во-первых, инициализируйте все начальные условия в разделе "дано". Затем вызовите тестируемый код ("когда"). Наконец, подтвердите ожидаемый результат ("тогда").

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

"Когда" ровно один вызов

Чистый ABAP > Содержание > Тестирование > Тестовые методы > Эта секция

Убедитесь, что раздел "когда" вашего тестового метода содержит только вызов тестируемого класса:

METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
ENDMETHOD.

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

Не добавляйте TEARDOWN если вам это не нужно

Чистый ABAP > Содержание > Тестирование > Тестовые методы > Эта секция

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

Сброс атрибутов тестового класса, особенно cut, или атрибутов тестовых двойников избыточен; они будут перезаписаны методом настройки перед setup следующего метода тестирования.

Тестовые данные

Чистый ABAP > Содержание > Тестирование > Эта секция

Упростите определение смысла

Чистый ABAP > Содержание > Тестирование > Тестовые данные > Эта секция

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

DATA(alert_id) = '42'.                             " well-known meaningless numbers
DATA(detection_object_type) = '?=/"&'.             " 'keyboard accidents'
CONSTANTS some_random_number TYPE i VALUE 782346.  " revealing variable names

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

" anti-pattern
DATA(alert_id) = '00000001223678871'.        " this alert really exists
DATA(detection_object_type) = 'FRA_SCLAIM'.  " this detection object type, too
CONSTANTS memory_limit TYPE i VALUE 4096.    " this number looks carefully chosen

Упростите поиск различий

Чистый ABAP > Содержание > Тестирование > Тестовые данные > Эта секция

exp_parameter_in = VALUE #( ( parameter_name = '45678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789END1' )
                            ( parameter_name = '45678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789END2' ) ).

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

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

Чистый ABAP > Содержание > Тестирование > Тестовые данные > Эта секция

CONSTANTS some_nonsense_key TYPE char8 VALUE 'ABCDEFGH'.

METHOD throws_on_invalid_entry.
  TRY.
      " when
      cut->read_entry( some_nonsense_key ).
      cl_abap_unit_assert=>fail( ).
    CATCH /clean/customizing_reader_error.
      " then
  ENDTRY.
ENDMETHOD.

Утверждения

Чистый ABAP > Содержание > Тестирование > Эта секция

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

Чистый ABAP > Содержание > Тестирование > Утверждения > Эта секция

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

METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
ENDMETHOD.

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

" anti-pattern
METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
  cl_abap_unit_assert=>assert_not_initial( log->get_messages( ) ).
  cl_abap_unit_assert=>assert_equals( act = sy-langu
                                      exp = 'E' ).
ENDMETHOD.

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

Чистый ABAP > Содержание > Тестирование > Утверждения > Эта секция

cl_abap_unit_assert=>assert_equals( act = table
                                    exp = test_data ).

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

" anti-pattern
cl_abap_unit_assert=>assert_true( xsdbool( act = exp ) ).

Утверждайте содержание, а не количество

Чистый ABAP > Содержание > Тестирование > Утверждения > Эта секция

assert_contains_exactly( actual   = table
                         expected = VALUE string_table( ( `ABC` ) ( `DEF` ) ( `GHI` ) ) ).

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

" anti-pattern
assert_equals( act = lines( log_messages )
               exp = 3 ).

Утверждайте качество, а не содержание

Чистый ABAP > Содержание > Тестирование > Утверждения > Эта секция

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

assert_all_lines_shorter_than( actual_lines        = table
                               expected_max_length = 80 ).

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

" anti-pattern
assert_equals( act = table
               exp = VALUE string_table( ( `ABC` ) ( `DEF` ) ( `GHI` ) ) ).

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

Чистый ABAP > Содержание > Тестирование > Утверждения > Эта секция

METHOD throws_on_empty_input.
  TRY.
      " when
      cut->do_something( '' ).
      cl_abap_unit_assert=>fail( ).
    CATCH /clean/some_exception.
      " then
  ENDTRY.
ENDMETHOD.

Не перехватывайте неожиданные исключения, а перенаправляйте их

Чистый ABAP > Содержание > Тестирование > Утверждения > Эта секция

METHODS reads_entry FOR TESTING RAISING /clean/some_exception.

METHOD reads_entry.
  "when
  DATA(entry) = cut->read_something( ).
  "then
  cl_abap_unit_assert=>assert_not_initial( entry ).
ENDMETHOD.

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

" anti-pattern
METHOD reads_entry.
  TRY.
      DATA(entry) = cut->read_something( ).
    CATCH /clean/some_exception INTO DATA(unexpected_exception).
      cl_abap_unit_assert=>fail( unexpected_exception->get_text( ) ).
  ENDTRY.
  cl_abap_unit_assert=>assert_not_initial( entry ).
ENDMETHOD.

Напишите собственные утверждения чтобы сократить код и избежать дублирования

Чистый ABAP > Содержание > Тестирование > Утверждения > Эта секция

METHODS assert_contains
  IMPORTING
    actual_entries TYPE STANDARD TABLE OF entries_tab
    expected_key   TYPE key_structure.

METHOD assert_contains.
  TRY.
      actual_entries[ key = expected_key ].
    CATCH cx_sy_itab_line_not_found.
      cl_abap_unit_assert=>fail( |Couldn't find the key { expected_key }| ).
  ENDTRY.
ENDMETHOD.

Вместо того, чтобы копировать это и вставлять снова и снова.