Files
open-wc/docs/CODING_STANDARD.md

253 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 не готов к интеграции.