Skip to content

Codestyle conventions

Pavel Kulbida edited this page Feb 10, 2020 · 43 revisions

Cоглашения по оформлению кода в проекте

Введение

В данном документе описаны стандарты (требующие обязательного исполнения) и рекомендации (оставляющие свободу выбора) для кодирования на языке C#.

Введение этих правил преследует следующие цели:

  • Обеспечить высокое функциональное качество программного кода
  • Обеспечить высокую читаемость кода
  • Обеспечить легкость сопровождения кода разными разработчиками

Замечание 1

Правила, собранные здесь, являются компиляцией из многочисленных стандартов кодирования для C#, а также накопленного личного опыта. Хотя ни с одним из готовых стандартов эти правила не совпадают на 100%, большинство основных правил являются общепринятыми и рекомендованными подавляющим большинством стандартов. Отличия данных правил незначительны. Большинство правил соответствуют также соглашениям, принятым в стандартной библиотеке .NET Framework (хотя правила, принятые в ней, тоже несколько неоднородны).

Поэтому в случае, если вы не можете найти ответ на какой-то вопрос по форматированию или именованию в настоящих правилах, можете воспользоваться образцами из этих источников:

Замечание 2

Также рекомендуется следующая литература по теме:



Ведение проекта

Структура

  • Не следует размещать в одном файле больше одного класса (за исключением, возможно, вложенных типов и типов делегатов).

  • В отсутствии противопоказаний имя файла должно совпадать с именем определенного в нем класса.

  • Предыдущее правило касается также перечислений и классов исключений.

Например, перечисление

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, если оно не занято.

Регистр и префиксы/суффиксы

Примечание. Далее используются следующие обозначения для способов именования составных идентификаторов:

Таблица правил:

Что именуем 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 и т.д.

Структура кода

Модификаторы доступа

  • Нельзя опускать модификаторы доступа internal/private для классов и их членов, полагаясь на правила языка.
  • При наличии других модификаторов (new, static, virtual и т.д.) модификаторы доступа рекомендуется писать в начале.

Классы

  • Поля, которые изменяются только в конструкторе, всегда должны помечаться readonly.
  • Классам необходимо всегда задавать модификаторы доступа, исходя из принципа необходимости.
    Например, если класс никем не наследуется и не используется как контракт-ДТО, следует задать ему internal sealed, а не public. И далее расширять доступность по мере необходимости.

Синхронизация

  • Не ставьте синхронизирующую блокировку (lock) на this или любые другие объекты, доступные клиентскому коду. Если вся логика синхронизации сосредоточена внутри класса, объявите специальный dummy-объект:

private object _syncObj = new object();

и синхронизируйтесь на нем. Это позволит исключить вмешательство в логику синхронизации извне.

Методы

В случае, если у метода количество параметров становится больше 4, то предпочтительней использовать в качестве параметра ДТО.

Проверка параметров

  • При необходимости в каждом публичном методе класса, принимающем какие-либо параметры, а также в set-блоках свойств (для индексаторов и в get-блоке) нужно не забывать проверять значения параметров на корректность, в первую очередь на null для ссылочных типов, например:

  • В случае, если некорректное значение параметра означает ошибку в программе, то необходимо осуществлять проверку.

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

  • Для непубличных методов также рекомендуется проверять параметры, особенно если возможно изменение реализации класса или его наследование.

Возврат коллекций

  • Если метод предусматривает возврат коллекции объектов, то в случае пустого результирующего множества лучше возвращать пустую коллекцию заявленного типа (это избавит клиентский код от лишнего сравнения с null). В любом случае в секции документирующих комментариев укажите особенности данной коллекции.

Создание объектов заданного типа с использование Reflection

  • Если необходимо для заданного типа Type type создать экземпляр, используя конструктор без параметров, следует применять метод Activator.CreateInstance, но не вызывать конструктор через Reflection. Класс Activator использует внутренние механизмы кеширования и работает на порядок быстрее.

  • Generic-вариант метода Activator.CreateInstance также работает достаточно быстро.

  • В целях оптимизации работы с Reflection рекомендуется использовать Expressions. Это даст большую производительность.

Параметры

  • Не рекомендуется использование параметров по умолчанию. Для более четкого контроля ошибок и уменьшения времени поддержки за счет контроля передаваемых в метод значений.

  • Не рекомендуется задавать именованные параметры при использовании в методе параметров по умолчанию. Это чревато ошибками.

  • Не рекомендуется создавать DTO с пустым конструктором, состоящее из автосвойств. Свойства следует делать get-only, а значения в свойства записывать в конструкторе. Таким образом контролируется заполненность DTO при передаче ее в обработку.

Коллекции

  • По возможности используйте generic-коллекции: List вместо нетипизированного ArrayList, **Dictionary<long, Employee>**вместо нетипизированного Hashtable.

  • На участках кода, где возможна работа с большими коллекциями и важна производительность, создавайте коллекции с указанием размера (capacity). Это позволит сэкономить на многократных перераспределениях памяти при наполнении коллекции.

LINQ

  • Рекомендуется использовать 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.

Инструкции и выражения (statements & expressions)

Операторы

Фигурные скобки

  • Фигурные скобки, ограничивающие блоки кода, следует располагать на отдельных строках, с тем же отступом, что и верхняя строка, обозначающая блок (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

При использовании цикла do/while ключевое слово while рекомендуется записывать на следующей строке после закрывающей фигурной скобки тела цикла. После слова while и условия цикла обязательно должен быть отступ.

    do
    {
        if (item == customer)
        {
            return false;
        }

        item = item.Customer;
    }
    while (item != null);

Оператор присваивания

  • Не следует злоупотреблять тем фактом, что в C# оператор присваивания возвращает значение. Использовать оператор присваивания внутри других выражений следует лишь в простых "цепных" присваиваниях:

   x = y = expr;

Операторы if, for, while, switch

  • Старайтесь не злоупотреблять широкими возможностями инструкции 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();

Null propagation (?.)

  • Не рекомендуется использовать оператор ?. более 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

  • Применение ключевого слова this для обращения к членам класса в коде самого класса рекомендуется ограничить (бывают случаи, когда нужно сaкцентировать внимание на текущем экземпляре).

Правила логирования и формирования сообщения об ошибках

  • Допустимо формирование технических логов (предназначенных для разработчиков и сопровождения) на английском языке и с использованием общепринятых технических терминов.
  • Логи и сообщения об ошибках, которые видны пользователям и представителям бизнеса, должны формироваться исключительно на русском языке с использованием понятных для данных лиц терминов.

Правила работы с файлами исходного кода

  • Все файлы должны быть в кодировке UTF-8 с BOM
  • Окончания строк файлов должны быть в формате Windows (CR LF)

На окончания строк и на кодировку, хранимую в репозитории может влиять не только конкретный редактор и IDE, но и Git-клиент в момент Push. Необходимо настроить Git-клиент должным образом.

Clone this wiki locally