refactor(player): encapsulate game state behind PlayerSettings, clear VisibilityModifier and HiddenField
- ParkourUser/ParkourPlayer/GeneratorState public mutable fields -> private + short accessors - per-player settings extracted to player/data/PlayerSettings (ParkourPlayer 188 -> 96 lines, PMD-counted methods 20 -> 9); this is what fixed the TooManyMethods violation that the encapsulation itself introduced — by decomposition, not by lowering the threshold - disk storage reads and writes the same canonical 12-key map as the SQL JSON column; legacy key names normalized on read, @Expose dropped from domain classes - 39 settings call sites rewritten across 7 files - HiddenField backlog cleared (Config, ObstacleGenerator, GhostService, Leaderboard, PagedMenu, Reward, ParticleData, World) - checkstyle ratchet: VisibilityModifier, HiddenField, EmptyLineSeparator and MissingDeprecated flipped to error once their counters hit zero; suppressions.xml empty - javac -Xmaxwarns raised so NullAway/Error Prone counts are honest (baseline 165/289, now 164/265) checkstyle 0 errors / 135 warnings, PMD 0 violations, tests 94/0 fail, ./gradlew check green. TODO.md: items 30 and 107 updated, new items 167-170, ПРОГРЕСС-6/7 recorded.
This commit is contained in:
parent
c120309663
commit
02e2947f47
90 changed files with 1103 additions and 660 deletions
99
TODO.md
99
TODO.md
|
|
@ -55,7 +55,7 @@
|
|||
- **27. SQL: утечка Hikari-пулов.** `StorageSQL.init(mode)` вызывается на каждый режим (6 лидербордов), каждый раз пересоздаёт `connectionManager` (StorageSQL.java:66-77, LeaderboardStorage.java:35) → до ~10 соединений висят навсегда. → Один пул на плагин, инициализация один раз.
|
||||
- **28. SQL: блокирующий connect на main thread** (SQLConnectionManager.java:34-56) — при лежащей БД включение висит до 5с×режимы. → `setInitializationFailTimeout(-1)` + асинхронный connect.
|
||||
- **29. SQL: адаптив не стартует.** `AdaptiveServices.init` вызывается до создания лидербордов, а `StorageSQL.runWhenConnected` выбрасывает колбэк при не-подключении (StorageSQL.java:45-51). → Очередь колбэков в SQLConnectionManager.onConnectCallbacks; порядок init: лидерборды → storage → adaptive.
|
||||
- **30. SQL: настройки игрока не сохраняются.** `SQLDataMapper.serializeSettings` всегда `"{}"`, десериализация no-op (SQLDataMapper.java:105-115) → при входе `schematicDifficulty`/`blockLead` и др. = null → NPE в `ParkourPlayer.updateGeneratorSettings:157-164` и `PAPIHook.java:140`. → Реализовать сериализацию/десериализацию всех настроек (JSON-колонка) + fallback на defaults.
|
||||
- **30. SQL: настройки игрока не сохраняются.** ✅ **СДЕЛАНО**: `SQLDataMapper.serializeSettings` пишет `PlayerSettingsManager.toSettingsMap(player)` (12 ключей) в JSON-колонку, `deserializeSettings` читает её тем же словарём (`normalizeNumericSettings` + `applySettings`) и падает на `applyDefaults` при пустоте/битости → `schematicDifficulty`/`blockLead` больше не null при входе (NPE в `updateGeneratorSettings`/`PAPIHook` не стало). Диск и SQL теперь используют **один и тот же** набор ключей.
|
||||
- **31. Схематики: креш на main thread от битых файлов.** `LPSchematicLegacy`/`SchemLoader` парсят без валидации; NPE/AIOOBE мимо `catch(IOException)` (SchematicManager.java:109-133, LpschemConverter.java:44-54). Битый файл = отказ включения плагина. → Валидация dimensions/palette/frameCount + `catch(RuntimeException)` + лимиты размера файла/блоков.
|
||||
- **32. JumpCalculator: возврат невалидного прыжка.** После 10 неудач валидация сдаётся и возвращает заведомо невалидный блок (JumpCalculator.java:125-136). → Пере-роллить offset/направление, а не только уменьшать distance/height; при исчерпании — safety-fallback (простой допустимый шаг).
|
||||
- **33. JumpCalculator: диагональный heading залипает.** `JumpDirector.getRecommendedHeading` в безопасной зоне возвращает current без изменений (JumpDirector.java:105-107), а `calculateJumpOffset` при `offset.getX()!=0` перезаписывает Z (JumpCalculator.java:110-115) → курс зигзагом и не выпрямляется. → При диагонали аппендикс не перезаписывать случайным; корректный поворот-домножитель.
|
||||
|
|
@ -86,12 +86,14 @@
|
|||
- **47. `UserRegistry.getUsers()` строит полный Set на каждый BlockBreak/Place/Drop/Damage** (UserRegistry.java:94-98) → O(n) аллокаций на горячем пути. → Кэш `Map<UUID, ParkourUser>` + привязка через PersistentDataContainer или entity.
|
||||
- **48. `sendTranslated(...).formatted()` бросает IllegalFormatException при `%` в нике/сообщении** (ParkourUser.java:102, Locales.java:76-82). → Обернуть в try или использовать replace-based формат.
|
||||
- **49. `getJoinCount` — int между main и async (bStats)** (UserRegistry.java:100-104). → AtomicInteger.
|
||||
- **168. `ParkourUser.chatType` — состояние без записи.** Поле инициализировано `PUBLIC`, читается в `ParkourRestrictionListener.onChat` (switch `chatType()`), но сеттера нет нигде → ветки PRIVATE/GUILD недостижимы, режим группового чата не работает. → Либо подключить переключатель (команда/меню), либо убрать switch и мёртвый enum.
|
||||
|
||||
### Modes
|
||||
|
||||
- **50. `GravityShiftMode` не снимает эффекты при выходе игрока** (только в reset для присутствующих, GravityShiftMode.java:149-163). → Снимать в unregister.
|
||||
- **51. `RaceMode.finishRace` — `reset(false)` оставляет игрока в воздухе** (RaceMode.java:142-147; GeneratorCleanup.reset чистит курс без регенерации/телепорта). → Телепорт в лобби + регенерация старта.
|
||||
- **52. `SpeedrunMode` оставляет удалённые блоки в `history`** → ломает координатные поиски. → Удалять координату из history при снятии блока.
|
||||
- **167. Elytra-под-режимы не отличаются от базового.** `ElytraGenerator.getMode()` возвращает `Modes.elytra()`, а 5 под-генераторов (`Close/MinSpeed/TimeTrial/SpeedDemon/ObstacleGenerator`) его не переопределяют → guard `getMode() instanceof CloseMode` в `create()` (строка 47 во всех 5 режимах) **никогда не истинен**: повторная команда в том же режиме не делает early-return, а пересоздаёт сессию. Геттеры `Modes.speedDemon()/minSpeed()/timeTrial()/close()/obstacle()` не вызываются нигде (5 статиков только пишутся). → Переопределить `getMode()` в под-генераторах (или сравнивать класс генератора) и использовать/удалить геттеры.
|
||||
|
||||
### Схематики (жизнь в игре)
|
||||
|
||||
|
|
@ -181,7 +183,7 @@
|
|||
- **104. `ProfileValue.asDouble/asInt` бросают NFE на нечисловых строках** (Profile.java:54,64) — только empty-guard.
|
||||
- **105. Дубликат логики `create()` во всех режимах** с несогласованными проверками дублей (instanceof DefaultMode vs instanceof SpeedrunGenerator).
|
||||
- **106. Мёртвый код**: `EffectManager` целиком, `SessionUserManager`, `SQLMigrationManager`, `SQLQueryExecutor`, `SkullSetter`, `Locations.max/min`, `ParkourBlockGenerateEvent`, `onPlayerJoin` (CoopMode.java:169), `PLAYER_COLUMNS`/`OptionContainer` (ParkourPlayer.java:38,205), `acceptingPlayers`-механизм, `Muted` в двух местах.
|
||||
- **107. Публичные mutable поля-государство**: `GeneratorState` — 15 публичных полей, `ParkourGenerator.session/island/profile/state/generatorOptions` публичные final, `Score` времени в строке. → Инкапсуляция.
|
||||
- **107. Публичные mutable поля-государство**: ✅ **ЧАСТИЧНО** — `GeneratorState` (15 полей) и весь кластер `player/*` (`ParkourUser`, `ParkourPlayer`, `PlayerSettings`) инкапсулированы в ПРОГРЕСС-6, `VisibilityModifier` переведён в `error`. Остались `ParkourGenerator.session/island/profile/state/generatorOptions` (публичные final — правило их разрешает, но состояние чужое мутируется) и `Score` времени в строке. → Инкапсуляция.
|
||||
|
||||
## 🧪 Тесты
|
||||
|
||||
|
|
@ -306,7 +308,19 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар
|
|||
- **135. [M] Починить `NPathComplexity` (legacy, 29 / 25 файлов).** Решается декомпозицией методов (файлы и так в списке на рефакторинг).
|
||||
- **136. [M] Починить `IllegalCatch` (legacy, 22 / 19 файлов).** catch(Exception/Throwable/RuntimeException)→сужение до конкретных исключений либо переименовать в `expected|ignore`.
|
||||
- **137. [M] Починить все нарушения PMD (45):** `CognitiveComplexity(17)` + `CyclomaticComplexity(7)` — декомпозиция; `GodClass(4)` + `TooManyMethods(8)` — декомпозиция; `AvoidReassigningParameters(7)` — переименование параметров; `EmptyCatchBlock(2)` — комментарий/конкретный тип.
|
||||
- **138. [S] Перевести PMD из report-only в жёсткий гейт.** После зачистки убрать `isIgnoreFailures=true` в `build.gradle.kts` (pmd-блок).
|
||||
- **138. [S] Перевести PMD из report-only в жёсткий гейт.** После зачистки убрать `isIgnoreFailures=true` в `build.gradle.kts` (pmd-блок). ✅ сделано в `b771cc1`.
|
||||
- **169. [M] Починить `ReturnCount` (69 warning / 52 файла) — ПЕРЕСТРУКТУРИРОВАНИЕМ, не порогом.** Все 69 — это половина
|
||||
правила про void (`maxForVoid` по умолчанию = 1 ⇒ запрещена любая guard-клауза `if (x) { return; }`); для value-методов
|
||||
при `max=5` нарушений 0. Владелец запретил поднимать порог → в каждом из 69 void-методов убираем ранний `return`
|
||||
(условие становится обычным `if`-блоком) либо выносим тело в helper-метод. Следить за `NestedIfDepth` (error, max=2)
|
||||
и `NPathComplexity`. После обнуления — перевести `ReturnCount` в `error`.
|
||||
- **170. [M] Починить `IllegalCatch` (23 warning / 18 файлов).** 19× `catch (Exception)`, 3× `catch (Throwable)`,
|
||||
1× `catch (RuntimeException)`. Там, где конкретика реальна, — сужать до `IOException`/`NumberFormatException`/
|
||||
`SQLException`/`YAMLException` и т. п. На истинных границах (диспетчер команд `LoParkourCommand:36`, `session-state:84`,
|
||||
`LoParkour:128`) `Throwable` **обязан** стать `RuntimeException`: `Throwable` глотает `Error` (OOM, StackOverflow) —
|
||||
это баг, а не стиль. Пограничные `catch (Exception)` в `onEnable/onDisable`, загрузчиках конфигов/схематчей и
|
||||
`SchematicManager` — оставляем широкими осознанно (релоад плагина не должен ронять сервер), но каждый такой site
|
||||
требует комментария «почему именно Exception»; после разбора правило остаётся в warning на незачищенном остатке.
|
||||
|
||||
После зачистки п.132–137 — удалить соответствующие записи из `config/checkstyle/suppressions.xml` (супрессии остаются только для ещё незачищенных legacy-файлов).
|
||||
|
||||
|
|
@ -398,8 +412,9 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар
|
|||
> `ClassFanOutComplexity` 30→12, `MagicNumber` и `FinalLocalVariable` **warning→error** (оба чистые!).
|
||||
> Добавлены: `ReturnCount` (max 5), `FinalClass`, `HiddenField`, `VisibilityModifier`, `NPathComplexity` (секция вернулась в явном виде).
|
||||
> - `severity="warning"` (build зелёный, но каждое нарушение видно в отчёте) — **бэклог дизайна**, правило:
|
||||
> **счётчик обнулился → сразу переводить в error**. Сейчас: `VisibilityModifier` 163, `ReturnCount` 69, `HiddenField` 30,
|
||||
> `IllegalCatch` 23, `ClassFanOutComplexity` 17, `NPathComplexity` 12, `ParameterNumber` 8, `MethodLength` 5, `CDAC` 2.
|
||||
> **счётчик обнулился → сразу переводить в error**. Актуальный список — в ПРОГРЕСС-6 ниже
|
||||
> (на шаге 0.5: `VisibilityModifier` 163, `ReturnCount` 69, `HiddenField` 30, `IllegalCatch` 23,
|
||||
> `ClassFanOutComplexity` 17, `NPathComplexity` 12, `ParameterNumber` 8, `MethodLength` 5, `CDAC` 2).
|
||||
> - **`suppressions.xml` ОБНУЛЁН** (45 записей удалено, включая FileLength/NPath/WhitespaceAround/IllegalCatch).
|
||||
>
|
||||
> **Красный baseline (честный, до фиксов): 431 нарушение в 129 файлах.** Замеры порогов перед выбором:
|
||||
|
|
@ -460,8 +475,80 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар
|
|||
> в `ParkourRestrictionListener.onChat` unboxing `Boolean` → `getOrDefault(CHAT, true)` как в `ParkourOption.mayPerform`.
|
||||
> - **Счётчики:** checkstyle **0 error / 329 → 240 warning**; `VisibilityModifier` **163 → 75**, `MethodLength` 5 → 4.
|
||||
> В `config/options` — **0 нарушений VisibilityModifier** (было 88), остаются 2 `IllegalCatch`.
|
||||
> `./gradlew check` зелёный: PMD 0, тесты 94/0 fail, JaCoCo/ArchUnit/Spotless ок, NullAway 48 (как в baseline).
|
||||
> `./gradlew check` зелёный: PMD 0, тесты 94/0 fail, JaCoCo/ArchUnit/Spotless ок, NullAway 48 (как в baseline;
|
||||
> ⚠️ как выяснилось на шаге 6 — это было артефактом обрезки javac на 100 предупреждениях, честные числа там же).
|
||||
> Пороги правил не снижались, `suppressions.xml` пустая. Реальных прогонов на сервере после правок не было (только `check`).
|
||||
>
|
||||
> ## ✅ ПРОГРЕСС-6 (Задача 2, шаг (c): инкапсуляция игрового состояния — `player/*` + `GeneratorState`, 2026-10-06)
|
||||
> **Checkstyle 329 → 165 warning; `VisibilityModifier` 75 → 0 и переведён в `error`.**
|
||||
> - **Кластер `player` закрыт:** `ParkourUser` (5 полей) и `ParkourPlayer` (12) → `private` + аксессоры без `get` (та же идиома,
|
||||
> что в ПРОГРЕСС-5 для `config/options`). ~46 обращений в 19 файлах переписаны.
|
||||
> - **Выключен баг тенимого `locale`:** `@Expose String locale` был объявлен и в `ParkourUser`, и в `ParkourPlayer` —
|
||||
> запись в поле и в аксессор расходилась. Осталось одно поле в родителе; на gson-2.11.0 проверено, что `@Expose`
|
||||
> наследуемого поля сериализуется, поэтому набор ключей на диске не изменился.
|
||||
> - **П.107 для `GeneratorState`:** 15 публичных полей → `private` + аксессоры/сеттеры; инкременты и сбросы оформлены
|
||||
> методами (`addGeneratedBlocks(int)`, `resetScore()`, `setScore(int)`), ~90 обращений в 25 файлах.
|
||||
> Публичные `ParkourGenerator.session/island/profile/state` — как были (остаток п.107).
|
||||
> - **SRP: настройки игрока вынесены в `player/data/PlayerSettings`** (11 полей + аксессоры). `ParkourPlayer` —
|
||||
> 194 → 93 строки, засчитываемых методов 20 → 9. Это и починило свежесломанное нарушение PMD `TooManyMethods` (гейт 0 нарушений
|
||||
> был испорчен моим же рефакторингом) — декомпозицией, а НЕ ослаблением порога и НЕ переименованием под name-based
|
||||
> освобождение правила (`getX()` в проекте не используется: 0 объявлений в `src/main`).
|
||||
> - **DRY по настройкам:** троекратное повторение санитизации «legacy-locale» → `PlayerSettingsManager.sanitizeLocale()`
|
||||
> (фолбэк берётся из конфига, а не захардкожен `"en"`); байт-в-байт одинаковая сборка 12-ключевого map в
|
||||
> `StorageDisk.readPlayer` и `SQLDataMapper.serializeSettings` → `toSettingsMap()`; удалены мёртвый `getColumnMappings()`,
|
||||
> `PLAYER_COLUMNS` и дубль `OptionContainer` (п.106); реестр-фасады `ParkourPlayer.isPlayer/getPlayer/getPlayers`
|
||||
> переехали в `UserRegistry` (22 файла);
|
||||
> дубли `parseIntSafe`/`parseDoubleSafe` схлопнуты в `numericOrFallback`.
|
||||
> - **Закрыт п.30** (SQL не сохранял настройки) и **формат disk-файла игрока пересобран сознательно:** раньше Gson писал
|
||||
> `ParkourPlayer` по `@Expose`-полям, а читал его через Unsafe-аллокацию «полупустого» объекта, чей map потом переливали
|
||||
> в живого игрока. Теперь `StorageDisk` пишет и читает тот же канонический `Map<String,Object>`, что и JSON-колонка SQL —
|
||||
> один словарь на оба бэкенда, `@Expose` из доменных классов убран совсем. Старые файлы остаются читаемыми: 3 ключа,
|
||||
> которые совпадали с именами полей (`particles`/`useSpecialBlocks`/`showFallMessage`), нормализует
|
||||
> `StorageDisk.normalizeLegacyKeys()`. ⚠️ Живых файлов игроков в `run/plugins/LoParkour/players/` нет — миграция
|
||||
> проверена кодом и тестами, не реальным апгрейдом.
|
||||
> - **Артефакт замера предупреждений:** в `build.gradle.kts` (JavaCompile) добавлен `-Xmaxwarns 100000`, потому что javac по умолчанию
|
||||
> обрывает список предупреждений на 100 — отсюда «NullAway 48» в ПРОГРЕСС-5. Честные замеры через `git stash`:
|
||||
> baseline HEAD **NullAway 165 / Error Prone 289**, сейчас **NullAway 164 / EP 265** — регресса нет.
|
||||
> - **Счётчики:** checkstyle **0 error / 165 warning** (бэклог: `ReturnCount` 69, `HiddenField` 30, `IllegalCatch` 23,
|
||||
> `ClassFanOutComplexity` 17, `NPathComplexity` 12, `ParameterNumber` 8, `MethodLength` 4, `CDAC` 2).
|
||||
> В ноль обнулены и переведены в `error`: `VisibilityModifier`, `EmptyLineSeparator`, `MissingDeprecated`.
|
||||
> PMD **0** (жёсткий гейт), `./gradlew check` зелёный, тесты **94 / 0 fail (1 skipped)**. Пороги правил не снижались,
|
||||
> `suppressions.xml` пустая. Реальных прогонов на сервере не было (только `check`).
|
||||
> - **Найдено по пути и оформлено отдельными пунктами (молча не чинилось):** **167** (elytra-под-режимы недостижимы —
|
||||
> `getMode()` не переопределён) и **168** (`ParkourUser.chatType` не записывается нигде).
|
||||
>
|
||||
> ## ✅ ПРОГРЕСС-7 (Задача 2, шаг (c): `HiddenField` обнулён + директива владельца про линтеры, 2026-10-06)
|
||||
> **Checkstyle 165 → 135 warning; `HiddenField` 30 → 0 и переведён в `error`.**
|
||||
> - **Директива владельца (зафиксирована, действует дальше):** нарушения линтеров **чинить**, а не **глушить** —
|
||||
> пороги правил не поднимать, `suppressions.xml` не наполнять, под name-based освобождения не подстраиваться.
|
||||
> В `error` правило переводится только после того, как счётчик реально обнулён правками.
|
||||
> - **`HiddenField` 30 → 0** — тенящие параметры/локалы переименованы (поля не трогали): `Config` (12 методов-делегата,
|
||||
> параметр `path` → `key`), `ObstacleGenerator` (внешнее поле `radius` → `obstacleRadius` — одним переименованием сняли
|
||||
> 7 теней в enum-реализациях `getPoints(center, radius)`), `GhostService.setReplaying(uuid, active)`,
|
||||
> `Leaderboard.sort(order)`, `PagedMenu.addToDisplay(menuItems)`, `Reward` (локал `string` → `command`),
|
||||
> `ParticleData` (fluent-сеттеры принимают `value`), `World.create()` (локал `world` → `existingWorld`, а
|
||||
> `World.world = world` превратился в обычное присваивание поля).
|
||||
> - **`ReturnCount` 69 — решено оформить отдельным пунктом 169:** все 69 нарушений приходятся на половину правила
|
||||
> про void (`maxForVoid` по умолчанию = 1, то есть запрещена любая guard-клауза `if (x) { return; }`);
|
||||
> для value-методов при `max=5` нарушений **0**. Поднимать `maxForVoid` я предлагал, владелец отказал — значит
|
||||
> 69 void-методов переструктурируются по-настоящему (early-return → `if`-блок или выделение helper'а),
|
||||
> с оглядкой на `NestedIfDepth` (error, max=2) и `NPathComplexity`.
|
||||
> - **`IllegalCatch` 23** — сузить типы там, где конкретика реальна (IO/NumberFormat/SQL/ClassNotFound),
|
||||
> а на истинных границах заменить `catch (Throwable)` → `catch (RuntimeException)` (это не «приглушение», а починка:
|
||||
> `Throwable` глотает `Error`-ы — OOM, StackOverflow). Диспетчер команд, `onEnable/onDisable`, загрузчики конфигов
|
||||
> и схематчей, session-state — пограничные точки, их фиксируем отдельно (п.170).
|
||||
> - **Авария и восстановление:** после частичного отката правок в IDE (отклонённые diffs) рабочий код был сломан:
|
||||
> `player/data/PlayerSettings.java` удалился целиком, `ParkourPlayer` вернулся к 11 `@Expose`-полям при уже
|
||||
> переписанных вызовах (`settings()` ссылался на несуществующее поле, `updateGeneratorSettings` пропал),
|
||||
> в `GeneratorCleanup` потерялся импорт `@Nullable` (2 ошибки компиляции), `checkstyle.xml` откатился к
|
||||
> HEAD-версии и **потерял все четыре флипа** (`VisibilityModifier`, `EmptyLineSeparator`, `MissingDeprecated`,
|
||||
> `HiddenField`), `PagedMenu`/`GhostService` вернулись к тенящим параметрам, а с `ParkourUser` слез лишний `@Expose`
|
||||
> (после перехода диска на map он мёртвый). Всё восстановлено и перепроверено:
|
||||
> `./gradlew check` — **BUILD SUCCESSFUL**, checkstyle **0 error / 135 warning** (test-отчёт пуст), PMD **0**,
|
||||
> тесты **94 / 0 fail (1 skipped)**, `suppressions.xml` пустая. Реальных прогонов на сервере по-прежнему не было.
|
||||
> Урок на будущее: после любой отменённой пачки правок сверять `git status` + полный `check`, а не только счётчики.
|
||||
> - **Остаток бэклога (135):** `ReturnCount` 69, `IllegalCatch` 23, `ClassFanOutComplexity` 17, `NPathComplexity` 12,
|
||||
> `ParameterNumber` 8, `MethodLength` 4, `ClassDataAbstractionCoupling` 2.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue