# 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. - Сложная оптимизация удаляется или упрощается, если она не даёт измеримого результата. ## Review checklist - Можно ли понять назначение сущности по имени? - Есть ли двусмысленные сокращения? - Выполняет ли функция одну операцию? - Можно ли убрать abstraction без потери correctness/boundary? - Есть ли скрытый mutable state или side effect? - Не появился ли второй источник истины? - Измерена ли сложная optimization? - Изолирована и документирована ли неизбежная сложность? - Соответствуют ли API docs и diagrams фактическому коду? Если reviewer не может объяснить data flow модуля после чтения имён, public API и module spec, work package не готов к интеграции.