Перейти к содержимому

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.