Everyone who has worked with someone else's code in 1C has experienced that feeling at least once: you open a module, and there is a procedure of 800 lines with the name "ProcessData". No comments. With variables "a", "b" and "Time". And you understand that in six months your own code can evoke exactly the same emotions in someone.
Let's figure it out, how to write procedures and functions in 1C so that the code remains readable, predictable and easy to maintain — even after years.
Procedure vs. function: what is the fundamental difference
At first glance, everything is simple: the function returns a value, the procedure does not. But behind this simplicity lies an important design principle.
Function — is a question. You ask the system about something and get an answer. It should not change the state of the database, it should not display messages to the user. It just calculates and returns the result.
Функция РассчитатьСуммуСоСкидкой(Сумма, ПроцентСкидки)
Возврат Сумма - Сумма * ПроцентСкидки / 100;
КонецФункцииProcedure — this is a command. You ask the system to do something: write a document, send a letter, fill in the tabular part. It performs an action and does not return a result.
Процедура ЗаполнитьЦеныВТабличнойЧасти(ТабличнаяЧасть, ТипЦен)
Для Каждого Строка Из ТабличнаяЧасть Цикл
Строка.Цена = ПолучитьЦенуНоменклатуры(Строка.Номенклатура, ТипЦен);
Строка.Сумма = Строка.Количество * Строка.Цена;
КонецЦикла;
КонецПроцедурыIf you find yourself writing a function that both writes data and returns a result, it's a signal that you should split the logic into two parts.
Naming: your code is read by people, not by the compiler
The name of a procedure or function is a contract with the person who will read the code after you. A good name makes a comment unnecessary.
Bad names are names that do not say anything about the purpose:
// What is it processing? What data? Why?
Процедура ОбработатьДанные()
// What exactly is it checking? What document?
Функция Проверка(Док)
// "Execute" is the most useless word in the titleроцедура ВыполнитьОперацию()Good names contain a verb and describe a specific action:
Процедура РассчитатьСебестоимостьПоПартиям(ДокументОбъект)
Функция ПолучитьОстатокНоменклатурыНаСкладе(Номенклатура, Склад, Дата)
Функция ЕстьПравоНаСкидку(Контрагент, СуммаЗаказа)A few rules that really work:
Start question functions with the words "Get", "Calculate", "Find", "Is" (for Boolean values).
Start command procedures with the words "Fill in", "Write", "Send", "Delete", "Set".
Don't be afraid of long names. "GetPaymentAmountForPeriodByCounterparty" is clearer than "GetAmount".
Parameters: less is better
If your procedure has more than five parameters, something went wrong. This is not just a question of aesthetics: a large number of parameters means that the procedure does too much or that the data is poorly structured.
Problem:
Процедура СоздатьЗаказ(Контрагент, Склад, Менеджер, ДатаОтгрузки,
ТипЦен, ВалютаДокумента, Организация, Комментарий, Приоритет)
// ...40 lines of document creation
КонецПроцедурыSolution — structure:
Функция НовыеПараметрыЗаказа()
Параметры = Новый Структура;
Параметры.Вставить("Counterparty");
Параметры.Вставить("Warehouse");
Параметры.Вставить("Manager");
Параметры.Вставить("Date of shipment", ТекущаяДата());
Параметры.Вставить("PriceType");
Параметры.Вставить("Currency");
Параметры.Вставить("Organization");
Параметры.Вставить("Comment", "");
Параметры.Вставить("Priority", 0);
Возврат Параметры;
КонецФункции
Процедура СоздатьЗаказ(ПараметрыЗаказа)
// Clean, clear, expandable
КонецПроцедурыThe "parameter constructor function" pattern is one of the most useful techniques in 1C. It allows you to add new parameters without breaking existing calls.
Principle of sole responsibility
This is perhaps the most important principle that is violated in the 1C code most often. One procedure should do one thing.
Antipattern — "combine procedure":
Процедура ОбработатьДокумент(ДокументОбъект)
// Completion check — 50 lines
// Calculation of amounts — 30 lines
// Formation of movements — 100 lines
// Sending a notification - 20 lines
// Counterparty status update - 25 lines
// Logging — 15 lines
КонецПроцедурыThis procedure cannot be tested, it is difficult to debug, and when something breaks — and it will break — you will re-read all 240 lines.
The right approach:
Процедура ОбработатьДокумент(ДокументОбъект)
ОшибкиЗаполнения = ПроверитьЗаполнениеДокумента(ДокументОбъект);
Если ОшибкиЗаполнения.Количество() > 0 Тогда
ВывестиОшибкиЗаполнения(ОшибкиЗаполнения);
Возврат;
КонецЕсли;
РассчитатьСуммыДокумента(ДокументОбъект);
СформироватьДвижения(ДокументОбъект);
ОтправитьУведомление(ДокументОбъект);
ОбновитьСтатусКонтрагента(ДокументОбъект.Контрагент);
КонецПроцедурыNow each operation is isolated. If you need to change the logic for calculating amounts, you know exactly where to look. If the sending of notifications is broken, you do not need to re-read the calculation code.
Error handling: don't hide problems
One of the most common mistakes is silently swallowing exceptions.
What not to do:
Процедура ОтправитьДанныеВоВнешнююСистему(Данные)
Попытка
// ...sending
Исключение
// It's empty. Well, it didn't go through, and that's okay.
КонецПопытки;
КонецПроцедурыThree months later, the accountant comes and says: "The data hasn't moved since last summer." And the logs are silent.
How to do it right:
Процедура ОтправитьДанныеВоВнешнююСистему(Данные)
Попытка
// ...sending
Исключение
ТекстОшибки = ОписаниеОшибки();
ЗаписьЖурналаРегистрации("IntegrationExternalSystem",
УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
ВызватьИсключение "Failed to send data: " + ТекстОшибки;
КонецПопытки;
КонецПроцедурыThe golden rule: only catch the errors that you know how to handle. The rest — log and throw higher.
Export: Don't make everything public
In 1C, there is a temptation to put "Export" on each function — what if it comes in handy. This leads to the fact that the module turns into a dump, where it is impossible to understand what is a public API and what is an internal kitchen.
The rule is simple: a procedure or function receives the "Export" modifier only when it is really needed by other modules. Everything else is a private implementation that you are free to change as you wish.
// This is a public API module — well documented, stable contract
Функция РассчитатьСтоимостьДоставки(Параметры) Экспорт
// ...
КонецФункции
// This is an internal implementation detail — no export
Функция ПолучитьТарифнуюЗону(АдресДоставки)
// ...
КонецФункцииReturned values: be predictable
The function must always return a value of one type. If a function can return a string, a number, or Undefined depending on the situation, each calling code turns into a detective.
Bad:
Функция НайтиКонтрагента(ИНН)
Запрос = Новый Запрос("...");
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат Ложь; // Sometimes boolean
КонецЕсли;
Возврат Результат.Выгрузить()[0].Контрагент; // Sometimes a link
КонецФункцииGood:
// ALWAYS returns a link. Empty link = not found.
Функция НайтиКонтрагентаПоИНН(ИНН)
Запрос = Новый Запрос("...");
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат Справочники.Контрагенты.ПустаяСсылка();
КонецЕсли;
Возврат Результат.Выгрузить()[0].Контрагент;
КонецФункцииComments: explain "why" and not "what"
Good code hardly needs any comments — it explains itself through understandable names. But sometimes comments are necessary: when you need to explain a non-obvious business rule or the reason for a non-standard solution.
Useless comment:
// Getting the date
Дата = ТекущаяДата();Helpful comment:
// We use the start date of the next month because the tariffs
// are updated on the first day and billing should be calculated at the new prices
ДатаРасчёта = НачалоМесяца(ДобавитьМесяц(ТекущаяДата(), 1));For export functions, be sure to add a description of the parameters and the return value. This is not a formality — it is the documentation of your API:
// Calculates the cost of delivery taking into account the zone and weight.
// // Parameters:
// DeliveryAddress - Line - full address in FIAS format
// WeightGram - Number - shipment weight in grams
// // Return value:
// Number - shipping cost in rubles, 0 if shipping is free
// CalculateShippingCost (ShippingAddress, WeightGrams) ExportIf you want to deepen your knowledge in programming and learn modern technologies, take a look at the application Code. This is an educational platform with courses in Python, JavaScript, HTML, CSS and other languages, where complex things are explained in simple language, and theory is immediately supported by practice.
And in our Telegram channel you will find a friendly community of more than 2000 developers, where you can always ask a question, get support and find like-minded people. Join us - it is much easier to grow in the profession together!
