RAII (Resource Acquisition Is Initialization)
Описание
Рекомендуется использовать идиому RAII для управления ресурсами. Ресурс должен захватываться при создании объекта и автоматически освобождаться при его уничтожении.
Это относится к памяти, файлам, потокам, мьютексам, дескрипторам ОС, объектам SDL/HID, графическим ресурсам и любым другим объектам, требующим явного освобождения.
WARNING
Если какой-либо тип ресурса используется всего один раз в контексте всего движка, создание отдельной RAII-обёртки не является обязательным. В таких случаях допускается ручное управление ресурсом, если это не ухудшает читаемость и сопровождение кода.
Когда использовать
Используйте RAII при работе с:
- памятью
- файлами
- потоками
- мьютексами
- графическими API
Преимущества
- ✅ Автоматическое освобождение ресурсов
- ✅ Безопасность при раннем выходе из функции
- ✅ Меньше вероятность утечек памяти и ресурсов
- ✅ Код становится проще и легче поддерживается
Группировка функциональности (Class vs Namespace)
Описание
Рекомендуется группировать связанную функциональность в namespace или class, избегая большого количества глобальных функций.
Выбор между ними определяется наличием состояния.
- Используйте namespace, если функции являются независимыми, не требуют хранения данных и представляют собой набор утилит
- Используйте class, если объект обладает состоянием, управляет ресурсами или описывает отдельную сущность, менеджер, сервис, etc
Если класс представляет собой сервис или компонент движка, именно он должен содержать всю связанную с ним логику. Это уменьшает связанность кода и делает интерфейс понятным.
WARNING
Исключением являются специальные классы, разделяющие ответственность между несколькими подсистемами. Например, CGamepadService управляет устройствами, а обработкой пользовательского ввода занимается отдельная система CInput.
Когда использовать
Используйте namespace
- математические функции
- утилиты
- функции преобразования
- сериализацию
- алгоритмы без внутреннего состояния
Используйте class
- сервисы
- менеджеры
- игровые сущности
- объекты с жизненным циклом
- классы, владеющие ресурсами
- объекты, хранящие состояние
Преимущества
- ✅ Логически связанный код находится в одном месте.
- ✅ Минимизируется количество глобальных функций.
- ✅ Проще сопровождать и расширять функциональность.
- ✅ Становится очевидно, кто отвечает за выполнение конкретной операции.
Недостатки
- ❌ Излишняя инкапсуляция иногда усложняет повторное использование небольших функций
Унифицированные интерфейсы
Описание
При добавлении альтернативной реализации существующей системы рекомендуется использовать общий интерфейс.
Например, если движок поддерживает растровые и векторные изображения, обе реализации должны предоставлять единый интерфейс для загрузки, освобождения и доступа к ресурсу.
Это позволяет изолировать различия между реализациями и избежать большого количества if, switch и #ifdef, разбросанных по коду.
WARNING
Если различия между реализациями слишком велики и не позволяют построить понятный общий интерфейс, допускается использование отдельных API. Не стоит создавать абстракцию ради самой абстракции.
Когда использовать
- альтернативные реализации одной подсистемы
- различные форматы ресурсов
- платформозависимые реализации
- взаимозаменяемые сервисы
Преимущества
- ✅ Код не зависит от конкретной реализации
- ✅ Минимизируется количество ветвлений (
if,switch,#ifdef)
Изоляция платформозависимого кода
Описание
Не используйте платформозависимые API, типы данных и заголовочные файлы в игровых, рендерных и других платформонезависимых модулях.
Весь платформозависимый код должен находиться в соответствующих Platform-реализациях (Windows, Linux, macOS и т.д.) и предоставлять единый интерфейс для остальной части движка.
Это позволяет минимизировать использование #ifdef, упростить сопровождение кода и облегчить добавление новых платформ.
WARNING
Использование #ifdef допускается только внутри платформенных модулей или при отсутствии разумной возможности вынести различия за общий интерфейс.
Когда использовать
- работа с файловой системой
- создание окон
- управление потоками
- системные вызовы
- взаимодействие с ОС
Преимущества
- ✅ Игровой код не зависит от конкретной платформы
- ✅ Упрощается поддержка новых платформ
- ✅ Значительно сокращается количество
#ifdef - ✅ Платформенная логика сосредоточена в одном месте
Недостатки
- ❌ Требуется поддерживать отдельные реализации для каждой платформы
Использование внутренних типов
Описание
При разработке рекомендуется использовать внутренние типы и псевдонимы IXR вместо прямого использования типов стандартной библиотеки или платформозависимых типов.
Это позволяет централизованно управлять реализацией, упрощает поддержку различных платформ и обеспечивает единообразие кода во всём проекте.
Например:
std::string→xr_stringstd::string_view→xr_string_viewstd::unordered_map→xr_hash_mapstd::unordered_set→xr_hash_setstd::vector→xr_vectorstd::unique_ptr→xr_unique_ptrstd::uint32_t→u32
WARNING
Если для используемого типа отсутствует внутренний аналог, допускается использование типа из стандартной библиотеки.
NOTE
В коде, который может выполняться до инициализации внутренних аллокаторов движка (например, при запуске приложения или обработке исключений), следует использовать контейнеры стандартной библиотеки (std::*). В этот момент внутренние типы IXR могут быть ещё недоступны.
Когда использовать
- контейнеры STL
- строковые типы
- целочисленные типы фиксированной длины
- умные указатели
- другие типы, имеющие внутренние аналоги
Преимущества
- ✅ Единый стиль кода во всём проекте
- ✅ Возможность централизованно изменять реализацию внутренних типов
- ✅ Упрощается переносимость и сопровождение проекта