Как правильно создать функцию в модуле расширения и избежать ошибки "Процедура или функция с указанным именем не определена"?

Программист 1С v8.3 (Управляемые формы)
← На главную

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

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

Исходная проблема: "Процедура или функция с указанным именем не определена"

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

Исходный код, который вызвал ошибку, выглядел следующим образом:


&Вместо("ПриИзмененииСумма")
Процедура Расш1_ПриИзмененииСумма(Форма, ИмяТаблицы, ЗначениеПустогоКоличества, ПрименяютсяСтавки4и2, СоответствиеРеквизитов)
    Массив = ПолучитьМассивНаСервере();
    // ... дальнейшая логика ...
КонецПроцедуры

Функция ПолучитьМассивНаСервере Экспорт
    Массив = Новый Массив;
    Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000001"));
    Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000002293"));
    Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000003489"));
    Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000217"));
    Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000002"));
    Возврат Массив;
КонецФункции

При попытке обновить расширение система выдает ошибку:

Процедура или функция с указанным именем не определена (ПолучитьМассивНаСервере)

Автор справедливо предположил, что проблема может быть связана с директивами компиляции &НаКлиенте/&НаСервере. Однако, даже их добавление не привело к желаемому результату.

Анализируем контексты выполнения и свойства модулей

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

Директивы компиляции: Клиент, Сервер, Контекст

  1. &НаКлиенте: Процедура или функция выполняется строго на клиентской стороне. Из нее доступен весь контекст формы (реквизиты, элементы), но нет прямого доступа к базе данных или другим серверным объектам.
  2. &НаСервере: Процедура или функция выполняется на сервере. Ей доступен контекст формы, и она может обращаться к базе данных. Однако передача контекста формы между клиентом и сервером может быть ресурсоемкой.
  3. &НаСервереБезКонтекста: Эта директива также указывает на выполнение на сервере, но при этом контекст формы не передается. Это значительно оптимизирует взаимодействие, поскольку передаются только параметры функции, а возвращается только результат. Идеально подходит для вспомогательных функций, которым не нужен прямой доступ к элементам формы.
  4. &НаКлиентеНаСервереБезКонтекста: Используется для кода, который должен быть доступен и выполняться как на клиенте, так и на сервере, исключая дублирование кода.

Важно: Прямой доступ к базе данных возможен только на серверной стороне. Если из клиентской процедуры требуется обратиться к данным или выполнить серверные операции, необходимо вызывать серверные процедуры или функции.

Свойства общих модулей и их значение

Общие модули в 1С имеют ряд флагов (свойств), которые определяют их контекст выполнения и доступность:

  1. Клиент (управляемое приложение): Если флаг установлен, код модуля может быть выполнен на клиентской стороне.
  2. Сервер: Если флаг установлен, код модуля может быть выполнен на серверной стороне.
  3. Вызов сервера: Это ключевой флаг. Если он установлен, серверные процедуры и функции этого модуля могут быть вызваны из клиентского кода. Без этого флага клиентская часть не "видит" серверные экспортные методы модуля.
  4. Внешнее соединение: Если флаг установлен, код модуля может быть выполнен при внешнем соединении.
  5. Привилегированный: Если установлен, код модуля выполняется с полными правами, независимо от прав текущего пользователя. (В расширениях нельзя создавать привилегированные модули).
  6. Глобальный: Если установлен, экспортные процедуры и функции модуля доступны без указания имени модуля. (В расширениях нельзя создавать глобальные модули).

В нашем случае, процедура Расш1_ПриИзмененииСумма была добавлена в расширение, замещая процедуру из ОбщийМодуль.ОбработкаТабличныхЧастейКлиентСервер. Полезное сообщение 5 указывает, что исходный общий модуль имеет флаги Клиент, Сервер, ВнешнееСоединение. Это означает, что он может быть вызван как с клиента, так и с сервера, и его методы доступны в обоих контекстах.

Когда мы добавляем процедуру Расш1_ПриИзмененииСумма через &Вместо, она становится частью контекста этого общего модуля. Если сама процедура ПриИзмененииСумма в исходной конфигурации вызывается из клиентского контекста формы, то и наша расширяющая процедура Расш1_ПриИзмененииСумма будет выполняться в клиентском контексте. А функция ПолучитьМассивНаСервере, очевидно, должна выполняться на сервере, так как она обращается к справочникам.

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

Особенности аннотации &Вместо

Аннотация &Вместо (или "вместо") является мощным инструментом расширений, позволяющим полностью заменить исходный метод конфигурации. Это означает, что при вызове оригинального метода будет выполнен только наш расширяющий метод.

Есть несколько важных нюансов при работе с &Вместо:

  1. Полное замещение: Расширяющий метод полностью замещает исходный. Если в расширяющем методе не будет вызова ПродолжитьВызов(), то оригинальный код никогда не будет выполнен.
  2. ПродолжитьВызов(): Эта функция позволяет вызвать исходный (расширяемый) метод конфигурации. После его выполнения управление возвращается в расширяющий метод. Это критически важно, если мы хотим не просто заменить, а дополнить или изменить поведение исходного метода.
  3. Только для функций: Для расширения функций в 1С можно использовать только &Вместо, так как нам нужно перехватить возвращаемое значение.
  4. Влияние на обновление: Использование &Вместо без ПродолжитьВызов() сродни снятию метода с поддержки, поскольку любые изменения в исходном методе конфигурации не будут учтены вашим расширением.

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

Поиск решения: от модуля формы до нового общего модуля

Давайте рассмотрим, какие варианты решения проблемы обсуждались и к какому результату это привело.

Первая идея: Перенос кода в модуль формы

Сообщение 8 предлагает перенести код функции ПолучитьМассивНаСервере непосредственно в модуль формы, связанный с табличной частью, или в модуль документа. Это может быть рабочим решением, если процедура ПриИзмененииСумма вызывается из конкретной формы.

  1. Почему это может сработать: В модуле формы мы можем явно определить клиентские и серверные процедуры/функции. Если процедура Расш1_ПриИзмененииСумма будет находиться в модуле формы и будет вызвана на клиенте, она может напрямую вызвать серверную функцию в том же модуле формы, используя директиву &НаСервере. Система автоматически обработает вызов сервера в рамках одного модуля формы.
  2. Недостатки: Если функция ПолучитьМассивНаСервере универсальна и должна использоваться в разных местах, перенос ее в модуль конкретной формы приведет к дублированию кода или ограничит ее повторное использование. Кроме того, ПриИзмененииСумма из общего модуля может вызываться из разных форм или даже в серверном контексте, что усложнит универсальное решение.

Окончательное решение: Создание нового общего модуля в расширении с флагом "Вызов сервера"

Самым эффективным и чистым решением, к которому пришли в ходе обсуждения (сообщения 9, 10, 11), стало создание нового общего модуля в расширении специально для функции ПолучитьМассивНаСервере.

Шаги для реализации решения:
  1. Создаем новый общий модуль в расширении:

    В конфигураторе, в дереве объектов расширения, находим ветку "Общие" -> "Общие модули". Добавляем новый общий модуль, например, Расш1_ПомощникСервер. Имя модуля желательно снабдить префиксом расширения, чтобы избежать конфликтов и улучшить читаемость.

  2. Настраиваем свойства нового общего модуля:

    Это самый важный шаг. Для нашего нового модуля Расш1_ПомощникСервер необходимо установить следующие флаги (свойства):

    • Сервер: Устанавливаем, так как функция ПолучитьМассивНаСервере должна выполняться на сервере.
    • Вызов сервера: Обязательно устанавливаем этот флаг. Он позволяет вызывать экспортные процедуры и функции этого модуля из клиентского контекста.
    • Клиент (управляемое приложение): В данном случае этот флаг можно не устанавливать, так как все функции модуля предназначены для выполнения на сервере. Это также соответствует рекомендации не определять общие модули одновременно для сервера и для клиента, если их функциональность разделена. Однако, если бы в этом модуле были функции, которые могли бы вызываться на клиенте, флаг нужно было бы установить.

    После установки этих флагов, наш модуль Расш1_ПомощникСервер будет доступен для вызова серверных функций из клиентского кода.

  3. Переносим функцию в новый модуль:

    Копируем нашу функцию ПолучитьМассивНаСервере в новый общий модуль Расш1_ПомощникСервер.

    
    // В общем модуле расширения Расш1_ПомощникСервер
    // Свойства: Сервер=Истина, ВызовСервера=Истина, Клиент=Ложь
    
    Функция ПолучитьМассивНаСервере() Экспорт
        Массив = Новый Массив;
        Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000001"));
        Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000002293"));
        Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000003489"));
        Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000217"));
        Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000002"));
        Возврат Массив;
    КонецФункции
    
  4. Изменяем вызов функции в расширяющей процедуре:

    Теперь, когда функция находится в отдельном общем модуле, мы должны вызывать ее, указывая имя модуля.

    
    &Вместо("ПриИзмененииСумма")
    Процедура Расш1_ПриИзмененииСумма(Форма, ИмяТаблицы, ЗначениеПустогоКоличества, ПрименяютсяСтавки4и2, СоответствиеРеквизитов)
        // Добавляем директиву, если процедура вызывается на клиенте,
        // а ПолучитьМассивНаСервере находится в общем модуле с ВызовСервера
        // Если Расш1_ПриИзмененииСумма сама по себе серверная, то &НаСервере не требуется
    
        // Если процедура Расш1_ПриИзмененииСумма выполняется в клиентском контексте,
        // а наш новый модуль Расш1_ПомощникСервер имеет флаг "Вызов сервера",
        // то можно вызывать функцию напрямую, платформа сама организует вызов сервера.
        Массив = Расш1_ПомощникСервер.ПолучитьМассивНаСервере();
        // ... дальнейшая логика ...
    КонецПроцедуры
    

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

Подробное объяснение: почему это работает

Давайте еще раз проанализируем, почему именно такое решение оказалось правильным:

  1. Проблема контекста: Изначально функция ПолучитьМассивНаСервере была помещена непосредственно рядом с процедурой Расш1_ПриИзмененииСумма, которая сама по себе находилась в контексте расширяемого общего модуля. Если этот расширяемый модуль (ОбщийМодуль.ОбработкаТабличныхЧастейКлиентСервер) вызывается из клиентского контекста, то и Расш1_ПриИзмененииСумма начинает выполняться на клиенте. Клиентский код не имеет прямого доступа к серверным функциям, если они не находятся в модуле с флагом "Вызов сервера".
  2. Роль флага "Вызов сервера": Когда мы создаем новый общий модуль Расш1_ПомощникСервер в расширении и устанавливаем для него флаг "Вызов сервера", мы явно сообщаем платформе 1С, что экспортные функции этого модуля (такие как ПолучитьМассивНаСервере) могут быть безопасно вызваны из клиентского контекста. Платформа самостоятельно организует необходимые механизмы для удаленного вызова серверной функции, передаст параметры и получит результат.
  3. Разделение ответственности: Это решение также соответствует хорошим практикам программирования. Мы выделили серверную функциональность, которая не зависит от контекста формы, в отдельный общий модуль. Это повышает модульность, упрощает тестирование и делает код более читаемым и поддерживаемым.

Дополнительные рекомендации и лучшие практики

Для более эффективной и надежной работы с расширениями и модулями в 1С, рассмотрим несколько дополнительных рекомендаций:

Префиксы в расширениях

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

Внимательное использование ПродолжитьВызов()

При использовании &Вместо всегда задавайтесь вопросом: "Нужно ли мне выполнить исходный код конфигурации?" Если ответ "да", то обязательно используйте ПродолжитьВызов(). Это сохранит работоспособность типового функционала и позволит вашему расширению правильно взаимодействовать с обновлениями конфигурации.


&Вместо("ИсходнаяПроцедура")
Процедура Расш1_ИсходнаяПроцедура(...)
    // Наша дополнительная логика до вызова исходной процедуры
    Сообщить("Выполняется код расширения до исходной процедуры");

    ПродолжитьВызов(); // Вызываем исходную процедуру конфигурации

    // Наша дополнительная логика после вызова исходной процедуры
    Сообщить("Выполняется код расширения после исходной процедуры");
КонецПроцедуры

Если мы расширяем функцию, то результат ПродолжитьВызов() необходимо принять:


&Вместо("ИсходнаяФункция")
Функция Расш1_ИсходнаяФункция(...)
    // Наша дополнительная логика до вызова исходной функции
    Сообщить("Выполняется код расширения до исходной функции");

    РезультатИсходнойФункции = ПродолжитьВызов(); // Вызываем исходную функцию конфигурации и получаем её результат

    // Модифицируем результат или используем его
    Если РезультатИсходнойФункции = Неопределено Тогда
        Возврат "НашНовыйРезультат";
    Иначе
        Возврат РезультатИсходнойФункции;
    КонецЕсли;
КонецФункции

Разделение клиентской и серверной логики

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

Использование директив &НаСервере или &НаСервереБезКонтекста внутри модуля формы является исключением, так как модуль формы специально предназначен для смешанной логики.

Ошибки "Процедура или функция с указанным именем не определена"

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

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

← На главную