1C-CodeОсновы 1С → Функции и процедуры: как код делится на части

Функции и процедуры: как код делится на части

Зачем разбивать код, чем функция отличается от процедуры и что такое область видимости.

Глава 3 из 36 · чтение примерно 28 минут

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

Код разбивают на части. Каждая часть делает одно дело, имеет имя и может быть вызвана оттуда, откуда нужно. Эти части и есть функции и процедуры.

Зачем разбивать код

Три причины, и первая не та, которую называют чаще всего.

Имя объясняет намерение. Строка Итого = СуммаСоСкидкой(Заказ); понятна без чтения того, что внутри. Пятнадцать строк расчёта на том же месте пришлось бы разбирать.

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

Проверяемость. Кусок кода с именем и понятным ответом можно проверить отдельно. Именно так устроены задачи в этом тренажёре: вы пишете функцию, а судья её вызывает.

Из чего состоит функция

Функция СуммаЗаказа(Цена, Количество) Экспорт
	Итого = Цена * Количество;
	Возврат Итого;
КонецФункции
ЧастьЧто означает
Функцияключевое слово: дальше объявляется функция
СуммаЗаказаимя — по нему функцию будут вызывать
(Цена, Количество)параметры: данные, которые функция получает снаружи
Экспортфункцию видно из других модулей. Без него — только внутри своего
отступ в телеодна табуляция: видно, что строки принадлежат функции
Возврат Итого;функция заканчивается и отдаёт значение вызвавшему
КонецФункцииконец объявления. Точка с запятой здесь не нужна

Прочитайте это как предложение: «функция СуммаЗаказа получает цену и количество, перемножает их и возвращает результат».

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

Объявление и вызов — разные вещи

// объявление: описали, что умеет делать
Функция СуммаЗаказа(Цена, Количество) Экспорт
	Возврат Цена * Количество;
КонецФункции

// вызов: попросили сделать, передав конкретные данные
Итого = СуммаЗаказа(250, 4);
ЧастьЧто означает
при объявленииЦена и Количество — просто имена, значений в них ещё нет
при вызовена их место подставляются 250 и 4
Итогосюда попадёт то, что функция вернула — 1000
можно вызывать много разс разными данными: СуммаЗаказа(100, 2) даст 200

Упражнение 1. Первая функция

Напишите функцию Удвоить, которая принимает одно число и возвращает его, умноженное на два.
Здесь нужно написать функцию целиком — вместе со строкой КонецФункции.

Решается в тренажёре: код запускается и проверяется сразу.

Параметры: как передать данные внутрь

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

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

Параметр можно не передать

Если у функции два параметра, а вызвали с одним — второй останется Неопределено. Программа не возразит при вызове, но упадёт при попытке умножить Неопределено на число.

Поэтому параметрам часто задают значение по умолчанию: Функция Скидка(Сумма, Процент = 10). Тогда вызов Скидка(1000) будет означать «со скидкой 10».

Упражнение 2. Два параметра

Напишите функцию СуммаСтроки с двумя параметрами — ценой и количеством. Она возвращает их произведение, округлённое до двух знаков командой Окр(Значение, 2).

Решается в тренажёре: код запускается и проверяется сразу.

Возврат: функция заканчивается сразу

Возврат делает две вещи одновременно: отдаёт значение и немедленно прекращает выполнение функции. Строки после него не выполнятся никогда.

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

✓ Ранний возврат

Функция Процент(Факт, План)
	Если План <= 0 Тогда
		Возврат 0;
	КонецЕсли;
	Возврат Факт / План * 100;
КонецФункции

Особый случай отсекается сразу, дальше идёт главное. Читается сверху вниз.

✕ Всё в одном Если

Функция Процент(Факт, План)
	Если План > 0 Тогда
		Возврат Факт / План * 100;
	Иначе
		Возврат 0;
	КонецЕсли;
КонецФункции

Тоже верно, но при трёх-четырёх проверках вложенность разрастается.

Процедура: когда возвращать нечего

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

Разница только одна: процедура не возвращает значения. Всё остальное — имя, параметры, Экспорт — точно так же.

Функция и процедура

ФункцияПроцедура
ОбъявлениеФункция … КонецФункцииПроцедура … КонецПроцедуры
Возвращает значениеДа, через ВозвратНет
Как вызываютИтого = Посчитать(5);Показать(5);
Возврат внутриВозврат Значение;Возврат; — просто выйти досрочно
Когда выбиратьнужен ответнужно действие

Как выбрать

Спросите себя: «я хочу получить ответ или совершить действие?»

«Посчитать сумму заказа» — ответ, значит функция. «Провести документ» — действие, значит процедура. Если и то и другое сразу — обычно это признак, что стоит разделить на две части.

Упражнение 3. Ранний возврат

Напишите функцию БезопасноеДеление(Делимое, Делитель). Если делитель равен нулю — вернуть 0, не выполняя деления. Иначе вернуть результат деления.

Решается в тренажёре: код запускается и проверяется сразу.

Область видимости: где переменная существует

Переменная, созданная внутри функции, снаружи не видна. Она рождается при входе в функцию и исчезает при выходе.

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

Каждая функция — свой мир

Функция Первая()
	Результат = 10;      // эта переменная живёт только здесь
	Возврат Результат;
КонецФункции

Функция Вторая()
	Результат = 20;      // ДРУГАЯ переменная, просто с тем же именем
	Возврат Результат;
КонецФункции
ЧастьЧто означает
две переменные Итогникак не связаны — одинаковое имя ничего не значит
почему такиначе функции ломали бы друг друга через общие имена
как передать значениечерез параметр при вызове и через Возврат обратно
выводфункция общается с миром только двумя способами — входом и выходом

Массив и структура передаются по ссылке

Число и строка передаются копией: изменили параметр внутри функции — снаружи ничего не поменялось.

А массив, структура, соответствие и таблица значений передаются по ссылке: внутри функции вы работаете с тем же самым объектом. Добавили элемент в массив-параметр — он добавился и у вызвавшего.

Это самая коварная особенность языка. Функция с невинным именем ПосчитатьИтоги может незаметно испортить переданную ей таблицу, и искать причину будут долго. Если функция не должна менять вход — работайте с копией.

Упражнение 4. Функция вызывает функцию

Напишите две функции. Удвоить(Ч) возвращает число, умноженное на два. Учетверить(Ч) возвращает результат двойного вызова первой — не пишите умножение на четыре.

Решается в тренажёре: код запускается и проверяется сразу.

Экспорт: кто может вызвать

Слово Экспорт после списка параметров означает: функцию видно снаружи модуля, в котором она написана.

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

Правило простое: Экспорт ставят только тем функциям, которые действительно вызывают извне. Что такое модуль — в следующей главе.

Упражнение 5. Процедура, а не функция

Напишите функцию Поприветствовать(Имя), которая выводит Сообщить("Здравствуйте, " + Имя) и ничего не возвращает. Проверьте на имени Иван — должно вывестись Здравствуйте, Иван.

Решается в тренажёре: код запускается и проверяется сразу.

Как разбивать код на части

Общее правило: одна функция — одно дело. Если, объясняя, что делает функция, вы говорите «и», её стоит разделить.

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

Третье: функция не должна удивлять. Если она называется «Посчитать», она не должна попутно записывать документ. Тот, кто её вызовет, об этом не догадается.

Параметры со значением по умолчанию

Параметру можно задать значение, которое подставится, если его не передали: Функция Скидка(Сумма, Процент = 10).

Тогда Скидка(1000) посчитает десять процентов, а Скидка(1000, 25) — двадцать пять. Одна функция вместо двух похожих.

Правило одно: параметры со значением по умолчанию идут последними. Иначе непонятно, какому из них соответствует переданное значение.

Пропустить параметр в середине

В 1С можно не передавать параметр, оставив его место пустым: Функция(Первый, , Третий). Две запятые подряд — это «второй по умолчанию».

Встречается в платформенных методах (мы видели это у Скопировать), но в своём коде лучше не злоупотреблять: через полгода никто не помнит, что там было пропущено.

Когда параметров становится много, вместо длинного списка передают структуру параметров. В типовых так и делают: у функции два-три обязательных параметра и один «ДополнительныеПараметры».

Упражнение 6. Скидка по умолчанию

Напишите СоСкидкой(Сумма, Процент = 10): вернуть сумму за вычетом процента. Проверка вызывает её без второго параметра с суммой 1000 — ожидается 900.

Решается в тренажёре: код запускается и проверяется сразу.

Знач: параметр только для чтения

По умолчанию параметр в 1С передаётся по ссылке: функция, изменившая параметр, изменит переменную вызывающего. Для чисел и строк это редко замечают, для коллекций — постоянно.

Слово Знач перед именем параметра это выключает: Функция Посчитать(Знач Строки) получает свою копию ссылки, и присваивание внутри наружу не выходит.

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

Знач защищает от присваивания, а не от изменения

Знач Список запрещает Список = Новый Массив — присваивание не выйдет наружу. Но Список.Добавить(5) изменит тот самый массив вызывающего.

Причина в том, что копируется ссылка, а не сама коллекция. Копия ссылки указывает на тот же объект.

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

Экспорт: кто увидит функцию

Слово Экспорт в конце объявления означает «эту функцию можно вызывать снаружи модуля».

Без него функция видна только внутри своего модуля. Это не ограничение, а инструмент: экспортные функции — то, чем модуль пользуется наружу, остальные — его внутренняя кухня, которую можно переписать, никого не сломав.

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

Функция, вызывающая саму себя

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

Устроена рекурсия всегда одинаково: сначала условие выхода («детей нет — возвращаем»), потом вызов себя для каждого потомка. Забытое условие выхода даёт то же, что бесконечный цикл, только заканчивается переполнением стека.

Знать надо и обратное: почти всё, что делают рекурсией по иерархии справочника, запрос делает одним обращением — и быстрее. Рекурсия остаётся там, где данные уже в памяти.

Сколько строк должно быть в функции

Часто называют число — двадцать, пятьдесят. Это неточный ориентир.

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

Что дальше

Вы умеете писать функции, передавать в них данные и получать ответ. Это позволяет решать задачи из раздела «Код» — там как раз просят написать функцию.

Следующая глава — про то, где этот код живёт: модули, клиентская и серверная часть, и почему в 1С это приходится различать.

Как закрепить

Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.

Открыть эту главу в тренажёре

Переменные: где программа хранит данные
Где живёт код: модули, клиент и сервер