Conventions¶
C# Coding Standards and Naming Conventions¶
Названия объекта | Нотация | Длина | Множественное число | Префиксы | Суффиксы | Аббревиатуры** | Используемые символы | Подчеркивания |
---|---|---|---|---|---|---|---|---|
Класс | PascalCase | 128 | Нет | Нет | Да | Нет | [A-z][0-9] | Нет |
Конструктор | PascalCase | 128 | Нет | Нет | Да | Нет | [A-z][0-9] | Нет |
Метод | PascalCase | 128 | Да | Нет | Нет | Нет | [A-z][0-9] | Нет |
Аргументы метода | camelCase | 128 | Да | Нет | Нет | Да | [A-z][0-9] | Нет |
Локальные переменные | camelCase | 50 | Да | Нет | Нет | Да | [A-z][0-9] | Нет |
Константы | PascalCase | 50 | Нет | Нет | Нет | Нет | [A-z][0-9] | Нет |
Поля | camelCase | 50 | Да | Нет | Нет | Да | [A-z][0-9] | Да |
Свойства | PascalCase | 50 | Да | Нет | Нет | Да | [A-z][0-9] | Нет |
Делегаты | PascalCase | 128 | Нет | Нет | Да | Да | [A-z] | Нет |
Enum | PascalCase | 128 | Да | Нет | Нет | Нет | [A-z] | Нет |
Аббревиатуры можно использовать, если они приняты в индустрии и у нас в компании.
Использовать PascalCasing для имен классов и методов¶
public class ClientActivity
{
public void ClearStatistics()
{
//...
}
public void CalculateStatistics()
{
//...
}
}
Почему: следование code-style Microsoft в .NET Framework и упрощение чтения.
Использовать camelCasing для аргументов методов и локальных переменных:¶
public class UserLog
{
public void Add(LogEvent logEvent)
{
int itemCount = logEvent.Items.Count;
// ...
}
}
Почему: следование code-style Microsoft в .NET Framework и упрощение чтения.
Не использовать венгерскую нотацию (Hungarian notation) или другой способ указания типа в названии¶
// Правильно
int counter;
string name;
// Неправильно
int iCounter;
string strName;
Почему: следование code-style Microsoft в .NET Framework и IDE сама помогает показывать типы с помощью подсказок (например, в VS через InteliSense). Следует в принципе избегать указания типа в любом идентификаторе
Не использовать caps для констант и readonly переменных:¶
// Правильно
public const string ShippingType = "DropShip";
// Неправильно
public const string SHIPPINGTYPE = "DropShip";
Почему: следование code-style Microsoft в .NET Framework. Caps привлекает слишком много внимания.
Использовать осмысленные названия для переменных. Например seattleCustomers лоя заказчиков из Сиэтла:¶
var seattleCustomers = from customer in customers
where customer.City == "Seattle"
select customer.Name;
Почему: следование здравому смыслу, code-style Microsoft в .NET Framework и упрощение чтения.
6. Избегать использования аббревиатур. Исключения: аббревиатуры, широко используемые как названия, например Id, Xml, Ftp, Uri; наши собственные аббревиатуры (SSNG, DEX)¶
// Правильно
UserGroup userGroup;
Assignment employeeAssignment;
// Неправильно
UserGroup usrGrp;
Assignment empAssignment;
// Exceptions
CustomerId customerId;
XmlDocument xmlDocument;
FtpHelper ftpHelper;
UriPart uriPart;
Почему: следование code-style Microsoft в .NET Framework и предотвращение несогласованных сокращений, чтобы не было разногласий в понимании
Использовать PascalCasing для аббревиатур от 3 символов и больше:¶
HtmlHelper htmlHelper;
FtpTransfer ftpTransfer;
UIControl uiControl;
Исключения: SSNG
Почему: следование code-style Microsoft в .NET Framework. CCaps привлекает слишком много внимания.
Не использовать нижнее подчеркивание в идентификаторах. Исключения: разделение префикса в приватных полях; naming тестовых методов¶
// Правильно
public DateTime clientAppointment;
public TimeSpan timeLeft;
// Неправильно
public DateTime client_Appointment;
public TimeSpan time_Left;
// Исключение (поле класса)
private DateTime _registrationDate;
Почему: следование code-style Microsoft в .NET Framework.
Использовать предопределенные названия типов (C# алиасы), например, int
, float
, string
для локальных переменных и объявления параметров типа. Использовать String
при доступе к статическим полям класса: or String.Join
¶
// Правильно
string firstName;
int lastIndex;
bool isSaved;
string commaSeparatedNames = String.Join(", ", names);
int index = Int32.Parse(input);
// Неправильно
String firstName;
Int32 lastIndex;
Boolean isSaved;
string commaSeparatedNames = string.Join(", ", names);
int index = int.Parse(input);
Почему: следование code-style Microsoft в .NET Framework и поддержка более читаемого кода.
Использовать var
для определения типа локальных переменных¶
var stream = File.Create(path);
var customers = new Dictionary();
var index = 100;
string timeSheet;
bool isCompleted;
Почему: удаляет беспорядок, особенно со сложными универсальными типами. Тип легко подсказывает IDE. Более легкий рефакторинг.
Использовать существительные или именные фразы для названий классов¶
public class Employee
{
}
public class BusinessLocation
{
}
public class DocumentCollection
{
}
Почему: следование code-style Microsoft в .NET Framework и более легкое запоминание.
Добавлять к интерфейсу префикс в виде букву I. Имена интерфейсов - существительные (или) или прилагательные.¶
public interface IShape
{
}
public interface IShapeCollection
{
}
public interface IGroupable
{
}
Почему: следование code-style Microsoft в .NET Framework.
Называть файлы так же, как и основной класс в файле. Исключения: названия файлов с partial
классами должны отражать свою специфику, например. designer, generated, и т.д.. В целом один публичный класс - один файл.¶
// Расположен в Task.cs
public partial class Task
{
}
// Расположен Task.generated.cs
public partial class Task
{
}
Почему: соответствует практикам Microsoft. Файлы сортируются в проекта в алфавитном порядке, partial классы идут рядом.
Namespace должны обладать четко понятной структурой:¶
// Examples
namespace Company.Product.Module.SubModule
{
}
namespace Product.Module.Component
{
}
namespace Product.Layer.Module.Group
{
}
Почему: следование code-style Microsoft в .NET Framework. Поддержка хорошей организации проекта.
Фигурные скобки выравнивать вертикально:¶
// Правильно
class Program
{
static void Main(string[] args)
{
//...
}
}
Почему: Мы не на Java пишем
Придерживаться следующей структуры классов:¶
- константы и static readonly поля
- все зависимости класса
- все поля класса
- все свойства класса
- делегаты и события
- конструктор
- методы.
// Правильно
public class Account
{
#region Константы
private const int MaxValue = 10;
public static readonly int EntityTypeId = 123;
# endregion
#region Зависимости
private readonly IDependency _dependecy;
#endregion
#region Поля
private static string _bankName;
private static decimal _reserves;
#endregion
#region Свойства
public string Number { get; set; }
public DateTime DateOpened { get; set; }
public DateTime DateClosed { get; set; }
public decimal Balance { get; set; }
#endregion
#region Делегаты и события
public event EventHandler SomethingHappened;
#endregion
// Constructor
public Account()
{
// ...
}
// тут идут методы
}
Почему: Общепринятая практика, которая предотвращает необходимость поиска объявлений переменных и прочего.
Использовать свойства, а не поля класса¶
Свойства имеют больше преимуществ:
- изменение функциональности без изменения API;
- возможность определить свойства в интерфейса;
- свойства могут быть readonly для внешних потребителей;
- возможность поставить break point в коде свойства:
- внутри них можно вызывать события
С точки зрения производительности, свойства совсем незначительно отличаются от полей класса. Пруф есть в разделе Производительность
Использовать слова в единственном числе для enum. Исключение: битовые маски.¶
// Правильно
public enum Color
{
Red,
Green,
Blue,
Yellow,
Magenta,
Cyan
}
// Исключение
[Flags]
public enum Dockings
{
None = 0,
Top = 1,
Right = 2,
Bottom = 4,
Left = 8
}
Почему: следование code-style Microsoft в .NET Framework, создание более читаемого кода. Для флагов используется множественная форма, потому что они хранят несколько значений.
Не указывать явно тип, от которого наследуется enum (исключение - битовые флаги):¶
// Неправильно
public enum Direction : long
{
North = 1,
East = 2,
South = 3,
West = 4
}
// Правильно
public enum Direction
{
North,
East,
South,
West
}
Почему: может создавать путаницу при использовании фактических типов и значений
Не используйте суффикс "Enum" в именах типов перечислений:¶
// Неправильно
public enum CoinEnum
{
Penny,
Nickel,
Dime,
Quarter,
Dollar
}
// Правильно
public enum Coin
{
Penny,
Nickel,
Dime,
Quarter,
Dollar
}
Почему: следование code-style Microsoft в .NET Framework и в соответствии с предыдущим правилом отсутствия индикаторов типа в идентификаторах.
Не используйте суффиксы "Flag" или "Flags" в именах типов перечислений:¶
// Не использовать
[Flags]
public enum DockingsFlags
{
None = 0,
Top = 1,
Right = 2,
Bottom = 4,
Left = 8
}
// Правильно
[Flags]
public enum Dockings
{
None = 0,
Top = 1,
Right = 2,
Bottom = 4,
Left = 8
}
Почему: следование code-style Microsoft в .NET Framework и в соответствии с предыдущим правилом отсутствия индикаторов типа в идентификаторах.
Проверка флагов через метод HasFlag
:¶
// Не использовать
var hasFlag = (questionFlags & QuestionFlags.HidePreviousButton) != 0;
// Правильно
questionFlags.HasFlag(QuestionFlags.HidePreviousButton)
Почему: Более читаемый код.
Раньше советовали делать проверку наличия флага через битовую операцию, т.к. работало быстрее, чем HasFlag. Но в .NET Core 3.1 это изменилось, HasFlag работает также быстро (пруф: https://www.code4it.dev/blog/hasflag-performance-benchmarkdotnet).
Для enum'ов, которые сохраняются в базу или передаются по сети, нужно первым делать значение Unknown=0
или None=0
, для остальных значений проставлять числовые значения:¶
// Правильно
public enum Dockings
{
None = 0,
Top = 1,
Right = 2,
Bottom = 4,
Left = 8
}
Почему: поддержка конситентности значений.
Использовать суффикс EventArgs при создании новых классов, содержащих информацию о событии:¶
// Правильно
public class BarcodeReadEventArgs : System.EventArgs
{
}
Почему: следование code-style Microsoft в .NET Framework и упрощение чтения.
Добавлять названиям обработчикам событий (делегатам, используемые в качестве типов событий) суффикс" EventHandler":¶
public delegate void ReadBarcodeEventHandler(object sender, ReadBarcodeEventArgs e);
Почему: следование code-style Microsoft в .NET Framework и упрощение чтения.
Не создавать имена параметров в методах (или конструкторах), которые отличаются только регистром:¶
// Неправильно
private void MyFunction(string name, string Name)
{
//...
}
Почему: следование code-style Microsoft в .NET Framework, легкое чтение и отсутствия конфликта в понимании.
Использовать суффикс Exception при создании новых классов, содержащих информацию об исключении:¶
// Правильно
public class BarcodeReadException : System.Exception
{
}
Почему: следование code-style Microsoft в .NET Framework и упрощение чтения.
При использовании fluent api
(длинная цепочка вызовов, LINQ
) отдельные методы писать с новой строк:¶
// Правильно
public void UpdateData()
{
var (using context = _contextFactory.Create())
{
var data = context
.Data
.Where(x => x.Id > 100)
.ToList();
// и что-то еще делается
}
}
Почему: Легкое чтение, легкий merge.
Если аргументов больше 2 или у них длинные названия типов и самого аргумента, переносить каждый на новую строку:¶
// Правильно
public void UpdateData(
[NotNull] NyLongNameData data,
[NotNull] SomethingTerrable terrableData,
int index,
DateTime timestamp
)
{
// и что-то еще делается
}
Почему: упрощение чтения, быстрое разделение аргументов глазами, легкий merge.
Логические проверки с множеством условий разбивать построчно:¶
// Правильно
if (condition1 &&
condition2 &&
condition3)
{
// что-то делаем
}
Почему: упрощение чтения, быстрое разделение аргументов глазами, легкий merge.
В параметрах аргумента аттрибут идет рядом с типом, при объявлении результата значения метода, свойства или поля класса - сверх:¶
// Правильно
[NotNull]
private Data _data;
// Неправильно
[CanBeNull] private Data _oldData;
// Правильно
[CanBeNull]
public Data UpdateData(
[NotNull] NyLongNameData data,
[NotNull] SomethingTerrable terrableData,
int index,
DateTime timestamp
)
{
// и что-то еще делается
}
Почему: упрощение чтения
Для асинхронных методов добавлять суффикс Async:¶
// Правильно
public Task<Data> UpdateDataAsync(
[NotNull] NyLongNameData data,
[NotNull] SomethingTerrable terrableData,
int index,
DateTime timestamp
)
{
// и что-то еще делается
}
Почему: следование code-style Microsoft в .NET Framework и упрощение чтения.
Все аргументы публичных методов и конструкторов должны проверяться:¶
// Правильно
public Task<Data> UpdateDataAsync(
[NotNull] NyLongNameData data,
[NotNull] SomethingTerrable terrableData,
int index,
DateTime timestamp
)
{
if (data == null) throw new ArgumentNullException(nameof(data));
if (!data.IsReady) throw new InvalidOperationException(nameof(data));
// и что-то еще делается
}
Почему: Безопасность - превыше всего. Метод гарантированно работает, если с данными все ок. Иначе падает сразу.
Методы стоит располагать в порядке их вызове друг другом¶
// Правильно
public void DoFirst()
{
// some logic
PrepareData();
// yet another logic
DoLast();
}
private void PrepareData()
{
// some logic
}
public void DoLast()
{
// some logic
}
Почему: Код легко читается, логика потока выполнения более четкая
Код должен быть задокументирован¶
Как минимум, все публичные контракты. Следуем гайду от Microsoft
Также оставляем комменты внутри методов.
Не каждый поймет нашу гениальность.
Между блоками кода должен быть максимум отступ в одну строку¶
Двойные и более пустые строки - недопустимы, а одиночные - нужны, для смыслового выделения каких-то моментов.
Создается ощущение неаккуратности или что был вырезан фрагмент кода, не факт что верно, либо что какой-то акцент важный, что тоже пугает.
Не использовать однострочный using
¶
В C# 8.0 появилась возможность вместо блоки using
делать однострочный using
.
Когда мы используем using
по-старинке, то у нас метод Dispose
будет вызван в конце блока using
. С однострочным using
конец блока растягивается до конца текущей области видимости, т.е. обычно до конца метода. Это плохо, ресурсы нужно освобождать сразу, как они перестали нам быть нужны.
Сокращение уровня вложенности кода за счет менее эффективного использование ресурсов - сомнительная оптимизация. Использовать однострочную версию только в крайней необходимости.