Любой разработчик рано или поздно сталкивается с необходимостью использования сравнения на константную строку в коде, а также с флаговыми переменными - когда необходимо сделать ветвление по сложному условию. Как сделать все это наиболее оптимально?
Например - при загрузке некоего файла, в зависимости от статуса загружаемой абстрактной заявки надо выполнить некое действие:
// Чтение файла Excell, получение и обработка статуса заявки текущей строки
СтатусЗаявки = СокрЛП(Excel.Cells(1, Сч).Text);
Если СтатусЗаявки = "На утверждении" Тогда
//...
ИначеЕсли СтатусЗаявки = "Товар в пути" Тогда
//...
ИначеЕсли СтатусЗаявки = "К отгрузке" Тогда
//...
КонецЕсли;
Здесь пока все очень просто, и вроде бы на виду. Но вот поступает дополнительно пожелание от клиента, и где-нибудь на 100 строк ниже понадобилось добавить:
Если СтатусЗаявки = "к отгрузке" Тогда
//...
ИначеЕсли СтатусЗаявки = "Отказ" Тогда
//...
КонецЕсли;
Наметанный глаз сразу увидит будущий косяк. Строка "к отгрузке" не равна "К отгрузке" - налицо ошибка программиста (буква "К" <> "к"). Можно лечить это приведением переменной к нижнему регистру. Посмотрите какой код мы после этого получим:
СтатусЗаявки = нРег(СокрЛП(Excel.Cells(1, Сч).Text));
Если СтатусЗаявки = "на утверждении" Тогда
//...
ИначеЕсли СтатусЗаявки = "товар в пути" Тогда
//...
ИначеЕсли СтатусЗаявки = "к отгрузке" Тогда
//...
КонецЕсли;
//...
// Еще любят делать вот так, тысячи раз вызывая функцию при обработке данных:
Если СтатусЗаявки = нРег("К отгрузке") Тогда
//...
ИначеЕсли СтатусЗаявки = нРег("Отказ") Тогда
//...
КонецЕсли;
При таком подходе код теряет в читабельности. Плюс непонятно - каким же был исходный текст. Сравнение на строку всегда связано с грязным кодом - это потеря читабельности, и высокая вероятность ошибки. Можно банально опечататься, набрать английскую "с" вместо русской, поставить лишний пробел и т.п.
Еще один неочевидный, но на самом деле очень неудобный момент - мы не можем видеть всех возможных значений переменной. Т.е. в примере значение поля "СтатусЗаявки" уже может иметь четыре значения. А вдруг, где-то в коде есть и пятое, и шестое?.. Зачем нужно их видеть - затем, чтобы убедиться, что все возможные значения учтены. К примеру возникли какие-то ошибки в вновь пришедшем на загрузку файле - добавилось еще одно значение статуса заявки - "в наборе". Чтобы убедиться что это действительно новое значение - при таком подходе понадобится прошерстить весь имеющийся код.
А что если не сравнивать тупо на строку? Что если загнать все возможные значения входящих статусов в структуру?
СтатусыЗаявок = Новый Структура;
СтатусыЗаявок.Вставить("НаУтверждении" , "На утверждении");
СтатусыЗаявок.Вставить("ТоварВПути" , "Товар в пути");
СтатусыЗаявок.Вставить("КОтгрузке" , "К отгрузке");
СтатусыЗаявок.Вставить("Отказ" , "Отказ");
//... Цикл по строкам
СтатусЗаявки = СокрЛП(Excel.Cells(1, Сч).Text);
Если СтатусЗаявки = СтатусыЗаявок.НаУтверждении Тогда
//...
ИначеЕсли СтатусЗаявки = СтатусыЗаявок.ТоварВПути Тогда
//...
ИначеЕсли СтатусЗаявки = СтатусыЗаявок.КОтгрузке Тогда
//...
КонецЕсли;
Уже все становится интереснее, не правда ли? Но пойдемте дальше. К примеру код по загрузке очень большой, разделен на пять разных методов, и объявлять в каждом структуру - это дублирование кода. Можно сделать ее глобальной переменной, но сколько может быть таких структур? И ведь каждая может состоять из большого количества элементов, все это будет в теле основной программы (разделе инициализации). Гораздо логичнее объявить метод "СтатусыЗаявок()". Этот метод будет возвращать структуру, и в коде пишем уже:
СтатусыЗаявок = СтатусыЗаявок();
Если Статус = СтатусыЗаявок.НаУтверждении Тогда
// ...
КонецЕсли;
Но это означает, что каждое обращение к методу создает новую структуру. Если метод дергается два-три раза, это не проблема. Но если тысячи, при обходе большой таблицы.. Это может породить тормоза при частом использовании метода "Вставить". Выход из такой ситуации - кэширование.
Если работа ведется в обработке - то можно объявить глобальную переменную, я ее привык называть "мСтруктураКэшДанных" (префикс "м" - флаг глобальной переменной). И кэшировать в нее все программные перечисления. Кроме того, по рекомендации коллеги BUDIVAL, которую он отправил в ЛС - лучше возвращать фиксированную структуру. Тогда можно быть 100% уверенным что коллекция заполняется именно в отвечающем за это методе. Вот как в итоге может выглядеть результирующий код:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ВнешняяОбработка.ЗагрузкаЗаявок.МодульОбъекта
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#Область ОписаниеПеременных
Перем мСтруктураКэшДанных;
#КонецОбласти
#Область ПрограммныйИнтерфейс
Функция ДанныеФайлаУспешноСчитаны() Экспорт
мСтруктураКэшДанных.Очистить(); // кэш при развитии кода может использоваться ниже по стеку
// если он работает на хранение не только Статусов, а и например
// различных вспомогательных таблиц - здесь его нужно очищать
// чтобы исключить ошибки из-за ранее закешированных значений
// при повторном вызове извне текущего экспортного метода
//...
СтатусЗаявки = СокрЛП(Excel.Cells(1, Сч).Text);
Статусы = СтатусыЗаявок();
Если СтатусЗаявки = Статусы.НаУтверждении Тогда
//...
ИначеЕсли СтатусЗаявки = Статусы.ТоварВПути Тогда
//...
ИначеЕсли СтатусЗаявки = Статусы.КОтгрузке Тогда
//...
КонецЕсли;
Возврат Истина;
КонецФункции
Функция КонтрагентыОтказныхЗаявок() Экспорт
мСтруктураКэшДанных.Очистить();
//...
СтатусыЗаявок = СтатусыЗаявок();
Если СтатусЗаявки = СтатусыЗаявок.Отказ Тогда
//...
КонецЕсли;
КонецФункции
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
#Область ПрограммныеПеречисления
Функция СтатусыЗаявок()
Перем Результат;
Ключ = "СтатусыЗаявок";
мСтруктураКэшДанных.Свойство(Ключ, Результат);
Если Результат = Неопределено Тогда
Результат = Новый Структура;
Результат.Вставить("НаУтверждении" , "На утверждении");
Результат.Вставить("ТоварвПути" , "Товар в пути");
Результат.Вставить("КОтгрузке" , "К отгрузке");
Результат.Вставить("Отказ" , "Отказ");
мСтруктураКэшДанных.Вставить(Ключ, Результат);
КонецЕсли;
Возврат Результат;
КонецФункции
#КонецОбласти
#КонецОбласти
#Область Инициализация
мСтруктураКэшДанных = Новый Структура;
#КонецОбласти
Была у меня задача, в которой было необходимо работать с событиями журнала регистрации. Вместо того чтобы писать кракозябро-подобные события ЖР, я завел программное перечисление:
Функция СобытияЖР()
Перем Результат;
Ключ = "СобытияЖР";
мСтруктураКэшДанных.Свойство(Ключ, Результат);
Если Результат = Неопределено Тогда
Результат = Новый Структура;
Результат.Вставить("СеансАутентификация", "_$Session$_.Authentication");
Результат.Вставить("СеансНачало", "_$Session$_.Start");
Результат.Вставить("СеансЗавершение", "_$Session$_.Finish");
Результат.Вставить("ДанныеИзменение", "_$Data$_.Update");
Результат.Вставить("ДанныеПроведение", "_$Data$_.Post");
Результат.Вставить("ТранзакцияНачало", "_$Transaction$_.Begin");
Результат.Вставить("ТранзакцияОкончание", "_$Transaction$_.Commit");
мСтруктураКэшДанных.Вставить(Ключ, Результат);
КонецЕсли;
Возврат Результат;
КонецФункции
// далее в коде обращался как:
// СобытияЖР = СобытияЖР();
// Если СобытиеЖР = СобытияЖР.СеансАутентификация Тогда
// //...
//
// ИначеЕсли СобытиеЖР = СобытияЖР.СеансНачало Тогда
// //...
//
// ИначеЕсли СобытиеЖР = СобытияЖР.СеансЗавершение Тогда
// //... и т.д.
//
// КонецЕсли;
//
Иногда необходимы вспомогательные флаговые переменные. Они тоже прекрасно реализуются с этим подходом. Например как-то была необходимость подобрать контрагента для указания в бизнес-процессе. Не буду сейчас вдаваться в методологию этого примера, суть была в том что контрагента необходимо было определять - как значение документа определенного типа, либо как значение свойства номенклатуры (если она одна в табличной части), либо как значение свойства организации. Реализовал я это, введя флаговую переменную, примерно так:
Перем мИсточник;
Перем мСтруктураКэшДанных;
Функция ВидыОпределенияКонтрагента()
Перем Результат;
Ключ = "ВидыОпределенияКонтрагента";
мСтруктураКэшДанных.Свойство(Ключ, Результат);
Если Результат = Неопределено Тогда
Результат = Новый Структура;
Результат.Вставить("Документ", "Документ");
Результат.Вставить("Номенклатура", "Номенклатура");
Результат.Вставить("Организация", "Организация");
мСтруктураКэшДанных.Вставить(Ключ, Результат);
КонецЕсли;
Возврат Результат;
КонецФункции
Функция СвойстваИсточника()
Перем Результат;
Ключ = "СвойстваИсточника";
мСтруктураКэшДанных.Свойство(Ключ, Результат);
Если Результат = Неопределено Тогда
ВидыОпределенияКонтрагента = ВидыОпределенияКонтрагента();
СвойстваИсточника = Новый Структура;
Если ТипЗнч(мИсточник) = Тип("ДокументОбъект.РеализацияТоваровУслуг") Тогда
ВидОпределенияКонтрагента = ВидыОпределенияКонтрагента.Документ;
ИначеЕсли ОбщегоНазначения.ЕстьТабЧастьДокумента("Товары", мИсточник.Метаданные()) И мИсточник.Товары.Количество() = 1 Тогда
ВидОпределенияКонтрагента = ВидыОпределенияКонтрагента.Номенклатура;
СвойстваИсточника.Вставить("Номенклатура", мИсточник.Товары[0].Номенклатура);
Иначе
ВидОпределенияКонтрагента = ВидыОпределенияКонтрагента.Организация;
КонецЕсли;
СвойстваИсточника.Вставить("ВидОпределенияКонтрагента", ВидОпределенияКонтрагента);
Результат = Новый ФиксированнаяСтруктура(СвойстваИсточника);
мСтруктураКэшДанных.Вставить(Ключ, Результат);
КонецЕсли;
Возврат Результат;
КонецФункции
// использование:
ВидыОпределенияКонтрагента = ВидыОпределенияКонтрагента();
СвойстваИсточника = СвойстваИсточника();
Если СвойстваИсточника.ВидОпределенияКонтрагента = ВидыОпределенияКонтрагента.Документ Тогда
Контрагент = мИсточник.Контрагент;
ИначеЕсли СвойстваИсточника.ВидОпределенияКонтрагента = ВидыОпределенияКонтрагента.Номенклатура Тогда
Контрагент = ЗначениеСвойстваОбъекта(СвойстваИсточника.Номенклатура, ПланыВидовХарактеристик.СвойстваОбъектов.ОсновнойПоставщик);
ИначеЕсли СвойстваИсточника.ВидОпределенияКонтрагента = ВидыОпределенияКонтрагента.Организация Тогда
Контрагент = ЗначениеСвойстваОбъекта(мИсточник.Организация, ПланыВидовХарактеристик.СвойстваОбъектов.ОсновнойПоставщик);
КонецЕсли;
Уже довольно давно использую этот прием, благодаря которому разработка стала гораздо эффективнее. Использование программных перечислений гармонично приводит к:
-
Ленивой инициализации - расчет значения будет выполнен только один раз - при первом обращении (см. вики)
-
устранению дублирования кода
-
повышению читабельности кода
Продолжение здесь Программные перечисления, ч.2: приемы кэширования при разработке