11

Я выстрелил себе в ногу

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

TL;DR Я обосрался с циклическими зависимостями

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

Так как обучение появилось в игре не сразу, есть ряд игроков, которые уже прокачались, и им не нужно показывать всё обучение или его часть. Для этого предусмотрена возможность пропустить квест по какому-то условию. Естественно, эти условия вычисляются при инициализации QuestService.

Добавляя последний квест, я сделал условие, которое зависело от сервиса Foo, при этом сам этот сервис Foo зависел от QuestService. В итоге, при вычислении условия, код получал не проинициализированную до конца зависимость, и падал с NRE (NPE, прим. для моих коллег джавистов). Обиднее всего то, что баг был плавающий, и не воспроизвелся, когда я тестировал, поэтому он попал на все площадки, где есть наша игра, и нам срочно пришлось выпускать хотфикс.

Чтобы не допустить такого в будущем, я буду потихоньку рефакторить весь код, и переходить от field injection к constructor injection. Ибо

Constructor injection guarantees no circular dependencies between classes, which is generally a bad thing to do. Zenject does allow circular dependencies when using other injections types however such as method/field/property injection

Изначально я поленился использовать constructor injection, потому что приходилось писать больше кода, и потому что я делал сервисами монобехи (а для монобехов не поддерживается constructor injection), чтобы хранить в них ссылки на объекты сцены или какие-то serialized параметры. Видимо, очень зря. Надо будет поискать какой-нибудь lombok для C# или плагинчик для Rider, который будет генерировать конструкторы, и делать монобехи со ссылками и параметрами отдельными зависимостями для сервисов, чтобы сервисы были обычным объектами.

Правила сообщества

ОБЩИЕ ПРАВИЛА:

- Уважайте чужой труд и используйте конструктивную критику

- Не занимайтесь саморекламой, пишите качественные и интересные посты

- Никакой политики


СТОИТ ПУБЛИКОВАТЬ:

- Посты о Вашей игре с историей её разработки и описанием полученного опыта

- Обучающие материалы, туториалы

- Интервью с опытными разработчиками

- Анонсы бесплатных мероприятий для разработчиков и истории их посещения;
- Ваши работы, если Вы художник/композитор и хотите поделиться ими на безвозмездной основе

НЕ СТОИТ ПУБЛИКОВАТЬ:

- Посты, содержащие только вопрос или просьбу помочь
- Посты, содержащие только идею игры

- Посты, единственная цель которых - набор команды для разработки игры

- Посты, не относящиеся к тематике сообщества

Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.

ЗАПРЕЩЕНО:

- Публиковать бессодержательные посты с рекламой Вашего проекта (см. следующий пункт), а также все прочие посты, содержащие рекламу/рекламные интеграции

- Выдавать чужой труд за свой

Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.


О РАЗМЕЩЕНИИ ССЫЛОК:

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

- Пост должен быть содержательным и интересным для пользователей, нести пользу для сообщества

- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз

- Cсылка размещается в формате: "Страница игры в Steam: URL"

Вы смотрите срез комментариев. Показать все
0
Автор поста оценил этот комментарий

в игре есть класс QuestService, который отвечает за все задания, и от него зависят все остальные сервисы

Когда я читал, то вот на этом моменте я аж почувствовал дискомфорт внутри.


Это ж повышение сопряжённости (связанности) кода. Это QuestService должен задействовать другие сервисы, сообщая им, что нужно включить ту или иную возможность, но никак не наоборот. Не должны другие сервисы зависеть от QuestService.


Нужен рефакторинг.

раскрыть ветку (9)
0
Автор поста оценил этот комментарий

Не понял идеи. Вы предлагаете, чтобы QuestService знал обо всех остальных сервисах?

раскрыть ветку (8)
0
Автор поста оценил этот комментарий

Вы предлагаете, чтобы QuestService знал обо всех остальных сервисах?

Да. QuestService будет знать о тех сервисах, которые ему нужны.


А способности и, например, дополнительные сюжетные линии будут иметь статус (active/blocked или enabled/disabled).


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


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

раскрыть ветку (7)
1
Автор поста оценил этот комментарий

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

раскрыть ветку (2)
1
Автор поста оценил этот комментарий

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

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

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

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

0
Автор поста оценил этот комментарий

В принципе, тоже вариант. То есть, сервис заданий (QuestService) будет эту задачу делегировать другой сущности.

0
Автор поста оценил этот комментарий

Ты бы хоть постеснялся такую хуйню предлагать)

С моей точки зрения, заставлять другие сервисы опрашивать QuestService на предмет завершения нужного задания - это очень плохое архитектурное решение

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

раскрыть ветку (2)
0
Автор поста оценил этот комментарий

Ты бы хоть постеснялся такую хуйню предлагать)

Это тебе бы стоило постесняться такое писать.


сервис квестов должен кидать событие

Во-первых, не факт, что там вообще механизм событий в той системе.


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


Во-вторых, сервис - это про операции.
И если операция обязательно должна что-то выполнить (например, разблокировать способность или доступ к подъемелью), то это надо явно делать именно в реализации операции.
Это может делать сам сервис (QuestService) или делегировать другому сервису (например, QuestRewardService) в явном виде обратившись к нему.
Но вот отдавать выполнение обязательного действия обработчику событий - это сомнительный подход.

раскрыть ветку (1)
0
Автор поста оценил этот комментарий

Во-первых, не факт, что там вообще механизм событий в той системе

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

Это может делать сам сервис (QuestService) или делегировать другому сервису в явном виде обратившись к нему.

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

EventBus.Send(new QuestService.QuestCompleted(curentQuest));

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

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

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

0
Автор поста оценил этот комментарий

Не могу с вами согласиться.

Вы смотрите срез комментариев. Чтобы написать комментарий, перейдите к общему списку

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества