Вместо предисловия - парочка флешбэков.
1. Однажды мне нужно было перенести часть более-менее типовой конфигурации заказчика на самописную конфигурацию. И всё бы ничего, но из кода во все стороны торчали "оборванные провода", вроде:
ОбщегоНазначенияКлиентСервер.СообщитьПользователю
и
ОбщегоНазначения.ЗначениеРеквизитаОбъекта
и ещё десятками других вызовов. Обидно было то, что тянулось это всё в целую кучу модулей, и при этом из каждого модуля использовалась одна-две функции. Переносить все эти модули - значит тащить целую кучу ненужных функций да ещё и цеплять другие модули, которые будут вызываться из этих первых. Переносить только эти функции - да, можно, но, во-первых, большинство из них уже были реализованы в каком-то виде в конфигурации-приёмнике, во-вторых, самое "приятное" было в том, чтобы ходить по коду и выискивать их вызовы. Кто-то скажет, что выискивать можно по сообщениям ошибок. И тут мы перейдём ко второму флешбэку.
2. Когда понадобилось портировать нашу разработку "Все службы доставки в Вашей 1С без изменения конфигурации" с УТ 11.2 на УТ 11.1, оказалось, что есть функции, которые в обеих конфигурациях одинаковые, но по сути разные. Например:
УправлениеКонтактнойИнформациейКлиент.ОткрытьФормуКонтактнойИнформации
в 11.1 принимает 5 параметров, а в 11.2 - три, при этом почти все они необязательные, т.е. на несоответствие количества параметров, если их три в вызове, ошибки не будет, но работать тоже не будет.
3. А ещё бывает так, что функция в УТ 11.1 называется ПОЧТИ также, как в УТ 11.2. Получаешь ошибку, переносишь функцию из одной конфигурации, а там уже есть такая же, только называется на четыре буквы короче.
4. Но самый 1С-стайл - это функции, которые называются одинаково, а действуют по-разному! Например:
УправлениеКонтактнойИнформацией.ПроверитьАдрес
Пока через неделю использования в голову не начнёт закрадываться подозрение, что что-то работает не так, ты туда не полезешь, а искусственный интеллект наших компьютеров ещё не умеет расшифровывать псевдонатуральный интеллект некоторых живых людей.
MVC - WTF?
Что такое MVC лучше расскажет Википедия. В самом общем смысле это идея о том, что Интерфейс не должен знать, что делается в Базе, а Базе должно быть совершенно наплевать, что там в Интерфейсе и вообще интерфейсов может быть много разных или не быть вообще. А общаться База и Интерфейс должны через маленькую дырочку - Контроллер. Зачем это нужно? Чтобы сделать и базу, и интерфейс максимально независимыми, чтобы для изменения базы не нужно было менять все интерфейсы, а при изменении интерфейсов не нужно было трогать базу.
Несмотря на то, что многие программисты в 1С пришли из других языков, где о такой штуке знают, в среде 1С-программирования она не распространена вообще никак. Из нажатия кнопки или обработчика событий элемента формы грязные пальцы вызовы лезут прямо в базу и бизнес-логику, а для изменения реквизитов базы иногда проще переписать конфигурацию с нуля.
Некоторые робкие потуги разделить Логику и Представление уже есть в типовых конфигурациях. Но образцов для подражания там ещё очень немного. Библиотеки, которые прочно вошли в жизнь других языков программирования, начали появляться и в 1С. В основном это - БСП. И там как раз очень не хватает... "библиотечности", возможности безболезненно взять какую-то область (механизм), безболезненно вынуть и вставить в другую среду, без вычищения "торчащих проводов".
На самом деле MVC я здесь задел "по касательной", как одно из воплощений идеи о том, что для упрощения поддержки и интеграции внешнее взаимодействие лучше вынести в ту самую прослойку - Контроллер.
Обратно в 1С
Вдоволь намучившись, в один прекрасный день я выпрыгнул в окно вынес все вызовы внешних модулей в отдельные функции в отдельной области модуля. Примерно так:
#Область ОбщийКод
Процедура ЧёНибудьСделать(КакиеТоПараметры)
Реквизит = Внешний_ЗначениеРеквизитаОбъекта(НашаСсылка, "НашРеквизит");
Внешний_СообщитьПользователю("Вот такой реквизит: " + Реквизит);
КонецФункции
#КонецОбласти
#Область ВнешниеВызовы
Процедура Внешний_СообщитьПользователю(Текст)
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(Текст);
КонецПроцедуры
Функция Внешний_ЗначениеРеквизитаОбъекта(Ссылка, Реквизит)
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, Реквизит);
КонецФункции
#КонецОбласти
Что получилось?
- В любой момент времени известно, сколько и какие есть из этого модуля внешние вызовы. При переносе такого модуля ещё до первого теста можно провести 90% работ по интеграции, проверить все вызовы на их соответствия ожиданиям, переопределить то, что изменилось, проверить те самые функции, которые одинаково называются, но действуют по-разному и прочие подводные камни.
- При любых ошибках во время интеграции можно прежде всего смотреть на небольшую область кода с внешними вызовами. Ведь внутреннее взаимодействие в коде не поменялось, ошибки, скорее всего, именно на стыках с внешней средой. А все стыки у нас теперь связаны в один пучок, не нужно ходить по всему коду и искать "торчащие провода".
- Зачастую внешние вызовы - это функции общего назначения, вроде сообщений пользователю и прочих. При этом они же зачастую могут меняться. Да и вызываются они обычно из большого количества мест. И здесь тоже всё стало проще - нужно, например, не сообщать пользователю, а писать в журнал регистрации? Или не вместо, а вместе? Пожалуйста - меняем одну функцию и вуаля.
- Простой, красивый, легко читаемый, легко дорабатываемый код. Мухи у нас отдельно от котлет, появляется некое подобие Модели, которая работает сама по себе, и Контроллера, который отвечает за внешнее взаимодействие.
Спасибо за уделённое время, надеюсь, кому-то данная информация поможет в разработке.