Кулинарная книга A1sCode · Часть 6

Антипаттерны
и диагностика

Девять типичных ошибок при работе с A1sCode. Для каждой — что именно идёт не так, какой эффект на производительность или логику, и как исправить. В конце — SelfTest и сводная таблица.

9 антипаттернов 3 категории исправление для каждого
Производительность
🐌
АП-1

Ensure в цикле вместо EnsureBatch

критично по производительности
N запросов к БД растёт со списком

Каждый вызов Ensure делает как минимум один запрос к БД (поиск по наименованию), а при создании — ещё один (запись). При 200 позициях в списке это 200–400 запросов. На большой базе это десятки секунд или таймаут.

✗ Антипаттерн — N запросов к БД
// 200 позиций = 200–400 запросов!
Для Каждого Стр Из ДанныеExcel Цикл
    Ном = A1sCatalogs.Ensure(
        "Номенклатура",
        Стр.Наименование,
        A1sDS.Of("Родитель", Гр));
    A1sDocs.AddRow(Dok, "Продукция",
        A1sDS.Of("Номенклатура", Ном, ...));
КонецЦикла;
✓ Исправление — 1 батч до цикла
// Все имена одним батчем ДО цикла
Имена = Новый Массив;
Для Каждого Стр Из ДанныеExcel Цикл
    Имена.Добавить(Стр.Наименование);
КонецЦикла;
A1sCatalogs.EnsureBatch(
    "Номенклатура",
    A1sAR.RemoveDuplicates(Имена),
    A1sDS.Of("Родитель", Гр));
// Теперь LoadRows найдёт их в базе
A1sDocs.LoadRows(Dok, "Продукция", ДанныеExcel);
⚠️
Правило «батч до цикла». Если перед циклом известен весь список объектов — всегда сначала один батч-вызов (EnsureBatch, PickByName), потом цикл. Внутри цикла — только работа с уже полученными ссылками. Это снижает число запросов к БД с N до 1.

📖
АП-2

GetObject для чтения вместо Details

лишние запросы и память
лишний запрос к БД загрузка всего объекта

GetObject() открывает весь объект документа или справочника — загружает все реквизиты и все ТЧ. Если нужно просто прочитать несколько полей — это избыточно. Details() делает один лёгкий запрос только за нужными полями.

✗ Открываем весь объект ради двух полей
ДокОбъект = ДокСсылка.GetObject();
Сообщить(ДокОбъект.Номер);
Сообщить(ДокОбъект.Дата);
Сообщить(ДокОбъект.Комментарий);
// Загружены ВСЕ ТЧ документа — зря
✓ Details — только нужные поля
Инфо = A1sDocs.Details(
    ДокСсылка,
    "Комментарий");
Сообщить(Инфо.Number);
Сообщить(Инфо.Date);
Сообщить(Инфо.Комментарий);
// Один лёгкий запрос, нет ТЧ
💡
Когда GetObject всё же нужен. Только когда вы собираетесь изменить объект. Для чтения — Details для документов и Details для справочников. Для изменения — GetObject + изменить + Записать или Update.

🔁
АП-3

Поиск / ByName внутри цикла загрузки

критично по производительности
N × M запросов экспоненциальный рост

Если внутри цикла по строкам документа вызывать ByName, ByAttr или Ensure для каждой строки — получаем N запросов. Если ещё и список групп ищется внутри — N×M. Решение: собрать все нужные ссылки до цикла через batch-функции.

✗ Поиск в каждой итерации
Для Каждого Стр Из ДанныеExcel Цикл
    // Запрос к БД на КАЖДУЮ строку!
    ЕдИзм = A1sCatalogs.ByName(
        "КлассификаторЕдиницИзмерения",
        Стр.Единица);
    Группа = A1sCatalogs.ByName(
        "НоменклатурныеГруппы",
        Стр.Группа);
    // ещё 2 запроса на строку...
КонецЦикла;
✓ Все ссылки батчем до цикла
// 2 запроса на весь список — до цикла
МапЕд = A1sCatalogs.PickByName(
    "КлассификаторЕдиницИзмерения",
    СписокЕдиниц);   // Array→Map
МапГрупп = A1sCatalogs.PickByName(
    "НоменклатурныеГруппы",
    СписокГрупп);

Для Каждого Стр Из ДанныеExcel Цикл
    ЕдИзм  = МапЕд[Стр.Единица];  // O(1)
    Группа = МапГрупп[Стр.Группа]; // O(1)
КонецЦикла;
Логические ошибки
🙈
АП-4

Игнорирование Failed из PostAll / UpdateAll

скрытая потеря данных
непроведённые документы необнаруженные ошибки

PostAll, UnpostAll, UpdateAll и MoveBatch возвращают структуру {Success, Failed}. Если не проверять Failed — часть операций молча провалится, документы останутся непроведёнными, а вы узнаете об этом только при следующей выгрузке или жалобе пользователя.

✗ Результат игнорируется
// PostAll вернул результат — мы его выбросили
A1sDocs.PostAll(МассивДоков);
Сообщить("Готово");
// Сколько провалилось? Неизвестно
✓ Всегда проверяем Failed
Итог = A1sDocs.PostAll(МассивДоков);
Сообщить("✓ " + Итог.Success.Количество());
Если Итог.Failed.Количество() > 0 Тогда
    Сообщить("✗ Ошибки: "
        + Итог.Failed.Количество());
КонецЕсли;
🚨
Минимум для продакшена. В любом батч-вызове, который возвращает {Success, Failed}, всегда добавляйте проверку: Если Итог.Failed.Количество() > 0 Тогда. Даже если просто пишете в лог — это лучше, чем молчание.

🔤
АП-5

AddRow со строками вместо AddRowAuto

ошибка типов при записи
ошибка при записи документа «Несоответствие типов»

AddRow ожидает готовые ссылки. Если передать строку в поле типа СправочникСсылка.Номенклатура — платформа выбросит исключение при записи документа или молча запишет пустую ссылку. Для строковых данных используйте AddRowAuto.

✗ Строка в ссылочное поле
// "Квартира 101" — строка, не ссылка
// При записи → ошибка "Несоответствие типов"
A1sDocs.AddRow(Dok, "Продукция",
    A1sDS.Of(
        "Номенклатура", "Квартира 101",
        "Количество", 1));
✓ AddRowAuto разрешит строку в ссылку
// AddRowAuto: видит что "Номенклатура" — ссылочное поле
// вызывает EnsureByType → получает ссылку
A1sDocs.AddRowAuto(Dok, "Продукция",
    A1sDS.Of(
        "Номенклатура", "Квартира 101",
        "Количество", 1));
💡
Простое правило. Данные пришли из внешней системы (Excel, JSON, CSV) — используйте AddRowAuto или LoadRows. Данные уже в виде ссылок (из запроса, из справочника) — используйте AddRow или AddRows.

♻️
АП-6

Ensure вместо EnsureUpdate при регулярной синхронизации

данные не обновляются
устаревшие цены и реквизиты иллюзия синхронизации

Ensure — «найди или создай». Нашёл → вернул ссылку и ничего не изменил. Если сегодня прайс изменился — Ensure не обновит цену у существующего элемента. Для регулярной синхронизации нужен EnsureUpdate — он всегда обновляет реквизиты.

✗ Ensure — обновление молча не происходит
// Квартира 101 уже есть в базе с ценой 3 500 000
// EnsureUpdate нужен, но используем Ensure
Для Каждого Поз Из НовыйПрайс Цикл
    A1sCatalogs.Ensure(
        "Номенклатура",
        Поз.Наименование,
        A1sDS.Of("Цена", Поз.Цена));
КонецЦикла;
// Цена не обновилась! Ensure нашёл элемент
// и вернул ссылку без изменений.
✓ EnsureUpdate — всегда обновляет
Для Каждого Поз Из НовыйПрайс Цикл
    A1sCatalogs.EnsureUpdate(
        "Номенклатура",
        Поз.Наименование,
        A1sDS.Of("Цена", Поз.Цена));
КонецЦикла;
// Нашёл → обновит Цену.
// Не нашёл → создаст с Ценой.

🔩
АП-7

Хардкод имени справочника вместо EnsureByType

хрупкий код — ломается при смене модели
не работает с другими документами ручное обновление при изменениях

Если в универсальном загрузчике прибить строку "Номенклатура" — он перестанет работать как только вы захотите использовать его с другим документом или ТЧ. EnsureByType берёт имя справочника из метаданных реквизита — код остаётся универсальным.

✗ Имя справочника вшито в код
// Работает только с "Номенклатура"
// Для другого документа — переписывать
Для Каждого Стр Из ДанныеJSON Цикл
    Ном = A1sCatalogs.Ensure(
        "Номенклатура",
        Стр.item);
    A1sDocs.AddRow(Dok, "Продукция",
        A1sDS.Of("Номенклатура", Ном));
КонецЦикла;
✓ EnsureByType — из метаданных
// Работает с любым документом и ТЧ
// Имя справочника — из метаданных
ТипНом = A1sDocs.TabAttrType(
    ИмяДокумента, ИмяТЧ, "Номенклатура");

Для Каждого Стр Из ДанныеJSON Цикл
    Ном = A1sCatalogs.EnsureByType(
        ТипНом, Стр.item);
    A1sDocs.AddRow(Dok, ИмяТЧ,
        A1sDS.Of("Номенклатура", Ном));
КонецЦикла;
Стиль и читаемость
💻
АП-8

Fluent на клиенте или в &НаКлиентеНаСервере

ошибка выполнения
«Обработка не найдена» необъяснимый сбой

Все четыре Fluent-обёртки создают объект обработки через Обработки.A1sDP_Docs.Создать(). Обработки видны только на сервере. На клиенте или в &НаКлиентеНаСервере этот вызов упадёт с ошибкой «Обработка не найдена».

✗ Fluent в клиентском контексте
// &НаКлиенте или &НаКлиентеНаСервере
&НаКлиентеНаСервере
Процедура ОбработатьФорму()
    // ОШИБКА: Обработки недоступны
    Ссылка = A1sDocs.On("ВыпускПродукцииУслуг", ...)
        .Post();
КонецПроцедуры
✓ Fluent только &НаСервере
// Обернуть в серверный вызов
&НаСервере
Функция СоздатьВыпускНаСервере(Данные)
    Возврат A1sDocs.On(
            "ВыпускПродукцииУслуг", Данные)
        .Post();
КонецФункции

// Или — обычные функции без Fluent
Dok = A1sDocs.Of(...); // тоже только &НаСервере!
⚠️
Не только Fluent — весь A1sCode &НаСервере. Все функции A1sCode работают только на сервере — они обращаются к базе, метаданным, обработкам. Никакой магии на клиенте нет. Клиентский код только вызывает серверные процедуры и передаёт параметры.

🔀
АП-9

A1sDS.Of с вложенной структурой вместо пар

лишний код, теряет смысл Of
избыточность два способа делать одно

Иногда создают структуру заранее, а потом передают её в Of как единственный аргумент. Это работает, но теряет весь смысл Of — инлайн-создание из пар. Если структура уже есть — передавайте её напрямую. Если нужны пары — используйте Of.

✗ Промежуточная структура без нужды
// Создаём структуру... чтобы передать в Of?
ДанныеШапки = Новый Структура;
ДанныеШапки.Вставить("Дата", '20260201');
ДанныеШапки.Вставить("Организация", Орг);

Dok = A1sDocs.Of(
    "ВыпускПродукцииУслуг",
    A1sDS.Of(ДанныеШапки));
// A1sDS.Of с одним аргументом — лишнее
✓ Передать структуру напрямую
// Если структура уже есть — просто передать
Dok = A1sDocs.Of(
    "ВыпускПродукцииУслуг",
    ДанныеШапки);   // уже структура

// Если нет — инлайн через Of
Dok = A1sDocs.Of(
    "ВыпускПродукцииУслуг",
    A1sDS.Of(
        "Дата", '20260201',
        "Организация", Орг));
Диагностика — SelfTest

SelfTest — проверить что модули работают

Каждый модуль содержит SelfTest() — набор встроенных тестов. Они проверяют базовые операции без обращения к реальным данным базы. Запускать при первой установке, после обновления библиотеки или при загадочных ошибках.

SelfTest — запустить все модули
// ── Запустить SelfTest всех модулей ──────────────────────────────────────
// Поместить в обработку-проверку или запустить из консоли запросов

Модули = A1sDS.Of(
    "A1sDS",       A1sDS.SelfTest(),
    "A1sAR",       A1sAR.SelfTest(),
    "A1sCatalogs", A1sCatalogs.SelfTest(),
    "A1sDocs",     A1sDocs.SelfTest());

Сообщить("════ SelfTest результаты ════");
Для Каждого КЗ Из Модули Цикл
    Сообщить((КЗ.Значение ? "✓ " : "✗ ") + КЗ.Ключ);
КонецЦикла;

// Ожидаемый вывод:
// ✓ A1sDS
// ✓ A1sAR
// ✓ A1sCatalogs
// ✓ A1sDocs

// ── Один модуль отдельно ──────────────────────────────────────────────────
Ок = A1sDocs.SelfTest();
Если НЕ Ок Тогда
    ВызватьИсключение "A1sDocs.SelfTest: проверка не пройдена";
КонецЕсли;

// ── Что проверяют SelfTest ────────────────────────────────────────────────
// A1sDS:      Of, OfKeys, HasKey, IsEmpty, Equals, Merge, Defaults...
// A1sAR:      Of, OfN, Contains, IsEmpty, RemoveDuplicates, Sort...
// A1sCatalogs: IsCatalog, IsRef, GetObject, GetRef...
// A1sDocs:    IsDocument, IsRef, IsObject, GetObject, GetRef, MetaCount...
Диагностика загадочной ошибки — чек-лист
// ════════════════════════════════════════════════════════════════════════════
// ЧЕК-ЛИСТ при загадочной ошибке
// ════════════════════════════════════════════════════════════════════════════

// 1. Проверить что модули работают (SelfTest выше)

// 2. Проверить что ТЧ не пустая перед Post
Если A1sDocs.RowCount(Dok, "Продукция") = 0 Тогда
    Сообщить("ВНИМАНИЕ: пустая ТЧ");
КонецЕсли;

// 3. Проверить что обязательные реквизиты шапки заполнены
Инфо = A1sDocs.Details(Dok, "Организация, Склад");
Если НЕ ЗначениеЗаполнено(Инфо.Организация) Тогда
    Сообщить("ВНИМАНИЕ: не заполнена Организация");
КонецЕсли;

// 4. Проверить существование справочников до загрузки строк
ТестСсылка = A1sCatalogs.ByName("Номенклатура", "Квартира 101");
Если НЕ ЗначениеЗаполнено(ТестСсылка) Тогда
    Сообщить("'Квартира 101' не найдена в справочнике");
КонецЕсли;

// 5. Проверить что код работает на сервере
// (добавить &НаСервере к процедуре если нет)

// 6. Проверить IsPosted у существующих документов
Если НЕ A1sDocs.IsPosted(ДокСсылка) Тогда
    Сообщить("Документ не проведён: " + ДокСсылка);
КонецЕсли;
Сводная таблица антипаттернов
# Антипаттерн Эффект Исправление Критичность
АП-1 Ensure в цикле N запросов к БД вместо 1 EnsureBatch до цикла Высокая
АП-2 GetObject для чтения Лишний тяжёлый запрос Details(ExtraAttrs) Средняя
АП-3 Поиск внутри цикла N×M запросов PickByName батчем до цикла Высокая
АП-4 Игнор Failed Скрытые непроведённые Проверять .Failed.Количество() Высокая
АП-5 AddRow со строками Ошибка типов при записи AddRowAuto / LoadRows Высокая
АП-6 Ensure при синхронизации Данные не обновляются EnsureUpdate Высокая
АП-7 Хардкод справочника Хрупкий код EnsureByType + TabAttrType Средняя
АП-8 Fluent на клиенте Ошибка «Обработка не найдена» Только &НаСервере Высокая
АП-9 Of с промежуточной структурой Лишний код без смысла Передать структуру напрямую Низкая