Unity 6.3
0 онлайн 67 гостей 3 в системе
Вход
Руководство по стилю C# для чистого и масштабируемого кода Глава 4 из 13 Оригинал, стр. 11

Соглашения об именовании

Соглашения об именовании

В выборе имени заключено больше смысла, чем кажется. Имя показывает, какое место сущность занимает в системе: что это, к чему относится и какую роль выполняет. Имена переменных, классов и методов — не просто метки: они несут смысл. Удачные соглашения об именовании напрямую влияют на то, насколько легко читатель программы поймет заложенную в нее идею. При выборе имен учитывайте следующие рекомендации.

Имена идентификаторов Идентификатор — это любое имя, присвоенное типу (классу, интерфейсу, структуре, делегату или перечислению), члену типа, переменной либо пространству имен. Хотя C# допускает специальные знаки, символы Unicode и обратную косую черту в идентификаторах, избегайте их. Они могут мешать работе некоторых инструментов командной строки Unity. Необычные символы также снижают совместимость с разными платформами.

Стили регистра В имени переменной нельзя использовать пробелы: C# разделяет ими идентификаторы. Для составных имен и фраз в исходном коде применяют разные стили регистра. Существует несколько общепринятых соглашений об именовании и регистре.

Стиль camelCase (camelCase) В стиле camelCase фразы записывают без пробелов и знаков препинания, а каждое следующее слово начинают с прописной буквы. Первая буква остается строчной. Так оформляют локальные переменные и параметры методов. Например: examplePlayerController maxHealthPoints endOfFile

Стиль PascalCase (PascalCase) PascalCase — разновидность camelCase, в которой первая буква тоже прописная. При разработке на Unity этот стиль используют для имен классов, открытых полей и методов. Например:

ExamplePlayerController MaxHealthPoints EndOfFile

Стиль snake_case (snake_case) В этом стиле пробелы между словами заменяют символами подчеркивания. Например: example_player_controller max_health_points end_of_file

Стиль kebab-case (kebab-case) Здесь пробелы между словами заменяют дефисами, словно нанизывая слова на «шампур». Например: example-player-controller Max-health-points end-of-file naming-conventions-methodology Стиль kebab-case широко применяется в веб-технологиях, особенно в CSS. Мы также рекомендуем использовать его в USS для UI Toolkit, о чем подробнее расскажем далее.

Венгерская нотация В таком имени переменной или функции обычно кодируется ее назначение либо тип. Например:

int iCounter string strPlayerName Венгерская нотация — устаревшее соглашение, которое редко применяется при разработке на Unity.

Поля и переменные При именовании переменных и полей соблюдайте следующие правила: — Используйте существительные для имен переменных. Имя должно быть содержательным, понятным и однозначным, поскольку переменная обозначает объект или состояние. Исключение составляют переменные типа bool, о которых сказано ниже. — Начинайте имена логических переменных с глагола. Такие переменные содержат true или false и часто отвечают на вопрос: бежит ли игрок, окончена ли игра? Глагол делает смысл имени очевиднее. Обычно за ним следует описание или условие, например isDead, isWalking или hasDamageMultiplier. — Выбирайте осмысленные имена и не сокращайте их без необходимости, кроме общепринятых математических обозначений. Имя должно ясно выражать назначение, легко произноситься и находиться поиском. Это важно не только для коллег: содержательные имена дают больше контекста инструментам ИИ и помогают им точнее генерировать код и рекомендации. Например, свойство HorizontalAlignment читается лучше, чем AlignmentHorizontal.

Однобуквенные переменные допустимы в циклах и математических выражениях, но в остальных случаях не сокращайте имена. Ясность важнее нескольких секунд, сэкономленных на пропущенных буквах.

При прототипировании возникает соблазн использовать короткие бессодержательные имена, однако это не сэкономит время, если позднее код придется рефакторить. Выбирайте осмысленные имена с самого начала.

Не рекомендуется

Рекомендуемый вариант

Примечания

C#
int d
C#
int elapsedTimeInDays

Избегайте однобуквенных сокращений, кроме счётчиков и выражений. Указывайте единицу измерения.

C#
int hp, int hp,tName, string int mvmtSpeed
C#
int healthPoints, int healthPoints, string teamName, int movementSpeed string teamName,
C#
int mvmtSpeed
int movementSpeed

Имена переменных должны передавать смысл. Выбирайте легко произносимые имена, по которым удобно выполнять поиск.

C#
int getMovementint SpeedgetMovementSpeed
C#
int movementSpeed

Используйте существительные. Глаголы оставляйте для методов, кроме логических переменных (см. ниже).

C#
bool dead bool dead
C#
bool isDead, bool isDead, bool isPlayerDead

Имена логических переменных формулируйте как вопрос, на который можно ответить true или false.

C#
string tName,
C#
bool isPlayerDead

— Используйте PascalCase (MyPropertyName) для открытых полей, а camelCase (myPrivateVariable) — для закрытых переменных. Вместо открытых полей можно применять свойства с открытым методом доступа get (см. раздел «Форматирование» ниже). — Рассмотрите возможность использования префиксов или иной системы обозначений. Некоторые руководства советуют начинать имена закрытых полей с подчеркивания (_), чтобы отличать их от локальных переменных. В наших руководствах используются префиксы m_ для закрытых полей, k_ для констант и s_ для статических переменных: так назначение переменной видно сразу. Например, movementSpeed превращается в m_movementSpeed. Допустимо сочетать префикс с PascalCase, например m_MovementSpeed, однако в современном C# такой вариант встречается реже. Другой вариант — различать поля и локальные переменные по ключевому слову this и отказаться от префикса. У открытых полей и свойств префиксов обычно нет. Локальные переменные и параметры оформляют в camelCase без префикса. Многие разработчики отказываются от префиксов и полагаются на редактор: современные IDE поддерживают подсветку, цветовое оформление и подробную контекстную информацию.

— Поля автоматически получают значения по умолчанию. Для числовых типов, например int, это обычно 0; поля ссылочных типов, например объектов, инициализируются значением null, а поля bool — значением false. Поэтому явно присваивать полю его значение по умолчанию обычно не требуется.

— Именуйте константы в PascalCase и добавляйте префикс k_. Это позволяет отличать их от обычных переменных и свойств, а также упрощает чтение и сопровождение кода.

C#
// ПРИМЕР: константы

public class MathConstants { public const int k_MaxItems = 100; }

— Последовательно указывайте или опускайте модификаторы доступа. Если модификатор не задан, компилятор считает уровень доступа private. Это допустимо, но выбранного подхода нужно придерживаться во всем коде. В рекомендациях Microsoft предлагается явно указывать private, чтобы уровень доступа был очевиден и не возникало неоднозначности. Другие руководства советуют опускать избыточные модификаторы (не писать private в области типа) и инициализаторы (например, = 0 для int и = null для ссылочных типов). Помните: если член позднее понадобится подклассу, потребуется protected. Мы рекомендуем ради простоты опускать неявные и потому избыточные элементы, такие как private, если это не ухудшает читаемость для вашей команды.

— Ставьте читаемость выше краткости. Как показывает пример из документации Microsoft, имя свойства CanScrollHorizontally лучше, чем ScrollableX, поскольку в имени ScrollableX неясно, что X обозначает горизонтальную ось.

Примеры фрагментов кода Фрагменты кода в этом руководстве сокращены и не предназначены для выполнения. Они демонстрируют только стиль и форматирование. Можно также обратиться к этому примеру руководства по стилю C# для разработчиков Unity — измененной версии Microsoft Framework Design Guidelines. Это лишь один из возможных вариантов организации командного руководства. Просмотрите каждое правило примера и адаптируйте его к предпочтениям команды. Детали отдельного правила менее важны, чем общее согласие соблюдать его последовательно. При разногласиях в вопросах стиля опирайтесь на руководство своей команды.

C#
// ПРИМЕР: открытые и закрытые переменные сгруппированы

public float DamageMultiplier = 1.5f;
public float MaxHealth;
public bool IsInvincible;
private bool m_isDead;
C#
private float m_currentHealth;
public void InflictDamage(float damage, bool isSpecialDamage) {
    // локальная переменная int totalDamage = damage;

    // локальная переменная и открытое поле if (isSpecialDamage) { totalDamage *= DamageMultiplier; }

    // локальная переменная и закрытое поле if (totalDamage > _currentHealth) { /// ... }

}

— Объявляйте по одной переменной в строке. Код получится менее компактным, но более читаемым.

— Избегайте избыточных имен. Если класс называется Player, его поля не нужно называть PlayerScore или PlayerTarget — достаточно Score и Target.

— Опускайте избыточные инициализаторы: не пишите = 0 для int, = null для ссылочных типов и тому подобное.

— Избегайте шуток и каламбуров. Имена вроде infiniteMonkeys или dudeWheresMyChar могут вызвать улыбку сейчас, но быстро утомят после нескольких десятков прочтений. Что еще важнее, они противоречат цели выбирать имена, раскрывающие контекст. — Избегайте неоднозначности и повышайте читаемость, но используйте var, когда тип очевиден из контекста. При удачных именах переменных намерение и так понятно. Рефакторинг с var проще: конкретный тип абстрагирован, поэтому при его изменении нужно обновлять меньше участков кода. В циклах foreach var гарантирует соответствие переменной итерации типу элементов, выдаваемых перечислителем. Явно указанный несовместимый тип компилятор иногда допускает, что приводит к ошибкам во время выполнения.

C#
// ПРИМЕР: уместное использование

var var powerUps = new List<PowerUps>();
var dictionary = new Dictionary<string, List<GameObject>>();
// НЕ РЕКОМЕНДУЕТСЯ: возможна неоднозначность

var powerUps = PowerUpManager.GetPowerUps();

Перечисления Перечисления — это особые типы значений, определенные набором именованных констант. По умолчанию константы имеют тип int и нумеруются начиная с 0. Оформляйте имена перечислений и их значений в PascalCase. Открытое перечисление можно объявить вне класса, сделав доступным глобально. Имя перечисления должно быть существительным в единственном числе, поскольку обозначает одно значение из набора. Не добавляйте к нему префиксы или суффиксы. Примечание. Исключение составляют битовые перечисления с атрибутом System.FlagsAttribute. Их имена обычно ставят во множественное число, поскольку значение может представлять сразу несколько вариантов.

C#
// ПРИМЕР: имя перечисления — существительное в единственном числе

public enum WeaponType { Knife, Gun, RocketLauncher, BFG }
public enum FireMode {
    None = 0, Single = 5, Burst = 7, Auto = 8,
}

// ПРИМЕР: имя перечисления флагов — во множественном числе (допустимо 1 << bitnum) [Flags] public enum AttackModes { // Десятичное

C#
// Двоичное

None = 0,

C#
// 000000

Melee = 1,

C#
// 000001

Ranged = 2,

C#
// 000010

Special = 4,

C#
// 000100

MeleeAndSpecial = Melee | Special

C#
// 000101

Классы и интерфейсы При именовании классов и интерфейсов соблюдайте следующие стандартные правила: — Именуйте классы существительными или именными словосочетаниями в PascalCase. Так имена типов отличаются от методов, которые называют глагольными выражениями. — Если файл содержит MonoBehaviour, имя исходного файла должно совпадать с именем этого класса. В файле могут находиться и другие внутренние классы, но MonoBehaviour должен быть только один.

— Начинайте имя интерфейса с прописной I, а затем добавляйте прилагательное, описывающее его назначение.

C#
// ПРИМЕР: оформление класса

public class ExampleClass : MonoBehaviour {
    public int PublicField;
    public static int MyStaticField;
    private int m_packagePrivate;
    private int m_myPrivate;
    private static int m_myPrivate;
    protected int m_myProtected;
    public void DoSomething() { }
}
// ПРИМЕР: интерфейсы

public interface IKillable { void Kill(); }
public interface IDamageable<T> { void Damage(T damageTaken); }

Методы В C# каждая выполняемая инструкция находится в контексте метода. Примечание. В разработке на Unity слова «функция» и «метод» нередко употребляют как синонимы. Однако в C# функцию нельзя написать вне класса, поэтому принят термин «метод».

Методы выполняют действия, поэтому именуйте их по следующим правилам: — Начинайте имя с глагола или глагольного выражения и при необходимости уточняйте контекст, например GetDirection или FindTarget. — Используйте camelCase для параметров. Оформляйте параметры метода так же, как локальные переменные. — Методы, возвращающие bool, должны задавать вопрос. Как и имена логических переменных, начинайте такие методы с глагола, чтобы имя выражало условие true или false, например IsGameOver или HasStartedTurn.

C#
// ПРИМЕР: имя метода начинается с глагола

public void SetInitialPosition(float x, float y, float z) {
    transform.position = new Vector3(x, y, z);
}
// ПРИМЕР: метод, возвращающий bool, задает вопрос

public bool IsNewPosition(Vector3 currentPosition) {
    return (transform.position == newPosition);
}

События и обработчики событий События C# реализуют паттерн «Наблюдатель». Он определяет отношения, при которых один объект — субъект, или издатель, — уведомляет зависимые объекты, называемые наблюдателями, или подписчиками. Так субъект сообщает наблюдателям об изменении состояния без жесткой связанности между объектами. Подробнее о «Наблюдателе» и других паттернах для проектов Unity рассказывается в электронной книге «Совершенствуйте код с помощью шаблонов проектирования и SOLID».

Для событий и связанных с ними методов субъекта и наблюдателей существует несколько схем именования. Рекомендуем следующие приемы: — Называйте событие глагольным выражением, точно передающим изменение состояния. Причастием настоящего или прошедшего времени обозначайте событие «до» или «после». Например, OpeningDoor — событие перед открытием двери, а DoorOpened — после него.

— Используйте для событий делегаты System.Action. В большинстве игровых сценариев достаточно Action либо обобщенных вариантов Action<T...>. Они позволяют передавать от 0 до 16 параметров разных типов и не возвращают значения (void). Готовые делегаты сокращают объем кода.

Примечание. Можно также использовать делегаты EventHandler и EventHandler<TEventArgs>. Команда должна заранее договориться о едином способе реализации событий.

C#
// ПРИМЕР: события // используется делегат System.Action

public event Action OpeningDoor;
C#
// событие «до»
C#
public event Action DoorOpened;
C#
// событие «после»
public event Action<int> PointsScored;
public event Action<CustomEventArgs> ThingHappened;

— Начинайте имя метода, вызывающего событие в субъекте, с On. Субъект обычно вызывает событие из метода с таким префиксом, например OnOpeningDoor или OnDoorOpened.

C#
// вызывает событие, если есть подписчики

public void OnDoorOpened() { DoorOpened?.Invoke(); }
public void OnPointsScored(int points) { PointsScored?.Invoke(points); }
— Рассмотрите схему, в которой имя метода обработки события в наблюдателе начинается с имени субъекта и символа подчеркивания (_). Если субъект называется GameEvents, методы наблюдателей могут называться GameEvents_OpeningDoor и GameEvents_DoorOpened. Такой метод называется методом обработки события;
не путайте его с делегатом EventHandler.

— Создавайте собственный EventArgs только при необходимости. Если событию нужно передавать пользовательские данные, определите новый тип EventArgs, унаследованный от System.EventArgs, либо пользовательскую структуру.

C#
// при необходимости определите EventArgs // ПРИМЕР: неизменяемая пользовательская структура для передачи ID и Color
C#
public struct CustomEventArgs {
    public int ObjectID { get; }
    public Color Color { get; }
    public CustomEventArgs(int objectId, Color color) {
        this.ObjectID = objectId;
        this.Color = color;
    }
}

Пространства имен Используйте пространства имен, чтобы классы, интерфейсы, перечисления и другие типы не конфликтовали с одноименными сущностями из других пространств имен или глобального пространства. Они также предотвращают конфликты со сторонними ресурсами из Asset Store. При использовании пространств имен: — Используйте PascalCase без специальных символов и подчеркиваний. — Добавляйте директиву using в начало файла, чтобы не повторять префикс пространства имен. — Создавайте вложенные пространства имен. Разделяйте уровни оператором точки (.), чтобы организовать скрипты по иерархическим категориям. Например, логические компоненты игры можно распределить между MyApplication.GameFlow, MyApplication.AI, MyApplication.UI и другими пространствами. — Некоторые разработчики строят пространства имен по структуре папок проекта. Логическая группировка связанных классов и компонентов упрощает поиск и понимание организации кодовой базы.

C#
namespace Enemy {
    public class Controller1 : MonoBehaviour { ... }
    public class Controller2 : MonoBehaviour { ... }
}

В коде эти классы обозначаются соответственно как Enemy.Controller1 и Enemy.Controller2. Чтобы не вводить префикс каждый раз, добавьте директиву using:

using Enemy; Встретив имена классов Controller1 и Controller2, компилятор поймет, что речь идет об Enemy.Controller1 и Enemy.Controller2. Если скрипт должен обращаться к одноименным классам из разных пространств имен, различайте их по префиксу. Например, если классы Controller1 и Controller2 находятся также в пространстве имен Player, указывайте полные имена Player.Controller1 и Player.Controller2, чтобы избежать конфликта. Иначе компилятор сообщит об ошибке.