Political World — рекурсия при инициализации правительства
✅ Reproduced 💥 StackOverflow Political World 1.7.0
В Political World был жёсткий краш после отделения государства: первая инициализация формы правления попадала в бесконечную рекурсию.
Это один из самых наглядных наших примеров того, почему ленивая инициализация внутри getter может быть опасной.
Симптом
Заголовок раздела «Симптом»Королевство, созданное после отделения, могло привести игру к жёсткому падению вскоре после запуска его политической инициализации.
Главная подсказка — не обычное перехватываемое исключение. Цепочка вызовов снова и снова входила в одни и те же методы, пока процесс не доходил до StackOverflow.
Рекурсивная цепочка
Заголовок раздела «Рекурсивная цепочка»Проблемная логика выглядела примерно так:
UpdateGovernmentForms() ↓SetGovernmentForm() ↓GetGovernmentPublicId() ↓GetGovernmentForm() ↓government data отсутствует ↓SetGovernmentForm() ↓GetGovernmentPublicId() ↓...Ключевая ошибка: путь чтения пытался восстановить отсутствующее состояние через setter, который сам зависел от getter того же состояния.
Почему проблема проявлялась при первой инициализации
Заголовок раздела «Почему проблема проявлялась при первой инициализации»У существующих государств форма правления уже была.
Новое государство могло попасть в GetGovernmentForm() без назначенного government state. Getter пытался автоматически создать отсутствующее значение, но сам путь создания снова требовал данные через ту же группу getter.
Обычная на вид «удобная» автоматическая инициализация превращалась в бесконечный цикл.
Исправление
Заголовок раздела «Исправление»Из пути первой инициализации убрали зависимость от рекурсивного getter.
Главное изменение архитектуры:
первая инициализация должна завершаться без вызова getter, который способен заново инициировать инициализацию того же состояния.
После исправления отделение государства больше не вызывало этот жёсткий краш в нашем тесте.
Общий вывод
Заголовок раздела «Общий вывод»Опасный паттерн:
public Government GetGovernment(){ if (government == null) SetGovernment(CreateDefaultGovernment());
return government;}если SetGovernment(...) или любой его helper способен снова вызвать GetGovernment().
Лучше разделять ответственность:
EnsureGovernmentInitialized()GetGovernment()SetGovernment()и гарантировать, что функция инициализации использует только данные, не зависящие рекурсивно от того же getter.
Правило отладки
Заголовок раздела «Правило отладки»Если краш происходит во время инициализации, а в стеке повторяется несколько одинаковых методов, ищите:
- циклы getter → setter → getter;
- property/getter, который одновременно меняет состояние;
- скрытую «создай default» логику внутри read-метода;
- Harmony patch, который повторно входит в патчимый метод;
- callback из setter, который снова читает то же состояние.
Граница доказательства
Заголовок раздела «Граница доказательства»Эта страница описывает баг, реально воспроизведённый и исправленный во внутренней системе Political World. Имена методов выше не заявляются как ванильный API WorldBox.