Пиши код правильно — по стандарту. Часть 2
В данной статье описываю кратко часть статей стандарта для использования в работе. Предполагаю, что эту информацию можно использовать как шпаргалку для быстрого напоминания отдельных пунктов стандарта либо для первоначального общего ознакомления со стандартами.
В статье есть ссылки на bsl-ls – подробнее можно ознакомиться по этому адресу.
Contents
- 1 Плагин для автоматической проверки кода на соответствие стандарту
- 2 Объем метода
- 3 Принцип единства ответственности
- 4 Когнитивная сложность
- 5 Цикломатическая сложность
- 6 Глубина вложенности управляющих конструкций
- 7 Работа с оператором Если
- 8 Магические даты и числа
- 9 Использование тернарного оператора
- 10 Многократное использование одинаковых строковых литералов
- 11 Хранение информации в коде
- 12 Результат выполнения функции
Плагин для автоматической проверки кода на соответствие стандарту
Для автоматического подсчета когнитивной или цикломатической сложности, а также для проверки соответствия кода положениям стандарта можно использовать различные дополнительные программы и утилиты, например PhoenixBSL.
Скачать установщик можно по этому адресу.
Описание программы и возможности доступно здесь.
После установки в конфигураторе можно выделить любой блок кода, нажать Ctrl + I и в отдельном окне получить информацию с рекомендациями для соблюдения стандарта.
Объем метода
Методы не стоит делать слишком длинными. Нужно стремиться делать методы небольшими, но рекомендуемый размер у разных авторов отличается. По мнению BSL-Language Server, рекомендуется ограничиваться 200 строками кода, другие предлагают ограничиваться количеством строк, которые вмещаются в два-три экрана пользователя.
Для уменьшения объема рекомендуется:
— разбивать большой метод на несколько более мелких логически цельных,
— вынести части внутренних механик в отдельные процедуры и функции.
Принцип единства ответственности
Каждый метод должен выполнять одно действие, используя такой принцип:
1. Объем метода будет небольшим.
2. Легче понять логику.
3. Выделяемые функции и процедуры можно вызывать или дорабатывать отдельно от общего метода.
В конфигуратор встроена функция по выделению части кода в отдельную процедуру или функцию.
Для выделения блока кода в отдельную процедуру в конфигураторе требуется выделить нужный блок, вызвать консольное меню (правой кнопкой мыши), выбрать «Рефакторинг»/ «Выделить фрагмент». После этого программа запросит название новой процедуры и создаст новую процедуру, а в исходном методе добавит вызов созданной новой процедуры.
Когнитивная сложность
Общая когнитивная сложность каждого метода не должна превышать 15 единиц сложности.
Правила для подсчета когнитивной сложности:
— каждые операторные скобки (Для … Цикл/ КонецЦикла) увеличивают когнитивную сложность на 1 сложности,
— каждое условие и тернарный оператор дает +1 сложности,
— каждая бинарная операция (И, Или) добавляет +1 сложности,
— альтернативная ветвь условия +1,
— попытка/ исключение также дают +1 сложности,
— переход на метку «Перейти ~ Метка;» — +1,
— за каждый уровень вложенности блоки получают дополнительно +1 сложности,
— альтернативные ветки, бинарные операции и переход на метку не увеличивают когнитивную сложность за вложенность (остаются +1 при вложенности).
Подробнее с условиями подсчета можно ознакомиться в описании к плагину по этому адресу.
Пример подсчета (в начале каждой строки указан добавочный балл когнитивной сложности):
Функция Когнитивно-СложныйКод(ТипКласса)
+1 Если ТипКласса.Неизвестен() Тогда
Возврат Символы.НеизвестныйСимвол;
КонецЕсли;
НеизвестностьНайдена = Ложь;
СписокСимволов = ТипКласса.ПолучитьСимвол().Потомки.Поиск(«имя»);
+1 Для Каждого Символ Из СписокСимволов Цикл
+2 Если Символ.ИмеетТип(Симовлы.Странное)
+1 И Не Символы.Экспортный() Тогда
+3 Если МожноПереопределять(Символ) Тогда
Переопределяемость = ПроверитьПереопределяемость(Символ);
+4 Если Переопределяемость = Неопределено Тогда
+5 Если Не НеизвестностьНайдена Тогда
НеизвестностьНайдена = Истина;
КонецЕсли;
+1ИначеЕсли Переопределяемость Тогда
Возврат Символ;
КонецЕсли;
+1 Иначе
Продолжить;
КонецЕсли;
КонецЕсли;
КонецЦикла;
+1 Если НеизвестностьНайдена Тогда
Возврат Символы.НеизвестныйСимвол;
КонецЕсли;
КонецФункции
Цикломатическая сложность
Цикломатическая сложность показывает количество вариантов, по которым может происходить выполнение метода.
Каждый путь выполнения требует своего теста, соответственно, чем больше цикломатическая сложность, тем выше сложность и количество проверок.
Рекомендуемый предел цикломатической сложности равен 20.
Подсчет отличается от когнитивной сложности.
Следующие операторы увеличивают цикломатическую сложность на одну единицу:
— Для … Цикл
— Если … Тогда
— ИначеЕсли … Тогда
— Иначе
— Попытка … Исключение … КонецПопытки
— Перейти ~ Метка
— Бинарные операции И, ИЛИ
— Тернарный оператор
— Процедура
— Функция
Вложенные операторы не увеличивают цикломатическую сложность.
Пример:
Функция СерверныйМодульМенеджера(Имя) // 1
ОбъектНайден = Ложь; // 0
ЧастиИмени = СтрРазделить(Имя, "."); // 0
Если ЧастиИмени.Количество() = 2 Тогда // 1
ИмяВида = ВРег(ЧастиИмени[0]); // 0
ИмяОбъекта = ЧастиИмени[1]; // 0
Если ИмяВида = ВРег("Константы") Тогда // 1
Если Метаданные.Константы.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
ИначеЕсли ИмяВида = ВРег("РегистрыСведений") Тогда // 1
Если Метаданные.РегистрыСведений.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
Иначе // 1
ОбъектНайден = Ложь; // 0
КонецЕсли; // 0
КонецЕсли; // 0
Если Не ОбъектНайден Тогда // 1
ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( // 0
НСтр("ru = 'Объект метаданных ""%1"" не найден, // 0
|либо для него не поддерживается получение модуля менеджера.'"), // 0
Имя); // 0
КонецЕсли; // 0
УстановитьБезопасныйРежим(Истина); // 0
Модуль = Вычислить(Имя); // 0
F = ?(Условие, ИСТИНА, НЕОПРЕДЕЛЕНО); // 1
А = ?(Условие, ИСТИНА, ?(Условие2, ЛОЖЬ, НЕОПРЕДЕЛЕНО)); // 2
M = ИСТИНА ИЛИ 7; // 1
Возврат Модуль; // 0
КонецФункции // итог 12
Глубина вложенности управляющих конструкций
Вне зависимости от цикломатической и когнитивной сложности вложенные управляющие конструкции (Если, циклы, …) не стоит делать глубже 4 уровней.
Работа с оператором Если
Не рекомендуется создавать громоздкое условие в блоке Если.
Сложные условия затрудняют чтение кода, и рекомендуется выделять их в отдельную функцию либо переменную.
Пример:
// Неправильно:
Если ИдентификаторОъекта = «АнализСубконто»
Или ИдентификаторОъекта = «АнализСчета»
Или ИдентификаторОъекта = «ОбортноСальдоваяВедомость»
Или ИдентификаторОъекта = «ОборотыСчета»
Или ИдентификаторОъекта = «ГлавнаяКнига»
Или ИдентификаторОъекта = «ШахматнаяВедомость» Тогда
ПараметрыРасшифровки.Вставить(«ОткрытьОбъект», Ложь);
КонецЕсли;
//Правильно:
Если НеОткрыватьОбъект(ИдентификаторОбъекта) Тогда
ПараметрыРасшифровки.Вставить(«ОткрытьОбъект», Ложь);
КонецЕсли;
Функция НеОткрыватьОбъект(ИдентификаторОбъекта)
Возврат ИдентификаторОъекта = «АнализСубконто»
Или ИдентификаторОъекта = «АнализСчета»
Или ИдентификаторОъекта = «ОбортноСальдоваяВедомость»
Или ИдентификаторОъекта = «ОборотыСчета»
Или ИдентификаторОъекта = «ГлавнаяКнига»
Или ИдентификаторОъекта = «ШахматнаяВедомость»;
КонецФункции
В ветках кода следует избегать дублирования кода, т.е. если с разными условиями выполняются одинаковые операторы, то эти два блока следует объединить.
Пример:
Функция СерверныйМодульМенеджера(Имя) // 1
ОбъектНайден = Ложь; // 0
ЧастиИмени = СтрРазделить(Имя, "."); // 0
Если ЧастиИмени.Количество() = 2 Тогда // 1
ИмяВида = ВРег(ЧастиИмени[0]); // 0
ИмяОбъекта = ЧастиИмени[1]; // 0
Если ИмяВида = ВРег("Константы") Тогда // 1
Если Метаданные.Константы.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
ИначеЕсли ИмяВида = ВРег("РегистрыСведений") Тогда // 1
Если Метаданные.РегистрыСведений.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
Иначе // 1
ОбъектНайден = Ложь; // 0
КонецЕсли; // 0
КонецЕсли; // 0
Если Не ОбъектНайден Тогда // 1
ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( // 0
НСтр("ru = 'Объект метаданных ""%1"" не найден, // 0
|либо для него не поддерживается получение модуля менеджера.'"), // 0
Имя); // 0
КонецЕсли; // 0
УстановитьБезопасныйРежим(Истина); // 0
Модуль = Вычислить(Имя); // 0
F = ?(Условие, ИСТИНА, НЕОПРЕДЕЛЕНО); // 1
А = ?(Условие, ИСТИНА, ?(Условие2, ЛОЖЬ, НЕОПРЕДЕЛЕНО)); // 2
M = ИСТИНА ИЛИ 7; // 1
Возврат Модуль; // 0
КонецФункции // итог 12
Функция СерверныйМодульМенеджера(Имя) // 1
ОбъектНайден = Ложь; // 0
ЧастиИмени = СтрРазделить(Имя, "."); // 0
Если ЧастиИмени.Количество() = 2 Тогда // 1
ИмяВида = ВРег(ЧастиИмени[0]); // 0
ИмяОбъекта = ЧастиИмени[1]; // 0
Если ИмяВида = ВРег("Константы") Тогда // 1
Если Метаданные.Константы.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
ИначеЕсли ИмяВида = ВРег("РегистрыСведений") Тогда // 1
Если Метаданные.РегистрыСведений.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
Иначе // 1
ОбъектНайден = Ложь; // 0
КонецЕсли; // 0
КонецЕсли; // 0
Если Не ОбъектНайден Тогда // 1
ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( // 0
НСтр("ru = 'Объект метаданных ""%1"" не найден, // 0
|либо для него не поддерживается получение модуля менеджера.'"), // 0
Имя); // 0
КонецЕсли; // 0
УстановитьБезопасныйРежим(Истина); // 0
Модуль = Вычислить(Имя); // 0
F = ?(Условие, ИСТИНА, НЕОПРЕДЕЛЕНО); // 1
А = ?(Условие, ИСТИНА, ?(Условие2, ЛОЖЬ, НЕОПРЕДЕЛЕНО)); // 2
M = ИСТИНА ИЛИ 7; // 1
Возврат Модуль; // 0
КонецФункции // итог 12
Функция СерверныйМодульМенеджера(Имя) // 1
ОбъектНайден = Ложь; // 0
ЧастиИмени = СтрРазделить(Имя, "."); // 0
Если ЧастиИмени.Количество() = 2 Тогда // 1
ИмяВида = ВРег(ЧастиИмени[0]); // 0
ИмяОбъекта = ЧастиИмени[1]; // 0
Если ИмяВида = ВРег("Константы") Тогда // 1
Если Метаданные.Константы.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
ИначеЕсли ИмяВида = ВРег("РегистрыСведений") Тогда // 1
Если Метаданные.РегистрыСведений.Найти(ИмяОбъекта) <> Неопределено Тогда // 1
ОбъектНайден = Истина; // 0
КонецЕсли; // 0
Иначе // 1
ОбъектНайден = Ложь; // 0
КонецЕсли; // 0
КонецЕсли; // 0
Если Не ОбъектНайден Тогда // 1
ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( // 0
НСтр("ru = 'Объект метаданных ""%1"" не найден, // 0
|либо для него не поддерживается получение модуля менеджера.'"), // 0
Имя); // 0
КонецЕсли; // 0
УстановитьБезопасныйРежим(Истина); // 0
Модуль = Вычислить(Имя); // 0
F = ?(Условие, ИСТИНА, НЕОПРЕДЕЛЕНО); // 1
А = ?(Условие, ИСТИНА, ?(Условие2, ЛОЖЬ, НЕОПРЕДЕЛЕНО)); // 2
M = ИСТИНА ИЛИ 7; // 1
Возврат Модуль; // 0
КонецФункции // итог 12
В случае когда используется несколько конструкций ЕслиИначе, то после последней обязательно должна следовать ветка с Иначе.
Это защитное программирование. Такие конструкции устойчивы к дальнейшим изменениями и не маскируют возможные ошибки.
В ветке Иначе либо должны быть действия, либо комментарий с описанием причин отсутствия каких-либо действий.
Пример:
// Неправильно:
Если ТипЗнч(ВходящийПараметр) = Тип(«Структура») Тогда
Результат = ЗаполнитьПоСтруктуре(ВходящийПараметр);
ИначеЕсли ТипЗнч(ВходящийПараметр) = Тип(«ДокументСсылка.ВажныйДокумент») Тогда
Результат ЗаполнитьПоДокументу(ВходящийПараметр);
КонецЕсли;
//Правильно:
Если ТипЗнч(ВходящийПараметр) = Тип(«Структура») Тогда
Результат = ЗаполнитьПоСтруктуре(ВходящийПараметр);
ИначеЕсли ТипЗнч(ВходящийПараметр) = Тип(«ДокументСсылка.ВажныйДокумент») Тогда
Результат ЗаполнитьПоДокументу(ВходящийПараметр);
Иначе
ВызватьИсключение «Передан параметр неверного типа»;
КонецЕсли;
Магические даты и числа
Магические даты и числа называются такими, происхождение которых в коде невозможно без погружения в задачу, которая решалась, когда код был написан.
Происхождение любой даты или числа в коде должны быть понятны без погружения в контекст задачи, которая решена данным кодом.
Пример:
// Неправильно:
Если ТекДата > ’20151021’ Тогда
ХоверБордБудетИзобретен = Истина;
КонецЕсли;
// Правильно:
ПредстказаннаяДата = ’20151021’;
Если ТекДата > ПредстказаннаяДата Тогда
ХоверБордБудетИзобретен = Истина;
КонецЕсли;
// Неправильно:
Функция ПопадаетВИнтервал(Длительность)
Возврат Длительность < 10 * 60 * 60;
КонецФункции
// Правильно:
Функция ПопадаетВИнтервал(ДлительностьВСекундах)
МинутВЧасе = 60;
СекундВМинуте = 60;
СекундВЧасе = СекундВМинуте * МинутВЧасе;
ЧасовВИнтервале = 10;
Возврат ДлительностьВСекундах < ЧасовВИнтервале * СекундВЧасе;
КонецФункции
Использование тернарного оператора
Использование тернарного оператора оправдано, когда условие простое и одно.
Пример:
// Неправильно:
Результат = ?(х % 15 <> 0, ?(х % 5 <> 0, ?(х % 3 <> 0, х, «Fizz»), «Buzz»), «FizzBuzz»);
В данном примере очень сложный набор условий, поэтому использование тернарного оператора не оправдано, требуется использовать условный оператор.
// Правильно:
Если х % 15 = 0 Тогда
Результат = «FizzBuzz»;
ИначеЕсли х % 3 = 0 Тогда
Результат = «Fizz»;
ИначеЕсли х % 5 = 0 Тогда
Результат = «Buzz»;
Иначе
Результат = х;
КонецЕсли;
Многократное использование одинаковых строковых литералов
При многократном использовании одинаковых строковых литералов рекомендуется выделить его в отдельную функцию или переменную.
Пример:
// Неправильно:
Процедура ПроверитьПараметр(Параметр)
Если Параметр = «ВРег» Тогда
Результат = ВРег(«Строковое значение»);
ИначеЕсли Параметр = «СокрЛП» Тогда
Результат = СокрЛП(«Строковое значение»);
Иначе
Результат = Нрег(«Строковое значение»);
КонецЕсли
КонецПроцедуры
// Правильно:
Процедура ПроверитьПараметр(Параметр)
Если Параметр = «ВРег» Тогда
Результат = ВРег(СтроковоеЗначение());
ИначеЕсли Параметр = «СокрЛП» Тогда
Результат = СокрЛП(СтроковоеЗначение());
Иначе
Результат = Нрег(СтроковоеЗначение());
КонецЕсли
КонецПроцедуры
Функция СтроковоеЗначение()
Возврат «СтроковоеЗначение»;
КонецФункции
Хранение информации в коде
Хранить информацию о сетевых адресах, логинах и паролях и прочую недопустимо по причинам:
— допуск к данной информации всех программистов, что недопустимо по требованиям безопасности;
— при изменении настроек требуется выпускать дополнительный релиз;
— для тестов часто используют другие настройки.
Пример:
// Неправильно:
СетевойАдрес = «192.168.1.1»;
КаталогОбмена = «с:/Обмен»;
Логин = «Администратор»;
Пароль = «12345»;
// Правильно:
ПараметрыАутентификации = СлужебныеПараметры. ПараметрыАутентификации (ТекущийКонтур());
Для хранения информации должна быть создана подсистема хранения служебных данных. В таком случае изменение данных параметров будет доступно в режиме предприятия, соответственно, доступ можно разделить по ролям.
Если создать подсистему хранения служебной информации нет возможности, в таком случае эту информацию рекомендуется выделить в отдельный общий модуль.
Результат выполнения функции
Функция всегда возвращает результат, даже если в теле функции не прописан возврат.
В случае если в функции не прописан «Возврат», то вернется значение «Неопределено».
По стандарту рекомендуется обязательно прописывать «Возврат» функции.
Если в функции ветвление условного оператора Если и в каждой ветви прописан «Возврат», то рекомендуется вместо этого прописать в ветвях ВозвращаемоеЗначение, а возврат функции прописать в конце функции.
Пример:
// Неправильно:
Функция ЗначениеПоПараметру(Параметр) Экспорт
Если Параметр = 1 Тогда
Возврат 2;
ИначеЕсли Параметр = 2 Тогда
Возврат 4;
Иначе
Возврат Неопределено;
КонецЕсли;
КонецФункции
// Правильно:
Функция ЗначениеПоПараметру(Параметр) Экспорт
ВозвращаемоеЗначение = 0;
Если Параметр = 1 Тогда
ВозвращаемоеЗначение = 2;
ИначеЕсли Параметр = 2 Тогда
ВозвращаемоеЗначение = 4;
Иначе
ВозвращаемоеЗначение = Неопределено;
КонецЕсли;
Возврат ВозвращаемоеЗначение;
КонецФункции

Comments
So empty here ... leave a comment!