Использование принципов SOLID в разработке на 1С

22.08.24

Разработка - Рефакторинг и качество кода

SOLID – принципы проектирования программных структур (модулей). Акроним S.O.L.I.D. образован из первой буквы пяти принципов. Эти принципы делают код более гибким, упрощают разработку. Принято считать, что принципы SOLID применимы только в объектно-ориентированном программировании. Но их можно успешно использовать и в 1С. Расскажем о том, как разобраться в принципах SOLID и начать применять их при работе в 1С.

Меня зовут Александр Пузаков, более 10 лет я занимаюсь разработкой на платформе 1С, последние 4 года работаю в компании «Магнит».

Я расскажу о принципах SOLID применительно к 1С.

  • Сначала опишу, что вообще такое принципы SOLID

  • Расскажу, чем принципы SOLID будут полезны 1С-никам.

  • Разберем каждый из пяти принципов – я дам определения, пояснения и много примеров кода:

    • Красным крестиком будут отмечены негативные примеры кода, противоречащие принципам SOLID,

    • Зеленой галочкой будет отмечен код, соответствующий принципам SOLID.

 

Рассказывать буду на примере кода вот этих двух красавцев.

  • Федор – классный парень, но ничего не знает о принципах SOLID.

  • Харитон Иванович – старый воин, давно работает разработчиком, хорошо знает и применяет принципы SOLID, знает толк в хорошем коде.

 

SOLID – принципы дизайна (проектирования)

 

SOLID – это акроним. Название образовано из первых букв пяти принципов. Сами принципы хорошо дополняют друг друга.

Еще есть такое слово – solid. Оно переводится на русский как «надежный, прочный». То есть эти принципы сделают ваш код надежнее или по задумке должны сделать.

 

 

Автор принципов SOLID – Роберт Мартин, также известный под псевдонимом «Дядюшка Боб». Если кого заинтересует эта тема, я советую почитать книгу «Чистая архитектура». Про принципы SOLID там относительно немного, зато есть и другая полезная информация.

 

Теперь про проблему, которую решат принципы SOLID в 1С.

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

Такой механизм состоит из тысяч или десятков тысяч строк кода. Работать с ним тяжело, изучать его долго. Но из-за его важности к нему ведет множество задач.

 

 

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

 

Соблюдение принципов SOLID приносит пользу:

  • Код станет проще и понятнее.

  • Кодовая база будет меньше засоряться.

  • Легче писать модульные тесты – код становится к ним более дружелюбным.

  • Код станет более гибким и адаптивным – уменьшатся трудозатраты на доработки.

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

 

SRP принцип единственной ответственности

 

Итак, начнем рассматривать принципы SOLID.

Первый принцип – это SRP (Single responsibility principle), принцип единственной ответственности. Этот принцип – очень важный и отлично применяемый в 1С. Другие применяются в 1С с натяжкой, а этот – хорошо заходит, поэтому про него расскажу побольше.

Принцип единственной ответственности звучит следующим образом: модуль должен иметь только одну причину для изменения.

Тут надо прояснить, что такое модуль и что такое причина изменения.

  • В 1С модуль – это модуль объекта, модуль формы, общий модуль и так далее. А с точки зрения SOLID модуль – это структура поменьше. Допустим, в модуле объекта метаданных «Документ» будет модуль проведения, модуль заполнения на основании, модуль печати и так далее. А при большом количестве кода декомпозиция будет еще сильнее – допустим, модуль проведения по взаиморасчетам, модуль проведения по бухучету и так далее.

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

Давайте разберем все это на примерах.

 

Стоит задача – провести документ «Реализация» по двум оборотным регистрам накопления.

  • регистр «Продажи» – создан для нужд отдела продаж.

  • регистр «Обороты бюджетов» – заведен для отдела бюджетирования.

Нужно добавить в обработку проведения формирование движений по этим регистрам.

 

На слайде показано решение задачи, как ее сделал Федор.

В 1С принято данные для движений собирать запросами – так же сделал и Федор.

  • Он написал пакетный запрос, который собирает данные документа и формирует из них таблицы, удобные для записи в регистры накопления.

  • Выгрузил результаты пакетов запроса в таблицы значений.

  • И загрузил данные из таблиц в движения регистров.

Обратите внимание, этот код отмечен крестиком, потому что в одном коде и в одном запросе объединены две ответственности – он отвечает сразу за два отдела.

 

Рассмотрим ближайшее будущее – к чему это может привести.

Поступила задача из отдела бюджетирования: нужно видеть в регистре сумму без НДС – при том, что в самом документе сумма содержится с НДС.

Федор немного поторопился и внес изменения не туда – теперь сумма без НДС ушла в оба регистра:

  • в регистр «Обороты бюджетов», как и задумывалось;

  • и в регистр «Продажи» – получается, неумышленно сломали логику движений по регистру продаж. Выяснится это не сразу.

Вот к чему приводит объединение ответственности.

 

А вот так решил задачу Харитон Иванович.

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

 

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

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

Работать с таким регистром потенциально намного сложнее.

  • Тут уже будут и какие-то хитрые отборы по измерениям.

  • Если у нас сумма одна общая, будет сложнее извлечь сумму НДС: либо добавляем новый ресурс «СуммаБезНДС» и редактируем много кода и много запросов; либо в существующих запросах на лету выделяем сумму НДС. Один вариант не лучше другого.

Когда объединили разные ответственности друг с другом, появилось много лишней работы.

 

На слайде – иллюстрация принципа единственной ответственности схематически.

Слева – модуль с двумя объединенными ответственностями; а справа – тот же код, только разделенный.

  • Модуль с двумя ответственностями:

    • визуально больше, потому что в нем содержится больше кода, разработчик будет работать с большей кодовой базой;

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

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

  • А когда эти модули разделены между собой, сразу же многие проблемы исключаются:

    • разработчик работает с меньшей кодовой базой;

    • не происходит непредвиденных поломок чужого кода.

 

OCP принцип открытости-закрытости

 

Следующий принцип – OCP (Open Closed Principle), принцип открытости-закрытости.

Принцип OCP звучит следующим образом: модуль должен быть открыт для расширения, но закрыт для изменения.

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

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

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

 

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

 

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

Принцип единственной ответственности здесь еще более ухудшился, при этом еще Федор влез в существующий запрос, отредактировал его и мог его потенциально сломать.

 

А у Харитона Ивановича новые движения добавляются независимо от существующего кода.

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

 

На слайде – графическая иллюстрация принципа открытости-закрытости.

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

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

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

 

LSP принцип подстановки Барбары Лисков

 

Следующий принцип – LSP (Liskov Substitution Principle), принцип подстановки Барбары Лисков.

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

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

 

Уточним, что такое тип с точки зрения SOLID.

Это не совсем то, что мы привыкли воспринимать. Да, у нас есть системные типы, прикладные типы, но тип – это понятие пошире. Например, на слайде:

  • Слева два разных типа «Структура». Они не взаимозаменяемы, у них разный набор ключей.

  • Справа – тип «ДокументОбъект». Это очень сложный тип, у него уже много кода и свое сложное поведение.

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

Важный вывод этого принципа в том, что код нужно рассматривать как часть типа.

 

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

Первый пример контракта – палитра свойств. У реквизита «Цена» заданы:

  • тип;

  • длина;

  • неотрицательное;

  • минимальное значение;

  • и проверка заполнения.

Набор этих правил образует контракт.

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

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

 

Когда контракты плохо спроектированы и не выдерживаются, возникают некоторые проблемы.

Первое следствие нарушения контракта – это возникающие ошибки.

Например, в коде, представленном на слайде, Федор нарушил контракт функции. Он передал коллекцию значений неподходящей структуре – обратите внимание, у коллекции значений «СтрокаТаблицы» нет колонки «ПроцентНДС», это приведет к ошибке.

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

 

Второе следствие нарушения контракта – засорение модуля сервисными механизмами

Здесь показан обработчик подписки на событие, который срабатывает для некоторых документов. У всех документов есть реквизит «Контрагент», а у документа «ТребованиеНакладная» такого реквизита нет. Из-за этого Федор нарисовал костыль – сервисный механизм, который каким-то хитрым способом опосредованно получает контрагента.

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

 

А у Харитона Ивановича с контрактами все хорошо, он принцип подстановки Барбары Лисков знает и соблюдает.

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

  • создал шаблон, который содержит все необходимые поля и заполняет их значениями по умолчанию;

  • задокументировал все условия в проверках – добавил для них комментарии в самом начале.

Здесь принцип подстановки Барбары Лисков соблюден.

 

ISP принцип разделения интерфейсов

 

Следующий принцип – ISP (Interface Segregation Principle), принцип разделения интерфейсов. Он звучит так: маленькие узкоспециализированные интерфейсы лучше, чем большие и универсальные. Клиенты не должны вынужденно зависеть от методов, которыми не пользуются.

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

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

Понятие интерфейса нам тоже немного знакомо. Проваливаясь в некоторый модуль, мы видим «#Область ПрограммныйИнтерфейс». Сигнатуры методов, перечисленные в этой области, (сигнатура – это название метода и его параметры) – это и будет интерфейсом.

 

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

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

  • читает данные из файла;

  • конвертирует – заполняет данными некоторые объекты;

  • и записывает эти объекты в базу.

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

 

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

В коде Харитона Ивановича методы переиспользовать гораздо проще.

 

DIP принцип инверсии зависимостей

 

Последний принцип – это DIP (Dependency Inversion Principle), принцип инверсии зависимостей.

Сформулирован принцип DIP довольно сложно и непонятно:

Высокоуровневые модули не должны зависеть от низкоуровневых модулей. Модули обоих видов должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракции.

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

 

Что вообще такое зависимость – в частности, в коде? На слайде два простых метода – метод «Рассчитать» вызывает метод «СложитьДваЧисла».

  • Метод «Рассчитать» зависит от метода «СложитьДваЧисла», он знает об этом методе.

  • А метод «СложитьДваЧисла» ничего не знает о методе «Рассчитать», он от него не зависит, как и от любого вызывающего кода.

То есть стрелка зависимости будет направлена от метода «Рассчитать» вниз к методу «СложитьДваЧисла». Это пример зависимости.

 

Теперь самое сложное – что такое бизнес-логика и почему она должна отделяться?

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

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

  • Код будет записывать значения в базу данных, или в файл, или отправлять на HTTP-сервис – неважно.

  • Также код будет отображать какие-то данные в интерфейсе или еще что-то.

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

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

 

Разберем на примере, как инвертировать зависимости.

Задача: загрузить файл JSON в регистр «Цены». Стандартная задача, с которой часто встречаются 1С-ники.

 

Вот как сделал Федор – у него один метод читает данные из JSON, разбирает его и тут же регистрирует цены, записывает их в регистр сведений.

В данном случае код регистрации цен – это бизнес-логика, высокоуровневый код. А чтение из файла – низкоуровневый код.

Но здесь бизнес-логика знает о низкоуровневой операции и зависит от нее – стрелочка направлена от бизнес-логики к низкоуровневому чтению из файла.

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

 

А вот как поступает Харитон Иванович.

Он создает промежуточную абстракцию в виде таблицы значений. Сначала он в эту таблицу значений считывает все данные из файла, и потом уже готовую таблицу значений с данными передает в отдельный метод. У него бизнес-логика инкапсулирована в одном методе, а чтение из файла – уже в другом.

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

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

 

Кто-то может спросить: «Зачем раздувать код и добавлять выгрузку данных в таблицу значений? Что это дает?»

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

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

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

 

Вопросы и ответы

 

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

Разделение по разным группам пользователей – это сильная движущая сила. Чем больше у системы пользователей, тем насущнее принцип единственной ответственности.

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

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

Заинтересовал принцип единой ответственности. Как он балансирует с вопросами производительности высоконагруженных систем? Меня смутило, что мы считываем одни и те же данные несколько раз.

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

Даже если и есть какая-то потеря времени, она очень незначительная, и она нивелируется теми плюсами, которые сулит принцип единственной ответственности.

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

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

Да, боевой код будет, конечно, сложнее. Там будут и какие-то общие временные таблицы.

А можно какой-то небольшой вывод? Вы призываете писать мелкие методы, тогда все будет хорошо?

Писать мелкие методы – это хорошая рекомендация. Даже не зная SOLID, она дает серьезный эффект, с таким кодом потом проще работать.

Но если говорить о глобальном выводе – я считаю, что принципы SOLID можно и нужно использовать в 1С.

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

У нас в «Магните» тысячи бизнес-пользователей в базах. У нас куча аналитиков, куча процессов и групп пользователей. Ранее, когда наша комплексная система была с большими кодами, ее сопровождать и дорабатывать было очень тяжело – мы месяцами ждали очереди для того, чтобы доработать объекты.

Когда мы стали разделять код и метаданные в соответствии с принципами SOLID, мы смогли оперативно дорабатывать объекты сразу для нескольких групп пользователей. Это большой плюс для сопровождения и внедрения доработок.

В докладе делается большой упор на использование принципов SOLID именно при написании кода. Но в классическом ООП речь идет о самих объектах, классах и, например, наследовании. Здесь я услышал, что у вас формируется некое наследование за счет механизма контрактов типов данных. При этом вы переводите понятие типа на другой уровень – это не типы объектов самой 1С, а некоторые абстрактные типы в рамках вашей разработки. А как применять эти принципы в рамках объектов метаданных самой конфигурации?

В ООП все делается через классы. Классы – это и данные, и код. Плюс эти данные и код еще можно куда-то передавать, по-разному вызывать.

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

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

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

 

*************

Статья написана по итогам доклада (видео), прочитанного на конференции Infostart Event 2022 Saint Petersburg.

См. также

Рефакторинг и качество кода Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

В последнее время термин «чистый код» стал очень популярным. Появились даже курсы по данной тематике. Так что же это такое?

16.09.2024    13981    markbraer    64    

38

Рефакторинг и качество кода Программист Бесплатно (free)

В статье рассматривается отказ от использования процедур и унификация формата ответа функций. Способ описывается на примере развития абстрактной информационной системы, работающей с PDF файлами.

10.09.2024    917    acces969    4    

6

Рефакторинг и качество кода Бесплатно (free)

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

28.08.2024    1164    Chernazem    3    

6

Рефакторинг и качество кода Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Рассмотрим основные принципы шаблона проектирования "Стратегия" на простом примере.

25.06.2024    4200    MadRave    34    

27

Рефакторинг и качество кода Программист Платформа 1С v8.3 Абонемент ($m)

В статье расскажу и покажу процесс проведения Code-review на примере обработки с GitHub.

1 стартмани

04.06.2024    6276    mrXoxot    55    

42

Рефакторинг и качество кода Платформа 1С v8.3 Бесплатно (free)

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

10.04.2024    13359    artbear    85    

108

Рефакторинг и качество кода Программист Платформа 1С v8.3 Россия Бесплатно (free)

Предлагаю вашему вниманию советы мастеров древности. Программисты прошлого использовали их, чтобы заострить разум тех, кто после них будет поддерживать код. Гуру разработки при найме старательно ищут их применение в тестовых заданиях. Новички иногда используют их ещё лучше, чем матёрые ниндзя. Прочитайте их и решите, кто вы: ниндзя, новичок или, может быть, гуру? (Адаптация статьи "Ниндзя-код" из учебника JavaScript)

01.04.2024    4236    DrAku1a    15    

39

Рефакторинг и качество кода Программист Бесплатно (free)

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

01.04.2024    1287    Prepod2003    6    

2
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. starik-2005 3087 22.08.24 17:08 Сейчас в теме
Хорошо написано.
Принцип подстановки Лисков (англ. Liskov Substitution Principle, LSP) — принцип организации подтипов в объектно-ориентированном программировании, предложенный Барбарой Лисков в 1987 году[1][2]: если
q(x) является свойством, верным относительно объектов x некоторого типа T, тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T

Более популярна интерпретация Роберта Мартина[3]: функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом.

В интерпретации от команд типовых от 1С выглядит это достаточно слабо из-за отсутствия простых механизмов наследования (хотя тот же документ, справочник и прочие объекты явно наследуются от базовых интерфейсов, т.к. имеют общие методы, и Документ.Записать(РежимЗаписи.Проведение) сработает для любого документа, вызвав в нем процедуры записи и проведения).

С другой стороны, ООП - это ж просто удобство. Условно с помощью структуры с полем, содержащим какой-либо модуль, вполне можно реализовать все эти наследования и полиморфизмы (они там вообщзе искоропки), если модуль строго описывает интерфейс, или если модуль ссылается на родителя. Например:
Базовый модуль:
Функция Прочитать(Данные) Экспорт
  //Какой-то код
КонецФункции

Функция Записать(Параметры) Экспорт
  //Какой-то код
КонецФункции

Модуль-потомок:
Функция МодульРодитель()
  Возврат БазовыйМодуль;
КонецФункции

Функция Прочитать(Данные) Экспорт
  // inherited
  МодульРодитель.Прочитать(Данные);
  //Какой-то код
КонецФункции

Функция Записать(Параметры) Экспорт
  // inherited
  МодульРодитель.Записать(Параметры);
  //Какой-то код
КонецФункции
Показать

Вот тебе и наследование.
Дальше такой код-конвертор, например:
МойБазовыйОбъект = ПолучитьСтруктуруОбъекта(БазовыйМодуль);
МойОбъектПотомок = ПолучитьСтруктуруОбъекта(МодульПотомок);
Данные = МойБазовыйОбъект.Модуль.Прочитать(КакиеТоДанные);
МойОбъектПотомок.Модуль.Записать(Данные);


Но все это баловство, конечно...

ЗЫ: в 1987-мгоду программисты были математиками, но потом пришел Мартин и сделал программистов гуманитариями )))
Serg O.; olexi2012; mingaleevn@mail.ru; Gesperid; k268kk; rpgshnik; alex_sayan; kalyaka; +8 Ответить
4. alex_sayan 51 23.08.24 08:14 Сейчас в теме
(1) да, из-за динамической типизации LSP сложнее применять в 1с. Всё будет держаться только на договорённостях внутри команды разработки. Но от этого принцип не становится менее насущным. ООП тут не нужно, скорее нужна статическая типизация. В новом Элементе её подвезли
7. starik-2005 3087 23.08.24 10:21 Сейчас в теме
(4)
LSP сложнее применять в 1с
В ООП в рамках солид-подхода при реализации фичи часто делают тип-заглушку, чтобы протестить интерфейсы. Он же помогает и с юнит-тестами, накрывая собой тип-предок, нуждающийся в покрытии тестами. По крайней мере это очень просто позволяет обернуть один тип в другой. А потом появились типы-декораторы, шаблоны типов и прочее - в зависимости от языка, которые сделали это еще проще.

В 1С подход процедурный, но первые реализации ООП должны были быть написаны на процедурных языках. Это как паскаль 7, который уже вполне ООП, был написан в среде паскаль 6, который не был полностью ООПшным, а он, в свою очередь, на паскаль 5, который вообще не было ООП. Поэтому и на 1С можно реализовать объектную модель, просто для этого нужно будет заюзать какие-то промежуточные типы данных, хранящие данные и методы. А все эти "селф.моясуперметода(блаблабла)" - это в принципе просто удобство.

Но вообще и без этого всего ООП вполне себе работа работается.
20. leemuar 21 26.08.24 11:53 Сейчас в теме
> Философия принципа ISP в том, что не нужно создавать большие универсальные методы

Нет, не в этом. Большие универсальные методы - это про Single Responsibility, и все показанные вами примеры - как раз про это. ISP про то чтобы не реализовывать кучу ненужных для задачи методов, которые ТРЕБУЕТ плохо спроектированный интерфейс.
Gesperid; +1 Ответить
25. alex_sayan 51 26.08.24 12:20 Сейчас в теме
(20) SRP это ключевой принцип, другие его конкретизируют. ISP как раз конкретизирует по части интерфейсов
35. leemuar 21 27.08.24 13:26 Сейчас в теме
(25) у вас интересный взгляд на то, что все остальные принципы крутятся вокруг SR. По крайней мере теперь понятно почему у вас так примеры выстроены
37. alex_sayan 51 28.08.24 10:09 Сейчас в теме
(35) я в прошлом году хотел сделать доклад на конференции, тема про закон Конвея, но в целом можно было бы назвать "SPR на максималках". Хотел рассказать про ответственности в коде и вот это всё. Только доклад мой не прошел в программу. В этом году что-то поленился на конференцию подаваться
39. leemuar 21 28.08.24 13:36 Сейчас в теме
(37) пишите статью!
alex_sayan; +1 Ответить
40. leemuar 21 28.08.24 22:44 Сейчас в теме
(25) кажется вы все-таки правы, если подумать IS действительно про SR в итоге
21. leemuar 21 26.08.24 12:00 Сейчас в теме
(4)
> из-за динамической типизации LSP сложнее применять в 1с. Всё будет держаться только на договорённостях внутри команды разработки.

Это называется Duck Typing в языках с динамической типизацией, и вполне успешно используется. Статическая типизация не обязательна для его применения, все что она дает вам - проверки во время или до компиляции, которые ПОМОГАЮТ вам поймать некоторые ошибки.
24. alex_sayan 51 26.08.24 12:16 Сейчас в теме
(21)
все что она дает вам - проверки во время или до компиляции, которые ПОМОГАЮТ вам поймать некоторые ошибки.

Не только проверки. Ещё IDE, подносящую заготовки кода (контракта) на блюдечке. Последнее возможно даже ценнее, чем проверки соблюдения типов до компиляции
8. booksfill 23.08.24 10:24 Сейчас в теме
(1)
Но все это баловство, конечно...

Вот именно. В конечном итоге все сводится к машинным кодам и при их помощи можно обеспечить любое поведение.

Всякие там классы, стандартные библиотеки, интерфейсы, callback methods, waits and so on - это про удобство разработки.
"Мы сейчас обработками, да модулями каак сделаем нечто похожее" - это уже не про удобство, а про извращение.
Думаю 1С об этом догадывается, поэтому в ближайшие годы нас ждет более-менее нормальный язык, возможно тоже с отставанием от языков общего назначения лет на 5, но хоть не на 25.
Хотелось бы просто увидеть использование "стандартного" языка, типа C# или JAVA, в сочетании с уже имеющимся мощным ORM, но такого не будет.

Ругать 1С не хочется, ну не могли они предвидеть, что их начнут пускать во что-то, выходящее за рамки чисто учетных задач с незначительной нагрузкой, плюс импортозамещать ими что-то типа SAP или BAAN.
Единственно, что раздражает так это EDT.
Хочешь конфигуратор - добро пожаловать в девяностые, хочешь нормальное IDE - вот тебе ауди, правда с тремя колесами и рычагами вместо руля - все равно лучше же!
user2041697; +1 Ответить
2. van_za 269 22.08.24 23:08 Сейчас в теме
Статья в целом хорошая, красивая и правильная, но мне кажется, что принцип инверсии зависимости недостаточно раскрыт. Не хватает объяснения, что такое зависимость и что именно мы инвертируем.

Вот моя попытка объяснить:

Представьте, что у нас есть система для рассылки рекламы. В этой системе:

Клиент — это обработка рассылки рекламы.
Сервер — это абстрактная обработка мессенджеров.
Реализация — это конкретные мессенджеры, такие как Telegram, которые реализуют интерфейс.
Когда вы создаете обработку рассылки, вы передаете реализацию (например, Telegram) в сервер. Сервер вызывает методы обработки, а сам не зависит от конкретной реализации. Он знает только о юз-кейсе, который ему нужен.

Таким образом, если вам нужно изменить мессенджер (например, перейти на WhatsApp), вам не придется изменять слой абстрактной обработки. Вы просто создаете новую реализацию и передаете её в сервер. Сервер продолжит работать с той же абстракцией, не зная о конкретной реализации.

Это позволяет легко менять реализации, не затрагивая основную логику сервера, что и является сутью инверсии зависимости.
olexi2012; kalyaka; acvatoris; +3 Ответить
5. alex_sayan 51 23.08.24 08:35 Сейчас в теме
(2)
Представьте, что у нас есть система для рассылки рекламы


Попробую порассуждать.
А) В самом верху должна находиться бизнес-логика. Тут бизнес-логикой будет код, определяющий кому и какую рекламу рассылать. Этот код не должен знать ничего о том, как и по каким каналам реклама будет доставлена. Код бизнес-логики ничего не должен знать о нижележащем коде.

Б) Ниже идет код вариантов использования (use cases). Код вариантов использования зависит от кода бизнес-логики, но код бизнес-логики не зависит от кода вариантов использования. Варианты использования будут получать готовый набор "кому какую рекламу отправить", и примет решение, по каким каналам реклама должна уйти (почта, мессенджер, смс).

В) Ещё ниже идёт сервисный код. Он отвечает за работу с телеграмом, почтой и тд. Инфраструктурный код зависит от кода вариантов использования, но код вариантов использования не зависит от инфраструктурного кода

Г) Наконец, в самом низу будет работа с интерфейсом, логирования, вызов системного API (почтового клиента, Telegram и др.) и т.д.

Направление зависимостей А -> Б -> В ->Г

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

Таким образом, если вам нужно изменить мессенджер (например, перейти на WhatsApp), вам не придется изменять слой абстрактной обработки. Вы просто создаете новую реализацию и передаете её в сервер. Сервер продолжит работать с той же абстракцией, не зная о конкретной реализации.


Да, это правильно
rpgshnik; +1 Ответить
10. rpgshnik 3795 23.08.24 10:48 Сейчас в теме
(5)
Направление зависимостей А -> Б -> В ->Г


+
12. Gesperid 2 23.08.24 15:20 Сейчас в теме
(5)
А -> Б -> В ->Г

Странные направления стрелочек.

В моём понимании DIP - это:
* создание общего интерфейса ИнтерфейсБ к Б (содержащем конкретные реализации)
* использование в А только типа ИнтерфейсБ
То есть акцент на общем интерфейсе к Б.
Без DIP: А -> Б
Применяя DIP: A -> ИнтерфейсБ <- Б

У вас даже в докладе есть "квадратик" абстракция с таким направлением стрелочек.
14. alex_sayan 51 24.08.24 10:39 Сейчас в теме
(12) ну да, стрелочки нужно было обратные делать. Это я в торопях так нарисовал
3. dab85 23.08.24 00:00 Сейчас в теме
А разве Принцип единственной ответственности не говорит о том, что каждый класс должен быть небольшим и отвечать за строго небольшую часть кода? И не громоздить модули по 13к строчек?) ООП предполагает краткость и стройность модулей.
6. alex_sayan 51 23.08.24 08:52 Сейчас в теме
(3) SRP не только про классы, хотя чаще всего трактуется в контексте классов. Роберт Мартин отдельно акцентирует внимание на том, что SRP можно применять и в не ООП языках. А вообще, по этому принципу можно целую статью написать, если не маленькую книгу)

У SRP есть очень тонкая материя. Попробую объяснить на примере. Допустим, у нас есть алгоритм закрытия месяца, он состоит из 10 тысяч строк кода (второстепенно, по-скольки модулям разбросан этот код). Периодически этот алгоритм дорабатывается. А далее у нас две условные крайности:
1) ответственности в алгоритме закрытия месяца поделены чётко, каждая доработка затрагивает не более 200-300 строк кода
2) ответственности в алгоритме закрытия скиданы в одну кучу, всё зависит от всего, каждая доработка затрагивает 2000-3000 тысячи строк кода

Дело в трудозатратах при программировании. Да-да, одна и та же логика может быть организована в коде сильно по-разному. В одних случаях кодить легко, изменения очень локальные. В других случаях нужно продираться через огромные пласты кода. Соответственно разработка становится сильно дороже
Перефразируя почти классика (Стив МакКонел): чем бОльшую часть программы можно игнорировать в единицу времени, тем лучше и комфортнее программировать

Альтернативная формулировка SRP: собирать в одном месте то, что изменяется в одно время и по одним причинам, разделять то, что изменяется в разное время по разным причинам
9. rpgshnik 3795 23.08.24 10:44 Сейчас в теме
Доклад супер! Продублируйте видео на ВК или Дзен или Рутуб, не грузится
dabu-dabu; abasovit; alex_sayan; +3 1 Ответить
11. Lars Ulrich 625 23.08.24 10:49 Сейчас в теме
Хорошо бы, чтобы эти принципы экстраполировались не только на код конкретного приложения, но и на прочие "предметные области": административное устройство, процессы, взаимодействие между сервисами, инфраструктуру и т.д. Тогда синергия еще лучше будет. Те же продажи и бухгалтерию в определенный момент развития хорошо бы не просто разделить по регистрам, а вообще по разным сервисам.
alex_sayan; +1 Ответить
13. starik-2005 3087 23.08.24 17:24 Сейчас в теме
(11)
а вообще по разным сервисам
Смотря что продавать. Если это условно что-то большое и красивое, уникальное, ну или нематериальное, то продажники давно в СРМ и BI сидят, а в 1С только кладовщики на складе, да и те в мобильном приложении и изредка до принтера ходят, чтобы на короба наклеить пе(а)летный лист.
15. mephistofel 16 25.08.24 14:57 Сейчас в теме
Спасибо! Давно хотелось почитать что-то про конструирование ПО с такой концентрацией годноты. Попробуем внедрить у себя.
alex_sayan; +1 Ответить
16. o.nikolaev 216 25.08.24 23:07 Сейчас в теме
Попытки натянуть сову (язык 1С) на глобус (ООП) не от хорошей жизни. Сложность того для чего пишется система растет и процедурный подход не тащит.
22. leemuar 21 26.08.24 12:04 Сейчас в теме
(16) ООП не ключевой фактор. У нас есть хороший пример большого проекта - ядро линукса. Он написан на Си, в котором нет ООП. И он вполне себе тащит. Есть даже мнение, что он лучше поддерживает 3 принципа ООП чем языки, в которых есть ООП. Потому что дело в подходе, а не инструментах
32. Gesperid 2 27.08.24 09:51 Сейчас в теме
(22) Почему тогда в этот хороший пример - ядро Linux - тащат более высокоуровневый и простой язык Rust?
Ну и в целом пример не совсем корректен, в ядре ОС - повышенные требования к производительности, памяти и безопасности, а в кровавом энтерпрайзе - важны частые изменения плохоформализуемых процессов.
34. leemuar 21 27.08.24 13:13 Сейчас в теме
(32) не знаю, этот вопрос лучше всего задать на рассылку LKML
36. leemuar 21 27.08.24 13:51 Сейчас в теме
(32) Ну и в целом пример не совсем корректен, в ядре ОС - повышенные требования к производительности, памяти и безопасности, а в кровавом энтерпрайзе - важны частые изменения плохоформализуемых процессов

Мне кажется здесь вы противопоставляете "производительность", "ООП" и "скорость изменений". Это не противоречащие друг другу вещи. Напомню, что та самая "энтерпрайзная" книга по паттернам вышла в 94м году, и примеры в ней были на С++ и SmallTalk. Java - язык, который широко разрекламировал то что сейчас понимают под ООП - вышел только в 1995м году.
28. alex_sayan 51 26.08.24 14:33 Сейчас в теме
(16) да, многие современные языки программирования - мультипарадигменные, сочетают в себе ООП, функциональное и процедурное программирование
17. muskul 26.08.24 03:52 Сейчас в теме
ООП не про скорость. а скорости в 1с и так мало. не говоря уже что с каждым релизом код становится все хуже и хуже. не работает нормально даже то что только что выпустили.
18. Farpost 116 26.08.24 08:43 Сейчас в теме
Статья хорошая, неожиданно для себя обнаружил, что использую этот принцип в программировании 1С :-)
Но удручает, что фирма 1С от этих принципов бежит в сторону "Фёдора", Ткнулся в УТ-11 остатки волос дыбом встали.
В общем если честно не вижу перспектив у фирмы 1С с таким отношением к разработке...
alex_sayan; +1 Ответить
26. alex_sayan 51 26.08.24 12:20 Сейчас в теме
(18) Да. К сожалению в типовых слишком много анти-SOLID
29. brr 184 26.08.24 15:33 Сейчас в теме
Попробуйте использовать SRP на проекте ведущимся в конфигураторе с командой хотя бы из 5 разработчиков. Будете висеть на ожиданиях когда отпустят корень конфигурации, такие задержки в сочетании с давлением бизнеса приводят к появлениям "именных" общих модулей :). При использовании EDT применение SRP приведёт к мержконфликтам на файликах типа Configuration.mdo. Сама структура файлов хранящих метаданные спроектирована так, что есть бутылочные горлышки в виде файлов конфигурации, подсистем, подписок, определяемых типов и т.д.
30. alex_sayan 51 26.08.24 16:35 Сейчас в теме
(29) SPR наоборот, позволяет распараллелить разработку
33. brr 184 27.08.24 11:24 Сейчас в теме
(30) Если нет бутылочных горлышек, например корень конфигурации. Попробуйте параллельно добавлять нужные объекты метаданных.
19. user1950534 26.08.24 09:26 Сейчас в теме
Гладко было на бумаге, да забыли про овраги))

Принцип, безусловно, правильный и нужный, но по жизни не всегда работает)
ИМХО 1С остается системой для быстрого сферического тайм-ту-маркета в вакууме. Когда надо быстро, тупо, но надежно. Когда ТЗ - это "точка зрения", а пожелалки бизнеса меняются чаще, чем курсы валют.

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

На практике в большинстве случаев такой подход будет приводить к излишнему дроблению функций, и необходимости матерясь и чертыхаясь искать ту самую "точку входа", чтобы рядом с продажей телевизоров автоматизировать какую-нибудь линию по производству мороженого))). Не спорю, профессиональные программеры с ххх-летним опытом, да еще и в команде с аналитиками и архитекторами, на уровне газпромчегототам без сомнений спроектируют и напишут базовую, образцовую систему с мини-сервисами и нужной декомпозицией. Но где ж их взять-то, профессиональных? Уровень входа в 1С - 1500р час, а 5к это уже сеньёры.... Так что ребят, немного не в ЦА рекомендации. Всё ИМХО, извините
23. alex_sayan 51 26.08.24 12:14 Сейчас в теме
(19)
а пожелалки бизнеса меняются чаще, чем курсы валют


Для этого многие принципы программирования и существуют. Чтобы сделать код адаптивным, легко поддающимся доработкам
27. acces969 362 26.08.24 13:50 Сейчас в теме
Очень полезный материал, спасибо. Читал книгу, и сам пришел к некоторым паттернам в 1С из книги и до прочтения, и после. Делал даже презентацию для джунов по антипаттернам, основанный на том, как НЕ НАДО делать по SOLID.
Прикрепленные файлы:
Презентация антипаттерны 2024.pptx
Automatik; alex_sayan; +2 Ответить
31. user864894 27.08.24 07:21 Сейчас в теме
хорошо бы продемонстрировать работу "Федора" и "Харитона Ивановича" на примере кода из ERP .
vas.kif-ae; alex_sayan; +2 Ответить
38. alex_sayan 51 28.08.24 10:11 Сейчас в теме
(31) предложение интересное. Но это уже много кода, тянет на видеоформат
41. vas.kif-ae 04.10.24 11:05 Сейчас в теме
Спасибо автору alex_sayan! Очень понравилась статья с примерами "Федора" и "Харитона Ивановича". часто изменяя одно забывают поправить другое. Такова жизнь. Все хотят побыстрей, а потом получают жесть.
Оставьте свое сообщение