(Не) Строгая типизация 1С
Статья на основе митапа по видами типизации кода в 1С
Существует множество языков программирования, и каждый имеет свои особенности по работе с типами данных. Слабые, явные, динамические и другие… Но кто же здесь 1С и почему с приходом “строгой” типизации EDT 1С-программистам стоит задуматься над изменением своих привычек.
Данная статья является текстовым вариантом внутреннего митапа компании Магнит
Существует множество языков программирования, и каждый имеет какие-то особенности по работе с типами. В том числе и наша любимая 1С.
Но в целом мы можем их разделить на какие-то общепринятые виды типизации языков программирования
Виды типизации языков программирования
Статическая | Динамическая |
Сильная | Слабая |
Явная | Неявная |
Но кто же здесь 1С и почему с приходом “строгой” типизации EDT 1С-программистам стоит задуматься над изменением своих привычек.
Все примеры кода из статьи специально упрощены для облегчения понимания. Когда в примере есть заведомо бессмысленный код наподобие А = 2 * “20”, то стоит понимать, что подразумевается, что в выражении используются реальные переменные, со значениями которых мы работаем.
Статическая и Динамическая
- Статическая типизация - переменная имеет конкретный один тип и изменить этот тип она не может. Мы создаём переменную, назначаем ей тип Cat. Создаётся ячейка памяти (грубо говоря, коробочка) куда мы можем поместить только котика и ничего больше.
Это проверяется на уровне компиляции и если мы попытаемся поместить туда кого-то другого, то мы не сможем даже запустить такую программу.
- Динамическая типизация - переменная может содержать любой тип. Можно сказать так, что тип имеет конкретное текущее значение переменной, а не она сама. И в любой момент времени тип значения в переменной может измениться. И в нашей коробочке под названием “Кот” окажется крокодил.
Да, в 1С мы можем назначить конкретный тип реквизиту (например, Сумма - Число). Но стоит понимать, что это только частично защищает от потенциальных проблем. Ведь ни Конфигуратор, ни Предприятие не укажут на ошибку в случаях, когда мы попытаемся в этот числовой реквизит поместить, например, массив. 1С лишь попытается привести переданный тип к типу реквизита.
Сильная и слабая
- Сильная (строгая) типизация - операции между разными типами данных запрещены.
- Слабая (нестрогая) типизация - позволяют проводить операции между разными типами данных Например, такая операция в 1С будет выполняться.
1
Сообщить(ТекущаяДата() - 5 * "2");
При этом существуют языки программирования с сильной (строгой) типизацией, в которых подобные операции будут запрещены и выявляться компилятором ещё до выполнения программы.
Явная и неявная
- Явная типизация - указание типа обязательно
- Неявная типизация - тип можно не указывать
А вот явная и неявная типизация - это такой маленький нюансик, описывающий необходимость явного указания типа. Или же тип мы просто не указываем и он определяется автоматически по контексту.
В явной типизации каждая переменная должна быть объявлена с явным указанием типа.
Нюансы классификаций
Любой язык программирования можно отнести к одному или другому виду по этим вариантам классификаций. Но на самом деле, не всё так однозначно.
Например, C# является языком со статической типизацией. Но при этом, он предоставляет особый тип dynamic, который является по сути возможностью использования динамической типизации.
Так же C# хоть и требует явное указание типа переменной (переменная без типа не может быть создана), но даёт ещё и ключевое слово var, которое можно поставить вместо типа и компилятор сам будет его определять. Если мы вызываем метод, который возвращает только строку, то компилятор поймёт, что переменная может быть только строкового типа и сам этот тип объявит при компиляции кода.
Таким образом, один язык может обладать свойствами обеих сторон типизации. Но всё же в большей степени относится к одному из них.
Подробнее можно прочитать, например, в старенькой статье на Хабре: Ликбез по типизации в языках программирования
«Требовательные» и «Дозволительные»
Все эти виды классификации мы можем дополнительно обозначить как «Требовательные» (которые больше требуют от разработчика) и «Дозволительные» (которые больше позволяют программисту “свобод”).
В плюсы “требовательных” ЯП можно отнести:
- Скорость выполнения. В статической типизации на переменную выделяется память заранее известного размера. И отсутствуют разного рода “неявные” преобразования типов, когда, например, строковое значение платформа пытается поместить в числовой реквизит, проводя анализ её содержимого.
- Надёжность. Из-за указания типов появляется возможность использовать множество дополнительных проверок в среде разработки и на этапе компиляции. Разработчик не передаст случайно в метод “неправильный тип” и не перепутает переменную. В динамической же типизации множество ошибок приходится отлавливать уже в процессе выполнения программы.
- Скорость сложной разработки. Чем больше проект, чем больше кода и метаданных, тем проще допустить какую-то ошибку. От этого нас оберегают множество проверок на этапе компиляции.
Но и “дозволительные” имеют свои преимущества:
- Легкость освоения. Начинающему программисту проще писать код, когда можно меньше задумываться о типах и операций над ними. Тем более, когда и сама система типов упрощена и в 1С, к примеру, все числовые типы “обычных” языков программирования объединены в просто “Число”.
- Краткость. Меньше букоффф, проще текст) Не нужно указывать типы ни для переменных, ни для параметров, ни для результатов метода. Не нужно дополнительно приводить типы к другим, ведь платформа часто сама это может сделать.
- Скорость простой разработки. Сделать срочно простенький отчет, обработку или печатную форму проще. Можно упрощать код универсальными коллекциями (массивами со всем подряд) и так далее.
Экосистема 1С
Экосистема 1С постепенно пополняется новыми языками и системами.
- 1С:Исполнитель
- 1С:Элемент
Созданные на их основе
- 1С:Шина
- 1С:Аналитика
- …
Все эти части нового мира 1С используют статическую типизацию, и лишь дополнительно поддерживает динамический подход.
А вот по этой ссылке можно прочитать обоснование почему 1С в языке Элемент отошла от динамической типизации и рекомендует использование статической.
1С - динамическая, слабая и неявная типизация
Наша любимая 1С относится к динамической, слабой (нестрогой) и неявной типизации.
- Типы переменных объявлять не нужно. По сути тип есть только у текущего значения переменной, а не у неё самой.
- Меняем тип в любой момент. Лень придумывать имя для новой переменной? А зачем? Можно ведь просто перезаписать старую.
- Можем сложить любой тип с любым. Глядишь, что-то из этого и получится. Особенно это удобно при построении текста сообщения, когда не нужно писать лишние “Строка()”.
- Можем передать что угодно куда угодно. Даже если это платформенный метод. Иногда это даже открывает новые недокументированные возможности, о чём я писал в статье.
Но у такого (казалось бы идеального) подхода есть и свои недостатки:
- Никогда не знаешь, что к тебе прилетит. Кто мешает в твой параметр с именем Массив передать ссылку на справочник? Проблему усугубляет то, что эту переменную случайно кто-то перезатёр в закоулках кода и текст ошибки никак не поможет это выяснить.
- Нужно проверять типы самому. Да, в БСП есть удобные методы для этого. Но что если твой универсальный метод может принимать не один-два конкретных типа? Например, мы пишем метод, который перебирает входящую в параметр коллекцию и возвращает порцию N элементов. Нам без разницы какого типа эта коллекция. Главное, чтобы она имела метод Количество() и её элементы можно было перебрать циклом. 1С лишена возможностей, которые есть в статических языках. Например, интерфейсов, благодаря которым мы могли бы просто проверить параметр на интерфейс “Коллекция”.
- Легко случайно что-то перепутать. Конфигуратор не проверяет типы, которые вы присваиваете или передаёте в методы. Предприятие зачастую тоже. Да и что проверять, если переменные типов-то и не имеют?)
- Об ошибке узнаешь уже на проде. Причём не всегда сразу. Может оказаться, что при конкретной комбинации действий где-то в мешанине нашего кода переменная случайно затирается другим типом, а потом попадает в реквизит документа, из-за чего его значение оказывается пустым или некорректным. И узнаем мы о таких исключительных ситуациях, когда они уже значительно накопятся. И будем снова изучать код в отладке.
Потому программист 1С со временем учится “компилировать” код в уме. Смотреть на метод и “догадываться” где и как можно было случайно что-то сломать.
Но тут на сцену врывается EDT…
Автоматический расчет типов EDT
EDT “из коробки” имеет автоматический расчет типов.
- Определение типов и свойств. Типы переменных, параметров. Свойства структур и колонки таблиц. Всё это IDE рассчитывает сама, основываясь на иерархии вызовов.
- Контекстная подсказка типов и свойств. Благодаря предыдущему пункту разработчику теперь не нужно использовать разные хитрости, чтобы заставить конфигуратор понять какого типа у нас переменная. EDT делает это автоматически. Ещё и понимает, например, какие есть свойства в прилетевшей нам структуре.
- Отслеживание изменений типов и свойств. Например, сначала переменная была массивом, а потом через несколько строк кода её затёрли строкой. EDT это отслеживает.
- Переменные, свойства, параметры, результаты функций и так далее.
Но что если автоматического анализа не хватает? Ведь EDT не идеальна…
- Избыточные типы. Например, из универсальных методов, которые есть и в платформе и в БСП. Методы, которые в зависимости от входящих параметров могут вернуть разные типы значений. В таких случаях EDT не знает какой конкретно тип вернётся, поэтому запоминает все возможные.
- Извлечение хранилища. Это пример максимально не типизированного метода самой платформы. Среда разработки не может понять, что в хранилище будет находиться во время выполнения кода, а значит и тип определить не может. Такие переменные будут “Произвольные”.
- Неявные создания. Когда мы, например, создаем структуру, передавая имена ключей через переменную.
- Неявные обращения. Когда вместо “Структура.Свойство” мы обращаемся опять же через переменную “Структура[ИмяСвойства]”.
- Результаты запросов. Запросы и так бывают очень сложные, так ещё и “собираемые” из кусочков. И кто вообще знает что там будет в результате?)
И получается, что вроде как у нас есть EDT, которая автоматически рассчитывает типы и свойства, но при этом множество мест, где сделать это не предоставляется возможным. Как же разработчику помочь среде разработки помогать ему разрабатывать?
Стандарт описания процедур и функций на ИТС
И тут на помощь приходят к нам стандарты разработки 1С, которые были написаны в стародавние времена. И которые мы и так должны были соблюдать в своём коде (ахахах, нет).
Больше всего нас сейчас интересуют:
Что же согласно стандартам 1С должен делать разработчик?
- Указывать типы входящих параметров. Каждый параметр должен обладать своим типом (или типами)
- Указывать типы возвращаемых значений. Функция должна возвращать что-то конкретное, а не что Бог пошлёт. При этом в идеале нужно описывать в каком случае вернётся конкретный тип.
- Указывать свойства структур и колонки таблиц. Если мы заранее знаем свойства, то нужно их описывать, чтобы о них знали не только мы.
- Ссылаться на поля метаданных, другие функции и их параметры. Чтобы не дублировать описания между разными методами, можно “ссылаться” на них. При этом можно ссылаться на результат функции (Параметр1), параметр метода (Параметр2) или метаданные (Параметр3). Ну или вообще полностью скопировать семантику метода (см. МояПроцедура)
Раньше, если мы следовали этим рекомендациям, то могли претендовать на звание “молодец года” и золотую шоколадную медальку. Конфигуратор практически никак не использует эти описания.
Но вот с приходом EDT в них появляется дополнительная практическая польза. Основываясь на описание метода, IDE может не только выдавать контекстную подсказку, но и выполнять дополнительные проверки наших действий. Но помимо стандартов ИТС новая среда разработки имеет ещё дополнительные возможности типизации.
Дополнительные возможности типизации в EDT
- Указание типа переменной. Мы можем при создании переменной сразу ограничить её тип. Например, массив строк:
Или указать тип тогда, когда определить его не удаётся
Это особенно удобно со всякими временными хранилищами
- Условия на тип. Когда значение переменной может быть составного типа, то условием на тип мы заставляем среду разработки понять, что внутри условия будет только то, на что мы проверяли
- Виртуальные типы. Существуют ещё и виртуальные типы, которые вроде есть, а вроде их и нет. Например, ОбъектМетаданныхОтчет, который имеет свои свойства (ОсновнаяСхемаКомпоновкиДанных), но в системе типов 1С на самом деле является просто “ОбъектМетаданных”, у которого родитель объект метаданных Отчеты.
Применение таких доп. возможностей IDE позволяет нам перейти на новый уровень типизации… “Строгая” типизация 1С.
Строгая типизация 1С в EDT
В EDT есть особый режим “строгой” типизации. С его активаций в модуле будут особые правила разработки.
- Контроль указания типов. Теперь нельзя создавать переменную, не объявляя её типа (если его автоматически получить нельзя). Такое будет “подчеркиваться” и считаться ошибкой.
Массивы и т.п. должны быть типизированы
- Контроль соответствия типов. Случайно передать в числовой параметр ссылку уже не выйдет - об этом мы получим предупреждение. И, например, не сможем передать число в имя ключа структуры.
- Запрет изменения типов. Любое изменение типа переменной\свойства - потенциальная ошибка.
- Контроль наличия свойств. Нельзя обращаться к свойствам, которые не описаны (а значит и не известны) Например, на скрине описана функция со свойством “А”, а мы ещё пытаемся обратиться к свойству “Б”, которого нет.
JavaScript и TypeScript
Разработчики EDT в документации сами признались, что вдохновлялись языком TypeScript, который является “строгой” версией JavaScript и позволяет легче разрабатывать и поддерживать сложные приложения и сайты. Главное отличие, что TypeScript - это отдельный язык, который прилетает в браузеры в виде обычного JavaScript, а вот язык 1С не новый, а лишь дополненный комментариями.
«Не совсем строгая» типизация EDT
На самом деле, “строгая” типизация 1С скорее “условно-строгая”. Ведь изначально язык 1С разрабатывался как динамический и нестрогий, а посему не обладает возможностями, которые дают другие языки как альтернативу универсальным переменным. У нас нет ни интерфейсов, ни обобщений, ни перегрузок методов и так далее. А некоторые вещи при “складывании” типов являются базовыми для языка. Поэтому, EDT пытается сохранить “свой особый путь 1С” и не сломать психику разработчиков.
- Операции над разными типами. Они частично разрешены. Например, на такое ругаться IDE не станет. И это отчасти верно, ведь вычитание чисел из даты - это базовый способ её “сдвинуть” на нужное число секунд
Тут тоже разрешено
А здесь будет ошибка:
- Составные типы. Да, без такой возможности нам бы пришлось плодить кучу переменных, параметров и отказаться от универсальных и вместительных коллекций
Например, можно указать, что массив должен содержать в себе или числа или строки. А остальное будет ошибкой
- Уточнение типов. Эта важная особенность заключается в том, что мы никак не можем переопределить тип, который сама рассчитала IDE. Мы можем его лишь дополнить.
Например, тут переменная может быть или числом или строкой (условие можно взять любое). И мы не можем заставить EDT забыть об этих типах. Но можем сказать, что ещё тут может быть Дата. Например, когда-нибудь в будущем мы планируем её туда поместить.
А в этом примере мы говорим, что в переменной будет наша структура. Но среда разработки всё равно помнит о типе Произвольный
- Подавление ошибок. Ошибки EDT - это не блокирующие проблемы. Мы всё равно можем обновить ИБ и выполнить свой код. И он может и прекрасно работать.
Ошибки EDT - это как замечания Sonar. Это предупреждения. Подсказки по типу “а ты точно ничего не перепутал?”. Поэтому эти ошибки можно отклонить комментарием. В идеале сопроводим пояснением, чтобы потом легче было вспомнить почему так.
Для этого используются “подавляющие” комментарии в коде.
- Несуществующие свойства. В описании типа мы можем наделить объект несуществующими свойствами. Например, указать параметр “Произвольным”, но сказать, что у него есть свойство “Имя”. И EDT это примет как данность.
К сожалению, в таком случае она не будет проверять, что передаваемый в метод объект должен обладать свойством “Имя”. Возможно, когда-нибудь такое появится.
Такая возможность даёт нам бесполезный, но интересный способ наделить, например, Строковый параметр свойством “Длина”. Было бы удобно)
Инструменты и документация
Чтобы самому погрузиться во все нюансы описания типов EDT, лучше всего начать с документации
Включение строгой типизации
Но как же включить строгую типизацию?
Делается это просто - комментарием в начале модуля
// @strict-types
Или же при помощи кнопки.
ПКМ - Источник - Включить строгую типизацию
Можно так же включить этот режим сразу для всех выделенных объектов:
Генератор описания методов
Чтобы облегчить себе процесс описания методов, можно использовать генератор.
ПКМ - Источник - Генерировать комментарий к методу
Причем он позволяет не только создавать новое описание, но изменять существующее, чтобы поддерживать его актуальность.
Здесь лучше всего увидеть как он работает в оригинальном видео этого доклада. Ссылка с меткой времени
Панель документирующего комментария
Чтобы проверить правильно ли описан метод и как EDT его понимает, можно использовать специальную панель.
Окно - Показать панель - Документирующий комментарий
Опять же, это лучше увидеть в видео по ссылке с меткой времени.
Нюансы использования
У всего есть свои нюансы и недостатки. На основе своего опыта работы со строгой типизацией, выделю эти:
- Баги. EDT в вечной бете. Багов и так много при обычной работе, а при строгой типизации и подавно)
- Не всё поддерживается.
Например, нельзя одной строкой описать типы ключа и значения соответствия. Нужно делать функцию-конструктор, которую вызывать. А иногда просто хочется сделать маленькое локальное соответствие в своём методе.
Нельзя сослаться на строку ТЧ. Ниже корректное описание по правилам 1С, но EDT только понимает, что это строка какой-то ТЧ. “ИмяКолонки” - это просто подсказка, что нужно ввести имя колонки.
Но это можно обойти костылём. Достаточно, чтобы где-то в коде вызывался наш метод с передачей строки ТЧ. И анализ кода сам догадается. На скрине жуткий костыль, берегите глаза )
Так же нельзя описывать методы объектов. Например, если мы используем какую-то компоненту. Самый простой вариант - сделать метод “обёртку”, внутри которого мы подавим ошибку, опишем тип. И этот метод уже вызывать везде в коде.
Ещё один вариант решения такой проблемы - сделать обработку “интерфейс”, которая будет описывать доступные методы и типы. И при создании компоненты указывать комментарием, будто это наша обработка
Ну а в идеале лучше делать так, чтобы обработка была не просто интерфейсом, а обёрткой над компонентой. И весь код конфигурации при использовании компоненты создавал обработку и вызывал её методы, а уже она сама “внутри себя” делала обращения к компоненте. Но это мы уже немного отходим от темы в пользу будущих докладов.
- Уровень строгости в настройках. На самом деле, уровень “строгости” EDT настраивается в проекте. И может содержать несколько профилей настроек. Например, в больших и сложных конфигурациях можно отключить особо медленные проверки для простоты разработки. А все проверки прогонять на сборочной линии. Ну или просто договориться, что в нашей конфигурации конкретная проверка не имеет смысла и мы её не будем включать.
И как это попробовать?
Если вы хотите опробовать новый подход в написании кода на 1С, то добро пожаловать в клуб.
Где же это пробовать?
- Рабочий проект. Никто вам не мешает писать на строгой типизации. Ведь она включается для конкретного модуля и не мешает другим. Начните со своей обработки.
- Библиотека компании. У нас в “Магнит” есть своя “БСП Магнита”. В ней есть общие универсальные подсистемы, которые сейчас переведены на строгую типизацию. Такого рода проекты стараются держать в “красивом” виде.
- Pet-проекты. Если вы разрабатываете свои личные проекты на 1С (вне работы), то чаще всего они небольшие и не составляет большого труда типизировать код.
- Open-source. А если у вас личного проекта нет, то можно поучаствовать в разработке какого-то open-source проекта. Например, одного из моих =)
- Внешние обработки, отчеты, расширения. В таких проектах свои отдельные профили настроек. И можно легко настроить их на свой лад и разрабатывать свой внешний инструмент.
- Новые модули. Создаете новый общий модуль, форму или обработку? Поставьте сразу строгую типизацию и попробуйте разрабатывать на ней.
- Рефакторинг. Если вам попалась задача перепилить простой инструмент, то можете заодно поставить в нём строгую типизацию.
Небольшие советы, чтобы вливание было проще:
- Пишите маленькие методы. Да, об этом трубят со всех углов. Чем меньше метод, тем лучше. Один метод - одна ответственность. Все эти правила чистого кода известны не просто так - это действительно удобно. И в строгой типизации становится просто необходимо. Конечно, существуют исключения, но в большинстве случаев идёт на пользу.
- Декомпозируйте. Не только задачи и методы, но и объекты. Большую структуру параметров с кучей подчиненных структур лучше разбить на составляющие. И описать их отдельно. Тогда их можно передавать отдельно в те методы, которым другие данные и не нужны. И в описании типа указывать ссылку на эту маленькую структуру.
- Область конструкторов. Выделяйте все “конструкторы” в отдельную область. Для каждой волшебной структуры с наборов свойств создавайте отдельный метод-“конструктор”. Функцию, которая возвращает заготовку структуры со значениями по умолчанию. И помещайте её в область конструкторов. Тогда будет легче и вызывать, и ссылаться, и находить при редактировании модуля.
В целом, все эти советы и так описаны на ИТС. Но в конфигураторе не так много пользы от следования им. И только со временем понимаешь насколько они облегчают жизнь.
А если что-то идёт не так? EDT психует? Что делать?
- Чистить кэш, перезаходить и так далее. Баги они такие. Бывает, что модуль вдруг становится весь красным от ошибок. Переоткрываешь и становится нормальным. Ну тут классический EDT.
- Ищем свои ошибки, сравниваем с документацией. Исправляем.
- Задаём вопросы в официальном чате по EDT. Помогают там быстро. Сколько же моих вопросов разной степени тупости нашли свои ответы)
- Ищем баги в багтрекере EDT. Ищем свою ошибку. Если нет - добавляем. Однажды её поправят (или нет).
- Давим ошибки. Баг ЕДТ? Давим беспощадно. Главное - ставьте комментарий. И периодически пробегайте по подавлениям и проверяйте актуальность. Потому что с выходом новых версий не только новые ошибки приходят, но иногда и старые пропадают.
Постепенно вы ощутите удобство использования строгой типизации и начнёте сразу писать так, что и замечания поправлять не придётся…
Ну или максимально разочаруетесь в EDT - я ставки делать не буду =)