47dec6c32c
Work-Package: M01-FND-COORD-BOUNDARY-GATE-001 Agent: sindo-main-codex
272 lines
14 KiB
Markdown
272 lines
14 KiB
Markdown
# OpenWC Coding Standard
|
||
|
||
## Главные принципы
|
||
|
||
1. Код читается значительно чаще, чем пишется.
|
||
2. Явное и точное имя предпочтительнее короткого и двусмысленного.
|
||
3. KISS применяется по умолчанию: выбирается самое простое решение, которое корректно выполняет текущую задачу и сохраняет архитектурные границы.
|
||
4. Сложность допускается только там, где она следует из предметной области, формата, протокола, производительности или совместимости 3.3.5a.
|
||
5. Неизбежная сложность должна быть локализована, протестирована и документирована.
|
||
|
||
## Имена
|
||
|
||
Не нужно бояться длинных имён переменных, функций, типов, сигналов и файлов. Имя должно позволять понять назначение сущности без поиска всех мест использования.
|
||
|
||
Хорошо:
|
||
|
||
```gdscript
|
||
var terrain_control_splat_cache_finalize_queue: Array = []
|
||
var maximum_concurrent_terrain_upgrade_tasks: int = 1
|
||
|
||
func enqueue_terrain_control_splat_cache_finalize(tile_coordinate: Vector2i) -> void:
|
||
pass
|
||
|
||
func should_retain_streaming_tile_outside_visible_radius(tile_state: TileState) -> bool:
|
||
return false
|
||
```
|
||
|
||
Плохо:
|
||
|
||
```gdscript
|
||
var q = []
|
||
var max_t = 1
|
||
|
||
func do_ctl(p):
|
||
pass
|
||
|
||
func chk(s):
|
||
return false
|
||
```
|
||
|
||
Длина не является самоцелью. Не повторять контекст, который уже однозначно задаётся типом или модулем:
|
||
|
||
```gdscript
|
||
# Внутри TerrainStreamingService:
|
||
func enqueue_finalize(tile: TerrainTile) -> void:
|
||
pass
|
||
```
|
||
|
||
Такое имя допустимо, если `finalize` в этом классе имеет только одно значение. Если существуют terrain mesh, material и liquid finalization, имя должно уточнять конкретную операцию.
|
||
|
||
## Naming rules
|
||
|
||
- Типы/classes: `PascalCase`.
|
||
- GDScript functions/variables/signals: `snake_case`.
|
||
- Constants: `UPPER_SNAKE_CASE`.
|
||
- Private implementation members: `_leading_underscore` по Godot convention.
|
||
- C++ следует Godot/GDExtension convention внутри существующего native module; новый API не смешивает стили в одной области.
|
||
- Boolean: `is_`, `has_`, `can_`, `should_`, `needs_`, `was_`.
|
||
- Collections называются во множественном числе или по роли: `loaded_tiles`, `pending_packet_events`, `entity_by_guid`.
|
||
- Functions начинаются с действия: `load_`, `parse_`, `build_`, `enqueue_`, `apply_`, `validate_`, `resolve_`, `convert_`, `release_`.
|
||
- Units указываются в имени: `_milliseconds`, `_seconds`, `_bytes`, `_meters`, `_tiles`, `_radians`, `_degrees`.
|
||
- Coordinate space указывается, если возможна путаница: `canonical_wow_position`, `godot_world_position`, `adt_tile_coordinate`.
|
||
- Версия/build/profile указывается в типе или namespace, если semantics различаются: `Wotlk335ProtocolProfile`.
|
||
|
||
Допустимые устоявшиеся доменные сокращения:
|
||
|
||
- WoW, MPQ, BLP, DBC;
|
||
- ADT, WDT, WDL;
|
||
- M2, WMO;
|
||
- GUID, SRP, API, UI;
|
||
- LOD, RID, GPU, CPU;
|
||
- SQL, TCP.
|
||
|
||
Новые локальные сокращения из двух-трёх букв без общеизвестного значения запрещены.
|
||
|
||
## Запрещённые размытые имена
|
||
|
||
Избегать без уточнения:
|
||
|
||
```text
|
||
data
|
||
info
|
||
item
|
||
object
|
||
value
|
||
temp
|
||
result
|
||
state
|
||
manager
|
||
helper
|
||
utils
|
||
process
|
||
handle
|
||
do_work
|
||
```
|
||
|
||
Они допустимы только когда тип и ближайший scope делают значение полностью однозначным. Например, `parse_packet() -> PacketParseResult` может использовать локальную переменную `result`, если функция мала и другого результата нет.
|
||
|
||
`Manager`, `Helper`, `Utils` почти всегда скрывают несколько обязанностей. Предпочтительны названия по роли:
|
||
|
||
```text
|
||
WorldSession
|
||
StreamingTargetPlanner
|
||
RenderBudgetScheduler
|
||
CoordinateMapper
|
||
AssetRepository
|
||
QuestValidator
|
||
ServerSchemaAdapter
|
||
```
|
||
|
||
## KISS decision order
|
||
|
||
При выборе реализации двигаться по порядку:
|
||
|
||
1. Простая typed function с явными inputs/outputs.
|
||
2. Маленький value object или immutable result.
|
||
3. Небольшой service с одной ответственностью.
|
||
4. Composition нескольких services.
|
||
5. Interface/strategy только при реальной вариативности или boundary.
|
||
6. Generic framework только после нескольких подтверждённых use cases.
|
||
7. Native/low-level optimization только после измеренного bottleneck.
|
||
|
||
Не переходить на следующий уровень, если предыдущий решает задачу без нарушения correctness и architecture.
|
||
|
||
## Простота функций
|
||
|
||
- Одна функция выполняет одну логическую операцию.
|
||
- Название функции описывает observable effect.
|
||
- Inputs и outputs явные; hidden global state минимален.
|
||
- Guard clauses предпочтительнее глубокой вложенности.
|
||
- Happy path должен читаться сверху вниз.
|
||
- Side effects не смешиваются с pure calculation без необходимости.
|
||
- Parsing, validation, state mutation и presentation отделяются.
|
||
- Функция не принимает набор boolean flags, меняющих её смысл.
|
||
|
||
Плохо:
|
||
|
||
```gdscript
|
||
func load_tile(path: String, high: bool, async: bool, editor: bool):
|
||
pass
|
||
```
|
||
|
||
Лучше:
|
||
|
||
```gdscript
|
||
func request_streaming_tile_load(request: StreamingTileLoadRequest) -> void:
|
||
pass
|
||
```
|
||
|
||
При этом `StreamingTileLoadRequest` оправдан только если параметры действительно образуют устойчивый contract. Для двух простых аргументов не нужно создавать объект только ради архитектурности.
|
||
|
||
## Простота data flow
|
||
|
||
- Один владелец mutable state.
|
||
- Один канонический тип для одной концепции.
|
||
- Один mapper для coordinate conversion.
|
||
- Один authoritative source для content/runtime state.
|
||
- Изменения передаются commands/events/read models, а не чтением чужих внутренних dictionaries.
|
||
- Не создавать второй cache, registry или event bus без описанной отдельной ответственности.
|
||
- Не использовать строки и `call()` как основной type system.
|
||
- Dictionary допустим на binary/dynamic boundary; внутри domain данные переводятся в typed representation.
|
||
|
||
## Простота архитектуры
|
||
|
||
- Не вводить interface только потому, что «когда-нибудь может появиться второй backend».
|
||
- Interface обязателен на реальной внешней границе: renderer facade, protocol profile, Lua VM, server schema adapter, asset repository.
|
||
- Не создавать base class с одним наследником без конкретного boundary/invariant.
|
||
- Предпочитать composition inheritance.
|
||
- Не строить универсальную ECS, service container, message broker или plugin framework до подтверждённых требований OpenWC.
|
||
- Не переносить архитектуру reference-проекта целиком; извлекать только проверенное решение конкретной проблемы.
|
||
|
||
## Работа со сложностью
|
||
|
||
Сложность считается оправданной для:
|
||
|
||
- binary formats и build-specific layouts;
|
||
- encrypted/framed network protocol;
|
||
- incremental world streaming;
|
||
- animation/material compatibility;
|
||
- Lua/FrameXML/taint semantics;
|
||
- cross-core schema mappings;
|
||
- async jobs, cancellation и deterministic cleanup;
|
||
- performance-critical batching после profiling.
|
||
|
||
Если сложность неизбежна:
|
||
|
||
1. Изолировать её в одном модуле.
|
||
2. Дать точные имена промежуточным этапам.
|
||
3. Разбить pipeline на небольшие transformations.
|
||
4. Добавить data-flow/sequence/state diagram.
|
||
5. Зафиксировать invariants и ownership.
|
||
6. Создать fixtures для edge cases.
|
||
7. Объяснить, почему более простое решение недостаточно.
|
||
|
||
## Conditions and branching
|
||
|
||
- Предпочитать именованные predicates длинным boolean expressions.
|
||
- Branch по capability/profile должен быть локальным и типизированным.
|
||
- Не распространять `if core == "azerothcore"` по проекту; использовать adapter/capability.
|
||
- Не смешивать Blizzlike и Enhanced branches в каждом shader/service: выбирать profile/strategy на boundary.
|
||
- Pattern/table-driven mapping предпочтительнее сотен одинаковых `if`, если таблица остаётся читаемой и валидируемой.
|
||
|
||
## Comments
|
||
|
||
Комментарий объясняет `why`, invariant, source behavior или ограничение. Код и имя объясняют `what`.
|
||
|
||
Хорошо:
|
||
|
||
```gdscript
|
||
# WoW M2 bone indices are relative to the skin-section bone palette,
|
||
# not directly to the model-wide bone array.
|
||
```
|
||
|
||
Плохо:
|
||
|
||
```gdscript
|
||
# Increment index by one.
|
||
index += 1
|
||
```
|
||
|
||
TODO/FIXME обязан содержать target/work-package ID или diagnostic code.
|
||
|
||
## Error handling
|
||
|
||
- Ошибка содержит operation, subject и actionable context.
|
||
- Не возвращать пустой Dictionary как неразличимое сочетание `not found`, `invalid` и `unsupported`.
|
||
- Expected failure моделируется typed result/status; programming invariant может использовать assert в подходящем profile.
|
||
- Не проглатывать ошибку ради продолжения renderer/editor: degraded behavior должен быть виден diagnostics.
|
||
- Recovery path проще и предпочтительнее сложной попытки угадать состояние.
|
||
|
||
## Performance and KISS
|
||
|
||
- Не оптимизировать по предположению.
|
||
- Сначала baseline/profile, затем smallest measured fix.
|
||
- Batch/buffer/RenderingServer/native path вводится после доказательства bottleneck.
|
||
- Оптимизированный path обязан иметь простой contract и reference tests.
|
||
- Медленный понятный reference implementation полезен как oracle для optimized implementation.
|
||
- Сложная оптимизация удаляется или упрощается, если она не даёт измеримого результата.
|
||
|
||
## Coordinate conversion boundary
|
||
|
||
- Cross-space world conversions выполняются только через typed API
|
||
`CoordinateMapper`; ручные world-center offsets, swaps осей и вычисление ADT
|
||
filename indices из Godot position запрещены.
|
||
- Scene/render adapter МОЖЕТ создать `Vector3` только после получения
|
||
`GodotWorldPosition` или перевести engine vector в typed scalar value на своей границе.
|
||
- Единственная native world-position формула находится в
|
||
`src/native/src/wow_chunk_reader.h`, поскольку parser workers не вызывают
|
||
GDScript. Новая native копия требует отдельного boundary decision и parity fixture.
|
||
- `verify_render_coordinate_calibration.gd` остаётся независимым test oracle;
|
||
production code не копирует его формулы.
|
||
- Model-local M2/WMO vertex basis и placement orientation не являются world
|
||
coordinate conversion. Они документируются и тестируются своим format adapter.
|
||
- `verify_coordinate_conversion_boundaries.gd` обязателен при изменении
|
||
coordinates, native parsers, sky, player, streaming или renderer diagnostics.
|
||
|
||
## Review checklist
|
||
|
||
- Можно ли понять назначение сущности по имени?
|
||
- Есть ли двусмысленные сокращения?
|
||
- Выполняет ли функция одну операцию?
|
||
- Можно ли убрать abstraction без потери correctness/boundary?
|
||
- Есть ли скрытый mutable state или side effect?
|
||
- Не появился ли второй источник истины?
|
||
- Все ли cross-space world coordinates проходят через `CoordinateMapper` или
|
||
документированную native boundary?
|
||
- Измерена ли сложная optimization?
|
||
- Изолирована и документирована ли неизбежная сложность?
|
||
- Соответствуют ли API docs и diagrams фактическому коду?
|
||
|
||
Если reviewer не может объяснить data flow модуля после чтения имён, public API и module spec, work package не готов к интеграции.
|