Work-Package: M01-FND-COORD-BOUNDARY-GATE-001 Agent: sindo-main-codex
14 KiB
OpenWC Coding Standard
Главные принципы
- Код читается значительно чаще, чем пишется.
- Явное и точное имя предпочтительнее короткого и двусмысленного.
- KISS применяется по умолчанию: выбирается самое простое решение, которое корректно выполняет текущую задачу и сохраняет архитектурные границы.
- Сложность допускается только там, где она следует из предметной области, формата, протокола, производительности или совместимости 3.3.5a.
- Неизбежная сложность должна быть локализована, протестирована и документирована.
Имена
Не нужно бояться длинных имён переменных, функций, типов, сигналов и файлов. Имя должно позволять понять назначение сущности без поиска всех мест использования.
Хорошо:
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
Плохо:
var q = []
var max_t = 1
func do_ctl(p):
pass
func chk(s):
return false
Длина не является самоцелью. Не повторять контекст, который уже однозначно задаётся типом или модулем:
# Внутри 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.
Новые локальные сокращения из двух-трёх букв без общеизвестного значения запрещены.
Запрещённые размытые имена
Избегать без уточнения:
data
info
item
object
value
temp
result
state
manager
helper
utils
process
handle
do_work
Они допустимы только когда тип и ближайший scope делают значение полностью однозначным. Например, parse_packet() -> PacketParseResult может использовать локальную переменную result, если функция мала и другого результата нет.
Manager, Helper, Utils почти всегда скрывают несколько обязанностей. Предпочтительны названия по роли:
WorldSession
StreamingTargetPlanner
RenderBudgetScheduler
CoordinateMapper
AssetRepository
QuestValidator
ServerSchemaAdapter
KISS decision order
При выборе реализации двигаться по порядку:
- Простая typed function с явными inputs/outputs.
- Маленький value object или immutable result.
- Небольшой service с одной ответственностью.
- Composition нескольких services.
- Interface/strategy только при реальной вариативности или boundary.
- Generic framework только после нескольких подтверждённых use cases.
- 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, меняющих её смысл.
Плохо:
func load_tile(path: String, high: bool, async: bool, editor: bool):
pass
Лучше:
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.
Если сложность неизбежна:
- Изолировать её в одном модуле.
- Дать точные имена промежуточным этапам.
- Разбить pipeline на небольшие transformations.
- Добавить data-flow/sequence/state diagram.
- Зафиксировать invariants и ownership.
- Создать fixtures для edge cases.
- Объяснить, почему более простое решение недостаточно.
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.
Хорошо:
# WoW M2 bone indices are relative to the skin-section bone palette,
# not directly to the model-wide bone array.
Плохо:
# 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 не готов к интеграции.