[Разработка веб-сайтов, JavaScript, Проектирование и рефакторинг, ООП, ReactJS] Техники повторного использования кода
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В этой статье я опишу различные техники повторного использования кода и разбиения сложных объектов на части, с которыми я столкнулся. Постараюсь объяснить, почему классическое наследование, а также некоторые другие популярные подходы не работают в сложных случаях, и какие есть альтернативы.Возможно многих удивит, что в основе большинства подходов повторного использования кода и создания составных объектов лежат стандартные структуры данных – массив, список, словарь, дерево, граф.Т.к. в последние годы я пишу на JavaScript и React, то они будут использоваться в некоторых примерах. Да и в целом, периодически я буду упоминать React и другие веб-технологии. Тем не менее, думаю, что значительная часть статьи должна быть понятна и полезна разработчикам из других стеков. Для некоторых подходов я добавил схемы, чтобы показать, как организованы составляющие сложных объектов. Будет часто упоминаться агрегация(агрегирование/делегирование/включение) и композиция. Описывается то, как я это понял, поэтому где-то информация может быть ошибочной.Чтобы разделить логику одного сложного объекта на составные части, существуют несколько механизмов:
- Разделение функционала на классы/объекты и смешивание их полей, методов в одном объекте.
- Вынесение части функционала в обертки и помещение в них основного объекта, либо вкладывание объектов один в другой с организацией списка вложенных объектов.
- Вынесение части функционала в отдельные объекты/функции и помещение их в основной объект.
- Разделение функционала объекта на независимые части и использование какого-то внешнего механизма для организации нужного поведения с использованием этих частей.
В статье же я разделил техники/паттерны в зависимости от получаемой структуры данных, используемой для хранения составляющих сложного объекта.1) Объединение (смешивание) функционала нескольких объектов в одном
— Смешивание и примеси (миксины)
— Классическое наследование
— Множественное наследование и интерфейсы2) Композиция/агрегация с использованием списка
— Прототипное наследование
— Паттерн декоратор и аналоги3) Композиция/агрегация с использованием одноуровневых структур данных (ссылка, массив ссылок, словарь)
— Паттерн стратегия
— Entity Component (EC) 4) Композиция/агрегация с вынесением логики вне объекта и его составляющих
— Entity Component System (ECS)5) Композиция/агрегация с использованием графов
— Паттерн State machine6) Композиция/агрегация с использованием деревьев
— Паттерн composite и другие древовидные структуры
— Behaviour tree7) Смешанные подходы
— React hooksОбъединение (смешивание) функционала нескольких объектов в одном.Смешивание и примеси (миксины)Самый простой, но ненадежный способ повторного использования кода – объединить один объект с другим(и). Подходит лишь для простых случаев, т.к. высока вероятность ошибки из-за замещения одних полей другими с такими же именами. К тому же, так объект разрастается и может превратиться в антипаттерн God Object.Существует паттерн примесь (mixin/миксина), в основе которого лежит смешивание.
Примесь – это объект, поля и методы которого смешиваются с полями и методами других объектов, расширяя функциональность объекта, но который не используется сам по себе.
Можно добавить несколько миксин к одному объекту/классу. Тогда это схоже с множественным наследованием.Классическое наследованиеЗдесь описывается классическое наследование, а не то, как наследование классов устроено в JS.Подразумеваю, что читатель уже знаком с понятиями "наследование" и "множественное наследование". В отличие от простого смешивания, в классическом наследовании есть ряд строгих правил и механизмов, которые позволяет избежать множество проблем. В основе классического наследования лежит все то же смешивание - члены нескольких объектов объединяются в один объект.
При наследовании происходит копирование членов родительского класса в класс-наследник. При создании экземпляра класса тоже происходит копирования членов класса. Я не исследовал детали этих механизмов, к тому же они явно отличаются в различных языках. Подробнее с этой темой можно ознакомиться в 4-ой главе книги "Вы не знаете JS: this и Прототипы Объектов".Когда можно использовать наследование, а когда не стоит?
Наследования не стоит использовать в качестве основной техники для повторного использования кода для сложных объектов. Его можно использовать совместно с композицией для наследования отдельных частей сложного объекта, но не для самого сложного объекта. Например, для React компонентов наследование плохо, а для частей (вроде объектных аналогов custom hooks) из которых мог быть состоять компонент-класс, наследования вполне можно использовать. Но даже так, в первую очередь стоит рассматривать разбиение на большее число составляющих или применения других техник, вместо наследования.При возможности появления сложной иерархии наследование (более 2-х уровней, где первый уровень иерархии – родитель, а второй уровень - наследники) тоже не следует использовать наследование. Множественное наследование и интерфейсыПри использовании множественного наследования получаются довольно запутанные иерархии классов. Поэтому во многих языках от отказались от множественного наследования реализации. Но множественное наследование по-прежнему применяют при наследовании абстракций в виде интерфейсов.Интерфейсы есть, например, в Typescript. Реализация нескольких интерфейсов в одном классе отчасти похоже на наследование, но с их использованием «наследуется» только описание свойств и сигнатура методов интерфейса. Наследование реализации не происходит. Интерфейсы следует понимать не как наследование, а как контракт, указывающий, что данный класс реализует такой-то интерфейс. Плохо, когда один класс реализует слишком много интерфейсов. Это означает, что-либо интерфейсы слишком сильно разбиты на части, либо у объекта слишком большая ответственность.
Композиция/агрегация с использованием спискаПрототипное наследованиеПри прототипном наследовании уже не происходит смешивания родительского объекта и его наследника. Вместо этого наследник ссылается на родительский объект (прототип).При отсутствии свойства (поле, метод и т.д.) в объекте, происходит поиск этого свойства в цепочке прототипов. То есть часть функционала делегируется вложенному объекту, который тоже может делегировать функционал вложенному объекту внутри себя. И так далее по цепочке. Прототип на любом уровне цепочки может быть только один.Стоит отметить, что в JavaScript операции записи/удаления работают непосредственно с объектом. Они не используют прототип (если это обычное свойство, а не сеттер). Если в объекте нет свойства для записи, то создается новое. Подробнее об этом.Цепочка прототипов организована как стек (Last-In-First-Out или LIFO). Какой объект добавлен в цепочку последним (если считать от итогового объекта-контейнера), к тому обращение будет первым.Также существует вариант, когда при создании нового объекта с прототипом, создается копия прототипа. В таком случае используется больше памяти (хотя это проблема разрешаема), но зато это позволяет избежать ошибок в других объектах из-за изменения прототипа конкретного объекта.
Паттерн Декоратор и аналогиДекоратор(wrapper/обертка) позволяет динамически добавлять объекту новую функциональность, помещая его в объект-обертку. Обычно объект оборачивается одним декоратором, но иногда используется несколько декораторов и получается своего рода цепочка декораторов.Цепочка декораторов устроена как стек (LIFO). Какой объект добавлен в цепочку последним (если считать от итогового объекта-контейнера), к тому обращение будет первым.Цепочка декораторов похожа на цепочку прототипов, но с другими правилами работы с цепочкой. Оборачиваемый объект и декоратор должны иметь общий интерфейс.
На схеме ниже пример использования нескольких декораторов на одном объекте:
Как в случае с прототипами, зачастую можно подменять декораторы во время выполнения. Декоратор оборачивает только один объект. Если оборачивается несколько объектов, то это уже что-то другое.HOF (higher order function) и HOC (Higher-Order Component) - паттерны с похожей идей. Они оборачивают функцию/компонент другой функцией/компонентом для расширения функционала.HOF - функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата. Примером HOF в JS является функция bind, которая, не меняя переданную функцию, возвращает новую функцию с привязанным к ней с помощью замыкания значением. Другим примером HOF является карринг.HOC - чистая функция, которая возвращает другой компонент (а он уже содержит в себе произвольную логику), который внутри себя "рендерит" переданный компонент. При этом сам переданный компонент не меняется, но в него могут быть переданы props.Также стоит упомянуть композицию функций. Это тоже своего рода обертка. С помощью этой техники создаются цепочки вложенных функций:
const funcA = сompose(funcB, funcC, funcD);
или же менее читабельный вариант:
const funcA = ()=> {
funcB( funcC( funcD() ) ) ;
};
То же самое можно получить такой записью:
function funcA() {
function funcB() {
function funcC() {
function funcD()
}
}
}
Недостатком последнего варианта является жесткая структура функций. Нельзя поменять их очередность или заменить одну из функций без создания новой аналогичной цепочки или ее части. funcC нельзя использовать без funcD, а funcB без funcC и без funcD. В первых же двух примерах – можно. Там функции независимы друг от друга.ИтогоПрототипное наследование и использование декораторов гибче, чем подходы со смешиванием.Часто говорят: «предпочитайте композицию наследованию». Стоит учесть, что существует множество вариантов композиции с различной эффективностью в той или иной ситуации. Только от простой замены наследования на композицию, вряд ли получиться решить проблемы без появления новых проблем. Нужно еще выбрать подходящую замену.Когда по аналогии с иерархией наследования используется несколько уровней вложения одних объектов в другие, получается иерархия вложенных объектов. Почти то же самое, что и наследование, только с возможностью подменять объекты в иерархии. Конечно, в случае декораторов этого обычно избегают и вместо иерархии получается цепочка. В цепочке декораторов выходит так, что каждый следующий используемый декоратор помимо своих членов классов должен реализовать члены всех остальных объектов в цепочке. В итоге, по аналогии с наследованием, снова получается раздутый объект с множеством полей и методов. На схеме выше был пример такого объекта - DecoratorC.Зачастую при использовании нескольких декораторов на одном объекте не добавляют новые поля и методы, а лишь подменяют реализацию уже существующих членов объекта. Остается другой недостаток – из-за большой вложенности довольно сложно разобраться, что же делает итоговый составной объект, т.к. для этого надо пройтись по цепочке вложенных объектов.Как видите, по-прежнему остаются довольно серьезные проблемы. Но, есть другие решения, о которых рассказано в следующих главах. Композиция/агрегация с использованием одноуровневых структур данных (ссылка, массив ссылок, словарь)Под одноуровневой структурой данных я подразумеваю структуру, элементы которой не ссылаются на другие элементы. Паттерн стратегия Паттерны декоратор и стратегия служат для одной цели – с помощью делегирования расширить функциональность объекта. Но делают они это по разному. Хорошо описана эта разница по ссылке: «Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи».»Паттерн Cтратегия описывает разные способы произвести одно и то же действие, позволяя динамически заменять эти способы в основном объекте (контексте). На схеме ниже пара примеров связи стратегий с основным объектом.
К похожим способам (использование ссылки) расширения функционала объекта и повторного использования кода можно отнести события в HTML элементах и директивы в Angular и Vue.
<button onclick="customAction()" /> // html
<input v-focus v-my-directive="someValue" /> // vue
Entity Component (EC)Я не знаю, как называется данный паттерн. В книге Game Programming Patterns он называется просто "Компонент", а по ссылке его называют системой компонентов/сущностей. В статье же я буду называть его Entity Component (EС), чтобы не путать с подходом, который будет описан в следующей главе.
Сначала пройдемся по определением:
- Entity (сущность) – объект-контейнер, состоящий из компонентов c данными и логикой. В React и Vue аналогом Entity является компонент. В Entity не пишут пользовательскую логику. Для пользовательской логики используются компоненты. Компоненты могут храниться в динамическом массиве или словаре.
- Component – объект со своими данными и логикой, который можно добавлять в любую Entity. В React компонентах похожим аналогом являются custom hooks. И описываемые здесь компоненты и пользовательские хуки в React служат для одной цели – расширять функционал объекта, частью которого они являются.
Обычно Entity может содержать вложенные entities, тем самым образуя дерево entities. Не думаю, что это является неотъемлемой его частью, а скорее является смешиванием нескольких подходов.Данный паттерн похож на паттерн стратегия. Если в объекте использовать динамический массив со стратегиями, организовать их добавление, удаление и получение определенной стратегии, то это будет похоже на Entity Component. Есть еще одно серьезное отличие - контейнер не реализует интерфейс компонентов или методы для обращения к методам компонентов. Контейнер только предоставляет доступ к компонентам и хранит их. Получается составной объект, который довольно своеобразно делегирует весь свой функционал вложенным объектом, на которые он ссылается. Тем самым EC избавляет от необходимости использования сложных иерархий объектов.Плюсы EC
- Низкий порог вхождения, т.к. в основе используется простая одноуровневая структура данных.
- легко добавлять новую функциональность и использовать код повторно.
- можно изменять составной объект (Entity) в процессе выполнения, добавляя или удаляя его составляющие (компоненты)
Минусы
- для простых проектов является ненужным усложнением из-за разбиение объекта на контейнер и компоненты
В одной из своих следующих статей я опишу применение этого подхода для React компонентов. Тем самым я покажу, как избавиться от первых двух недостатков компонентов на классах, описанных в документации React-а:
https://ru.reactjs.org/docs/hooks-intro.html#its-hard-to-reuse-stateful-logic-between-components
https://ru.reactjs.org/docs/hooks-intro.html#complex-components-become-hard-to-understandЭтот подход используется с самого начала выхода движка Unity3D для расширения функционала элементов (объектов) дерева сцены, включая UI элементы, где вы можете получше ознакомится с данным подходом. Но в таком случае придётся потратить не мало времени на изучение движка.ИтогоПаттерн стратегия сам по себе не очень мощный, но если развить его идею, можно получить довольно эффективные техники. К такой можно отнести Entity Component.В случае использования EC может появиться новая проблема – при большом количестве компонентов, связанных между собой в одном объекте, становиться сложно разобраться в его работе. Выходом может стать некий компонент, который контролирует взаимодействия между компонентами в одной Entity или в группе вложенных Entities. Такой подход известен как паттерн Посредник (Mediator). Но даже “посредника“ будет недостаточно для более сложных случаев. К тому же он не является универсальным. Для каждой Entity с множеством связанных компонентов придёться реализовывать новый тип “посредника”. Есть и другой выход. EC можно комбинировать с другими подходами на основе графов и деревьев, которые будут описаны позже.Композиция/агрегация с вынесением логики вне объекта и его составляющихEntity Component System (ECS)Я не работал с этим подходом, но опишу то, как я его понял.В ECS объект разбивается на 3 типа составляющих: сущность, компонент (один или несколько), система (общая для произвольного числа объектов). Этот подход похож на EC, но объект разбивается уже на 3 типа составляющих, а компонент содержит только данные.Определения:
- Entity – его основное назначение, это идентифицировать объект в системе. Зачастую Entity является просто числовым идентификатором, с которым сопоставляется список связанных с ним компонентов. В других вариациях Entity также может брать на себя роль контейнера для компонентов. Как и в EC подходе, в Entity нельзя писать пользовательский код, только добавлять компоненты.
- Component - объект с определенными данными для Entity. Не содержит логики.
- System - в каждой системе описывается логика. Каждая система перебирает список компонентов определенных типов или компоненты определенных entities и выполняет логику с использованием данных в компонентах. Может извлекать компоненты из entities. Результатом выполнения системы будет обновление данных в компонентах. В некоторых случаях системы могут быть обычными функциями, получающими на вход нужные данные.
Также может быть некий объект-менеджер (или несколько менеджеров), который хранит все системы и объекты, а также периодически запускает все системы. Здесь уже реализация произвольная.Пример простой ECS: Допустим есть несколько объектов, у которых есть идентификаторы. Несколько из этих объектов ссылаются на компоненты Position, в которых хранятся текущие координаты x, y, и на компонент Speed, который содержит текущую скорость. Есть система Movement, которая перебирает объекты, извлекает из них компоненты Position и Speed, вычисляет новую позицию и сохраняет новые значения x, y в компонент Position. Как я уже говорил, реализации ECS могут отличаться. Например:a) entity является контейнером для своих компонентов b) компоненты содержится в массивах/словарях. Entity является просто идентификатором, по которому определяется компонент, связанный с сущностью.
http://jmonkeyengine.ru/wiki/jme3/contributions/entitysystem/introduction-2
http://entity-systems.wikidot.com/fast-entity-component-system#java
https://www.chris-granger.com/2012/12/11/anatomy-of-a-knockout/На схеме изображен первый вариант, когда entity ссылается на свои компоненты.
Плюсы ECS
- Слабое сцепление составляющих объекта, поэтому легко добавлять новую функциональность комбинирую по-разному составляющие.
- Проще тестировать, т.к. нужно тестировать только системы. Компоненты и сущности тестировать не нужно.
- Легко выполнять многопоточно.
- Более эффективное использование памяти, кэша и, следовательно, большая производительность.
- Легко реализовать сохранение всего приложения, т.к. данные отделены от функционала.
Минусы ECS
- Высокая сложность, не стандартный подход.
- для простых проектов является ненужным усложнением.
Так как я занимаюсь фронтенд разработкой, а она по большей части относится к разработки UI, то упомяну, что ECS используется в игре World of Tanks Blitz для разработки UI:
https://www.youtube.com/watch?v=nu8JJEJtsVEИтогоECS является хорошей альтернативой созданию сложных иерархий наследования. В ECS можно создать иерархии наследования для компонентов или систем, но вряд ли от этого почувствуется выгода. Скорее, быстро почувствуются проблемы от таких попыток. Как и в аналогичном случае с EС, системы в данном подходе можно комбинировать с подходами на основе графов и деревьев. В таком случае логика по-прежнему будет реализована в системах, а хранение данных в компонентах. Но, я не знаю, эффективно ли совмещение этих подходов на практике. В первую очередь нужно стремиться реализовывать системы попроще, а уже потом рассматривать комбинацию с другими подходами.Композиция/агрегация с использованием графовК данному способу повторного использования кода я отнес паттерн «машина состояний» (State machine/Finite state machine/конечный автомат). Аналогом машины состояний простой является switch:
switсh (condition) {
case stateA: actionA();
case stateB: actionB();
case stateC: actionC();
}
Недостатком является то, что он может сильно разрастить, а также у разработчика может не быть возможности добавить новые состояния в систему.Для сложных случаев каждое состояние с логикой можно вынести в отдельный объект и переключаться между ними.В более сложных случаях можно разделить на большее число составляющих: состояние, действие, переход, условие перехода, событие.Также существуют иерархические машины состояний, где состояние может содержать вложенный граф состояний.Я уже описывал паттерн “Машина состояний” и его составляющие, и вкратце писал о иерархической машине состояний в статье "Приемы при проектировании архитектуры игр" в главе "машина состояний".Преимущества использования машины состояний:Хорошо описано по ссылке.Добавлю, что становится легче предусмотреть, обработать и протестировать все возможные случаи работы контекста (подсистемы), т.к. видны все его состояния и переходы. Особенно, если состояния являются просто объектами с данными и отделены от остальной логики и отображения.Где при разработке UI можно использовать машину состояний?Например, для логики сложной формы, у которой поведение и набор полей немного отличается в зависимости от роли пользователя и других параметров. Каждый объект-состояние может описывать состояние всех компонентов формы (активный, видимый, фиксированный текст элемента в таком-то состоянии и т.д.), отображение которых может отличаться в зависимости от роли пользователя и других параметров. Компоненты формы получают часть объекта-состояния, которая им нужна для своего отображения.Другие примеры использования в UI:
https://24ways.org/2018/state-machines-in-user-interfaces/
https://xstate.js.org/docs/ (библиотека для JS, которую можно использовать c React, Vue, Svelte)
https://github.com/MicheleBertoli/react-automata (библиотека для React)
https://habr.com/ru/company/ruvds/blog/346908/Подходит ли State machine в качестве основного механизма повторного использования кода и разбиения сложных объектов на составные части?Иногда он так и используется. Но, он мне кажется сложноватым и не всегда подходящим для использования в качестве основного. Зато он точно хорош в качестве дополнительного, когда нужно организовать взаимодействия между несколькими объектами или частями составного объекта.Стоит учитывать, что граф может получиться слишком сложным. Если у вас обычный код получается запутанным, то и граф скорее всего получится запутанным. В сложном графе нужно стремиться уменьшать количество связей, группировать состояния.Композиция/агрегация с использованием деревьевПаттерн composite и другие древовидные структурыДеревья часто встречается в разработке. Например, объекты в JavaScript могут содержать вложенные объекты, а те также могут содержать другие вложенные объекты, тем самым образую дерево. XML, JSON, HTML, DOM-дерево, паттерн Комповщик (Composite) – все это примеры древовидной композиции.Дерево является графом, в котором между любыми 2-мя его узлами может быть только один путь. Благодаря этому, в нем гораздо проще разобраться, чем в графе получаемом с помощью машине состояний.Behaviour treeИнтересным вариантом композиции является Behaviour tree(дерево поведения). Это организация логики программы (обычно AI) или ее частей в виде дерева. В дереве поведения в качестве узлов выступают управляющие блоки - условие, цикл, последовательность действий, параллельное выполнение действий и блоки действий. В теории, могут быть реализованы и другие управляющие блоки, вроде switch case, асинхронного блока и других аналогов управляющих конструкций языка, но я не встречал подобного. Код действий для деревьев поведения пишет разработчик. Обычно решения для деревьев поведений содержат визуальный редактор для их создания и отладки.Я уже описывал деревья поведений в прошлом в этой статье.Более наглядный пример схемы готового дерева из плагина banana-tree
Дерево поведения можно рассматривать как аналог обычной функции с вложенными функциями. Я думаю, понятно, что схему выше можно перевести в функцию с вложенными функциями, условиями и циклами.Если в функции написать слишком много кода или же в ней будет слишком много вложенных условий, то она будет нечитабельна и с ней будет тяжело работать. Аналогично и с деревьями поведения. Их следует делать как можно проще и с меньшей вложенностью блоков с циклами и условиями.Деревья поведения позволяют создавать сложную логику с помощью комбинации более простых действий. Сложная логика тоже может быть оформлена в виде узла дерева (в виде действия или поддерева). Также деревья поведения предоставляют единый механизм для разработки в таком стиле. Этот подход мотивирует выносить функционал в отдельные настраиваемые объекты, зачастую предоставляет наглядное средство для отладки, упорядочивает и уменьшает связи между объектами, позволяет избежать жесткой зависимости составляющих.Для простых случаев, как обычно, этот подход будет ненужным усложнением.Смешанные подходыДля более эффективной организации кода можно комбинировать некоторые подходы. Например, в качестве узлов машины состояний можно использовать деревья поведения.Довольно многое можно отнести к смешанных подходам. Entity Component в Unity3D реализован так, что позволяет хранить не только компоненты, но и вложенные сущности. А для пользовательских компонентов можно использовать наследование в простых случаях, либо объединить компоненты с более продвинутыми техниками (паттерн mediator, машина состояний, дерево поведения и другие).Примером смешивания подходов является анимационная система Mecanim в Unity3D, которая использует иерархическую машину состояний с деревьями смешивания (blend tree) для анимаций. Это относится не совсем к коду, но является хорошим примером комбинации подходов.К смешанным подходам я отнес React компоненты с хуками, т.к. там довольно специфичный случай. С точки зрения разработчика, для повторного использования кода в компоненте используется дерево функций (хуков). С точки зрения организации данных в компоненте – компонент хранит список с данными для хуков. К тому же связь между хуками и компонентом устанавливается во время выполнения. Т.к. я разрабатываю на React, то решил включить частичное описание внутреннего устройство компонента с хуками в статью. React hooksЭта часть статьи для разработчиков, знакомых c React. Остальным многое в ней будет не понятно. Особенность функциональных компонентов в React-е в том, что разработчик не указывает сам связь между компонентом и его хуками. Отсюда возникает вопрос, как React определяет к какому компоненту применить такой-то хук?Как я понял, хуки при вызове добавляют к текущему обрабатываемому компоненту (точнее к fiber-ноде) свое состояние – объект, в котором могут быть указаны переданные сallback-и (в случае useEffect, useCallback), массив зависимостей, значения (в случае useState) и прочие данные (в случае useMemo, useRef, …).А вызываются хуки при обходе дерева компонентов, т.е. когда вызывается функция-компонент. React-у известно, какой компонент он обходит в данный момент, поэтому при вызове функции-хука в компоненте, состояние хука добавляется (или обновляется при повторных вызовах) в очередь состояний хуков fiber-ноды. Fiber-нода – это внутреннее представление компонента.
Стоит отметить, что дерево fiber элементов не совсем соответствует структуре дерева компонентов. У Fiber-ноды только одна дочерняя нода, на которую указывает ссылка child. Вместо ссылки на вторую ноду, первая нода ссылается на вторую (соседнюю) с помощью ссылки sibling. К тому же, все дочерние ноды ссылаются на родительскую ноду с помощью ссылки return.Также для оптимизации вызова эффектов (обновление DOM, другие сайд-эффекты) в fiber-нодах используются 2 ссылки (firstEffect, nextEffect), указывающие на первую fiber-ноду с эффектом и следующую ноду, у которой есть эффект. Таким образом, получается список нод с эффектами. Ноды без эффектов в нем отсутствуют. Подробнее об этом можно почитать по ссылкам в конце главы.Вернемся к хукам. Структура сложного функционального компонента с несколькими вложенными custom hooks для разработчика выглядит как дерево функций. Но React хранит в памяти хуки компонента не как дерево, а как очередь. На схеме ниже изображен компонент с вложенными хукам, а под ним fiber-нода с очередью состояний этих же хуков.
На схеме в fiber-ноде отображены также поля, которые участвует в создании различных структур для оптимизации рендеринга. Они не будут рассмотрены в рамках статьи.Чтобы просмотреть содержимое fiber-ноды, достаточно воспользоваться console.log и вставить туда JSX код, который возвращает компонент:
function MyComponent() {
const jsxContent = (<div/>);
console.log(jsxContent);
return jsxContent;
}
Корневую fiber-ноду можно просмотреть следующим образом:
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
console.log(rootElement._reactRootContainer._internalRoot);
Также есть интересная наработка: react-fiber-traverseПод спойлером приведен код компонента с хуками и отображение его fiber-ноды
import { useState, useContext, useEffect,useMemo, useCallback,
useRef, createContext } from 'react';
import ReactDOM from 'react-dom';
const ContextExample = createContext('');
function ChildComponent() {
useState('childComponentValue');
return <div />;
}
function useMyHook() {
return useState('valueB');
}
function ParentComponent() {
const [valueA, setValueA] = useState('valueA');
useEffect(function myEffect() {}, [valueA]);
useMemo(() => 'memoized ' + valueA, [valueA]);
useCallback(function myCallback() {}, [valueA]);
useRef('refValue');
useContext(ContextExample);
useMyHook();
const jsxContent = (
<div>
<ChildComponent />
<button onClick={() => setValueA('valueA new')}>Update valueA</button>
</div>
);
console.log('component under the hood: ', jsxContent);
return jsxContent;
}
const rootElement = document.getElementById('root');
ReactDOM.render(
<ContextExample.Provider value={'contextValue'}>
<ParentComponent />
</ContextExample.Provider>,
rootElement,
);
С более подробным описанием работы внутренних механизмов React на русском языке можно ознакомиться по ссылкам:Как Fiber в React использует связанный список для обхода дерева компонентов
Fiber изнутри: подробный обзор нового алгоритма согласования в React
Как происходит обновление свойств и состояния в React — подробное объяснение
За кулисами системы React hooks
Видео: Под капотом React hooksУ подхода с хуками на данный момент есть недостаток - фиксированное дерево функций в компонентах. При стандартном использовании хуков, нельзя изменить логику уже написанного компонента или хуков, состоящих из других хуков. К тому же это мешает тестированию хуков по отдельности. В какой-то степени можно улучшить ситуацию композицией (compose) хуков. Например, существует такое решение.Линейность кода и составляющих сложного объектаИзвестно, что множество вложенные условий, callback-ов затрудняют читаемость кода: https://refactoring.guru/ru/replace-nested-conditional-with-guard-clauses
https://habr.com/ru/company/oleg-bunin/blog/433326/ (в статье упоминается линейный код)
https://www.azoft.ru/blog/clean-code/ (в статье упоминается линейность кода)Я думаю, что наследование, большие цепочки и иерархии вложенных объектов могут привести к аналогичной ситуации, но для составляющих сложного объекта. Даже если объект расположен линейно в коде, внутри он может быть устроен так, что необходимо пройтись по множеству родительских классов или вложенных объектов, чтобы разобраться в его функционале. Я уже писал ранее про деревья поведения, что в них следует избегать большой вложенности. Так и в других случаях.
===========
Источник:
habr.com
===========
Похожие новости:
- [CSS, ReactJS] Mr. Stylin
- [.NET, C#, ООП, Промышленное программирование] Lazy Properties Are Good. That Is How You Are to Use Them
- [JavaScript, VueJS] Nuxt.js app от UI-кита до деплоя. Часть 2: Темная тема
- [Алгоритмы, ООП] Вычисление динамических объектов по вектору
- [Разработка веб-сайтов, CSS, HTML, Графический дизайн] Три способа создания клякс с помощью CSS и SVG (перевод)
- [JavaScript, Программирование, HTML, TensorFlow] Отслеживание лиц в реальном времени в браузере с использованием TensorFlow.js. Часть 6 (перевод)
- [Open source, Разработка под Android, Kotlin] Reaction — обработка результатов методов в Kotlin
- [Разработка веб-сайтов, PHP, Laravel] Laravel–Дайджест (22 февраля – 7 марта 2021)
- [Разработка веб-сайтов, Программирование, ООП] Можно ли пингвина наследовать от птицы?
- [Анализ и проектирование систем, Управление продуктом, Дизайн, Инженерные системы] О заказчиках и приказчиках
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_oop (ООП), #_reactjs, #_proektirovanie (проектирование), #_oop (ооп), #_kompozitsija (композиция), #_patterny (паттерны), #_react, #_javascript, #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
), #_oop (
ООП
), #_reactjs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:18
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В этой статье я опишу различные техники повторного использования кода и разбиения сложных объектов на части, с которыми я столкнулся. Постараюсь объяснить, почему классическое наследование, а также некоторые другие популярные подходы не работают в сложных случаях, и какие есть альтернативы.Возможно многих удивит, что в основе большинства подходов повторного использования кода и создания составных объектов лежат стандартные структуры данных – массив, список, словарь, дерево, граф.Т.к. в последние годы я пишу на JavaScript и React, то они будут использоваться в некоторых примерах. Да и в целом, периодически я буду упоминать React и другие веб-технологии. Тем не менее, думаю, что значительная часть статьи должна быть понятна и полезна разработчикам из других стеков. Для некоторых подходов я добавил схемы, чтобы показать, как организованы составляющие сложных объектов. Будет часто упоминаться агрегация(агрегирование/делегирование/включение) и композиция. Описывается то, как я это понял, поэтому где-то информация может быть ошибочной.Чтобы разделить логику одного сложного объекта на составные части, существуют несколько механизмов:
— Смешивание и примеси (миксины) — Классическое наследование — Множественное наследование и интерфейсы2) Композиция/агрегация с использованием списка — Прототипное наследование — Паттерн декоратор и аналоги3) Композиция/агрегация с использованием одноуровневых структур данных (ссылка, массив ссылок, словарь) — Паттерн стратегия — Entity Component (EC) 4) Композиция/агрегация с вынесением логики вне объекта и его составляющих — Entity Component System (ECS)5) Композиция/агрегация с использованием графов — Паттерн State machine6) Композиция/агрегация с использованием деревьев — Паттерн composite и другие древовидные структуры — Behaviour tree7) Смешанные подходы — React hooksОбъединение (смешивание) функционала нескольких объектов в одном.Смешивание и примеси (миксины)Самый простой, но ненадежный способ повторного использования кода – объединить один объект с другим(и). Подходит лишь для простых случаев, т.к. высока вероятность ошибки из-за замещения одних полей другими с такими же именами. К тому же, так объект разрастается и может превратиться в антипаттерн God Object.Существует паттерн примесь (mixin/миксина), в основе которого лежит смешивание. Примесь – это объект, поля и методы которого смешиваются с полями и методами других объектов, расширяя функциональность объекта, но который не используется сам по себе. Можно добавить несколько миксин к одному объекту/классу. Тогда это схоже с множественным наследованием.Классическое наследованиеЗдесь описывается классическое наследование, а не то, как наследование классов устроено в JS.Подразумеваю, что читатель уже знаком с понятиями "наследование" и "множественное наследование". В отличие от простого смешивания, в классическом наследовании есть ряд строгих правил и механизмов, которые позволяет избежать множество проблем. В основе классического наследования лежит все то же смешивание - члены нескольких объектов объединяются в один объект. При наследовании происходит копирование членов родительского класса в класс-наследник. При создании экземпляра класса тоже происходит копирования членов класса. Я не исследовал детали этих механизмов, к тому же они явно отличаются в различных языках. Подробнее с этой темой можно ознакомиться в 4-ой главе книги "Вы не знаете JS: this и Прототипы Объектов".Когда можно использовать наследование, а когда не стоит? Наследования не стоит использовать в качестве основной техники для повторного использования кода для сложных объектов. Его можно использовать совместно с композицией для наследования отдельных частей сложного объекта, но не для самого сложного объекта. Например, для React компонентов наследование плохо, а для частей (вроде объектных аналогов custom hooks) из которых мог быть состоять компонент-класс, наследования вполне можно использовать. Но даже так, в первую очередь стоит рассматривать разбиение на большее число составляющих или применения других техник, вместо наследования.При возможности появления сложной иерархии наследование (более 2-х уровней, где первый уровень иерархии – родитель, а второй уровень - наследники) тоже не следует использовать наследование. Множественное наследование и интерфейсыПри использовании множественного наследования получаются довольно запутанные иерархии классов. Поэтому во многих языках от отказались от множественного наследования реализации. Но множественное наследование по-прежнему применяют при наследовании абстракций в виде интерфейсов.Интерфейсы есть, например, в Typescript. Реализация нескольких интерфейсов в одном классе отчасти похоже на наследование, но с их использованием «наследуется» только описание свойств и сигнатура методов интерфейса. Наследование реализации не происходит. Интерфейсы следует понимать не как наследование, а как контракт, указывающий, что данный класс реализует такой-то интерфейс. Плохо, когда один класс реализует слишком много интерфейсов. Это означает, что-либо интерфейсы слишком сильно разбиты на части, либо у объекта слишком большая ответственность. Композиция/агрегация с использованием спискаПрототипное наследованиеПри прототипном наследовании уже не происходит смешивания родительского объекта и его наследника. Вместо этого наследник ссылается на родительский объект (прототип).При отсутствии свойства (поле, метод и т.д.) в объекте, происходит поиск этого свойства в цепочке прототипов. То есть часть функционала делегируется вложенному объекту, который тоже может делегировать функционал вложенному объекту внутри себя. И так далее по цепочке. Прототип на любом уровне цепочки может быть только один.Стоит отметить, что в JavaScript операции записи/удаления работают непосредственно с объектом. Они не используют прототип (если это обычное свойство, а не сеттер). Если в объекте нет свойства для записи, то создается новое. Подробнее об этом.Цепочка прототипов организована как стек (Last-In-First-Out или LIFO). Какой объект добавлен в цепочку последним (если считать от итогового объекта-контейнера), к тому обращение будет первым.Также существует вариант, когда при создании нового объекта с прототипом, создается копия прототипа. В таком случае используется больше памяти (хотя это проблема разрешаема), но зато это позволяет избежать ошибок в других объектах из-за изменения прототипа конкретного объекта. Паттерн Декоратор и аналогиДекоратор(wrapper/обертка) позволяет динамически добавлять объекту новую функциональность, помещая его в объект-обертку. Обычно объект оборачивается одним декоратором, но иногда используется несколько декораторов и получается своего рода цепочка декораторов.Цепочка декораторов устроена как стек (LIFO). Какой объект добавлен в цепочку последним (если считать от итогового объекта-контейнера), к тому обращение будет первым.Цепочка декораторов похожа на цепочку прототипов, но с другими правилами работы с цепочкой. Оборачиваемый объект и декоратор должны иметь общий интерфейс. На схеме ниже пример использования нескольких декораторов на одном объекте: Как в случае с прототипами, зачастую можно подменять декораторы во время выполнения. Декоратор оборачивает только один объект. Если оборачивается несколько объектов, то это уже что-то другое.HOF (higher order function) и HOC (Higher-Order Component) - паттерны с похожей идей. Они оборачивают функцию/компонент другой функцией/компонентом для расширения функционала.HOF - функция, принимающая в качестве аргументов другие функции или возвращающая другую функцию в качестве результата. Примером HOF в JS является функция bind, которая, не меняя переданную функцию, возвращает новую функцию с привязанным к ней с помощью замыкания значением. Другим примером HOF является карринг.HOC - чистая функция, которая возвращает другой компонент (а он уже содержит в себе произвольную логику), который внутри себя "рендерит" переданный компонент. При этом сам переданный компонент не меняется, но в него могут быть переданы props.Также стоит упомянуть композицию функций. Это тоже своего рода обертка. С помощью этой техники создаются цепочки вложенных функций: const funcA = сompose(funcB, funcC, funcD);
const funcA = ()=> {
funcB( funcC( funcD() ) ) ; }; function funcA() {
function funcB() { function funcC() { function funcD() } } } К похожим способам (использование ссылки) расширения функционала объекта и повторного использования кода можно отнести события в HTML элементах и директивы в Angular и Vue. <button onclick="customAction()" /> // html
<input v-focus v-my-directive="someValue" /> // vue Сначала пройдемся по определением:
https://ru.reactjs.org/docs/hooks-intro.html#its-hard-to-reuse-stateful-logic-between-components https://ru.reactjs.org/docs/hooks-intro.html#complex-components-become-hard-to-understandЭтот подход используется с самого начала выхода движка Unity3D для расширения функционала элементов (объектов) дерева сцены, включая UI элементы, где вы можете получше ознакомится с данным подходом. Но в таком случае придётся потратить не мало времени на изучение движка.ИтогоПаттерн стратегия сам по себе не очень мощный, но если развить его идею, можно получить довольно эффективные техники. К такой можно отнести Entity Component.В случае использования EC может появиться новая проблема – при большом количестве компонентов, связанных между собой в одном объекте, становиться сложно разобраться в его работе. Выходом может стать некий компонент, который контролирует взаимодействия между компонентами в одной Entity или в группе вложенных Entities. Такой подход известен как паттерн Посредник (Mediator). Но даже “посредника“ будет недостаточно для более сложных случаев. К тому же он не является универсальным. Для каждой Entity с множеством связанных компонентов придёться реализовывать новый тип “посредника”. Есть и другой выход. EC можно комбинировать с другими подходами на основе графов и деревьев, которые будут описаны позже.Композиция/агрегация с вынесением логики вне объекта и его составляющихEntity Component System (ECS)Я не работал с этим подходом, но опишу то, как я его понял.В ECS объект разбивается на 3 типа составляющих: сущность, компонент (один или несколько), система (общая для произвольного числа объектов). Этот подход похож на EC, но объект разбивается уже на 3 типа составляющих, а компонент содержит только данные.Определения:
http://jmonkeyengine.ru/wiki/jme3/contributions/entitysystem/introduction-2 http://entity-systems.wikidot.com/fast-entity-component-system#java https://www.chris-granger.com/2012/12/11/anatomy-of-a-knockout/На схеме изображен первый вариант, когда entity ссылается на свои компоненты. Плюсы ECS
https://www.youtube.com/watch?v=nu8JJEJtsVEИтогоECS является хорошей альтернативой созданию сложных иерархий наследования. В ECS можно создать иерархии наследования для компонентов или систем, но вряд ли от этого почувствуется выгода. Скорее, быстро почувствуются проблемы от таких попыток. Как и в аналогичном случае с EС, системы в данном подходе можно комбинировать с подходами на основе графов и деревьев. В таком случае логика по-прежнему будет реализована в системах, а хранение данных в компонентах. Но, я не знаю, эффективно ли совмещение этих подходов на практике. В первую очередь нужно стремиться реализовывать системы попроще, а уже потом рассматривать комбинацию с другими подходами.Композиция/агрегация с использованием графовК данному способу повторного использования кода я отнес паттерн «машина состояний» (State machine/Finite state machine/конечный автомат). Аналогом машины состояний простой является switch: switсh (condition) {
case stateA: actionA(); case stateB: actionB(); case stateC: actionC(); } https://24ways.org/2018/state-machines-in-user-interfaces/ https://xstate.js.org/docs/ (библиотека для JS, которую можно использовать c React, Vue, Svelte) https://github.com/MicheleBertoli/react-automata (библиотека для React) https://habr.com/ru/company/ruvds/blog/346908/Подходит ли State machine в качестве основного механизма повторного использования кода и разбиения сложных объектов на составные части?Иногда он так и используется. Но, он мне кажется сложноватым и не всегда подходящим для использования в качестве основного. Зато он точно хорош в качестве дополнительного, когда нужно организовать взаимодействия между несколькими объектами или частями составного объекта.Стоит учитывать, что граф может получиться слишком сложным. Если у вас обычный код получается запутанным, то и граф скорее всего получится запутанным. В сложном графе нужно стремиться уменьшать количество связей, группировать состояния.Композиция/агрегация с использованием деревьевПаттерн composite и другие древовидные структурыДеревья часто встречается в разработке. Например, объекты в JavaScript могут содержать вложенные объекты, а те также могут содержать другие вложенные объекты, тем самым образую дерево. XML, JSON, HTML, DOM-дерево, паттерн Комповщик (Composite) – все это примеры древовидной композиции.Дерево является графом, в котором между любыми 2-мя его узлами может быть только один путь. Благодаря этому, в нем гораздо проще разобраться, чем в графе получаемом с помощью машине состояний.Behaviour treeИнтересным вариантом композиции является Behaviour tree(дерево поведения). Это организация логики программы (обычно AI) или ее частей в виде дерева. В дереве поведения в качестве узлов выступают управляющие блоки - условие, цикл, последовательность действий, параллельное выполнение действий и блоки действий. В теории, могут быть реализованы и другие управляющие блоки, вроде switch case, асинхронного блока и других аналогов управляющих конструкций языка, но я не встречал подобного. Код действий для деревьев поведения пишет разработчик. Обычно решения для деревьев поведений содержат визуальный редактор для их создания и отладки.Я уже описывал деревья поведений в прошлом в этой статье.Более наглядный пример схемы готового дерева из плагина banana-tree Дерево поведения можно рассматривать как аналог обычной функции с вложенными функциями. Я думаю, понятно, что схему выше можно перевести в функцию с вложенными функциями, условиями и циклами.Если в функции написать слишком много кода или же в ней будет слишком много вложенных условий, то она будет нечитабельна и с ней будет тяжело работать. Аналогично и с деревьями поведения. Их следует делать как можно проще и с меньшей вложенностью блоков с циклами и условиями.Деревья поведения позволяют создавать сложную логику с помощью комбинации более простых действий. Сложная логика тоже может быть оформлена в виде узла дерева (в виде действия или поддерева). Также деревья поведения предоставляют единый механизм для разработки в таком стиле. Этот подход мотивирует выносить функционал в отдельные настраиваемые объекты, зачастую предоставляет наглядное средство для отладки, упорядочивает и уменьшает связи между объектами, позволяет избежать жесткой зависимости составляющих.Для простых случаев, как обычно, этот подход будет ненужным усложнением.Смешанные подходыДля более эффективной организации кода можно комбинировать некоторые подходы. Например, в качестве узлов машины состояний можно использовать деревья поведения.Довольно многое можно отнести к смешанных подходам. Entity Component в Unity3D реализован так, что позволяет хранить не только компоненты, но и вложенные сущности. А для пользовательских компонентов можно использовать наследование в простых случаях, либо объединить компоненты с более продвинутыми техниками (паттерн mediator, машина состояний, дерево поведения и другие).Примером смешивания подходов является анимационная система Mecanim в Unity3D, которая использует иерархическую машину состояний с деревьями смешивания (blend tree) для анимаций. Это относится не совсем к коду, но является хорошим примером комбинации подходов.К смешанным подходам я отнес React компоненты с хуками, т.к. там довольно специфичный случай. С точки зрения разработчика, для повторного использования кода в компоненте используется дерево функций (хуков). С точки зрения организации данных в компоненте – компонент хранит список с данными для хуков. К тому же связь между хуками и компонентом устанавливается во время выполнения. Т.к. я разрабатываю на React, то решил включить частичное описание внутреннего устройство компонента с хуками в статью. React hooksЭта часть статьи для разработчиков, знакомых c React. Остальным многое в ней будет не понятно. Особенность функциональных компонентов в React-е в том, что разработчик не указывает сам связь между компонентом и его хуками. Отсюда возникает вопрос, как React определяет к какому компоненту применить такой-то хук?Как я понял, хуки при вызове добавляют к текущему обрабатываемому компоненту (точнее к fiber-ноде) свое состояние – объект, в котором могут быть указаны переданные сallback-и (в случае useEffect, useCallback), массив зависимостей, значения (в случае useState) и прочие данные (в случае useMemo, useRef, …).А вызываются хуки при обходе дерева компонентов, т.е. когда вызывается функция-компонент. React-у известно, какой компонент он обходит в данный момент, поэтому при вызове функции-хука в компоненте, состояние хука добавляется (или обновляется при повторных вызовах) в очередь состояний хуков fiber-ноды. Fiber-нода – это внутреннее представление компонента. Стоит отметить, что дерево fiber элементов не совсем соответствует структуре дерева компонентов. У Fiber-ноды только одна дочерняя нода, на которую указывает ссылка child. Вместо ссылки на вторую ноду, первая нода ссылается на вторую (соседнюю) с помощью ссылки sibling. К тому же, все дочерние ноды ссылаются на родительскую ноду с помощью ссылки return.Также для оптимизации вызова эффектов (обновление DOM, другие сайд-эффекты) в fiber-нодах используются 2 ссылки (firstEffect, nextEffect), указывающие на первую fiber-ноду с эффектом и следующую ноду, у которой есть эффект. Таким образом, получается список нод с эффектами. Ноды без эффектов в нем отсутствуют. Подробнее об этом можно почитать по ссылкам в конце главы.Вернемся к хукам. Структура сложного функционального компонента с несколькими вложенными custom hooks для разработчика выглядит как дерево функций. Но React хранит в памяти хуки компонента не как дерево, а как очередь. На схеме ниже изображен компонент с вложенными хукам, а под ним fiber-нода с очередью состояний этих же хуков. На схеме в fiber-ноде отображены также поля, которые участвует в создании различных структур для оптимизации рендеринга. Они не будут рассмотрены в рамках статьи.Чтобы просмотреть содержимое fiber-ноды, достаточно воспользоваться console.log и вставить туда JSX код, который возвращает компонент: function MyComponent() {
const jsxContent = (<div/>); console.log(jsxContent); return jsxContent; } const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement); console.log(rootElement._reactRootContainer._internalRoot); import { useState, useContext, useEffect,useMemo, useCallback,
useRef, createContext } from 'react'; import ReactDOM from 'react-dom'; const ContextExample = createContext(''); function ChildComponent() { useState('childComponentValue'); return <div />; } function useMyHook() { return useState('valueB'); } function ParentComponent() { const [valueA, setValueA] = useState('valueA'); useEffect(function myEffect() {}, [valueA]); useMemo(() => 'memoized ' + valueA, [valueA]); useCallback(function myCallback() {}, [valueA]); useRef('refValue'); useContext(ContextExample); useMyHook(); const jsxContent = ( <div> <ChildComponent /> <button onClick={() => setValueA('valueA new')}>Update valueA</button> </div> ); console.log('component under the hood: ', jsxContent); return jsxContent; } const rootElement = document.getElementById('root'); ReactDOM.render( <ContextExample.Provider value={'contextValue'}> <ParentComponent /> </ContextExample.Provider>, rootElement, ); С более подробным описанием работы внутренних механизмов React на русском языке можно ознакомиться по ссылкам:Как Fiber в React использует связанный список для обхода дерева компонентов Fiber изнутри: подробный обзор нового алгоритма согласования в React Как происходит обновление свойств и состояния в React — подробное объяснение За кулисами системы React hooks Видео: Под капотом React hooksУ подхода с хуками на данный момент есть недостаток - фиксированное дерево функций в компонентах. При стандартном использовании хуков, нельзя изменить логику уже написанного компонента или хуков, состоящих из других хуков. К тому же это мешает тестированию хуков по отдельности. В какой-то степени можно улучшить ситуацию композицией (compose) хуков. Например, существует такое решение.Линейность кода и составляющих сложного объектаИзвестно, что множество вложенные условий, callback-ов затрудняют читаемость кода: https://refactoring.guru/ru/replace-nested-conditional-with-guard-clauses https://habr.com/ru/company/oleg-bunin/blog/433326/ (в статье упоминается линейный код) https://www.azoft.ru/blog/clean-code/ (в статье упоминается линейность кода)Я думаю, что наследование, большие цепочки и иерархии вложенных объектов могут привести к аналогичной ситуации, но для составляющих сложного объекта. Даже если объект расположен линейно в коде, внутри он может быть устроен так, что необходимо пройтись по множеству родительских классов или вложенных объектов, чтобы разобраться в его функционале. Я уже писал ранее про деревья поведения, что в них следует избегать большой вложенности. Так и в других случаях. =========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ), #_javascript, #_proektirovanie_i_refaktoring ( Проектирование и рефакторинг ), #_oop ( ООП ), #_reactjs |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:18
Часовой пояс: UTC + 5