Пост

1С-ная магия

Мы рассмотрим разные примеры поведения кода 1С...


Введение

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?».

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

Еще я автор телеграм-канала Желтый чайник 1С, люблю испытывать платформу на прочность и делиться результатами на своем канале. Также практикую C# и Unity.

Для начала дисклеймер:

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

…Что все это – просто такая «1С-ная магия»…

Кто такие маги 1С? Это специалисты – каждый в своей отрасли. Кто-то из них – эксперт по платформе, кто-то – по архитектуре, кто-то – по девопсу, кто-то – по консультированию…

У них есть множество сертификатов, которые подтверждают, что они – маги 1С. Например, есть люди, на которых держится 7.7, кто-то любит SonarQube, кто-то – всякие новомодные фреймворки и так далее.

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

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

Зачем он это делает? Об этом поговорим чуть позже.

Простые примеры магии. Приведение типов

Начнем с приведения типов. Все знают мем о том, что в JavaScript очень непонятное приведение строк и чисел – объединение строк и чисел приводит к иногда неожиданным результатам для тех, кто только начинает работать.

Оказывается, 1С в этом тоже недалеко ушла и даже в некотором роде превзошла JavaScript.

Например, на слайде показано, как приводятся к числам различные варианты строк.

  • Первый вариант – «4E1». Здесь можно догадаться, что это 40 + 2 = 42.
  • Второй вариант – «12E3». Понятно, что это такая запись чисел, где после E указано количество нулей, которые мы добавляем – т.е. это 12000. Оказывается, такие строки тоже приводятся к числам.
  • Но следующие строки – вообще очень странные. Для обычного разработчика выглядят как кракозябры. Например, эти два символа в третьем примере означают 69. А предпоследний и последний примеры показывают совмещение экспонентной записи и этих странных чисел.

Оказывается, если на вход приведения к числу попадет такая строка, 1С не выдаст ошибку – она приведет ее к числу так, как считает нужным.

Я провел небольшое исследование и сформировал простой отчет, который показывает, какие символы 1С считает цифрами. В этом отчете каждый символ — это отдельная цифра.

Мы можем догадаться, что это, скорее всего, какие-то региональные символы, которые обозначают цифры в тех или иных языках. Часть из этих символов здесь не отображается – просто потому, что я не нашел подходящий шрифт, который бы отобразил все символы. Но каждый из этих символов что-то значит.

И 1С все это поймет. А JavaScript, кстати, нет.

ВЫБРАТЬ ПЕРВЫЕ

Давайте посмотрим на практический пример, в котором наш злой и вредный маг 1С подставляет разработчиков.

Метод, показанный в примере, возвращает первое количество записей какой-либо таблицы. На вход этому методу подается количество записей и полное имя таблицы, а он, используя обычный шаблон запроса, подставляет в него параметры — полное имя таблицы и количество записей.

Но что здесь не так? Возможно, вы уже догадались, потому что эта проблема в 1С встречается часто, и наверняка многие с ней уже сталкивались.

Дело в том, что, когда мы приводим число к строке, у нас добавляются пробелы в качестве разделителей групп. Например, 1000 будет уже выглядеть как «1 000». И такой кусочек запроса не выполнится – он просто упадет в ошибку.

Казалось бы, все очевидно и логично, но не для начинающего разработчика, который с этим еще не столкнулся. Ему кажется, что он написал правильный метод, но на самом деле его код упадет. Хотя упадет не всегда, а только тогда, когда начнут выбирать больше 999.

Естественно, это можно поправить обычной функцией Формат(). Тогда мы решим эту проблему, и наш вредина-маг 1С будет не очень доволен.

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

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

Переопределение (ТЧ, реквизит)

Перейдем к следующему примеру. Что здесь у нас не так?

Это просто обработка, которая так и называется – ПростоОбработка. Она имеет табличную часть Документы и реквизиты – Организации и Пользователи. Вроде ничего страшного.

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

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

Проблема достаточно тривиальная, простая. И решается вроде бы логично – достаточно просто не называть табличную часть словом «Документы». Но это приходит в голову разработчику только после того, как он с этим уже столкнулся.

Ведь в принципе-то название «Документы» логичное – у нас есть коллекция, в которой будут храниться документы. Вроде всё правильно.

Возможно, эта обработка уже содержит огромное количество строк кода, завязанное на логику, что у нас в «Документах» лежат документы. Но как только нам понадобится обратиться к какому-то реальному документу системы, появятся проблемы. В таких случаях правильнее всего переделать эту обработку. Или использовать для исправления какие-то костыльные методы, о которых я расскажу позже.

Такая же проблема – с реквизитом «Пользователи», потому что такое же имя имеет стандартный общий модуль БСП, который содержит функциональность работы с пользователями.

У него не очень хорошее название, потому что в результате мы нигде не можем использовать реквизит или переменную, которая называется «Пользователи». Если же мы все-таки заведем такую переменную или реквизит, мы не сможем обратиться к текущему пользователю. При попытке его вызвать платформа будет обращаться к реквизиту «Пользователи», в котором лежит какое-то значение – в данном случае, список.

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

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

Для Каждого ТекущийЭлемент Из Коллекция….

Естественно, это не сработает. Или сработает не так, как разработчик этого ожидал.

Помимо свойств, форма может иметь еще огромное количество реквизитов. Разработчики часто любят накидывать в форму реквизиты, которые даже не имеет смысла добавлять – они не вводятся на форму, их можно было бы просто сгруппировать – сделать какой-нибудь реквизит вида «Структура» с названием «ДополнительныеСвойства» или «СгруппироватьПоСмыслу» и помещать туда любые данные, не засоряя тем самым контекст.

Например, на скрине выше показана типовая обработка, у которой созданы такие реквизиты формы, как «ПолноеИмя», «Причина», «КодАлгоритма», «Алгоритм» и так далее. Это достаточно общие названия, которые можно было бы использовать в качестве переменной. И какой-нибудь разработчик, когда начнет добавлять сюда свою кнопочку, может поначалу просто не понять, что переопределяет существующий реквизит формы, на который завязана, возможно, какая-то логика.

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

  • Самый интересный вариант – это захватить имя переменной, сделать так, чтобы в контексте этого метода (процедуры или функции) эта переменная не была привязана к форме. Например, у формы есть свойство Заголовок. Если его менять, изменится заголовок у формы. Но, если мы его сделаем, например, параметром метода, это будет независимая переменная – после этого обратиться к заголовку формы мы не сможем. Естественно, сломать его тоже.
  • Или, например, мы можем добавить в модуль Перем – он вообще замечательный, может переопределить все подряд. Например, если вы в начале процедуры напишете Перем, и объявите переменные Метаданные, ЭтотОбъект, Документы, Справочники и так далее, то тем самым “захватите” эти ключевые слова. И можете поприсваивать туда вообще другие контексты. Очень интересные поведения потом можно наблюдать.

Директивы

А еще эту проблему можно решать изолированными методами – такими, которые создаются с помощью директив компиляции НаСервереБезКонтекста, НаКлиентеБезКонтекста, НаКлиентеНаСервереБезКонтекста.

Наверное, вы уже догадались, что здесь есть подвох – не бывает у нас НаКлиентеБезКонтекста. Или бывает?

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

1
2
3
4
5
#Если Клиент Тогда



#КонецЕсли

В случае, если код обернут в такую конструкцию, он компилируется только на клиенте.

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

Оказывается, директивы компиляции взаимозаменяемы с инструкциями препроцессора. Можно писать и так, как слева, и так, как справа.

Можете попробовать провести эксперименты, посмотреть, что будет – это очень интересно, иногда даже можно вставлять несколько директив компиляции на один метод, можете попробовать.

РеквизитФормыВЗначение

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

У нас есть метод РеквизитФормыВЗначение – он создаёт из текущей формы объект, к которому эта форма подвязана, и помещает в этот изолированный объект все реквизиты из формы.

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

На слайде – пример с табличной частью: мы добавляем в новый изолированный объект строчку, и она не появляется на форме.

Но, когда дело касается именно списка значений, поведение другое – мы создаём изолированный объект, добавляем в список значений строчку, и эта строчка появляется и на форме. Даже если мы не переносим данные из объекта обратно в форму.

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

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

И если тебе важно где-то менять список в изолированном объекте, не меняя его на форме, ты можешь столкнуться с таким нюансом и удивиться.

Максимальное значение в массиве

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

Например, у нас есть конструктор Новый Массив, в который можно передать количество элементов создаваемого массива.

Сделаем обработку, которая выводит:

  • размер создаваемого массива – то количество параметров, которое мы передали в конструктор;
  • и размер созданного массива – то, что у нас в результате получится.

Проведем эксперименты, на что вообще готов Новый Массив.

Сначала кинем в массив 2 миллиарда записей – на форме мы видим, что размер созданного массива 2 миллиарда, все правильно.

Но если кинем 3 миллиарда, то получим ошибку – исключение «Недопустимое значение параметра». Видимо, мы нащупали какое-то ограничение по количеству элементов в массиве.

Но, допустим, передадим туда 5 миллиардов. И получаем 705 миллионов. С копейками.

Почему так? Не совсем понятно. Вроде мы хотели 5 миллиардов, а получили 705 – не совпадает.

На самом деле, методом тыка можно проверить и увидеть, что, оказывается, есть ограничение – 2 147 483 647. Это число напоминает некий тип данных в C++.

Видимо, когда мы превышаем границы этого числа, конструктор массива перестает работать. Или перестает работать корректно.

Например, если мы увеличим это число в 2 раза, накинем еще несколько цифр, размер массива пойдет снова с 0 – мы передаем 4 миллиарда, а получаем единичку. Передаем 5 миллиардов, а получаем миллиард.

Таких странностей в платформе 1С много. Но в массиве мы просто превысили какое-то слишком большое число, такой массив в принципе создавать не имеет смысла.

Генератор случайных чисел

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

Обратите внимание, при вызове его метода СлучайноеЧисло() нужно передать два параметра – нижнюю и верхнюю границы. Согласно справке, оба параметра у нас целые – чтобы получить число, нужно передать целый параметр.

Но мы все-таки можем передать дробные. Кто нам мешает? Давайте попробуем.

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

Оказывается, что если передать дробное число, то мы таким образом регулируем частоту выпадания целых чисел на границах этого числа. Например, если мы передадим в качестве нижней и верхней границы 0 и 0.1, мы регулируем частоту выпадания 0 и 1.

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

Дата()

Еще один пример – метод, который возвращает дату. Мы в него можем передать строку со значениями, соответствующими дню, месяцу, году и так далее – они должны быть указаны в строгом порядке.

Но что, если мы будем туда передавать какие-то вообще необычные числа, не подходящие для даты?

Оказывается, метод сработает и даст вполне рабочие даты, просто неожиданные для нас. Например, дату, где все нули, превратит в 30 ноября 2-го года.

Хотя примеры на слайде изначально странные и непонятные, они рабочие – если на вход методу «Дата» попадет такая строка, метод сработает, исключения не будет. Поэтому прежде чем в этот метод передавать какое-то строковое значение, стоит подумать – действительно ли вам нужно получать такие даты?

Обратите внимание на четвертый, предпоследний, пример – если мы передадим 1 марта 2022 года с минус 1 секундой, мы получим конец последнего дня февраля. Это интуитивно понятно, и мы можем примерно догадаться, что 1С сначала создает 1 марта 00:00, а потом вычитает из этой даты 1 секунду, и мы получаем 28 февраля 23:59. Таким образом мы можем узнать последний день февраля в этом году – естественно, с учетом високосных месяцев.

Еще мне нравится последний пример – 256 января. Что это за дата? 1С знает, что в 2022 году 13 сентября был днем программиста. Естественно, мы опять можем догадаться, как это работает: до тех пор, пока числа попадают в месяц, все хорошо. А если у нас там уже 32 января, это будет 1 февраля, и 1С будет продолжать прибавлять все эти дни.

Таким образом можно и вычитать – и дни, и года, и месяцы, и секунды – и прибавлять, и так далее. И все это 1С с удовольствием нам конвертнёт в дату.

Дату еще можно задать константой с одинарными кавычками. Но иногда прям подбешивает, что сходу не вспомнить – какой вообще порядок этих чисел? Года, месяца, дня и так далее?

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

Кстати, на этом слайде показана еще одна особенность, которая вроде и документирована, но о ней мало кто помнит.

Потому что последняя дата в 1С не 3999, а 9999. Просто 3999 – максимальная, которая запишется в базу данных, но в коде мы можем оперировать и такими датами.

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

ОписаниеТипов как «костыль» для создания типов

Мы подошли к самому интересному пункту. Оказывается, описание типов – это классный и универсальный инструмент для создания новых объектов.

Например, у нас здесь есть функция НовыйОбъект, которая просто использует ОписаниеТипов и метод ПривестиЗначение().

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

Например, мы можем создать ДанныеФормыКоллекция, которую так просто нельзя создать – для этого нужно иметь форму, получить ее реквизит и так далее. Здесь мы можем это делать вне формы.

Таким же образом мы можем создать ДинамическийСписок, ГруппуФормы, HTTPОтвет, HTTPСервисЗапрос, РезультатЗапроса, который тоже нельзя просто так создать.

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

Но это можно взять на вооружение в качестве эксперимента и вынести из этого какую-то пользу.

На слайде показаны полезности, как это можно использовать на практике.

  • Во-первых, можно создать независимую табличную часть, которая будет вообще вне объекта, при этом объект создаваться не будет.
  • Можно сделать какую-то обработку, накидать шаблоны табличных частей, а потом на их основании быстро создавать таблицы значений нужной структуры (вторая строчка).
  • Мы можем создать менеджер документов – помните, у нас была проблема, когда мы переопределили документы табличной частью? Мы можем создать свой менеджер, и он будет абсолютно такой же, как и стандартный. Это, скорее всего, синглтон-объект.
  • Также мы можем создать менеджер конкретного объекта.
  • И можем получать значения по умолчанию, потому что они у разных системных перечислений разные – где-то «Авто», где-то «Равно», где-то «Пустая», где-то «Используется» и так далее.

Т.е. если нам нужно срочно получить какое-то значение по умолчанию для нужного типа, мы можем использовать функцию НовыйОбъект, которая просто использует ОписаниеТипов.

Таблица значений и дерево значений на клиенте

Ну и последняя фишечка – таблица и дерево значений на клиенте.

Оказывается, таблица значений в тонком клиенте существует и полноценно работает – можно добавлять колонки, индексы, сортировать и т.д.

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

Точно так же можно работать и с деревом значений.

И все-таки это лучше не использовать

Но почему это нельзя использовать, как бы вам этого ни хотелось?

Напомню вам, у нас есть прекрасный пример – оператор «вопросительный знак», который очень быстро забанили, когда статья про него набрала популярность.

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

Но, слава Богу, «вопросительный знак» никто и не использовал, потому что это слишком уж эзотерическое программирование.

Но предыдущие примеры наверняка кто-то использует. И, если вы это делаете, подумайте хорошенько – может быть, вам сразу заранее переписать код и не слушать того вредного мага-программиста?

Кстати, давайте все-таки вернёмся к нему.

Выводы

Всё это – маги 1С. Они специалисты и профессионалы в своём деле.

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

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

Благодаря нему они становились сильнее как специалисты.

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

Ну или ему просто нравится всё разрушать. Его же не спросишь)

Авторский пост защищен лицензией CC BY 4.0 .