Задача из тех, что приходит одной строкой в чате: "выгрузка отдаёт не тот артикул". Пользователь открывает карточку товара, видит галочку в одном положении, смотрит, что уехало во внешнюю торговую площадку, и там артикул другого вида.
Дальше обычно открывают код выгрузки и ищут ошибку в нём. Я разберу другой порядок действий: четыре шага, которые начинаются не с кода, а с подсчёта самих данных. На этой задаче он дал ответ за один запрос и попутно вытащил то, чего никто не искал: 88 481 запись, которую в этой базе некому было создать.
Как устроена конструкция
Розничная компания торгует на внешней площадке. Товар туда уезжает регулярной выгрузкой, и у каждой позиции есть артикул. Артикулов два: свой, внутренний, и артикул поставщика. Наружу должен уехать ровно один, и какой именно, решает булево свойство номенклатуры - галочка в дополнительных сведениях.
Карточек товара в базе тоже два вида, и это ключевая деталь всей истории:
- карточка головной учётной системы - собственная номенклатура компании, то, чем торгуют и что видят в интерфейсе;
- карточка поставщика - позиция из номенклатуры поставщика, приезжает обменом вместе с его прайсом и артикулами.
Эти две карточки связаны между собой, и связь ведёт себя интереснее, чем ожидаешь: у карточки есть и реквизит в шапке, и строки табличной части, и оба места говорят про одно и то же отношение "этот наш товар соответствует вот этой позиции поставщика". Запомните деталь, к концу она пригодится.
Само свойство никто руками не расставляет: его пишет обработчик входящего обмена, когда разбирает данные от поставщика. Живёт эта конструкция несколько лет, за которые её трогали разные люди.
Ситуация типовая до зевоты. Любая база старше трёх лет, которая общается хоть с одной внешней системой, устроена примерно так же: две стороны данных, флаг-переключатель между ними и выгрузка наружу.
Развилка, на которой обычно сворачивают не туда
Жалоба указывает на выгрузку, поэтому первое желание - открыть её модуль. Там будет условие по свойству, и дальше можно долго смотреть на верный код, не понимая, почему он даёт неверный результат.
Проблема этого пути в том, что он проверяет одно место из нескольких. Свойство кто-то пишет, кто-то читает, и таких мест больше одного. Модуль выгрузки - лишь один читатель, и вероятность, что ошибка именно в нём, обратно пропорциональна возрасту базы.
Порядок, который я предлагаю вместо этого, состоит из четырёх шагов, и первые три не требуют ничего, кроме запросов.
Шаг 1. Посчитать распределение значений по владельцам
Прежде чем лезть в модуль, надо посмотреть на само поле: сколько у него записей, у каких объектов они стоят и какие значения принимают. Группировка по типу объекта и по значению, пять минут работы.
| Чьи карточки | Записей всего | Со значением "Да" | Со значением "Нет" |
|---|---|---|---|
| Головной системы | 94 655 | 10 704 | 83 951 |
| Поставщиков | 31 874 | 27 344 | 4 530 |
Первое, что видно: свойство, которое по смыслу относится к товару поставщика, стоит на карточках головной системы почти в три раза чаще. Перекос тут трёхкратный, а не "заметный".
Массово заполненное поле там, где его быть не должно, означает одно: кто-то писал его без разбора. Взял связку карточек и проставил обеим сторонам.
Так и оказалось. Обработчик входящего обмена проходит по связке и ставит свойство всем участникам, без фильтра по владельцу. Соседние свойства в том же цикле отфильтрованы, а это - нет. Один пропущенный фильтр дал 94 тысячи лишних записей.
Что даёт шаг. Вы ещё не открыли ни строчки кода, а уже знаете, что данные лежат не там, где должны, и примерно понимаете, кто их туда положил.
Шаг 2. Выписать всех, кто может это записать
Теперь ищем писателей. Поиск по конфигурации даёт список мест, где свойство пишется, и для каждого надо выписать не "что он делает", а какие значения он в принципе способен поставить.
Здесь нашёлся ровно один механизм - тот самый обработчик обмена. И у него обнаружилась особенность: он умеет ставить только "Да". Значение "Нет" он не пишет никогда, такой ветки в коде просто нет.
Возвращаемся к таблице первого шага. Записей со значением "Нет" в базе 83 951 плюс 4 530, то есть 88 481.
Ни одна из них не могла появиться от единственного найденного писателя. Значит, писатель есть второй, и на момент разбора он не найден.
Обратите внимание, чем это отличается от обычного хода расследования. Я не нашёл второй механизм. Я не увидел его следов в журнале. Я не догадался о нём по косвенным признакам. Я вычел одно число из другого, и остаток оказался больше нуля.
Существование того, чего не нашли поиском по коду, доказано арифметикой. Это не версия и не подозрение, которое надо проверять. Либо данные врут, либо писатель есть.
Почему поиск по коду его не нашёл
Вариантов немного, и все они скучные: обработка во внешнем файле, регламентное задание из расширения, загрузка через служебный интерфейс, ручная правка универсальным инструментом, обмен из смежной базы.
Любой из них не оставляет следа в конфигурации, и глобальный поиск его не покажет. Поиск отвечает на вопрос "кто мог бы", а данные отвечают на вопрос "кто уже". Это разные вопросы, и второй сильнее.
Что даёт шаг. Вы знаете, полон ли ваш список писателей. Наблюдаемые значения не покрываются найденными механизмами - чинить данные пока рано, и об этом ниже отдельно.
Шаг 3. Выписать всех, кто это читает
Теперь то же самое с обратной стороны, и здесь нашёлся ответ на исходную жалобу.
Свойство читается в шести местах. Пять из них - формы, отчёты, вспомогательная выгрузка - берут его с карточки головной системы. Шестое, боевая выгрузка на площадку, берёт его с карточки поставщика.
Вот и весь "косяк". В интерфейсе человек видит значение с одной карточки, наружу уходит значение с другой. Оба куска кода правильные с точки зрения своего автора, оба делают ровно то, что в них написано, и ни один не содержит ошибки.
Дальше интересный вопрос: кто из шести прав.
Голосованием получается, что правы пятеро. По существу правым оказывается один, потому что прав тот, чей результат видит внешний мир. Формы и отчёты показывают значение сотрудникам, и ошибка стоит недоумения. Выгрузка отдаёт артикул наружу, и ошибка стоит неправильного товара в чужой витрине.
И тут выясняется главное. Это не баг ни в одном из шести мест. Это отсутствие хозяина у семантики поля. Никто никогда не записывал, на какой стороне связки живёт значение, поэтому каждый следующий разработчик решал этот вопрос заново и по-своему. Пять раз решили одинаково, один раз иначе, и расходилось это годами.
Что даёт шаг. Вы понимаете, надо ли вообще чинить код. Здесь чинить его было нечего: расходились трактовки, а не реализации.
Практический вывод отсюда неудобный: поле, которое читают из шести мест, требует одной строки документации там, где её видно всем. Достаточно строки в комментарии к свойству: "значение живёт на карточке поставщика, читать оттуда". Стоит она пять минут, а экономит вот такой разбор.
Шаг 4. Выбрать сторону и не стать ещё одним писателем
Обязательная часть про то, что пошло не так у меня.
Первая версия скрипта исправления писала значение на карточку поставщика: 171 карточка из 196 нуждающихся. Покрытие 87 процентов, скрипт отработал без ошибок, результат выглядел отличным.
Отвергнут он был после третьего шага. Логика ошибки простая: я чинил ту сторону, которую читает боевая выгрузка, то есть подгонял данные под самого важного читателя. Но пять остальных читателей смотрят на другую сторону, и после моей починки они начали бы показывать сотрудникам значение, которого раньше не было. То есть я собирался добавить в базу ещё один несогласованный источник записи - ровно ту болезнь, которую разбирал.
Отдельно про 87 процентов. Высокое покрытие неверного действия читается как успех, и это ловушка, в которую легко попасть при приёмке. "Обработали 171 из 196" звучит как сделанная работа. Вопрос "а туда ли писали" в такой формулировке не возникает вовсе, потому что цифра уже ответила на вопрос "хорошо ли сработали".
Проверять в этом месте надо направление, а доля отвечает на вопрос "сколько" и не знает, что вы делаете не то.
Итоговое решение: писать на карточку головной системы, то есть на ту сторону, где значение видят люди, и следом править шестого читателя. Обратный порядок дал бы правильную выгрузку и неправильный интерфейс - та же задача, только повёрнутая другой стороной.
Почему чинить данные раньше поиска писателя бессмысленно
Это неприятная часть, и с ней обычно спорят.
Пока второй писатель не найден, любая чистка данных обратима. Вы приводите значения к правильным, а он в следующий свой запуск ставит их обратно, и вы об этом даже не узнаете: молча, без ошибок, без записи в журнал.
Хуже того, вы получите ложное подтверждение, что починили: сразу после прогона всё правильно. Через неделю всё снова не так, и разбор начинается с нуля, потому что связь между вашей правкой и возвратом никто не увидит.
Возражение здесь очевидное, и оно справедливое: бизнесу нужны правильные данные сегодня, а поиск неизвестного механизма может занять неделю. Я с этим не спорю. Но тогда правка данных должна называться заплаткой с известным сроком жизни, а не исправлением. Разница в том, что заплатку ставят на видное место и возвращаются к ней, а исправление закрывают и забывают.
Три грабли, которые попадутся по дороге
Связь считается двумя способами и даёт разные ответы
Та деталь из начала статьи, про реквизит шапки и табличную часть.
В базе знаний давно лежала запись, что связь между карточками идёт через реквизит шапки, и все запросы писались по ней. Замер: через табличную часть видно 313 связанных карточек, через реквизит шапки - 171. То есть канонический запрос показывал 54,6 процента связей, а про остальную половину никто не знал.
Прежняя запись при этом не была неверной. Она была верна для той задачи, для которой её сделали, и записана как общее правило. Между "в нашей задаче связь идёт через шапку" и "связь идёт через шапку" разница в одно слово и в 142 карточки.
Проверка стоит одного запроса: посчитайте одно и то же отношение двумя способами и сравните числа. Совпали - можно пользоваться любым. Разошлись - у вас два разных отношения, и надо разбираться, какое из них вам нужно.
Масштаб проблемы и размер задачи - разные числа
Карточек головной системы без этого свойства вообще - 10 208. Звучит как большая работа: десять тысяч объектов надо заполнить.
Но из этих десяти тысяч связанный поставщик есть только у восьми. Ноль целых восемь сотых процента. У остальных заполнять свойство бессмысленно: без связки оно ни на что не влияет.
Массовая проблема и точечная задача - это разные множества, и путать их дорого в обе стороны. Испугаться десяти тысяч и просить неделю плохо. Взять и заполнить десять тысяч "чтобы было" ещё хуже: вы своими руками увеличите тот самый перекос, с которого началась статья.
Запись свойства тянет за собой очередь выгрузки
Техническая деталь, которая испортит вечер тому, кто её не ждёт.
Запись этого свойства не проходит бесследно: подписка на событие ставит карточку в очередь повторной выгрузки во внешнюю таблицу. Массовая правка на несколько тысяч объектов означает несколько тысяч карточек в очереди.
Обходится флагом обмена данными на наборе записей, но знать про это надо до запуска. Иначе получится классика: починили данные, положили очередь, и следующий разбор будет уже про неё.
Общее правило, которое я вывел для себя: перед любой массовой правкой проверить, кто подписан на запись этого объекта. Одна подписка превращает тихую правку данных в событие, которое видно всей системе.
Что осталось незакрытым
Границы называю честно, потому что материал без них выглядит красивее, чем работа была на самом деле.
Второй писатель не найден. Это главный незакрытый вопрос: 88 тысяч записей создал кто-то, кого мы не нашли. Пока он не найден, всё сделанное обратимо.
Читатель не поправлен. Выбранный вариант пишет значение на карточку головной системы, а боевая выгрузка читает с поставщика. Значит, она не увидит правильное значение, пока не поправят её саму. Это записано прямо: исправление данных без исправления читателя не работает, и объявлять задачу закрытой на этом шаге было бы враньём.
Цена вопроса не измерена. Сколько товаров реально уехало наружу с чужим артикулом и что это стоило - неизвестно. А это единственное число, которое интересует бизнес, и его у меня нет.
Соседние свойства не проверены. Тот же обработчик обмена пишет ещё несколько свойств, и фильтры там расставлены иначе. Это подозрительно, но руки не дошли.
Порядок действий целиком
Короткая версия для тех, кто пришёл за методикой. Работает на любом поле, вокруг которого регулярно возникают вопросы "почему тут одно, а там другое".
- Посчитайте распределение значений по владельцам. Группировка по типу объекта и по значению. Перекос в разы туда, где поля быть не должно, виден сразу и сам называет виноватого.
- Выпишите всех писателей и для каждого - какие значения он может записать. Именно может, даже если обычно не пишет.
- Сравните множества. Значения, которых нет ни у одного писателя, доказывают существование писателя, которого вы не нашли. Это вычитание, а не догадка.
- Выпишите всех читателей и откуда каждый берёт значение. Источники разошлись - у вас не баг, а две несовместимые трактовки одного поля, и чинить надо трактовку.
- Только теперь правьте данные, и правьте ту сторону, где значение видят люди. Читателя, который смотрит в другую сторону, чините следом, а не вместо.
Первые три шага делаются за час на любой базе. Четвёртый дольше, потому что читателей всегда больше, чем кажется, и половина из них найдётся в отчётах.
Есть тут очевидное возражение: всё это разговор про базу, где никто не вёл документацию. В идеальном мире семантика поля описана, писатель один, читатели ходят через общий метод. Возражение верное, и я не знаю ни одной живой базы старше трёх лет, к которой оно применимо.
Другие наши инструменты:
- Анализ кода внешних обработок 1С - разбирает обработки из справочника и показывает, кто из них что пишет в базу. Ровно то место, где чаще всего и прячется писатель, которого не видно в конфигурации.
- Матрица прав доступа - кто вообще имеет право записать этот объект. Список подозреваемых сужается до тех, у кого есть доступ.
- Выгрузка структуры метаданных - полная карта объектов и реквизитов в текстовом виде, удобно искать все места, где поле вообще упоминается.
- Трансформатор SQL в 1С - переводит запрос из профайлера обратно в имена справочников и регистров. Помогает, когда писателя ловят на уровне СУБД и надо понять, во что он пишет.
Вопрос, на который у меня нет ответа
Тот самый второй писатель меня не отпускает. 88 481 запись - это не случайная правка руками и не разовая загрузка, это чья-то регулярная работа, о которой в базе не осталось следов.
Как вы ищете такое у себя? Журнал регистрации хранит месяц, а записи копились годами. Подписка на запись покажет только будущее, а вопрос про прошлое. Версионирование объектов на регистр сведений обычно не включают.
У меня в голове три варианта: временная подписка на запись с логированием стека вызовов, разбор внешних обработок в справочнике на предмет записи в этот регистр и просто ожидание, пока значения не начнут меняться сами.
Все три так себе. Если у вас был случай, когда неизвестный писатель нашёлся, расскажите, чем именно вы его поймали. Это как раз тот класс задач, где чужой опыт экономит недели, а придумать самому получается только медленно.
Вступайте в нашу телеграмм-группу Инфостарт