В прошлом уроке мы говорили о том, что чистый код — это код, который легко читается и поддерживается. Звучит красиво, но с чего конкретно начать? Ответ проще, чем кажется: с имён.
Представьте: вы приходите на новый проект — студия разрабатывает игру. Первый рабочий день, вы открываете проект, находите главный игровой класс и видите вот это:
public class pc
{
public string N;
public int hp;
public int x;
public int y;
public void Do()
{
hp = hp - 10;
if (hp < 0) hp = 0;
}
public bool Check()
{
return hp > 0;
}
}
Что такое pc? Player Character? Personal Computer? Что за N? Имя? Никнейм? Номер? Что делает Do()? Что именно проверяет Check()? У вас десятки вопросов — и ни одного ответа в коде.
Вы оборачиваетесь к коллеге за соседним столом. Это Фёдор — он здесь уже третий год.
Фёдор: А, это классика. Предыдущий разработчик писал так, будто за каждый символ платил из зарплаты. Мы полдня потратили, пока разобрались, что
Do()— это получение урона, аCheck()— проверка, жив ли персонаж. Именно поэтому существуют соглашения об именовании.
И Фёдор прав. Соглашения об именовании — это как дорожные знаки: если все следуют одним правилам, никто не заблудится. Код читается как текст, а не как шифровка. Давайте разберёмся, какие «знаки» приняты в C#. Начнём с самого фундаментального — стилей записи имён. В C# их два основных.
Название пришло из языка Pascal, где этот стиль был стандартом. C# унаследовал его и сделал основным для большинства элементов кода.
Почему «верблюжий»? Посмотрите на слово playerScore — заглавные буквы посередине торчат вверх, как горбы верблюда. Отсюда и название.
Правило простое: PascalCase — для «больших» вещей (классы, методы, свойства), camelCase — для «маленьких» (локальные переменные, параметры, приватные поля). Скоро мы разберём это подробнее.
Есть ещё несколько стилей, которые вам могут встретиться, хотя в современном C# они почти не используются:
snake_case — слова разделяются подчёркиваниями: player_score, max_value. Любимый стиль Python и SQL, но в C# он не прижился.
SCREAMING_SNAKE_CASE — тот же snake_case, но капсом: MAX_SPEED, DEFAULT_SIZE. Часто используется для констант в C/C++. В C# Microsoft рекомендует писать константы в PascalCase, но некоторые команды по привычке используют «кричащий» стиль.
Венгерская нотация — к имени добавляется префикс с типом: strName, iCount, btnStart. Когда-то это было необходимо, потому что IDE не подсказывали типы. Сегодня это рудимент — в C# язык строго типизирован, а IDE показывает всё сама.
Теперь давайте разберёмся, что именно как называть. Вместо того чтобы перечислять двенадцать правил подряд, сгруппируем их по логике.
Помните класс pc, от которого вы пришли в ужас? Фёдор говорит:
Фёдор: Смотри, принцип простой. Всё, что является «большой сущностью» — классы, интерфейсы, методы, свойства, события, перечисления — пишется в PascalCase. Всё, что «внутренняя мелочь» — локальные переменные, параметры, приватные поля — в camelCase. Есть пара специальных правил, но их немного.
Давайте посмотрим, как выглядит класс, написанный по всем соглашениям. Вот тот самый pc, только по-человечески:
public class PlayerCharacter // Класс — PascalCase
{
private string _name; // Приватное поле — _camelCase
private int _health;
public string Name { get; set; } // Свойство — PascalCase
public int Health { get; private set; }
public bool IsAlive => Health > 0; // Свойство — PascalCase
public event Action<int> DamageTaken; // Событие — PascalCase
public void TakeDamage(int damageAmount) // Метод — PascalCase, параметр — camelCase
{
int newHealth = Health - damageAmount; // Локальная переменная — camelCase
Health = newHealth < 0 ? 0 : newHealth;
DamageTaken?.Invoke(damageAmount);
}
}
Видите, как всё сразу встало на свои места? Каждое имя говорит само за себя: PlayerCharacter — не загадочный pc, TakeDamage — не таинственный Do(), IsAlive — не непонятный Check().
Теперь — специальные правила. Их всего два:
Интерфейсы всегда начинаются с буквы I:
public interface IMovable // I + PascalCase
{
void Move(float deltaTime);
}
public interface IDamageable
{
void TakeDamage(int amount);
}
Обобщённые параметры (Generics) начинаются с буквы T:
public class Repository<TEntity> // T + PascalCase
{
public TEntity Find(int id) { ... }
}
public interface IFactory<TProduct>
{
TProduct Create();
}
Для наглядности — сводная таблица всех соглашений:
| Элемент | Стиль | Пример |
| --- | --- | --- |
| Пространство имён | PascalCase | GameStudio.Engine.Physics |
| Класс / Структура | PascalCase | PlayerCharacter |
| Интерфейс | I + PascalCase | IMovable |
| Метод | PascalCase | TakeDamage() |
| Свойство | PascalCase | Health, IsAlive |
| Событие | PascalCase | DamageTaken |
| Делегат | PascalCase | DamageHandler |
| Перечисление (enum) | PascalCase | GameState.Playing |
| Обобщённый параметр | T + PascalCase | TEntity, TProduct |
| Публичное поле | PascalCase | Score |
| Приватное поле | _camelCase | _health |
| Локальная переменная | camelCase | newHealth |
| Параметр метода | camelCase | damageAmount |
Со стилями разобрались. Но правильный регистр — это только половина дела. Имя может быть в идеальном PascalCase и при этом оставаться бесполезным. Фёдор заглядывает в ваш первый коммит и начинает код-ревью:
Фёдор: Слушай, у тебя тут метод
Process(). Что именно он «процессит»? Загружает уровень? Обрабатывает урон? Сохраняет данные? Имя должно отвечать на вопрос что именно делает код.
И Фёдор прав. Вот главные принципы хороших имён:
Имя должно раскрывать намерение. Хорошее имя отвечает на вопрос «Что это?» или «Что делает?» без заглядывания в реализацию.
// Плохо — приходится гадать
int value = 10;
void Process() { }
// Хорошо — всё понятно с первого взгляда
int maxHealth = 100;
void LoadLevel(string levelName) { }
Не сокращайте — если только это не общепринятая аббревиатура вроде HTTP, UI, API.
// Плохо — экономия на спичках
int plrSpd = 5;
string crtPlr = "Alex";
// Хорошо — читается как текст
int playerSpeed = 5;
string currentPlayer = "Alex";
Если сомневаетесь, сокращать или нет, — не сокращайте. Длинное понятное имя всегда лучше короткого непонятного.
Избегайте двусмысленности. Имя не должно допускать два толкования.
// Плохо — что конкретно значит "обработать"?
void ProcessPlayer(Player player) { }
// Хорошо — сразу ясно
void ValidatePlayer(Player player) { }
void SavePlayer(Player player) { }
Не злоупотребляйте словами-пустышками. Слова Manager, Data, Info, Handler кажутся универсальными, но часто ничего не добавляют к смыслу.
// Плохо — три слова, ноль информации
public class PlayerInfoDataManager { }
// Хорошо — конкретно и лаконично
public class PlayerStats { }
Фёдор: И ещё — никаких магических чисел. Если я вижу
speed2илиamountX, я не знаю, что за «2» и что за «X». НазовиbonusSpeedиdamageMultiplier— и вопросов не будет.
// Плохо — магические имена
int speed2 = 10;
int amountX = 3;
// Хорошо — имя объясняет роль
int bonusSpeed = 10;
int damageMultiplier = 3;
Аббревиатуры. Если аббревиатура длиннее двух букв, в PascalCase только первая буква заглавная: HttpClient, JsonParser, ApiService. Если аббревиатура из двух букв, обе остаются заглавными: UIManager, IOStream.
Помните тот ужасный класс pc, с которого мы начали? Давайте посмотрим на него ещё раз — и рядом положим отрефакторенную версию:
// БЫЛО — код-загадка
public class pc
{
public string N;
public int hp;
public int x;
public int y;
public void Do()
{
hp = hp - 10;
if (hp < 0) hp = 0;
}
public bool Check()
{
return hp > 0;
}
}
// СТАЛО — код, который говорит сам за себя
public class PlayerCharacter
{
public string Name { get; set; }
public int Health { get; private set; }
public int PositionX { get; set; }
public int PositionY { get; set; }
public bool IsAlive => Health > 0;
public void TakeDamage(int damageAmount)
{
Health -= damageAmount;
if (Health < 0) Health = 0;
}
}
Ни одного комментария — и всё абсолютно понятно. TakeDamage — получает урон. IsAlive — жив ли персонаж. Health — здоровье. Код читается как текст. Это и есть сила хороших имён.
Ссылки на официальную документацию Microsoft:
- .NET Naming Guidelines — главный документ с правилами именования в .NET.
- C# Coding Conventions — практичный набор соглашений по оформлению кода.
PascalCase — каждое слово с заглавной буквы; используется для классов, интерфейсов, методов, свойств, событий, перечислений.
camelCase — первое слово с маленькой буквы, остальные с заглавной; используется для локальных переменных, параметров и приватных полей.
Приватные поля — именуются с подчёркиванием в начале: _health.
Интерфейсы — всегда начинаются с буквы I: IMovable, IDamageable.
Обобщённые параметры — начинаются с буквы T: TEntity, TProduct.
Хорошее имя — раскрывает намерение, не содержит сокращений и не допускает двусмысленности.
Соглашения об именовании — общепринятые правила, которые делают код предсказуемым и читаемым для всей команды.