GameDev: разработка на Unity3D
443 subscribers
6 photos
2 videos
7 files
40 links
Все для успешного развития разработчика в геймдев: от junior до CTO. SOLID на практике, IOC, DI. Новые паттерны для gamedev. Личный опыт.

Заявка на разбор тестовых https://forms.gle/kqVPv1jWT97Bkps9A
Download Telegram
#solid #solid_геймдев
Для чего нужен SOLID

Всем привет, сегодня говорим про SOLID (далее по тексту - классический SOLID).
Публикацию разобьем на 2 части: в первой упростим и выделим суть каждого принципа применительно к геймдеву, во второй рассмотрим проблемы.

SOLID - это набор из 5 принципов ООП, которые сформулировал Роберт Мартин. Аббревиатура была предложена Майклом Фэзерсом.
Определение звучит так: "SOLID - это руководства, которые также могут применяться во время работы над существующим программным обеспечением для его улучшения, например, для удаления «дурно пахнущего кода»".

Про SOLID спрашивают, его обсуждают с докладами на конференциях, его относят к правилам хорошего тона. Те, кто поопытнее пытаются применять SOLID на практике.

Но действительно ли разработчики понимают, как правильно применять SOLID принципы в геймдеве?

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

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

Поехали 🚀🚀🚀
S - Single responsibility principle (SRP)
Суть принципа: написанный вами код (реализация) внутри каждой категории (про категории говорили тут) должен максимально соответствовать этой категории.

На примере связи Игрок - Инвентарь. Если в каком-то классе, относящемся к категории игрока, мы решили что-то сделать с ячейками инвентаря, поменять их местами (написать логику свопа в классе, который относится к категории игрока), такой подход будет нарушать SRP.
Более правильной реализацией будет работа с инвентарем в классах, относящихся к категории инвентаря.

O - Open-closed principle (OCP)
На практике этот принцип соблюсти достаточно тяжело.Так как в геймдеве часто меняются бизнес-требования.
Суть принципа: класс открыт для добавлений, но закрыт для изменений.
Моя рекомендация - пишите код так, чтобы его было легко менять и удобно расширять.

L - Liskov substitution principle (LSP)
Суть принципа: если у вас в коде есть класс A и класс B, который является наследником A, то при замене всех классов A на B в работе кода ничего не должно поменяться.
Моя рекомендация - не злоупотребляете наследованием. Иногда композиция или имплементация интерфейса сработает гораздо эффективнее.

I - Interface segregation principle (ISP)
Суть принципа: если и используете интерфейсы, не тащите в них то, что не нужно.
Пример подхода, нарушающего ISP: если какой-то интерфейс в реализации должен работать с игровым объектом, при этом описание игрового объекта есть в коллекции контента. Нарушением принципа будет передача в интерфейс самой коллекции контента и id того объекта, с которым хотим работать.
Чтобы обойти нарушение ISP, необходимо передавать сам интерфейс объекта, который мы предварительно получаем по id из коллекции контента.
Принцип бритвы Оккама тут может быть хорошей аналогией.

D - Dependency inversion principle (DIP)
Суть принципа: когда вы пишете какую-либо логику в классе и у вас есть зависимости, пишите логику не опираясь на конкретные реализации этих зависимостей. Работайте с зависимостями через абстракции.
Моя рекомендация - учитывать, что абстракции - это не только интерфейсы и абстрактные классы.

Продолжение следует…
#solid #solid_геймдев

Привет, друзья.
Вернемся к SOLID в разрезе геймдева.
Сегодня рассмотрим проблемы применения принципов в чистом виде и сформируем набор рекомендаций.

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

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

В геймдев проектах можно относительно безболезненно реализовать SRP и LSP, сложности возникнут с OCP, ISP и DIP.

Рассмотрим проблемы, характерные для боевых проектов:
- SRP и LSP желателен постоянный код-ревью, при этом условии возможно безболезненное применение принципов
- OCP достаточно часто нарушается из-за переменчивости бизнес-требований, исходящих от гейм-дизайнеров, меняющих ГДД (гейм-дизайн документ). Это влечет за собой выпиливание части кода и будет сопряжено с трудностями, т.к. классы в коде уже плотно зацепились с классами из других категорий
- ISP и DIP часто интерпретируют неверно, связывая их с интерфейсами. Например: DIP - путают с Dependency Injection
- Дополнительную нагрузку при планировании и проектировании реализации DIP добавляют Dependency Injection фреймворки, наподобии Zenject, Ninject или собственные реализации. Обычно к собственной реализации приходят, если готовый фреймворк оказался сложен или избыточен.

В совокупности применение принципов без адаптации к геймдеву приводит к тому, что приложение обрастает интерфейсами и их реализациями. Задумайтесь, чем больше проект, тем неподъемнее конструкция. Каждая реализация обязана содержать public методы, что раздувает объем кодовой базы и снижает инкапсуляцию с механизмом сокрытия (тут я имею в виду private методы).

Опыт показал, что для геймдева нужен немного другой набор принципов.
Сформулирую набор принципов и рекомендаций, которые я выделяю, исходя из своего опыта:
- В приложении должна быть единая точка входа в код
- Приложение должно быть разбито на слои абстракции
- Должна быть возможность пройти по всему дереву приложения из IDE
- Жизненный цикл всех объектов в приложении должен быть строго детерминирован
- Максимально, где это возможно, должна использоваться инкапсуляции с механизмом сокрытия. А именно: объекты слоя бизнес-логики должны иметь минимальное количество публичных методов, в идеале - не иметь вообще
- Использовать динамический полиморфизм. Причем, который не базируется на интерфейсах или абстрактных классах
- Минимизировать связи между объектами слоев абстракций (аналог ISP)
- Возможность легко менять и расширять код
- Использовать принцип подстановки Барбары Лисков (LSP)
- Код для работы с различными зависимостями должен быть написан декларативно (аналог DIP) и отвечать на вопрос: “что делать”. Подход обеспечит сильную связанность и слабое зацепление в сочетании с динамическим полиморфизмом.
- Простая сериализация/десериализация объектов.

Продолжение следует…
#архитектура #solid #новые_подходы #simple

Приветствую
👋 
У меня для вас большой блок информации для осмысления и анализа.

Все, кто интересуется архитектурой приложений, знают и стараются опираться на принципы SOLID и паттерны проектирования.

Впервые принципы SOLID были представлены в 2000 году в статье Design Principles and Design Patterns Роберта Мартина, также известного как Дядюшка Боб.
https://web.archive.org/web/20150906155800/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
С тех пор прошло: 24 года.

Паттерны проектирования были адаптированы в 1987 году под язык SmallTalk Кентом Бэком и Вардом Каннингемом.
https://c2.com/doc/oopsla87.html
С тех пор прошло: 37 лет.

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

При разработке серьезных проектов тех руководитель должен решать задачу многокритериальной оптимизации:
- минимизация сроков разработки
- сдерживание роста кривой сложности доработок
- сохранение максимального уровня чистоты кода (понятность, читабельность, разделение на слои, и т.д.)
- контролируемость технического долга
- рост компетентности сотрудников
- bus фактор в отделе разработки > 1.

Предлагаемый подход позволяет решить такую задачу.

Напомню ключевые моменты Composition Tree. ⬇️
Каждое приложение начинает свою работу с определенной точки, называемой точкой входа (Entry Point).
Из данной точки происходит дальнейшее разветвление и исполнение кода.

Структура этого разветвления похожа на дерево и называется Composition Tree.
Места, где происходит разветвление, называются вершинами/узлами.
Самая первая вершина - это Entry Point.

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

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

Продолжение 👇
#архитектура #solid #новые_подходы #simple

Итак рассмотрим подход на базе Composition Tree и концепции реактивного программирования.

Подход включает в себя 6 основных принципов, название каждого из которых образует аббревиатуру SIMPLE.

Рассмотрим каждый из принципов:

S - Принцип разделения на основе потоков данных (Stream-based Segregation Principle):
Потоки данных описываются как последовательности событий или значений, которые меняются со временем.
В реактивном программировании потоки данных играют ключевую роль.
Принцип разделения на основе потоков данных подразумевает, что классы в приложении должны быть организованы вокруг потоков данных, которые они обрабатывают, обеспечивая тем самым отзывчивость и простоту управления состоянием.
Основой могут быть реактивные объекты: ReactiveProperty, ReactiveCommand.

I - Независимость от интерфейсов в бизнес-логике (Interface Free In Business Logic Principle):
Бизнес-логика должна быть свободна от традиционных интерфейсов, предпочитая вместо этого изменения и подписки на потоки данных для взаимодействия классов.
Это облегчает динамичное взаимодействие между классами и улучшает отзывчивость приложения.

‼️При этом слой сервисов и внешних библиотек допускает использование интерфейсов.

M - Модульное разделение (Modular Decoupled Principle):
В приложении должно быть четкое разделению ответственности между различными модулями, аналогично принципу единственной ответственности из SOLID.
Это подразумевает независимость бизнес-логики, пользовательского интерфейса, сетевых запросов и других частей приложения.

P - Принцип проактивной предсказуемости (Proactive Predictability Principle):
Системы, построенные с использованием реактивного подхода, должны обеспечивать легкость предсказания поведения и взаимодействий потоков данных.
Это требует четкой структуризации и отслеживания зависимостей, что упрощает тестирование и отладку.
Все потоки данных должны иметь чёткое назначение и иметь только необходимую ответственность.

L - Независимость слоёв (Layered-Independent Principle):
Используется слоистый подход к архитектуре, в котором каждый слой (UI, бизнес-логика, данные и др.) работает независимо, что повышает модульность и упрощает тестирование.
Слой в архитектуре приложений - это логическое разделение классов приложения, которое обеспечивает разграничение функциональности.
Каждый слой отвечает за определенную область задач и обладает своей ответственностью в контексте многоуровневой архитектуры.
Основных слоёв - 7:
- Entity Layer (вершина дерева Composition Tree)
- Presentation Model Layer (слой бизнес-логики)
- View Layer (слой представления)
- Service Layer (слой сервисов)
- Communication Layer (слой связей)
- Content/Data Layer (слой контента/данных)
- State Layer (слой состояний).
Вспомогательными слоями могут выступать сторонние библиотеки.

E - Закрытая инкапсуляция (Enclosed Encapsulation Principle):
Методы и переменные классов бизнес-логики приложения должны быть максимально закрыты (с модификатором доступа "private").
Это снижает зависимость между различными частями кода и упрощает поддержку и изменение приложения.

Благодарю за внимание!
Жду ваши вопросы в комментариях!
Хороших выходных
🖐️