-
Notifications
You must be signed in to change notification settings - Fork 37
Codestyle conventions
В данном документе описаны стандарты (требующие обязательного исполнения) и рекомендации (оставляющие свободу выбора) для кодирования на языке C#.
Введение этих правил преследует следующие цели:
- Обеспечить высокое функциональное качество программного кода
- Обеспечить высокую читаемость кода
- Обеспечить легкость сопровождения кода разными разработчиками
Правила, собранные здесь, являются компиляцией из многочисленных стандартов кодирования для C#, а также накопленного личного опыта. Хотя ни с одним из готовых стандартов эти правила не совпадают на 100%, большинство основных правил являются общепринятыми и рекомендованными подавляющим большинством стандартов. Отличия данных правил незначительны. Большинство правил соответствуют также соглашениям, принятым в стандартной библиотеке .NET Framework (хотя правила, принятые в ней, тоже несколько неоднородны).
Поэтому в случае, если вы не можете найти ответ на какой-то вопрос по форматированию или именованию в настоящих правилах, можете воспользоваться образцами из этих источников:
- Рекомендации от разработчиков среды SharpDevelop
- Рекомендации по кодированию команды RSDN
- Рекомендации по кодированию Lance
- Стандарт кодирования Phillips
Также рекомендуется следующая литература по теме:
- Стив Макконнелл. Совершенный код: практическое руководство по разработке программного обеспечения.
- Кржиштоф Цвалина, Брэд Абрамс. Инфраструктура программных проектов. Соглашения, идиомы и шаблоны для многократно используемых библиотек .NET
- Ведение проекта
- Культура
- Комментарии
- Именование
- Структура кода
- Инструкции и выражения (statements & expressions)
- Строки
- Числовые литералы
- Объявление и использование переменных
- Приведение типов
- Маппинг и деконструкция
- Ключевое слово this
- Правила логирования и формирования сообщения об ошибках
- Правила работы с файлами исходного кода
-
Не следует размещать в одном файле больше одного класса (за исключением, возможно, вложенных типов и типов делегатов).
-
В отсутствии противопоказаний имя файла должно совпадать с именем определенного в нем класса.
-
Предыдущее правило касается также перечислений и классов исключений.
Например, перечисление
enum MoveDirection
{
Up,
Down,
Left,
Right
}следует разместить в файле MoveDirection.cs
-
Если класс разросся до существенных размеров, допускается его разделение по принципу разделения интерфейсов. Использование **partial class **рекомендктолуется только в случаях использования существующих фреймворков (например, Windows Forms).
-
Структура директорий должна быть максимально приближена к структуре пространств имен.
Возможно отступать от этого правила, чтобы выделить в отдельную поддиректорию группу схожих классов, которые, тем не менее, не целесообразно выделять в отдельное пространство имен, т.к. они не отличаются существенно по функциональному признаку. Например, можно вынести нескольких классов исключительных ситуаций (Exceptions) в поддиректорию Exceptions без выделения подпространства имен Exceptions. Это позволит удобнее работать со списком файлов; в то же время, нелогично выделять для исключений свое пространство имен. Также бывает удобно вынести в отдельный подкаталог Enums все файлы перечислений, не выделяя их в отдельное пространство имен.
-
Старайтесь не располагать в одной директории очень много разнородных классов — выносите логически связанные классы в отдельные пространства имен/директории.
-
Все имена файлов, включенных в проект, должны, по возможности, именоваться с использованием PascalCase (первые буквы в словах заглавные, остальные — маленькие).
- Неиспользуемый код (проекты, файлы, классы, переменные и т.д.) в систему контроля версий попадать не должен.
При внесении исправлений в существующий код нужно придерживаться общепринятого форматирования. Если правки, вносимые в файл незначительны, рекомендуется сделать 2 коммита: 1) с полным форматирование файла, 2) с непосредственными правками. Также рекомендуется сообщить на ревью, какие именно части файла правились.
В целом правила форматирования кода соответствуют настройкам шаблона Resharper.
Resharper позволяет переформатировать выделенный фрагмент кода в соответствии с текущими настройками. Это можно сделать с помощью комбинации Ctrl+Alt+F.
- Стандартный отступ должен оформляться 2 (двумя) пробелами.
- Использование символа табуляции для отступов запрещается.
Для настройки этого правила в Visual Studio следует:
- Выполнить команду меню Resharper > Options
- Выбрать Code Editing > C# > **Formating Style >Tabs and Indents
** - Установить Indent size: 2, Tab Width: 2
- Выбрать **Indent Style = Spaces
**
- Длину строк рекомендуется ограничивать 90 символами.
- В любом случае длина строки не должна превышать 140 символов.
-
При переносе строк рекомендутся либо использовать один дополнительный стандартный отступ, либо выравнивать начало перенесенной строки относительно эелементов верхней строки для повышения читабельности кода.
-
Запятая остается в строке перед переносом.
-
Для методов с большим количеством параметров может применяться форматирование вида "строка на параметр", при этом полезно при передаче параметров со значением по умолчанию передавать default (для использования необходима версия C# не ниже 7.1):
...
server.SomeMethod(someParameter, someParameter, someParameter, someParameter,
anotherParameter, yetAnotherParameter);
...
server.SomeMethod(someParameter, someParameter, someParameter, someParameter,
anotherParameter, yetAnotherParameter);
...
PropertyInfo pinfo = type.GetProperty(
"Table",
BindingFlags.Public | BindingFlags.Static,
default,
typeof(DataTable),
default,
default);
...-
Методы и свойства класса рекомендуется отделять друг от друга одной пустой строкой.
-
Не рекомендуется использовать более двух пустых строк подряд
-
Хорошей практикой для повышения читаемости кода считается использование пустых строк для отделения шагов внутри тела метода (с одновременным комментированием этих шагов). Если шаги разрастаются – их удобно выделять в отдельный приватный метод.
Пример желательного оформления:
private string _someProperty;
public string SomeProperty
{
get { return _someProperty };
}
public void DoSomethingWork(Work workToBeDone)
{
// Проверки корректности аргументов_
if (workToBeDone == null)
{
throw new ArgumentNullException("workToBeDone");
}
// Пытаемся найти сотрудника, который должен выполнить работу...
Employee emp = FindEmployee(workToBeDone.WorkKind);
if (emp == null)
{
// Сотрудник не найден => работу выполнить не можем_
workToBeDone.Suspend();
}
else
{
// Выполняем работу при помощи найденного сотрудника
emp.DoWork(workToBeDone);
}
}Все инструкции следует располагать на отдельных строках. Допустимые исключения — простые get и set блоки свойств:
...
public Color ForegroundColor
{
get { return _foregroundColor; }
set { _foregroundColor = value; }
}
...А еще лучше делать так:
...
public Color ForegroundColor
{
get => return _foregroundColor;
set => _foregroundColor = value;
}
...Все нижеперечисленные правила соответствуют стандартной настройке Visual Studio 2017, так что вам почти не придется ничего делать, чтобы их соблюдать.
- Пробел ставится после ключевого слова управляющей инструкции (if / while / for /foreach и т.д.).
- Пробел ставится после запятой в перечне аргументов, после точки с запятой в инструкции for.
- Пробел ставится после открывающей и перед закрывающей фигурной скобкой однострочного блока.
- Пробел (как минимум один) ставится после двойного или тройного слеша в комментариях.
- Пробел ставится вокруг знаков бинарных операторов и знака = в присваивании.
- Пробел не ставится вокруг точки при обращении к членам типа.
- Пробел не ставится после ключевого слова typeof.
- Пробел не ставится после закрывающей круглой скобки при приведении типа.
string s = (string)obj;- Пробел не ставится после открывающей скобки.
Пример правильного форматирования:
for (int i = 0; i < arr.Length; i++)
{
ProcessElement(arr[i], (arr.Length - i - 1) / 2);
}- Не используйте для комментариев форму /* ... */.
- Не следует помещать в систему контроля версий файлы, содержащие закомментированные блоки кода - это затрудняет чтение кода. При необходимости можно получить старую версию файла из системы контроля версий.
- В конфигурационных файлах также полезно писать комментарии.
Документирующие комментарии исключительно важны, т.к. предоставляют пользователям ваших классов самую первую и самую быструю информацию о вашем классе. Также документирующие комментарии являются основным источником для автоматической генерации документации.
- Рекомендуется писать документирующие комментарии у всех публичных типов, а также у публичных членов типов.
- Документирующий комментарий не должен "прилипать" к предыдущему члену типа, то есть должен быть отделен от него хотя бы одной пустой строкой. Это справедливо и для комментариев к элементам перечисления.
- В документирующем комментарии конструктора рекомендуется скопировать комментарии к классу и возможные особенности использования данного конструктора.
- Комментарии должны быть составлены на русском языке.
Правила именования имеют иногда даже бо́льшую важность, чем правила комментирования и оформления кода. Для пользователя скомпилированной сборки не так важно, как был отформатирован и откомментирован исходный программный текст, как важно то, как поименованы внешние интерфейсы модуля, насколько интуитивны используемые имена и насколько они сочетаются с именами, используемыми в других модулях.
-
Для именования старайтесь использовать правильные, полные английские слова. Лучше уточните написание в словаре, у коллег, напишите доступный комментарий на русском языке, но не называйте класс Nakladnaya. Помните, что в английском прилагательные идут всегда впереди существительного.
-
Не используйте сокращения без необходимости, кроме общепринятых. Visual Studio предоставляет средства для быстрого набора доступных идентификаторов.
-
Не пытайтесь закодировать в имени класса или метода полную семантику. Это, во-первых, снижает уровень абстракции, во-вторых, ухудшает читаемость кода, в-третьих, это в общем случае всё равно невозможно.
Например, вместо
PrintTaskData[] GetAllInProgressTasksAndLimitCompletedTasks(
TimeSpan maxAge,
int maxCount);лучше написать
PrintTaskData[] GetTasksForMonitoring(
TimeSpan maxAge,
int maxCount);-
Не используйте слишком длинные составные имена. Вместо этого лучше разгруппировать классы в несколько пространств имен.
-
Очень не рекомендуется использовать в качестве имен зарезервированные и ключевые слова C#, имена, использующиеся в стандартной библиотеке, а также имена, отличающиеся от вышеперечисленных, только регистром букв.
-
Вообще не рекомендуется злоупотреблять тем фактом, что имена, набранные в разном регистре, являются различными.
-
Не используйте излишнюю квалификацию членов класса, как Employee.EmployeeName. Достаточно Employee.Name.
-
Для классов выбирайте имена существительные, возможно дополненные уточняющим прилагательным спереди (например: RoundedRectangle).
-
Для методов выбирайте глаголы, возможно уточненные существительными сзади (например: CalculateAvgSalary).
-
Для свойств — существительные, возможно дополненные прилагательными спереди (например:BackgroundColor). Для свойств булевого типа — прилагательные (например:Available,Visible) или выражения вида IsXXX,CanXXX,HasXXX (например: IsRoot, HasChildren, CanRead). При этом пользуйтесь следующим правилом для определения того, нужен ли префикс:
-
Если смысл самого свойства от отсутствия префикса существенно не изменяется, то префикс вводить не надо. Пример: Enabled но не IsEnabled, Visible но не IsVisible, ReadOnly, но не IsReadOnly.
-
А вот если префикс изменяет смысл, то он нужен. Обычно это так, если слово за префиксом — существительное, а само свойство — булево. Пример: HasChildren (без "Has" получится свойство "Дети", а не "Есть ли дети?"), IsRoot (без "Is" интуитивно будет "ссылка на корень", а не "является ли корнем"), CanRead (без "Can" будет вообще странное свойство "Читать").
-
Для событий используется инговая или прошедшая форма. Например:
-
Doing — перед выполнением метода Do;
-
Done — после выполнения метода Do.
-
Короткими или однобуквенными именами допускается именовать переменные только для счетчиков в циклах и в лямбда-выражениях.
-
Используйте наименования, которые отражают суть классов. PackinListProcessor - не лучшее название класса, который синхронизирует упаковочные листы. Чтобы придумать название можно найти метафору, описывающую предназначение класса и назвать класс в соответствие с метафорой. PackingListSynchronizer говорит о предназначении класса гораздо больше.
-
Для счетчиков циклов for используйте имена i, j, k, если не требуется уточнения и акцентирования (например, в случае нескольких вложенных циклов).
-
Для именования исключения в обработчиках исключений используйте имя e, если оно не занято.
Примечание. Далее используются следующие обозначения для способов именования составных идентификаторов:
- PascalCase // P
- camelCase // c
- cpChar // _c венгерская нотация
Таблица правил:
| Что именуем | public | protected | internal | private | Примечание |
|---|---|---|---|---|---|
| Файл проекта | P | Совпадает с именем сборки и пространством имен | |||
| Исходный файл класса | P | Совпадает с именем класса | |||
| Другие файлы | P | По возможности | |||
| Пространство имен | P | Частично совпадает с названием проекта/сборки | |||
| Класс или структура | P | P | P | P | Совпадает с именем класса |
| Интерфейс | P | P | P | P | Префикс I |
| Generic-класс | P | P | P | P | Тип параметра T, K, или префикс T (например:TKey,TValue) |
| Метод | P | P | P | P | Глагол [Уточнение] |
| Свойство | P | P | P | P | [Уточнение] Существительное, для булевых - прилагательное или префиксы "Is", "Has", "Can", и т.п. |
| Поле | P | c | P | _c | Не рекомендуется делать поля, кроме private (за исключением static readonly) |
| Константа | P | P | P | P | |
| Статическое поле | P | c | P | _c | |
| Перечисление и его элементы | P | P | P | P | Не нужно суффикса Enum! |
| Делегат, используемый в объявлении события | P | P | P | P | Суффикс EventHandler |
| Событие | P | P | P | P | в залоге doing и done |
| Локальная переменная | c | ||||
| Параметр метода | c |
Правила в текстовом виде:
-
Не используйте венгерскую нотацию, кроме случаев описания Windows API функций.
-
Не используйте символ подчеркивания для отделения частей составных идентификаторов (some_identifier), за исключением обработчиков событий (описано ниже) и тестовых методов.
-
Для именования пространств, классов, свойств, методов, событий используется PascalCase.
-
Для именования параметров методов, локальных переменных используется camelCase.
-
Для именования приватных полей класса используется camelCase c префиксом "_": _department.
-
Неприватные поля делать вообще не рекомендуется, но, если все-таки сделали, используйте PascalCase нотацию, это позволит при необходимости легко изменить поле на свойство.
-
Для именования констант используется PascalCase.
-
Для именования интерфейсов используется PascalCase с префиксом "I" (например: IComparable).
-
В именах методов – инициализаторов событий используется префикс "On" или "Notify".
-
В обработчиках событий используется название вида <ИсточникСобытия>_<НазваниеСобытия> (например: _applyButton_Click). Это имя метода автоматически предлагается Visual Studio.
-
Типы-делегаты, используемые в объявлении событий, следует именовать с использованием суффикса EventHandler. Другие делегаты рекомендуется называть с использованием суффикса Callback
-
Аргументы событий следует наследовать от EventArgs и именовать с суффиксом EventArgs.
-
Исключения именуются с суфиксом Exception
-
Классы атрибутов именуются с суффиксом Attribute
-
Кроме упомянутых выше случаев, использования префиксов/суффиксов, не отражающих предметную область, лучше избегать (а вместо этого, активно использовать пространства имен). Исключением может являться случай, когда есть большое количество однородных классов, которые по каким-то причинам сложно именовать без префикса (например, если без префикса идентификаторы будут совпадать с ключевыми словами или с параллельной иерархией классов).
Пример допустимых имен: Department, Employee, UserSettings,ApplicationSettings (суффиксы отражают реальную предметную область, фактически это определяющее смысл класса существительное); PlusExpression, SelectExpression, JoinExpression, UnionExpression (сложно обойтись без суффикса);
Пример недопустимых имен: clsEmployee, CEmployee, EmployeeKindEnum (префиксы/суффиксы отражают характеристику типа, а не его функциональное назначение).
-
При написании аббревиатур длиной больше двух символов заглавной рекомендуется делать только первую букву: XmlValue, но не XMLValue. При написании аббревиатур из двух символов обе рекомендуется делать заглавными: AbstractUIElement.
-
Используйте следующее написание стандартных аббревиатур (не все из них согласуются с предыдущим правилом): UI, Db, Xml, Id.
Можно предложить следующее эмпирическое правило для написания двухбуквенных аббревиатур:
- Обе буквы пишутся заглавными, если образованы от двух слов (например, UI = User Interface)
- Заглавной пишется только первая буква, если обе буквы взяты из одного слова, — фактически, аббревиатура является сокращением слова (например, Db = Database)
В частности, этому правилу удовлетворяют все упомянутые стандартные аббревиатуры.
- В примере пытаемся убедиться в отсутствии дубликатов, в результате запроса возвращается объект dataStructure, а не его имя.
var sameName = dataStructures.Where(x => x.Id != _id && x.Name == _name).FirstOrDefault();Более правильным именем было бы existingStructure, duplicatedStructure и т.д.
На основе: https://enterprisecraftsmanship.com/posts/you-naming-tests-wrong/
-
Имя тестовой сборки = [Имя тестируемой сборки] + ".Tests"
-
Имя тестового класса = [Имя тестируемого класса] + "Test"
-
Имя тестового метода = [MethodUnderTest] _ [Scenario] _ [ExpectedResult]
Где:
- MethodUnderTest — это название метода, который вы тестируете.
- Scenario - это условие, при котором вы тестируете метод.
- ExpectedResult - то, что должен делать тестируемый метод в текущем сценарии.
Пример:
public void Delivery_with_a_past_date_is_invalid() -
Именование в коде тестового метода:
- Структура юнит-теста: Arrange/Act/Assert (AAA)
- sut - тестируемая система (System Under Test). В зависимости от типа теста, системой могут быть различные сущности.
- actual - фактический результат тестируемой функции
- expected - ожидаемый результат тестируемой функции
- default - значения элементов, которые не важны для данного юнит-теста
- stub — объект, возвращающий при вызовах его функций/свойств предустановленные (hardcoded) результаты, при этом код реального объекта не выполняется. Когда же функция возвращаемого значения не имеет, вызов просто игнорируется.
- mock — объект, который позволяет проверять поведение SUT посредством отслеживания обращений к функциям/свойствам объекта (например, были ли в ходе юнит-теста вызваны функции мока, в правильном ли это произошло порядке, были ли переданы в них ожидаемые аргументы и т. п. Также mock может включать функциональность stub).
- Нельзя опускать модификаторы доступа internal/private для классов и их членов, полагаясь на правила языка.
- При наличии других модификаторов (new, static, virtual и т.д.) модификаторы доступа рекомендуется писать в начале.
- Поля, которые изменяются только в конструкторе, всегда должны помечаться readonly.
- Классам необходимо всегда задавать модификаторы доступа, исходя из принципа необходимости.
Например, если класс никем не наследуется и не используется как контракт-ДТО, следует задать ему internal sealed, а не public. И далее расширять доступность по мере необходимости.
- Не ставьте синхронизирующую блокировку (lock) на this или любые другие объекты, доступные клиентскому коду. Если вся логика синхронизации сосредоточена внутри класса, объявите специальный dummy-объект:
private object _syncObj = new object();
и синхронизируйтесь на нем. Это позволит исключить вмешательство в логику синхронизации извне.
В случае, если у метода количество параметров становится больше 4, то предпочтительней использовать в качестве параметра ДТО.
-
При необходимости в каждом публичном методе класса, принимающем какие-либо параметры, а также в set-блоках свойств (для индексаторов и в get-блоке) нужно не забывать проверять значения параметров на корректность, в первую очередь на null для ссылочных типов, например:
-
В случае, если некорректное значение параметра означает ошибку в программе, то необходимо осуществлять проверку.
-
В случае, если некорректное значение может быть получено в результате действий пользователя, то должно быть выброшено кастомное исключение с дружественной диагностикой.
-
Для непубличных методов также рекомендуется проверять параметры, особенно если возможно изменение реализации класса или его наследование.
- Если метод предусматривает возврат коллекции объектов, то в случае пустого результирующего множества лучше возвращать пустую коллекцию заявленного типа (это избавит клиентский код от лишнего сравнения с null). В любом случае в секции документирующих комментариев укажите особенности данной коллекции.
-
Если необходимо для заданного типа Type type создать экземпляр, используя конструктор без параметров, следует применять метод Activator.CreateInstance, но не вызывать конструктор через Reflection. Класс Activator использует внутренние механизмы кеширования и работает на порядок быстрее.
-
Generic-вариант метода Activator.CreateInstance также работает достаточно быстро.
-
В целях оптимизации работы с Reflection рекомендуется использовать Expressions. Это даст большую производительность.
-
Не рекомендуется использование параметров по умолчанию. Для более четкого контроля ошибок и уменьшения времени поддержки за счет контроля передаваемых в метод значений.
-
Не рекомендуется задавать именованные параметры при использовании в методе параметров по умолчанию. Это чревато ошибками.
-
Не рекомендуется создавать DTO с пустым конструктором, состоящее из автосвойств. Свойства следует делать get-only, а значения в свойства записывать в конструкторе. Таким образом контролируется заполненность DTO при передаче ее в обработку.
-
По возможности используйте generic-коллекции: List вместо нетипизированного ArrayList, **Dictionary<long, Employee>**вместо нетипизированного Hashtable.
-
На участках кода, где возможна работа с большими коллекциями и важна производительность, создавайте коллекции с указанием размера (capacity). Это позволит сэкономить на многократных перераспределениях памяти при наполнении коллекции.
- Рекомендуется использовать LINQ вместо циклов, когда читаемость от этого улучшается.
Например, вместо
``cs var result = new List(); var query = _session.Cast();
foreach (var dataStructure in query) { result.Add(DataStructureConverter.ToDto(dataStructure)); }
желательно написать
```cs
var query = _session.Cast<DataStructure>();
var result = query
.Selec*(DataStructureConverter.ToDto)
.ToList();
- Если ожидается, что метод должен вернуть коллекцию, то нежелательно ни при каких ситуациях возвращать null. В случае, когда нет возможности получить необходимую коллекцию, следуя логике метода, лучше возвращать Enumerable.Empty вместо null.
- При использовании IEnumerable.First() или IEnumerable.FirstOrDefault() необходимо всегда оставлять однострочные комментарии, так как, чаще всего, их использование обусловлено особенностью бизнес-логики.
Например, в коллекции заказов есть поле Получатель, которое во всех элементах коллекции заполнено одинаково, и нам все равно, с какого элемента брать это значение. Необходимо оставить комментарий, почему так было сделано.
- Декларации атрибутов для классов, полей, свойств и методов располагаются по одному на строку, каждый в своей паре квадратных скобок, без круглых скобок, если параметры не нужны:
[Serializable]
[SomeAnotherAttribute(...)]
public class SomeEventArgs : EventArgs
{
...
}-
Перед вызовом делегата проверяйте его значение на null
-
Если дополнительная информация в событии не нужна, передайте в параметр EventArgs значение EventArgs.Empty
-
Если ваше событие не требует передачи дополнительной информации, используйте для его объявления делегат System.EventHandler.
-
Если ваше событие требует передачи дополнительной информации, объявите класс-потомок EventArgs и используйте для объявления события **event **EventHandler(object sender, EventArgs e) или event ****EventHandler(object sender, TEventArgs e).
-
Запрещается для реализации событийной модели использовать **event **Action и event ****Function (https://stackoverflow.com/questions/1431359/event-action-vs-event-eventhandler).
-
Если вы часто используете в коде некоторый внешний тип или, тем более, несколько типов из одного пространства имен, включите в файл соответствующую директиву using для этого пространства имен (например, using System.Text;).
-
Директивы using следует указывать на самом верхнем уровне, а не внутри объявления пространств имен. Размещать по одной директиве на одну строку. Неиспользуемые директивы using рекомендуется удалять перед помещением файла в систему контроля версий.
-
Если вы используете некоторый внешний тип только в одном-двух местах, можно использовать полное имя (System.Text.Encoding) или включить один этот тип с помощью директивы using (например, using Encoding = System.Text.Encoding;). При этом старайтесь не переименовывать тип без необходимости, чтобы у читающего ваш код не возникало удивления по поводу незнакомого имени.
Необходимость может возникнуть, например, если в двух и более пространствах имен используется тип с одинаковым именем. -
При объявлении переменных, методов, параметров, свойств стандартных типов, используйте синонимы C#: int, string, decimal, но не Int32, String, Decimal.
При обращении к статическим методам и свойствам стандартных типов допустимо использование CLR-имен: String.Parse(...), Decimal.MaxValue. Хотя всё же и в данном случае предпочтительнее использование ключевых слов C#: string.Parse(...), decimal.MaxValue.
- Фигурные скобки, ограничивающие блоки кода, следует располагать на отдельных строках, с тем же отступом, что и верхняя строка, обозначающая блок (if / else / switch / while / for /foreach / заголовок метода или класса, и т.д.):
try
{
...
if (condition)
{
DoSomeAction();
DoAnotherAction();
}
...
switch (userChoice)
{
case Actions.Continue:
Work.Continue();
break;
case Actions.Terminate:
Work.Terminate();
break;
...
}
...
}
catch (ApplicationException e)
{
...
}
finally
{
...
}- Однострочные блоки также рекомендуется отделять от последующего кода пустой строкой:
...
if (yetAnotherCondition)
{
DoAction();
}
...
if (someCondition)
break;
if (anotherCondition)
continue;
...- Обязательно заключать в фигурные скобки блок else, следующий за многострочным блоком if:
...
if (yetAnotherCondition)
{
DoSomeAction();
DoAnotherAction();
}
else
{
break;
}
...При использовании цикла do/while ключевое слово while рекомендуется записывать на следующей строке после закрывающей фигурной скобки тела цикла. После слова while и условия цикла обязательно должен быть отступ.
do
{
if (item == customer)
{
return false;
}
item = item.Customer;
}
while (item != null);- Не следует злоупотреблять тем фактом, что в C# оператор присваивания возвращает значение. Использовать оператор присваивания внутри других выражений следует лишь в простых "цепных" присваиваниях:
x = y = expr;
-
Старайтесь не злоупотреблять широкими возможностями инструкции for. Если вы написали инструкцию for, отходящую от "типичной" формы, например, без условия продолжения или с несколькими инструкциями приращения, то подумайте, нельзя ли это выразить более понятным образом. Читающий "не ожидает" от for "необычного" поведения.
-
Если вам нужен бесконечный цикл, оформите его в виде while (true) {...}
-
Инструкцию if...else if...else if оформляйте следующим образом:
if (...condition...)
{
...
}
else if (...condition...)
{
...
}
else if (...condition...)
{
...
}
else
{
...
}-
Старайтесь использовать тернарный оператор (a ? b : c) только в очень простых выражениях, когда он действительно повысит краткость, выразительность и читаемость кода.
-
Рассмотрите вариант трехстрочного форматирования тернарного оператора, если операнды имеют большой размер:
string fileName = IsFileNameSpecified()
? GetSpecifiedFileName()
: GetDefaultFileName();- Не рекомендуется использовать оператор ?. более 1 раза для конкретного объекта. Исключение из правила допускается, когда сам Framework вынуждает нас действовать иначе (например, HttpContext?.Application?.Contents).
Например, следующее выражение – прямое нарушение Закона Деметры:
var x = Object1?.Object2?.Object3;-
Избегайте многократной конкатенации строк, например в циклах или в часто вызываемых методах. Используйте StringBuilder.
-
Не используйте StringBuilder по мелочам, особенно если это ухудшит читаемость по сравнению с простой конкатенацией.
-
Активно используйте возможности string.Format, а еще лучше интерполяцию строк (оператор $). Это позволит вынести строку в ресурсы, улучшит читабельность кода и упростит формирование строки.
-
При разборе (парсинге) числовых строк и дат всегда указывайте IFormatProvider и явно задавайте в них разделители дат, времени, десятичную точку и т.п. Исключение — если данные приходят напрямую из пользовательского интерфейса и, соответственно, могут зависеть от региональных настроек конкретного компьютера.
-
Аналогичные правила — для преобразования чисел и дат в строки.
-
Для проверки строки на пустоту не используйте сравнение с "" или string.Empty. Вместо этого проверьте String.IsNullOrEmpy(s). Для пользовательских интерфейсов стоит рассмотреть использование String.IsNullOrWhiteSpace(s).
-
Постарайтесь не использовать непосредственно в коде "волшебных значений", особенно если они встречаются в коде несколько раз. Вынесите их в приватную константу в классе. Это позволит существенно упростить как изменение значения, так и преобразование его в свойство, при необходимости.
- В числовых литералах типа long не следует использовать суффикс 'l', так как его легко можно спутать с цифрой '1'. Вместо этого следует использовать заглавную 'L'.
var x = 2L;
-
Не используйте коды возврата ошибок, возвращаемые признаки успешности выполнения и т.п. С большой вероятностью клиентский код не будет их проверять. Вместо этого выбросьте подходящий тип исключения.
-
Перехватывайте только те исключения, для которых вы можете предоставить нетривиальную обработку.
-
Перехват всех исключений (catch (Exception)) без последующего проброса наверх (throw) допустим только на самом верхнем уровне (функции потока, точки входа в приложение и т.п.)
-
Для каждого потока должна существовать точка перехвата всех исключений с минимальной обработкой (журналирование, сообщение пользователю). В противном случае необработанное исключение молча обрушит приложение.
-
Если в ответ на пойманное исключение вы выбрасываете новое, по возможности передавайте в конструктор нового исключения ссылку на исходное (innerException).
-
Если вы поймали исключение и собираетесь перебросить его выше в неизменном виде, используйте конструкцию throw; не указывая экземпляр исключения (инструкция throw e; в данном случае усечет информацию о стеке вызовов)
-
Крайне не рекомендуется выбрасывать экзмепляр Exception, такое исключение проблематично обработать. Вместо этого используйте специфический тип исключения.
-
Не стройте глубокое дерево наследования классов исключений, "растите" дерево вширь.
-
Объявляйте и инициализируйте локальные переменные непосредственно перед местом их использования (если, конечно, это не противоречит структуре кода)
-
Объявляйте по одной переменной на строке:
Color foreColor;
Color backColor;
Color borderColor;
int x = 0;
int y = 0;-
Не используйте одну и ту же переменную для разных целей
-
Используйте всегда, где это возможно, var.
var result = new List<DataStructureDto>();- Не используйте для финансовых вычислений типы float и double. Используйте decimal.
- В старых версиях C# используйте оператор as только если логика работы предусматривает, что возможна штатная ситуация, при которой приведение невозможно. Соответственно, после приведения через as всегда должна идти проверка на null.
В остальных случаях используйте cast operator: (Employee)person. В версиях C#, поддерживающих сопоставление с шаблоном, следует использовать сопоставление.
- Для проверки приводимости типов используйте оператор is:
if (person is Employee)
{
((Employee)person).DoWork();
}- Не следует злоупотреблять множественным матчингом через is в одной строке:
if (o is int i || (o is string s && int.TryParse(s, out i)){}Такой код плохо читаем, особенно, если матчинг используется более 2 раз.
- Для проверки o != null допустимо выражение: o is object
Часто бывает, что приходится маппить Dto в Entity и наоборот. Для удобства рекомендуется использовать деконструкторы вместо использования отдельных классов-мапперов.
- Применение ключевого слова this для обращения к членам класса в коде самого класса рекомендуется ограничить (бывают случаи, когда нужно сaкцентировать внимание на текущем экземпляре).
- Допустимо формирование технических логов (предназначенных для разработчиков и сопровождения) на английском языке и с использованием общепринятых технических терминов.
- Логи и сообщения об ошибках, которые видны пользователям и представителям бизнеса, должны формироваться исключительно на русском языке с использованием понятных для данных лиц терминов.
- Все файлы должны быть в кодировке UTF-8 с BOM
- Окончания строк файлов должны быть в формате Windows (CR LF)
На окончания строк и на кодировку, хранимую в репозитории может влиять не только конкретный редактор и IDE, но и Git-клиент в момент Push. Необходимо настроить Git-клиент должным образом.