Возьмем для примера фрагмент кода из типовой бухгалтерии
Если ((НомерИзменяемойКолонки = "4") Или (НомерИзменяемойКолонки = "5"))
Или (((НомерИзменяемойКолонки = "7") Или (НомерИзменяемойКолонки = "8")
И (ГруппаРасчета = мИдГруппы7))) Тогда
Расчет(ГруппаРасчета);
КонецЕсли;
Не правда ли от количества скобок "рябит в глазах"? Конечно скобки иногда необходимы для расстановки последовательности вычисления, но записывая их линейно в таком количестве мы заметно усложняем восприятие (читаемость) выражения.
Приоритеты логических операций
Для начала вспомним приоритеты логических операций. Сначала выполняется Не, потом И и затем Или. Скобки традиционно имеют наивысший приоритет выполнения и гарантируют порядок выполнения вложенных в них операций. Однако их избыточное применение создает продемонстрированные в примере трудности. Зачастую скобки ставят лишь для того, чтобы перестраховаться в сложных и плохо читаемых логических выражениях.
Методика И-ИЛИ дерева
Я же предлагаю сложные логические выражения оформлять в виде И-ИЛИ дерева. Под И-ИЛИ деревом я подразумеваю дерево, нетерминальные узлы которого представляют собой группы (последовательности) одинаковых логических операций И или ИЛИ, а терминальные - остальные логические выражения. Хорошим примером такого дерева является отбор настроек компоновки данных, правда у него верхняя (корневая) группа всегда имеет тип "И". В случае встроенного языка мы этим не ограничены и можем использовать любой тип верхней группы. В рассмотренном примере как раз сверху расположена группа "ИЛИ".
1. Встаем на первую открывающую скобку и с помощью сочетания клавиш CTRL+] находим тело первого узла и переносим целиком в одну следующую строку с отступом относительно слова Если
Если
((НомерИзменяемойКолонки = "4") Или (НомерИзменяемойКолонки = "5"))
Или (((НомерИзменяемойКолонки = "7") Или (НомерИзменяемойКолонки = "8")
И (ГруппаРасчета = мИдГруппы7))) Тогда
2. Следующий логический оператор будет И или ИЛИ. Он и определяет тип группы этого уровня при условии что все группы обрамлены скобками. Для повышения наглядности вставляем в начало группы операцию с не нарушающим результат вычисления группы константным значением. Для И это будет ИСТИНА, а для ИЛИ это будет ЛОЖЬ. ИСТИНА не меняет результат конъюнкции (ИСТИНА И), и ЛОЖЬ не меняет результат дизъюнкции (ЛОЖЬ ИЛИ). Рассмотрим преобразование выражения примера к И-ИЛИ дереву.
Если Ложь
Или ((НомерИзменяемойКолонки = "4") Или (НомерИзменяемойКолонки = "5"))
Или (((НомерИзменяемойКолонки = "7") Или (НомерИзменяемойКолонки = "8")
И (ГруппаРасчета = мИдГруппы7))) Тогда
3. Встаем на следующую открывающую скобку корневого уровня и повторяем шаг 1.
Если Ложь
Или ((НомерИзменяемойКолонки = "4") Или (НомерИзменяемойКолонки = "5"))
Или (((НомерИзменяемойКолонки = "7") Или (НомерИзменяемойКолонки = "8") И (ГруппаРасчета = мИдГруппы7))) Тогда
4. Слово Тогда для наглядности я переношу на отдельную строку с тем же отступом, что и Если, а все внутренние строки условия имеют больший отступ. Таким образом мы четко обозначаем начало и конец условия.
Если Ложь
Или ((НомерИзменяемойКолонки = "4") Или (НомерИзменяемойКолонки = "5"))
Или (((НомерИзменяемойКолонки = "7") Или (НомерИзменяемойКолонки = "8") И (ГруппаРасчета = мИдГруппы7)))
Тогда
5. Далее повторяем шаги 1-3 для всех вложенных узлов (условий в скобках). Следует заметить, автор оригинального выражения не все группы обрамил скобками и последние 2 оператора из числа образующих группы различны и не разделены скобками. В итоге получаем
Если Ложь
Или (Ложь
Или (НомерИзменяемойКолонки = "4")
Или (НомерИзменяемойКолонки = "5"))
Или (Ложь
Или (НомерИзменяемойКолонки = "7")
Или (Истина
И (НомерИзменяемойКолонки = "8")
И (ГруппаРасчета = мИдГруппы7)))
Тогда
6. Теперь нам становится понятно, что логическое выражение можно упростить. Все вложенные однотипные (И или ИЛИ) группы можно смело всегда поднимать в родительскую группу.
Если Ложь
Или (НомерИзменяемойКолонки = "4")
Или (НомерИзменяемойКолонки = "5")
Или (НомерИзменяемойКолонки = "7")
Или (Истина
И (НомерИзменяемойКолонки = "8")
И (ГруппаРасчета = мИдГруппы7))
Тогда
7. В таком виде уже можно довольно безопасно убрать скобки вокруг условий внутри строк. В итоге получаем
Если Ложь
Или НомерИзменяемойКолонки = "4"
Или НомерИзменяемойКолонки = "5"
Или НомерИзменяемойКолонки = "7"
Или (Истина
И НомерИзменяемойКолонки = "8"
И ГруппаРасчета = мИдГруппы7)
Тогда
Кажется, что в итоге получилось заметно более простое и наглядное выражение. В исходном выражении было 16 скобок, в преобразованном - всего 2.
По идее, как изначально оформлять логические выражение по этой методике уже должно быть понятно.
Хочу обратить ваше внимание на отсутствие унарной операции НЕ как типа группы. При желании конечно можно было бы ввести в методику и группу НЕ, но она слишком отличается от рассмотренных и я лично сторонник обходить ее стороной и опускать на самые нижние узлы.
Думаю, что эту методику можно успешно применять не только к встроенному языку 1С, но и многим другим языкам.
Использую эту методику уже много лет.
Плюсы:
- повышает наглядность выражения после некоторого привыкания, особенно для однотипных условий
- облегчает рефакторинг, т.к. позволяет четко видеть пути упрощения (устранения избыточности) выражения
- облегчает отладку, т.к. позволяет быстро комментировать(выключать)/раскомментировать(включать) фрагменты выражения
- облегчает изменение порядка фрагментов выражения
- позволяет дописывать комментарии к каждому узлу дерева
- сокращает левые отступы, вложенность условий, количество строк в коде по сравнению с некоторыми другими подходами
Минусы:
- не все сразу понимают чисто оформительское назначение "Истина И" и "Ложь Или"
- уходит больше времени на начальное написание выражения
- выражение занимает большее число строк
- часто не оправдывает себя в простых выражениях
- автоформатирование кода выравнивает все строки условия по одной границе, но в ИР команда с поддержкой такого оформления
- выражение незначительно дольше вычисляется
Шаблоны
Для удобства написания сложных логических выражений по этой методике рекомендую добавить себе шаблоны
Условие с корневой группой "И" -
Если Истина И <?> Тогда КонецЕсли;
Условие с корневой группой "Или"
Если Ложь Или <?> Тогда КонецЕсли;