Легаси он везде
Каждая информационная система, какой бы продвинутой в части архитектуры она не была, сталкивается с различными проблемами кодовой базы:
- Использование устаревших возможностей платформы
- Дублирование кода в ходе разработки (особенно командной)
- Появление неиспользуемого кода отдельных процедуры и функций, так и целых модулей
- Появление "мертвого" кода в ходе обновления типовых конфигураций, когда разработчик решает не удалять старые процедуры, т.к. он их использовал в своих решениях.
- И много чего еще.
На все эти вопросы есть множество ответов, методик разработки, проработки архитектуры и много, много, много еще интересного. И, конечно же, мы всего этого здесь не коснемся. Рассмотрим лишь маленькую тему - как узнать какой код в конфигурации (а также внешних обработках) используется, а какой нет. Это может показаться не такой большой проблемой, о которой нужно писать, но это лишь на первый взгляд.
Спустя годы поддержки конфигурации и разрастания мира легаси для нее - боль может стать невыносимой как в части сопровождения, так и в части разработки.
Зачем вообще этим заниматься
Работает - не трогай! Возможно, это первое, что приходит на ум в таком случае. Зачем удалять неиспользуемый код, если он не создает багов и вообще сейчас все отлично работает.
От части - это правда. Нет смысла бросаться и удалять в конфигурации все, что не используется в данный момент. Особенно, если это типовая конфигурация, в которой Вы не используете общие модули или отдельные процедуры подсистем из БСП. Это же не значит, что их можно удалить :)
Очистка кода от неиспользуемых процедур и функций или целых модулей необходимо только:
- Вы на поддержке и регулярно устанавливаете типовые обновления. При этом в общих модулях от поставщика почему-то не удалили процедуры и функции предыдущих релизов, хотя по факту они не используются в свежих версиях конфигурации. Да, они могут использоваться в самописных модулях или внешних отчетах / обработках, но возможно это повод их отрефакторить и привести к актуальному виду.
- В конфигурации огромный объем кода, который вроде бы никто не использует, но он остался с давних времен. Может с этапа внедрения какой-нибудь подсистемы или более глобальных событий. В любом случае, со временем появится путаница какие процедуры и функции стоит использовать и сопровождать, дорабатывать, а какие нет.
- Сталкивался с такими запущенными решениями. Очень часто можно было встретить такое поведение разработчиков: никто не знает что это за функция, где она используется и можно ли ее вообще изменять. Поэтому я добавлю новую! Со своими фичами и багами! :) Думаю, Вы понимаете к чему это потом приводит...
- Вы делаете тиражируемое решение и внутренне качество должно быть на уровне. С появлением неиспользуемых модулей в таком решении его сопровождение может стать проблемным. Как для Вас, так и для клиентов из-за нескончаемого потока багов...
Список можно продолжать. Главное понять, что поддержание кодовой базы в тонусе - это стратегически правильное решение. Но мы остановимся и перейдем непосредственно к способам поиска и определения использования кода в модулях.
От простого...
Допустим, из-за причин выше у Вас появилась необходимость удалить процедуру / функцию из конфигурации или внешних отчетов / обработок. У Вас могут быть и другие причины :) В любом случае, нам уже известен модуль, где располагается эта процедура / функция, названия метаданных и содержимое этого модуля. Как действовать дальше?
Метод "На продакшене все и узнаем"
Самым распространенным подходом, к сожалению, остается метод тестирования на рабочем окружении. Разработчик вроде бы проверил, что удаляемая часть кодовой базы нигде не используется, "потыкал" функционал конфигурации, где что знал и решил, что пора это "выкатывать" на продакшн.
Время покажет, где этот код ранее использовался! Да и что страшного может произойти? Ведь в случае ошибки ее быстро можно поправить, выгнать пользователей из базы и обновиться. Да и динамическое обновление никто не запрещает использовать.
В общем, не стоит это того, чтобы проверять корректность вносимых изменений. Пользователи - лучшие тестировщики! Хаос и непредсказуемый результат - то что мы любим!
Мы этот вариант особо описывать не будем, ведь с ним и так все понятно. Надеюсь что эти "bad practice" Вы не используйте вне зависимости от масштаба информационной системы / базы.
Метод "Ручной поиск"
Следующий способ уже лучше, но надежность тоже не всегда высока. Чтобы узнать используется ли какой-либо общий модуль или конкретная функция просто идем в глобальный поиск в конфигураторе и ... ищем!
Тут стоит отметить, что поиск в самой конфигурации может быть недостаточным, т.к. обычно в базе еще используется множество внешних отчетов и обработок и их необходимо выгрузить и также включить в глобальный поиск. А теперь еще есть и расширения, в которых также нужно выполнять проверку. Выгрузить обработки можно простыми скриптами или с помощью отдельных инструментов, а затем на вкладке "Файлы" добавить каталог с ними для включения в поиск. Расширения, как видно на анимации выше, включаются в поиск на владке "Конфигурация" как и основная конфигурация информационной базы.
А вот так выглядит поиск по файлам внешних отчетов и обработок. Заметите знакомые обработки, попавшие в поиск? :)
В любом случае, с глобальным поиском может быть множество ложных результатов, если он выполнялся по какому-либо часто повторяющемуся слову в других модулях. Желательно поиск делать по какой-то уникальной части программного кода или полному имени к процедуре / функции.
Например, если нужно найти где используется функция "ТолькоЦифрыВСтроке" из общего модуля "СтроковыеФункцииКлиентСервер", то в глобальном поиске лучше использовать следующее значения поиска:
- "СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке(" - то есть полный путь к вызову функции.
- ".ТолькоЦифрыВСтроке(" - только имя функции.
Поиск по полному имени не всегда может помочь, если в коде используется что-то вроде этого:
// ...
СтроковыеФункцииКлиентСервер
.ТолькоЦифрыВСтроке(
// ...
Плюс ко всему, глобальный поиск не сможет помочь, если вызов идет каким-нибудь таким способом:
Модуль = ОбщегоНазначения.ОбщийМодуль("СтроковыеФункцииКлиентСервер");
Если Модуль <> Неопределено Тогда
Результат = Модуль.ТолькоЦифрыВСтроке(КакоеТоЗначение);
КонецЕсли;
Ситуация еще может усложняться, если к информационной базе идет вызов со стороны других систем и код на исполнение формируется на стороне этих систем. То есть этот код хранится не в конфигурации или внешних отчетах и обработках, а именно в другой системе. Такое может быть, если был добавлен веб-сервис для выполнения произвольного кода в базе и им начали пользоваться для решения повседневных задач. Да здравствует хаос! Теперь в нашей базе выполняется код, которых нам даже неизвестен и мы не можем нигде его найти! Никакой глобальный поиск тут не поможет. А если он вызывает искомый фрагмент кода, то при его удалении из конфигурации мы узнаем об этом только на рабочей базе.
Конечно, использовать такие решения, когда программный код на выполнение генерируется в другой системе - это настоящий ад сопровождения и поддержания работоспособности. Многим разработчикам такой подход кажется инновационным и с энтузиазмом внедряется, а потом...
Частным примером такой проблемы могут быть правила конвертации данных, в которых тексты обработчиков хранятся в самих правилах и глобальный поиск тоже не сможет помочь. Весь мир бореться с выполнением произвольного кода, но не в мире 1С, где это нормальное дело.
Но попытка хотя бы вручную найти места использования программного кода уже лучше, чем тестирование на продакшене. Но неужели нет более надежного способа безболезненного удаления устаревших или неактуальных частей программного кода?
Метод "Добавить метку"
Идем дальше, становимся более осторожными. Особенно если уже набили шишек на рабочей базе и потеряли премию. Есть способ точно узнать используется ли модуль, процедура, функция или отдельный фрагмент кода с практически 100% гарантией, но это потребует некоторого времени. Вернемся к той же функции, что и в предыдущем примере:
РезультатПроверки = СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке(ИсходнаяСтрока);
Содержимое функции, которое я взял из "Библиотеки стандартных подсистем", выглядит так:
// Проверяет, содержит ли строка только цифры.
//
// Параметры:
// Значение - Строка - проверяемая строка.
// Устаревший - Булево - устаревший параметр, не используется.
// ПробелыЗапрещены - Булево - если Ложь, то в строке допустимо наличие пробелов.
//
// Возвращаемое значение:
// Булево - Истина - строка содержит только цифры или пустая, Ложь - строка содержит иные символы.
//
// Пример:
// Результат = СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке("0123"); // Истина
// Результат = СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке("0123abc"); // Ложь
// Результат = СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке("01 2 3",, Ложь); // Истина
//
Функция ТолькоЦифрыВСтроке(Знач Значение, Знач Устаревший = Истина, Знач ПробелыЗапрещены = Истина) Экспорт
Если ТипЗнч(Значение) <> Тип("Строка") Тогда
Возврат Ложь;
КонецЕсли;
Если Не ПробелыЗапрещены Тогда
Значение = СтрЗаменить(Значение, " ", "");
КонецЕсли;
Если СтрДлина(Значение) = 0 Тогда
Возврат Истина;
КонецЕсли;
// Если содержит только цифры, то в результате замен должна быть получена пустая строка.
// Проверять при помощи ПустаяСтрока нельзя, так как в исходной строке могут быть пробельные символы.
Возврат СтрДлина(
СтрЗаменить( СтрЗаменить( СтрЗаменить( СтрЗаменить( СтрЗаменить(
СтрЗаменить( СтрЗаменить( СтрЗаменить( СтрЗаменить( СтрЗаменить(
Значение, "0", ""), "1", ""), "2", ""), "3", ""), "4", ""), "5", ""), "6", ""), "7", ""), "8", ""), "9", "")) = 0;
КонецФункции
Считаем, что она используется, если есть хотя бы 1 ее вызов за последние 3 месяца. Период проверки может быть и другим, но обычно 3 месяца дает ответ об использовании кода с гарантией 99%.
Думаю, по названию Вы уже догадались. Нужно добавить сохранение каким-либо способом в логи информацию о вызове. Это можно сделать:
- Через запись в журнал регистрации, если он используется и программный код находится на сервере. На клиенте делать такое тоже возможно, то может потребовать затрат на клиент-серверный вызов или отложенную передачу данных с клиента на сервер для записи в журнал.
- Через замер времени операции с помощью подсистемы "Оценка производительности" из БСП.
- Можно нагородить своих "костылей" и записывать информацию в отдельный регистр сведений.
Используйте то, что Вам больше подходит. В нашем случае рассмотрим два примера. Первый - добавить сохранение записи в журнал регистрации, если вызов выполнен с сервера.
Функция ТолькоЦифрыВСтроке(Знач Значение, Знач Устаревший = Истина, Знач ПробелыЗапрещены = Истина) Экспорт
#Если Сервер Тогда
ЗаписьЖурналаРегистрации("АнализКода.ПроверкаИспользования",
УровеньЖурналаРегистрации.Информация,,,
"Выполнен вызов функции СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке");
#КонецЕсли
// тут продолжение функции ...
КонецФункции
Если же использовать подсистему "Оценка производительности", то сохранение информации о вызове можно сделать так:
Функция ТолькоЦифрыВСтроке(Знач Значение, Знач Устаревший = Истина, Знач ПробелыЗапрещены = Истина) Экспорт
#Если Клиент Тогда
УИДЗамера = ОценкаПроизводительностиКлиент.ЗамерВремени("Использование_НаКлиенте_СтроковыеФункцииКлиентСервер_ТолькоЦифрыВСтроке",Ложь,Ложь);
ОценкаПроизводительностиКлиент.ЗавершитьЗамерВремени(УИДЗамера, Ложь);
#ИначеЕсли Сервер Тогда
НачалоЗамера = ОценкаПроизводительности.НачатьЗамерВремени();
ОценкаПроизводительности.ЗакончитьЗамерВремени("Использование_НаСервере_СтроковыеФункцииКлиентСервер_ТолькоЦифрыВСтроке", НачалоЗамера);
#КонецЕсли
// тут продолжение функции ...
КонецФункции
Вся фишка в том, что при фиксации замера на клиенте он сначала фиксируется в буфере и периодически отправляется на сервер для записи. Так выполнена оптимизация клиент-серверного взаимодействия в подсистеме "Оценка производительности". Подробнее об этой подсистеме мы говори в одной из публикаций.
Через некоторый период времени проверяем собранную информацию, анализируем были ли вызовы и как часто. Остается вопрос: а кто вызвал и откуда? Если посмотреть предыдущие события журнала регистрации или добавить дополнительную информацию в собираемый лог (например, можно получить стэк вызовов программно, но это может привести к замедлению работы). Но можно поступить иначе и использовать следующий подход для выявления факта использования программного кода.
Метод "У меня все в логах записано"
И тут нам на помощь приходит, барабанная дробь, технологический журнал! Для получения информации о вызове функции "ТолькоЦифрыВСтроке" настроим сбор ТЖ с помощью следующей настройки:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
<dump create="false"/>
<log location="F:\Logs1С\" history="168">
<event>
<eq property="name" value="scall"/>
<like property="context" value="%СтроковыеФункцииКлиентСервер%ТолькоЦифрыВСтроке%"/>
</event>
<property name="all"/>
</log>
</config>
Делаем отбор событий исходящего удаленного вызова (SCALL) с отбором по контексту (context). В контексте как-раз и хранится стэк вызовов. В результате в ТЖ будут попадать следующие события:
45:59.073005-1,SCALL,5,process=rphost,p:processName=trade_11_4,OSThread=16444,t:clientID=354,t:applicationName=1CV8C,
t:computerName=YY-COMP,t:connectID=30,SessionID=2,Usr=Администратор (ОрловАВ),AppID=1CV8C,DBMS=DBMSSQL,
DataBase=YY-COMP\trade_11_4,ClientID=362,Interface=b3711610-b248-42aa-a215-5d7150a5f7fd,
IName=IClusterMiscUtils,Method=0,CallID=1893457,MName=isItTimeToGetLocks,DstClientID=3062,
Context='
Форма.Вызов : ВнешняяОбработка.ПроверкаИспользованияКода.Форма.Форма.Модуль.ВызватьНаСервереНаСервере
ВнешняяОбработка.ПроверкаИспользованияКода.Форма.Форма.Форма : 9 : ТестВызова();
ВнешняяОбработка.ПроверкаИспользованияКода.Форма.Форма.Форма : 32 : Результат = СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке("231313А");
ОбщийМодуль.СтроковыеФункцииКлиентСервер.Модуль : 359 : НачалоЗамера = ОценкаПроизводительности.НачатьЗамерВремени();
ОбщийМодуль.ОценкаПроизводительности.Модуль : 21 : Если ОценкаПроизводительностиВызовСервераПовтИсп.ВыполнятьЗамерыПроизводительности() Тогда
ОбщийМодуль.ОценкаПроизводительностиВызовСервераПовтИсп.Модуль : 19 : Возврат Константы.ВыполнятьЗамерыПроизводительности.Получить();'
Причем раз обор по контексту выполнен по шаблону, то уже не важно как именно написан код - с переносами или без. Все равно вызов попадет в логи.
Но для клиентского приложения поиск вызовов отловить сложнее. Событие "SCALL" также может быть отловлено на стороне клиента, но стэк вызовов формируется несколько иначе. В нем нет явной информации о вызове функции "СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке" как на стороне сервера. Вместо этого в стэке присутствует программный клиентский код, влияющий на вызовы. Надежного способа поиска выполнения кода на клиенте я так и не нашел, но радует что клиентская часть чаще всего не является проблемой.
Таким образом, с помощью ТЖ можно найти подробную информацию кто, как часто и откуда вызывает тот или иной код на сервере. Отлавливаться будут все события как для кода из конфигурации или внешних отчетов и обработок (даже если они открыты из файла), так и для тех случаев, когда код для выполнения был прислан из вне. Но стоит учитывать, что даже хорошо поставленный фильтр событий ТЖ может влиять на производительность и нужно подходить к этим настройкам с умом.
Вместо заключения
Мы рассмотрели основных способы проверки использования кода в конфигурации от простого к сложному:
- Проверка на пользователях. Самый ужасный способ :)
- Глобальный поиск по конфигурации, расширениям и файлам внешних отчетов и обработок.
- Через добавления записи события в лог при вызове кода и последующий анализ собранных данных.
- И в конце рассмотрели вариант с использованием технологического журнала.
Может показаться, что все эти способы очень замороченные и на практике их использовать очень проблематично. Ну, кроме первого способа :). На самом деле никто не мешает их комбинировать как это обычно приходится применять мне:
- Сначала ищем использование с помощью глобального поиска и изменяем / удаляем код там где найдем.
- Добавляем запись метки в лог при вызове анализируемого кода.
- Если вызовы до сих пор остаются и не удается найти откуда именно они выполняются, то задействуем технологический журнал.
Анализ полученных данных можно выполнять инструментами, которые Вы привыкли использовать. Мой набор это:
- Для журнала регистрации - отчет Просмотр и анализ журнала регистрации (отчет на СКД).
- Для замеров - Анализ производительности APDEX
- Для технологического журнала - инструменты разработчика и регулярные выражения.
На этом все.
А как Вы решаете подобные задачи? Добро пожаловать в комментарии, если есть что дополнить.
Другие ссылки
- К сожалению, похожих публикаций не заметил. Дайте знать истину в комментариях :)
Авторские разработки
-
Транслятор запросов 1С в SQL - инструмент для трансляции запросов платформы 1С в SQL, а также их диагностики.
-
Просмотр и анализ структуры базы данных (отчет на СКД) - отчет для просмотра и анализа структуры базы данных с поддержкой файловых баз (ограниченный режим), а также баз на SQL Server и PostgreSQL.
-
Просмотр и анализ журнала регистрации (отчет на СКД) - отчет на базе системы компоновки данных (СКД) для просмотра записей журнала регистрации.
-
История работы пользователей (отчет на СКД) - отчет для просмотра истории работы пользователей (СКД, просмотр для любого пользователя).
-
Экспорт журнала регистрации. Набор инструментов (приложения + исходный код) - набор инструментов для экспорта данных журнала регистрации во внешние хранилища для Windows и Linux. Готовые приложения и исходный код.
-
Технические проверки данных регистров бухгалтерии (отчет на СКД) - отчет для технических проверок данных бухгалтерских регистров.
-
Путеводитель по истории релизов - отчет по истории выпуска релизов продуктов фирмы "1С" и анализа информации по обновлениям.