framework_eng/skills/bsl-practices/ssl-patterns/SKILL.md
MUST use WHEN you use or extend functionality of БСП (Standard Subsystems Library). Provides a catalog of ready-made ОбщегоНазначения functions and rules for calling subsystems without duplication.
npx skillsauth add steelmorgan/1c-agent-based-dev-framework ssl-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
БСП code is tested on millions of installations, updated centrally, and familiar to other developers. Duplicating БСП is an anti-pattern.
БСП function signatures — via
get_signature_help.ОбщегоНазначенияand other БСП module functions have many parameters and overloads; do not guess the order or set of arguments. At the call site,get_signature_help(uri, line, character)shows the parameters and overloads of the called method right there — without opening the БСП module definition. Use it when calling any function from the catalog below if you are unsure of the signature.
Before writing your own implementation, check whether БСП already has a ready-made function.
| Function | When to use |
|---------|-------------------|
| ЗначениеРеквизитаОбъекта() | Instead of Ссылка.Реквизит (avoid dot notation) |
| ЗначенияРеквизитовОбъекта() | Several attributes in one call |
| СообщитьПользователю() | Message tied to a field (instead of Сообщить()) |
| МенеджерОбъектаПоСсылке() | Instead of Выполнить("Справочники." + Имя) |
| ПодсистемаСуществует() | Conditional module invocation |
| ОбщийМодуль() | Dynamic call to a БСП module |
| ЭтоСсылка() | Parameter validation |
| СсылкаСуществует() | Check before access |
// ПЛОХО: three database accesses through dot notation
Наименование = КонтрагентСсылка.Наименование;
ИНН = КонтрагентСсылка.ИНН;
Ответственный = КонтрагентСсылка.ОсновнойМенеджер;
// ПРАВИЛЬНО: one access through БСП
РеквизитыКонтрагента = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(
КонтрагентСсылка,
"Наименование, ИНН, ОсновнойМенеджер");
The module contains optimized functions that handle edge cases correctly.
| Function | When to use |
|---------|-------------------|
| ПодставитьПараметрыВСтроку() | An equivalent of СтрШаблон(), with additional checks |
| СтрокаСЧисломПредметов() | Declension: "5 documents", "1 document" |
| ЕстьНедопустимыеСимволы() | Input validation |
| ТолькоЦифрыВСтроке() | Validation of INN, KPP |
| РазложитьСтрокуВМассивПодстрок() | Parsing by delimiter |
// Declension: "1 document", "2 documents", "5 documents"
ТекстОповещения = СтроковыеФункцииКлиентСервер.СтрокаСЧисломПредметов(
КоличествоДокументов,
НСтр("ru = 'документ, документа, документов'"));
The directive &НаКлиентеНаСервереБезКонтекста is available on both client and server.
| Function | Description |
|---------|----------|
| ДополнитьМассив() | Merge two arrays |
| ДополнитьСтруктуру() | Merge two structures |
| СвойствоСтруктуры() | Safe property read (default value if absent) |
| ПроверитьПараметр() | Type validation with an informative error |
// Safe access with a default value
ДатаНачала = ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(
ПараметрыОтчёта, "ДатаНачала", НачалоГода(ТекущаяДатаСеанса()));
navigate_symbol("ЗначенияРеквизитовОбъекта")grep -r "Функция.*КурсВалюты" src/CommonModules/| Situation | Decision | |----------|---------| | БСП has a suitable function | Use БСП | | БСП has a similar function, but with extra functionality | Use БСП - extra functionality does not hurt | | The needed function is not in БСП | Write your own in the БСП style | | Configuration without БСП | Write your own |
See error-handling, rule 7.
Direct file handling does not account for access rights, temporary files, or cross-platform compatibility.
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
Попытка
ТабличныйДокумент.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.XLSX);
// ... работа с файлом ...
Исключение
// Обработка ошибки
КонецПопытки;
// Явное удаление
УдалитьФайлы(ИмяВременногоФайла);
Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
Если НЕ ЗначениеЗаполнено(Контрагент) Тогда
ОбщегоНазначения.СообщитьПользователю(
НСтр("ru = 'Не заполнен контрагент.'"),
ЭтотОбъект, "Контрагент",, Отказ);
КонецЕсли;
// Условное исключение реквизитов из проверки
Если ВидОперации = Перечисления.ВидыОпераций.Услуга Тогда
ОбщегоНазначенияКлиентСервер.УдалитьЗначениеИзМассива(
ПроверяемыеРеквизиты, "Склад");
КонецЕсли;
КонецПроцедуры
Процедура Печать(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм,
ОбъектыПечати, ПараметрыВывода) Экспорт
Если УправлениеПечатью.НужноПечататьМакет(КоллекцияПечатныхФорм, "Счёт") Тогда
ТабличныйДокумент = Новый ТабличныйДокумент;
ТабличныйДокумент.КлючПараметровПечати = "Документ.РеализацияТоваровУслуг.Счёт";
УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию(
КоллекцияПечатныхФорм, "Счёт", НСтр("ru = 'Счёт на оплату'"),
ТабличныйДокумент);
КонецЕсли;
КонецПроцедуры
| What people often write themselves | What is in БСП |
|----------------------|----------------|
| Get an attribute by reference | ОбщегоНазначения.ЗначениеРеквизитаОбъекта() |
| String substitution | СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку() |
| Word declension | СтроковыеФункцииКлиентСервер.СтрокаСЧисломПредметов() |
| Sending mail | РаботаСПочтовымиСообщениями |
| Exchange rate | РаботаСКурсамиВалют.ПолучитьКурсВалюты() |
| Long-running operation in the background | ДлительныеОперации.ВыполнитьФункцию() |
| Secret / password storage | БезопасноеХранилище.ПрочитатьДанные() |
| Access right profiles | ГруппыДоступаПользователей / ПрофилиГруппДоступа |
| Registering an external processor | СведенияОВнешнейОбработке() |
| Module suffix | Environment | Example |
|----------------|-------|--------|
| (no suffix) | Server | ОбщегоНазначения |
| Клиент | Client | ОбщегоНазначенияКлиент |
| КлиентСервер | Both environments | ОбщегоНазначенияКлиентСервер |
| ПовтИсп | Server, with caching | ОбщегоНазначенияПовтИсп |
For client-side form code, first look in *КлиентСервер, then in *Клиент. For server-side code, look primarily in the main module (without suffix). *ПовтИсп is for frequently requested reference data.
Use the ДлительныеОперации subsystem for any server work that takes longer than about 3 seconds. Do not block the UI with a homemade wait loop.
// Launch a background task
&НаСервере
Функция ЗапуститьОперацию(Параметры)
ПараметрыФона = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
ПараметрыФона.НаименованиеФоновогоЗадания = НСтр("ru = 'Обработка данных'");
Возврат ДлительныеОперации.ВыполнитьФункцию("ОбщийМодуль.ФункцияДляФона",
ПараметрыФона, Параметры);
КонецФункции
// Connect waiting on the client
&НаКлиенте
Процедура ЗапуститьОперациюНаКлиенте()
Операция = ЗапуститьОперацию(ПараметрыРасчёта);
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
ПараметрыОжидания.ВыводитьПрогресс = Истина;
ДлительныеОперацииКлиент.ОжидатьЗавершение(Операция,
Новый ОписаниеОповещения("ОперацияЗавершена", ЭтотОбъект), ПараметрыОжидания);
КонецПроцедуры
// Handle the result
&НаКлиенте
Процедура ОперацияЗавершена(Операция, ДополнительныеПараметры) Экспорт
Если Операция = Неопределено Тогда
Возврат; // Canceled by the user
КонецЕсли;
Если Операция.Статус = "Ошибка" Тогда
СтандартныеПодсистемыКлиент.ОбработатьОшибкуФоновогоЗадания(Операция);
Возврат;
КонецЕсли;
// Get result
РезультатОперации = ПолучитьРезультатСервер(Операция.АдресРезультата);
КонецПроцедуры
Key rules:
ДлительныеОперации.СообщитьПрогресс() inside the background procedure.Never store passwords, tokens, or secrets in:
// Запись секрета
БезопасноеХранилище.Записать(ЭтотОбъект, Новый Структура("Пароль", ПарольПользователя));
// Чтение секрета
ДанныеХранилища = БезопасноеХранилище.ПрочитатьДанные(ЭтотОбъект);
Если ДанныеХранилища <> Неопределено Тогда
Пароль = ДанныеХранилища.Пароль;
КонецЕсли;
// Удаление при удалении объекта
БезопасноеХранилище.Удалить(ЭтотОбъект);
In the object's ПередУдалением handler, always call БезопасноеХранилище.Удалить() - otherwise "orphaned" records accumulate in the storage.
When developing subsystems with role-based access, use the БСП profile mechanism instead of assigning roles directly.
// Example of a profile description in ОписаниеПрофилейГруппДоступа()
Профиль = УправлениеДоступом.ОписаниеПрофиля();
Профиль.Идентификатор = "ИдентификаторПрофиля_UUID";
Профиль.Наименование = НСтр("ru = 'Менеджер по продажам'");
Профиль.Роли.Добавить("РольМенеджерПродаж");
Профили.Добавить(Профиль);
Key rules:
ПривилегированныйРежим() strictly locally, and disable it immediately after the operation.УправлениеДоступом.ПроверитьДопустимостьДействия(), not directly through РольДоступна() - the latter does not take RLS into account.Registering an external processor in a БСП-based configuration requires the СведенияОВнешнейОбработке() function in the processor's main module.
// В модуле обработки
Функция СведенияОВнешнейОбработке() Экспорт
СведенияОВнешнейОбработке = ДополнительныеОтчётыИОбработки.СведенияОВнешнейОбработке();
СведенияОВнешнейОбработке.Вид = ДополнительныеОтчётыИОбработкиКлиентСервер
.ВидОбработки().ДополнительнаяОбработка;
СведенияОВнешнейОбработке.Наименование = НСтр("ru = 'Моя обработка'");
СведенияОВнешнейОбработке.Версия = "1.0";
СведенияОВнешнейОбработке.БезопасныйРежим = Истина;
// Описание команды
Команда = СведенияОВнешнейОбработке.Команды.Добавить();
Команда.Представление = НСтр("ru = 'Выполнить'");
Команда.Идентификатор = "Выполнить";
Команда.ИспользованиеКонтекста = ДополнительныеОтчётыИОбработкиКлиентСервер
.ИспользованиеКонтекстаКоманды().ВПроцедуреВыполнитьКоманду;
Возврат СведенияОВнешнейОбработке;
КонецФункции
// Точка входа для команды
Процедура ВыполнитьКоманду(Идентификатор, ПараметрыКоманды, ОбъектыНазначения) Экспорт
// ... реализация ...
КонецПроцедуры
If search_ssl_functions did not return a result, use ask_ai_assistant (VALIDATE_BSL template from buddy-prompting): pass a code fragment and get recommendations for replacing it with БСП methods. Also use SEARCH_DOCS for documentation on a specific БСП method.
testing
MUST use BEFORE making a judgment about the cause of a conflict, a test failure, or an artifact dispute. Defines the end-to-end verification method L1→L6 and the classification of the first broken link.
development
MUST use AFTER a work cycle with ≥2 iterations (wrote → error → fixed → success). Provides the retrospective procedure and the format for recording practice/anti-patterns in references/learned-patterns.md or {project}/.context/learned-patterns.md.
tools
MUST use WHEN you are writing reusable knowledge into RLM (pattern / architectural decision / stable domain fact) OR reading it before a non-trivial task/solution in the domain. Provides the breakdown of native-push vs RLM-pull, tools for writing and reading RLM, H-MEM levels, and hygiene.
testing
MUST use WHEN the task is classified as simple (< 20 lines, 1 file, no new metadata objects, no architectural decisions). Provides a short cycle of 3 steps with a guard on the self path and mandatory verify.