Опечатка в параметре запроса не вызывает ошибку. Как понять, что ответ пришёл не из той базы

21.09.26

Интеграция - WEB-интеграция

Я сел проверять внутренний поисковый сервис по коду: можно ли доверять ему в автоматическом режиме. Сверка с обычным текстовым поиском по файлам дала 261 из 261, 264 из 264 и 182 из 182 совпадений, разбор структуры файла - 318 функций из 318. Стопроцентная точность. Этот же замер и вскрыл, что опираться на сервис нельзя: искал он безупречно, только совсем не в том месте, которое я ему называл. Внутри разбор трёх дефектов, сложившихся в один тихий отказ: аргумент, проглоченный без ошибки; умолчание, выставленное когда-то наугад; снимок данных, у которого в ответе не указан возраст. Плюс регулярное задание, которое вся команда считала работающим и которого не существовало никогда. Перенос на 1С прилагается: готовый цикл проверки, после которого HTTP-сервис перестаёт молча принимать мусор.

Внутренний сервис кодового индекса больше трёх месяцев отвечал мне про чужую систему. Я передавал database=<имя нужной базы>, сервис этот аргумент выбрасывал и отдавал результат из базы по умолчанию. Правильное имя параметра - db. Ошибки не было. Предупреждения тоже. Приходил нормальный результат: путь к файлу, номер строки, кусок кода вокруг совпадения. Просто из другого проекта.

Нашлось это не потому, что что-то упало. Ломаться там нечему: сервис работал ровно все три месяца и продолжал бы работать дальше. Нашлось потому, что я сел делать скучную вещь - сверять выдачу инструмента с обычным поиском по файлам, чтобы понять, можно ли ему доверять в автоматическом режиме. Точность оказалась стопроцентной. И ровно этот замер вскрыл, что доверять нельзя.

 

Кому это близко

Речь про внутренний сервис кодового индекса в крупной розничной сети: он держит копию исходников нескольких учётных систем, умеет искать по ним и разбирать структуру файла. Ходят к нему разработчики и ИИ-агенты, которым надо быстро ответить на вопрос "где у нас вызывается вот этот метод и что он делает".

Формально это не про 1С. Практически - ровно про неё, потому что класс отказа перенесён один в один. У любого, кто публиковал HTTP-сервис на встроенном языке, стоит регламентное задание синхронизации или держит выгрузку конфигурации в файлы для поиска, есть все три ингредиента: параметры запроса, источник по умолчанию и снимок данных, у которого есть возраст. У нас же в отрасли к этому добавился ещё один потребитель, самый доверчивый из всех: агент, который получил ответ и пошёл на его основании писать код.

Отказ, который я разбираю, устроен так: инструмент исправен, данные валидны, ответ синтаксически безупречен, и он про другой объект. Мониторинг такое не ловит по построению. Двухсотый код ответа, тело непустое, время отклика в норме.

 

Сначала я проверял совсем не то

Первая гипотеза была стандартная и неправильная: инструмент врёт. Отвечает мимо, потому что индексатор битый, разбор файлов кривой, поиск теряет совпадения. Это самая привычная мысль про любой самописный сервис, и проверяется она сверкой.

Взял три метода из разных систем, прогнал через сервис и через обычный текстовый поиск по выгруженным файлам. Совпадения сравнил построчно.

  • 261 из 261 совпадения по первому методу
  • 264 из 264 по второму
  • 182 из 182 по третьему
  • 318 из 318 функций при разборе структуры файла

Ни одного пропуска, ни одного лишнего. Плюс сервис отдаёт номера строк и границы функций, чего текстовый поиск не умеет вообще. По всем метрикам, которые я умел снять, инструмент был лучше моего ручного способа.

Гипотезу "врёт" я закрыл. Только вопрос оказался поставлен неверно. Я мерил, насколько точно сервис находит то, что попросили, в том корпусе, куда он пошёл искать. Про сам выбор корпуса вопроса в замере не было.

Практика: сверка "нашёл или не нашёл" проверяет механику поиска и ничего не говорит про источник. Если вы верифицируете любой поисковый сервис, первым делом сверяйте не количество совпадений, а то, в чём именно он их искал.

 

Что происходило с моим запросом

Разбор оказался коротким и выглядел глупо. Я отправил заведомо мусорный аргумент - выдуманное имя параметра со случайным значением. Ответ пришёл такой же, как всегда. Успешный.

Дальше стало понятно всё сразу. Сервис читает известные ему ключи и молча пропускает остальные. Мой database= в список известных не входил, поэтому уходил в никуда, а имя базы оставалось незаданным. На незаданное имя срабатывало умолчание, и умолчанием была не та система, с которой я работаю каждый день.

Тут два дефекта, и по отдельности они безобидны.

Первый: неизвестный аргумент игнорируется без единого звука. Это распространённое поведение, у него есть внятное оправдание. Клиенты разных версий шлют разные наборы полей, старый клиент не должен падать на новом сервере, ошибка на лишнее поле ломает обратную совместимость. Логика понятная. Цена её в том, что опечатка перестаёт быть опечаткой и становится изменением смысла запроса.

Второй: базой по умолчанию выбрана не самая используемая. Если бы умолчанием стояла основная система, промах по имени параметра почти ничего бы не стоил: почти все запросы и так про неё. Умолчание же выставили когда-то произвольно, первым попавшимся. Именно оно превратило проглоченный аргумент в тихую подмену источника.

Сложите эти два, и получите худший вид ошибки программного интерфейса. Ответ выглядит правильным, потому что он и есть правильный. Правильный ответ на вопрос, которого я не задавал.

Практика: если у вашего сервиса есть параметр выбора источника, умолчанием должен быть либо самый частый источник, либо явный отказ работать. Промежуточный вариант - произвольный источник по умолчанию - это мина.

 

Индекс от 17 апреля при аудите 20 июля

Дальше выяснилось, что даже когда я передавал имя правильно, свежесть ответа была отдельным вопросом. Снимок кодового индекса был собран 17 апреля. Аудит я делал 20 июля. Между ними 94 дня.

За 94 дня в активно правимой конфигурации меняется очень многое. Причём вводит в заблуждение именно то, что ответ приходит: инструмент честно находит метод, показывает файл и строку, и всё это состояние трёхмесячной давности. Отличить такой ответ от свежего невозможно, потому что в выдаче нет метки времени индекса. Ни поля, ни заголовка, ни строчки в подвале ответа.

Мета-индекс, второй слой того же сервиса, отставал ещё сильнее: примерно с первого коммита в марте. То есть внутри одного инструмента жили два слоя данных разной свежести, и на смежные вопросы они отвечали из разных эпох.

Отсутствие даты в ответе я долго считал мелочью интерфейса. Это дефект достоверности. Пользователь физически лишён возможности проверить, стоит ли верить полученному, и единственное, что ему остаётся, - верить целиком.

Практика: любой индекс, кэш, снимок или выгрузка обязаны отдавать свой возраст вместе с данными. Одно поле в ответе. Самый дешёвый фикс из всех возможных и почти всегда самый недооценённый.

 

Робота не существовало

Дальше я пошёл выяснять, почему индекс трёхмесячной давности. Спросил у команды. Ответ был мгновенный и уверенный: "его робот обновляет".

Робота не существовало. Ни задания в планировщике cron (штатный планировщик задач в Linux), ни systemd-таймера, ни джобы в CI (система непрерывной сборки). Автообновления не было никогда, с самого запуска сервиса. Индекс обновляли руками, последний раз - в конце апреля.

Откуда взялась уверенность в роботе? Рядом действительно работала ежедневная регулярная задача - сборка дампа. Она стабильно отрабатывала каждую ночь, была видна в логах, про неё все знали. И к индексу она не имела никакого отношения.

Это отдельный класс, и он мне нравится больше всего в этой истории: соседняя автоматика, принятая за нужную. Рядом что-то регулярно жужжит, значит оно делает то, что мы думаем. Проверять никто не идёт, потому что вопрос "а точно ли этот процесс существует" звучит оскорбительно для всех присутствующих.

В 1С этот класс встречается регулярно. "Ночью отрабатывает регламент, который пересчитывает итоги" - а регламент отключён в этой базе полгода назад. "Обмен идёт по расписанию" - идёт, но из копии, снятой на позапрошлой неделе. Проверяется одним обходом:

Для Каждого Задание Из РегламентныеЗадания.ПолучитьРегламентныеЗадания() Цикл
    Последнее = Задание.ПоследнееЗадание;
    Если Последнее = Неопределено Тогда
        Сообщить(Задание.Наименование + ": не выполнялось ни разу");
    Иначе
        Сообщить(Задание.Наименование + ": " + Последнее.Конец
            + " состояние " + Последнее.Состояние);
    КонецЕсли;
КонецЦикла;

Практика: когда кто-то говорит "это обновляется автоматически", просите показать конкретную строку расписания и последний факт выполнения. Описание процесса за доказательство не считается.

 

Ноль результатов, который означает не то, что вы думаете

Побочная находка того же дня. Поиск в сервисе оказался не полнотекстовым: запрос со скобками отдавал ноль результатов. Ищешь ПолучитьОстатки( - ноль. Ищешь ПолучитьОстатки без скобки - полная выдача.

Ноль результатов читается человеком однозначно: такого кода в базе нет. Значило это другое - "твой запрос не поддержан синтаксисом поиска". Разница между "нет" и "не понял вопрос" в интерфейсе никак не показана.

Отдельно неприятно, что на таком ответе агент останавливается и делает вывод. Пустая выдача для него - завершённый факт, он идёт дальше и строит на нём решение.

Практика: перед тем как поверить нулю, отправьте запрос, на который источник обязан ответить хоть чем-то. Если и он даёт ноль, ноль был не про данные.

 

Что дал прогон после ручного обновления

Индекс я обновил руками и снял те же замеры повторно. Цифры до и после:

Замер До После Дельта
Файлов в индексе 6 252 6 516 +264 (+4,2 %)
Вхождений искомого метода 261 279 +18 (+6,9 %)

Прирост вхождений обгоняет прирост файлов в полтора с лишним раза, и это ожидаемо: правят чаще всего то, что и так активно используется, новые вызовы садятся в старые файлы. Внутренне цифры согласованы, и это единственная причина, по которой я им верю. Замер, который не сходится сам с собой, я бы выкинул.

Обратите внимание на масштаб: 4,2 % файлов за 94 дня. Именно из-за скромности этой цифры дефект и жил так долго. Индекс был устаревшим ровно настолько, чтобы отвечать почти всегда правильно и изредка неправильно. Будь разрыв заметным на глаз, его бы вычислили за неделю.

Практика: медленно протухающий источник опаснее быстро протухающего. Ставьте порог возраста и сигнал по его превышению, потому что заметить деградацию на глаз вы не сможете.

 

Чего я не измерил, и это дыра

Главная цифра в этой истории отсутствует. Я не знаю, сколько запросов за три месяца ушло в базу по умолчанию и сколько решений принято по чужому коду. Логи запросов сервиса такой разбивки не хранят, восстанавливать её задним числом не по чему.

Это неприятно, потому что вопрос "а сколько раз оно нас укусило" - первый, который задаст любой руководитель, и честный ответ на него: не знаю. Мой личный счёт я восстановил по истории переписки, и там были случаи, когда я подолгу искал в коде то, чего в этой системе никогда и не было. Сколько таких же случаев было у остальных, включая агентов, которые ходят туда без меня, - открытый вопрос.

Второе, что не сделано: поведение программного интерфейса осталось прежним. Мы поставили ежедневный таймер синхронизации, обновили индекс, добавили две недостающие базы разреженными клонами (клон, который тянет только нужные каталоги вместо всего репозитория). Молчаливое игнорирование неизвестных аргументов не тронули. То есть завтра тот же промах повторится в точности, просто индекс под ним будет свежий.

И третье: я не проверил, у скольких коллег в конфигурации до сих пор прописано database= вместо db=. Подозреваю, что не у меня одного, потому что имя database подсказывает здравый смысл, а db надо знать.

Честная граница применимости всей истории: она про сервисы с несколькими источниками данных и параметром выбора. Если у вашего сервиса источник ровно один, ничего из перечисленного вам не грозит, кроме устаревшего снимка.

 

Как этот же класс выглядит в 1С

Перенесу на нашу поляну, механика там та же. HTTP-сервис на встроенном языке ведёт себя точно так же: лишние параметры в строке запроса платформа не отвергает, обработчик их просто не читает.

Функция НайтиДанные(Запрос)
    ИмяБазы = Запрос.ПараметрыЗапроса.Получить("db");
    Если ИмяБазы = Неопределено Тогда
        ИмяБазы = БазаПоУмолчанию();   // вот здесь и живёт тихая подмена
    КонецЕсли;
    ...

Клиент прислал database=Склад, обработчик прочитал db, получил Неопределено, взял умолчание. Всё штатно, двухсотый код, тело с данными.

Заставить сервис ругаться на неизвестное стоит одного цикла на входе:

Известные = Новый Массив;
Известные.Добавить("db");
Известные.Добавить("query");
Известные.Добавить("limit");

Для Каждого КлючЗначение Из Запрос.ПараметрыЗапроса Цикл
    Если Известные.Найти(КлючЗначение.Ключ) = Неопределено Тогда
        Ответ = Новый HTTPСервисОтвет(400);
        Ответ.УстановитьТелоИзСтроки("Неизвестный параметр: " + КлючЗначение.Ключ);
        Возврат Ответ;
    КонецЕсли;
КонецЦикла;

И метка времени источника в каждый ответ, отдельным заголовком, чтобы её было видно даже без разбора тела:

Ответ.Заголовки.Вставить("X-Source-Built",
    Формат(ДатаСборкиИсточника, "ДФ=yyyy-MM-ddTHH:mm:ss"));

Та же метка нужна не только сервису, но и файлам, которые вы отдаёте модели. У меня выгрузку структуры конфигурации для чата делает Выгрузка метаданных для LLM, и она подписывает файл сама: в шапке имя и версия конфигурации, версия платформы и дата снятия. Ровно затем, чтобы через два месяца было видно, из какой эпохи структура, по которой агент пишет запрос. Дырка там та же, о которой вся статья, и я её называю: в шапку идёт имя конфигурации, а не информационной базы, поэтому тест и бой по ней не различить. Имя базы к файлу приходится дописывать руками.

Третье место, где тот же класс встречается у нас постоянно, - список информационных баз. Языковой сервер у нас был разложен на два экземпляра, каждый видел только свою систему, и спросить не тот порт означало ровно то же самое, что промахнуться параметром. У 1С-разработчика аналог - два одинаково названных ярлыка на тестовую и боевую, и вопрос "а в какой я сейчас" решается тем, что в заголовке окна видно имя базы. Вот эта простая штука, имя источника прямо в ответе, и есть лекарство от всего разобранного.

 

Открытый вопрос

Я потратил на это разбирательство куда больше, чем собирался, и доказал себе одно: инструмент, который отвечает со стопроцентной точностью, может быть при этом полностью бесполезен. Точность механики и достоверность ответа - две метрики, и первая ничего не говорит про вторую. Я мерил первую, а доверял второй, и разрыв между ними прожил три месяца.

Отдельно скажу про спорное. Я знаю аргумент в защиту молчаливого игнорирования: обратная совместимость, старые клиенты, лишние поля от прокси и балансировщиков. Аргумент рабочий, я сам так делал. После этой истории считаю, что он справедлив для полей данных и не работает для параметров, которые выбирают источник. Опечатка в фильтре стоит пустой выдачи, опечатка в имени базы стоит неверного решения, и разводить эти два случая надо на уровне кода, а не на уровне договорённостей.

Теперь вопрос к вам, и он ровно тот, из-за которого я это пишу. Ваш HTTP-сервис или обмен сейчас отвечает двухсоткой на запрос с выдуманным параметром? Проверяется за минуту: добавьте в строку запроса &xyzzy=1 и посмотрите на код ответа. Напишите в комментариях, что получилось, и заодно - считаете ли вы, что программный интерфейс обязан падать на неизвестных аргументах. Мне интересно, много ли нас таких, кто узнал про это через собственный трёхмесячный дефект.


Другие наши инструменты для работы с нейросетями:

Вступайте в нашу телеграмм-группу Инфостарт

HTTP-сервис 1С параметры запроса проверка входных параметров неизвестный параметр значение по умолчанию тихий отказ молчаливое игнорирование индекс кода устаревший снимок данных метка актуальности регламентное задание диагностика интеграции отладка обмена

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

WEB-интеграция Разработчик Аналитик 1С:Предприятие 8 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Оптовая торговля, дистрибуция, логистика ИТ-компания Платные (руб)

Модуль "Экспортер" — это расширение для 1С, предназначенное для автоматизации процессов выгрузки данных. Оно позволяет эффективно извлекать, преобразовывать и передавать данные из систем 1С в интеграционную платформу Spot2D. Подсистема упрощает настройку, снижает количество ручных операций и обеспечивает удобный контроль данных.

17568 руб.

20.12.2024    7239    32    4    

34

WEB-интеграция Анализ продаж Системный администратор Разработчик Пользователь 1С:Предприятие 8 1С:Розница 2 1С:Управление нашей фирмой 1.6 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Управленческий учет Платные (руб)

Модуль "Подсистема интеграции AmoCRM с 1С" позволяет обеспечить единое информационное пространство, в котором пользователи могут эффективно управлять клиентской базой, следить за статусами сделок и поддерживать актуальность данных как в AmoCRM, так и в 1С.

60000 руб.

07.05.2019    44244    76    45    

32

WEB-интеграция Системный администратор Разработчик Пользователь 1С:Предприятие 8 1C:Бухгалтерия 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 Автомобили, автосервисы Россия Управленческий учет Платные (руб)

Интеграционный модуль обмена по API между конфигурацией 1С:Альфа-Авто 6 и порталом LogicStar. Позволяет работать с несколькими обменами LogicStars разных брендов (CHERY, OMODA, JAECOO, EXEED, TENET) в одной информационной базе в ручном и автоматическом режиме. Поддерживается выгрузка заказ-нарядов, реализаций товаров и товарных остатков.

20740 руб.

13.05.2025    2780    4    0    

7

WEB-интеграция Разработчик 1С:Предприятие 8 1С:Бухгалтерия 3.0 Бытовые услуги, сервис Платные (руб)

Расширение для автоматизации передачи данных между сервисом Vetmanager с 1С: Бухгалтерия 3.0. Решение позволяет загружать документы и справочники из Ветменеджер в 1С:Бухгалтерию, сокращая время на ручной ввод данных и минимизируя ошибки.

24000 руб.

02.02.2021    23945    72    52    

44

Сайты и интернет-магазины WEB-интеграция Системный администратор Разработчик Пользователь 1С:Предприятие 8 1C:Бухгалтерия 1С:Управление торговлей 11 Автомобили, автосервисы Россия Управленческий учет Платные (руб)

Интеграционный модуль обмена между конфигурацией Альфа Авто 5 и Альфа Авто 6 и порталом AUTOCRM / LOGICSTARS. Данный модуль универсален. Позволяет работать с несколькими обменами AUTOCRM / LOGICSTAR разных брендов в одной информационной базе в ручном и автоматическом режиме.

42700 руб.

03.08.2020    25300    40    26    

29
Для отправки сообщения требуется регистрация/авторизация