Распространенное выражение среди программистов, но мне всегда было только примерно понятно, что оно означает. А вчера я смог это прочувствовать на себе.
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, который будет генерировать конструкторы, и делать монобехи со ссылками и параметрами отдельными зависимостями для сервисов, чтобы сервисы были обычным объектами.