3. Code convention
Архитектура и файлы
- Каждый отдельный объект, например класс, интерфейс или структура, должен находиться в отдельном файле.
- По возможности функционал выносится в отдельные модули или ООП-классы.
- Архитектура проекта строится в data-oriented подходе: данные, логика обработки и представление должны быть разделены там, где это снижает связность и упрощает обработку данных.
- Runtime-данные, конфигурации и визуальное представление должны быть разделены.
- В проекте обязательно использование namespace верхнего уровня, сформированного на основании имени проекта.
- Каждый объект должен иметь модификатор доступа с минимально возможной видимостью.
Работа с данными
- Явно описывать владельца данных и жизненный цикл данных: кто создает, кто изменяет, кто очищает.
- В runtime-коде избегать прямого обращения к глобальному состоянию.
- Зависимости передавать явно через контекст, конструктор, метод инициализации или выделенный сервис.
- Для систем обработки данных фиксировать порядок выполнения явно, если результат зависит от порядка.
- Стараться не выполнять повторный поиск или сбор рабочих наборов данных внутри горячих участков кода.
- Если набор данных требуется многократно, использовать заранее подготовленный буфер, кеш или явный контекст обработки.
- Стараться читать значение данных или объекта один раз в пределах одной операции обработки, если повторное чтение не требуется логикой.
- Для часто обрабатываемых массивов данных предпочитать
List<T>иNativeArray<T>. - Стараться не использовать ссылочные типы в структурах данных, предназначенных для обработки в runtime.
- Для больших структур данных избегать копирования по значению без необходимости.
- Не обновлять данные, если их значение фактически не изменилось.
- Для value-типов, которые сравниваются по значению, требуется корректно определять
EqualsиGetHashCodeили использовать явно заданный способ сравнения. - Поля, которые инициализируются в методе, должны быть в том же порядке, в котором они объявлены в классе или структуре.
Unity и gameplay
- Не смешивать изменение данных и применение Unity-side effects в одном проходе.
- По возможности сначала считать или рассчитать изменения, затем отдельно применить их к Unity-объектам.
- Не использовать реактивные или event-driven механики как основной способ реализации gameplay-логики, если порядок выполнения, стоимость вызовов или жизненный цикл подписок становятся неочевидными.
- События допускаются для связи между модулями и уведомления о результатах выполнения логики.
- По возможности минимизировать проверку на
nullдля наследников классаUnityEngine.Object. - Запрещено использовать операторы
??и?.для наследников классаUnityEngine.Object. - Запрещено в игровой логике использовать механизм перехвата исключений (
try-catch), однако разрешается бросать исключения. - Запрещено добавлять тихие сценарии ошибки.
- Рекомендуется редактировать префабы с отключенным
Auto Save, если изменения затрагивают общие или часто используемые префабы. - По возможности минимизировать сериализацию сложных объектов через инспектор.
- По возможности минимизировать наследование от класса
MonoBehaviour. - Подписка и отписка на ивенты должны располагаться в методах
OnEnableиOnDisable. - Каждому подписанию на ивент должна соответствовать отписка от ивента.
Производительность
- Для часто вызываемых участков кода избегать неявных аллокаций:
LINQ,boxing, замыканий, временных коллекций и форматирования строк. - Запрещено использовать
Enum.HasFlagпри проверке битовых флагов. - Вместо
Enum.HasFlagнужно использовать:==, если нужно совпадение с каждым битом из маски;!= Enum.None, если нужно совпадение с любым битом из маски.
- Метод
Enum.HasFlagвыполняет упаковку (boxing) входного значения.
Общие правила кода
- Запрещено использование любых магических чисел и строк в коде.
- Не злоупотреблять операторами
break,returnиcontinueв циклах и методах. - Перед вызовом события всегда проверять наличие подписчиков или использовать безопасный вызов события.
- По умолчанию при объявлении класса выставлять ключевое слово
sealed. - Запрещается для события создавать свой собственный делегат.
- Предпочтительно использовать expression body вместо однострочных тел.
- Запрещается оставлять пустые тела методов, кроме случаев, когда метод предназначен для перегрузки в наследниках.
- При явном переопределении метода всегда вначале вызывается базовый функционал.
- В редких исключениях базовый функционал вызывается в конце метода.
- Запрещается использовать публичные изменяемые поля.
- Для внешнего доступа использовать свойства с ограниченной записью или методы изменения состояния.
- Перегружать методы необходимо только тогда, когда они совпадают по назначению и различаются только входными параметрами.
- Метод или конструктор не должен принимать более 6 параметров.
- Запрещено в одной строке объявлять более одной переменной.
- Для каждого значения
enumнеобходимо явно указывать числовое значение. - Любое перечисление должно содержать
None. - Если это
int-перечисление, тоNoneдолжен соответствовать0. - Битовые
enumс[Flags]использовать только для масок и наборов независимых признаков, которые могут комбинироваться. - Классы с методами расширения допускается объявлять
partial. - Нужно избегать разделения объявления и инициализации локальных переменных.
- Локальные методы рекомендуется объявлять внизу метода.
- Запрещено использовать ключевое слово
unsafeбез явного технического обоснования. - Следует отдавать предпочтение перегрузке метода или конструктора для аргументов метода, а не использованию параметров по умолчанию.
- Импортируйте все пространства имен, которые используете в коде.
Float
- Следует избегать проверки
float-переменных на неравенство. - При необходимости использовать функцию
Mathf.Approximately(). - Оператор
!=для сравненияfloatиспользовать запрещено.
Switch
Оператор switch обязательно должен содержать default case, который:
- заканчивается
break; - расположен в самом низу
switch.
Форматирование
- Рекомендуется использовать IDE Rider.
- Использовать пробелы вместо табуляции.
- Размер отступа - 4 пробела.
- Форматирование должно быть закреплено в
.editorconfigи применяться через Rider. - Все регионы пишутся в формате
PascalCase. - В конструкции
if/elseпишемelseна новой строке без пропуска на пустую строку. - Запрещено использовать expression-body для реализации методов, если получившаяся строка кода занимает более одной строки или более 120 символов.
Пустые строки
- Между методами - 1 пустая строка.
- Между подключенными пространствами имен и объявлением класса или namespace - 1 пустая строка.
- Между методами и полями, свойствами или конструктором - 2 пустые строки.
- Между регионами - 2 пустые строки.
- Внутри региона отступ от директивы
region- 1 пустая строка. - Между методами, свойствами или классами внутри региона - 1 пустая строка.
- Между смысловыми блоками кода внутри метода или в полях - 1 пустая строка.
Перенос длинных строк
Длинные строки кода, более 120 символов, необходимо разносить на несколько строк.
При переносе придерживаться правил:
- Перенос строки должен быть после оператора.
- Перенос строки должен быть после запятой.
- Каждая строка, начиная со второй, должна находиться на следующем уровне табуляции по сравнению с первой строкой.
Комментарии
- Комментарии, как и весь код, должны быть написаны исключительно на английском языке.
- Размещайте комментарии на отдельной строке, а не в конце строки кода.
- Начинайте текст комментария с заглавной буквы.
- Между
//и текстом должен быть один пробел. - Не создавайте форматированные блоки звездочек вокруг комментариев.
Пример:
// The following declaration creates a query. It does not run the query.