Стандарт оформления CSharp кода

Материал из GraphLabs Wiki
Перейти к: навигация, поиск

Язык: C#

Среда разработки: Visual Studio 2012


Содержание

Введение

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

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

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

Настоятельно рекомендуется прочитать эти правила один раз целиком, а затем обращаться к ним по мере необходимости.

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

Структура проекта, именование файлов

  • Не следует размещать в одном файле больше одного класса (за исключением, возможно, вложенных типов и типов делегатов).
  • В отсутствии противопоказаний имя файла должно совпадать с именем определенного в нем класса.
  • Предыдущее правило касается также перечислений и классов исключений.
Например, перечисление
    enum MoveDirection
    {
        Up,
        Down,
        Left,
        Right
    }
следует разместить в файле MoveDirection.cs
  • Если класс разросся до существенных размеров, и при этом не видится целесообразным его разделение на несколько классов, то разделите класс на несколько файлов-разделов (partial class), сгруппировав в каждом файле близкую функциональность. При этом имя файла должно строиться по принципу <ИмяКласса>.<Уточнение>.cs, где <Уточнение> — краткое, в одно-два слова, определение блока функциональности раздела класса. Возможно также вынесение в отдельный файл большого вложенного класса, в этом случае файл с основным классом назовите, как обычно, <ИмяОсновногоКласса>.cs, а файл с вложенным классом — <ИмяОсновногоКласса>.<Имя ВложенногоКласса>.cs
  • Структура директорий должна быть максимально приближена к структуре пространств имен.
Примечание
Возможно отступать от этого правила, чтобы выделить в отдельную поддиректорию группу схожих классов, которые, тем не менее, не целесообразно выделять в отдельное пространство имен, т.к. они не отличаются существенно по функциональному признаку. Например, можно вынести нескольких классов исключительных ситуаций (Exceptions) в поддиректорию Exceptions без выделения подпространства имен Exceptions. Это позволит удобнее работать со списком файлов; в то же время, нелогично выделять для исключений свое пространство имен. Также бывает удобно вынести в отдельный подкаталог Enums все файли пречислений, не выделяя их в отдельное пространство имен.
  • Старайтесь не располагать в одной директории очень много разнородных классов — выносите логически связанные классы в отдельные пространства имен/директории.
  • Все имена файлов, включенных в проект, должны, по возможности, именоваться с использованием PascalCase (первые буквы в словах заглавные, остальные — маленькие).

Форматирование кода

В целом правила форматирования кода соответствуют настройкам по умолчанию Visual Studio. Так что соблюдать их будет довольно просто. Минимальные отличия в настройках будут указаны ниже.

Для справки
Visual Studio позволяет переформатировать выделенный фрагмент кода в соответствии с текущими настройками. Это можно сделать через меню (Edit > Advanced > Format Selection) или с помощью комбинации Ctrl+K,F.

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

Отступы

  • Стандартный отступ должен оформляться 4 (четырьмя) пробелами.
  • Использование символа табуляции для отступов запрещается.
Для настройки этого правила в Visual Studio следует:
  • Выполнить команду меню Tools > Options
  • Выбрать Text Editor > All languages (или только C#) > Tabs
  • Установить TabSize: 4, IndentSize: 4
  • Выбрать Insert Spaces

Длина строк

  • Длину строк рекомендуется ограничивать 120 символами.
Примечание
Как ни странно, но настройка показа "ограничительной линии" для текстового редактора в Visual Studio не вынесена в диалог настройки. Но показ такой "ограничительной линии" можно включить, воспользовавшись расширением Editor Guidelines, которое для VS 2012 можно скачать здесь.

Переносы строк

  • При переносе строк рекомендутся либо использовать один дополнительный стандартный отступ, либо выравнивать начало перенесенной строки относительно элементов верхней строки для повышения читабельности кода.
  • Запятая остается в строке перед переносом.
  • Для методов с большим количеством параметров может применяться форматирование вида "строка на параметр", при этом полезно добавить однострочный комментарий для неочевидных параметров:
                ...
                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,
                    null,                       // use default binder
                    typeof(DataTable),          // return type
                    null,                       // arg types - not indexer
                    null                        // arg modifiers - not indexer
                );
                ...

Форматирование внутри строк

  • Допускается повышающее читаемость кода форматирование строк с помощью пробелов, например, для выделения однородной структуры нескольких инструкций или частей инструкций:
    public enum Color
    {
        Red             = 1,
        Green           = 2,
        GrayBrwnMagenta = 3
    }

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

  • Фигурные скобки, ограничивающие блоки кода, следует располагать на отдельных строках, с тем же отступом, что и верхняя строка, обозначающая блок (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 (someCondition) { break; }
            if (anotherCondition) { continue; }
            ...
            if (yetAnotherCondition)
            {
                DoAction();
            }
            ...
            if (someCondition) 
                break;
 
            if (anotherCondition) 
                continue;
            ...
  • Обязательно заключать в фигурные скобки блок else, следующий за многострочным блоком if:
            ...
            if (yetAnotherCondition)
            {
                DoSomeAction();
                DoAnotherAction();
            }
            else
            {
                break;
            }
            ...

Расположение инструкций

Все инструкции следует располагать на отдельных строках. Допустимые исключения — простые, короткие, однострочные, тривиальные условные инструкции if, блоки case, get и set блоки свойств:

    ...
    if (fileName == null) { throw new ArgumentNullException("fileName"); }
    ...
    switch (moveDirection)
    {
        case Direction.Up:     Y--; break;
        case Direction.Left:   X--; break;
        case Direction.Down:   Y++; break;
        case Direction.Right:  X++; break;
    }
    ...
    public Color ForegroundColor
    {
        get { return _foregroundColor; }
        set { _foregroundColor = value; }
    }
    ...

Оформление цикла do/while

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

    do
    {
        if (item == customer)
        {
            return false;
        }
        item = item.Customer;
    } while (item != null);

Использование пробелов в инструкциях

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

  • Пробел ставится после ключевого слова управляющей инструкции (if / while / for и т.д.).
  • Пробел ставится после запятой в перечне аргументов, после точки с запятой в инструкции for.
  • Пробел ставится после открывающей и перед закрывающей фигурной скобкой однострочного блока.
  • Пробел (как минимум один) ставится после двойного или тройного слеша в комментариях.
  • Пробел ставится вокруг знаков бинарных операторов и знака = в присваивании.
  • Пробел не ставится вокруг точки при обращении к членам типа.
  • Пробел не ставится после ключевого слова typeof.
  • Пробел не ставится после закрывающей круглой скобки при приведении типа.
   string s = (string)obj;
  • Пробел не ставится после открывающей скобки.

Пример правильного форматирования:

    for (int i = 0; i < arr.Length; i++)
    {
        ProcessElement(arr[i], (arr.Length - i - 1) / 2);
    }

Использование именованных регионов

  • Рекомендуется использовать именованные регионы (#region..#endregion) для логического разбиения членов класса по функциональному признаку. Можно выделять также такие блоки как "свойства", "конструкторы", "константы", "вложенные типы".
  • Но при этом следует помнить, что разбиение просто по формальному уровню доступа private/protected/public не очень полезно.
  • Не следует также увлекаться большой вложенностью регионов — в большинстве случаев достаточно одного уровня.
  • При закрытии региона следует приписывать его имя:
 
        #region Example
            ...
        #endregion // Example

Использование пустых строк

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

Пример хорошего оформления:

        #region Свойства
 
        public string SomeProperty
        {
            get { return _someProperty;
        }
 
        private string _someProperty;
 
        #endregion // Свойства
 
 
        #region Методы
 
        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);
            }
        }
 
        #endregion // Методы

Применение ключевых слов this и base

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

Объявление и использование переменных

  • Объявляйте и инициализируйте локальные переменные непосредственно перед местом их использования (если, конечно, это не противоречит структуре кода)
  • Где возможно, используйте неявное объявление переменных
  • Объявляйте по одной переменной на строке:
    Color foreColor;
    Color backColor;
    Color borderColor;
    int x = 0;
    int y = 0;
  • Не используйте одну и ту же переменную для разных целей

Ссылки на внешние типы

  • Если вы часто используете в коде некоторый внешний тип или, тем более, несколько типов из одного пространства имен, включите в файл соответствующую директиву using для этого пространства имен (например, using System.Text;).
  • Директивы using следует указывать на самом верхнем уровне, а не внутри объявления пространств имен. Размещать по одной директиве на одну строку. Неиспользуемые директивы using (за исключением using System; using System.Collections.Generic;) рекомендуется удалять перед помещением файла в систему контроля версий.
  • Если вы используете некоторый внешний тип только в одном-двух местах, можно использовать полное имя (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, 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();

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

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

Атрибуты

  • Декларации атрибутов для классов, полей, свойств и методов располагаются по одному на строку, каждый в своей паре квадратных скобок, без круглых скобок, если параметры не нужны:
 [Serializable]
 [SomeAnotherAttribute(...)]
 public class SomeEventArgs : EventArgs
 {
    ...
 }

Комментирование

"Обычные" комментарии кода

  • Не используйте для комментариев форму /* ... */

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

  • Не раскрашивайте блоки комментариев :
 // +---------------------------+ 
 // | *** SomeMethod        *** |
 // +---------------------------+

Документирующие комментарии

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

  • В обязательном порядке должны быть прописаны документирующие комментарии у всех типов (независимо от их видимости), а также у членов типов, за исключением, возможно, приватных членов.
Примечание
Для проектов библиотек классов обязательно следует включить опцию в Visual Studio, которая будет генерировать xml-описание результирующей сборки, и, соответственно, проверять наличие и корректность всех необходимых документирующих комментариев:
  • диалог со свойствами проекта
  • страница Build
  • внизу "включить" опцию XML documentation file
  • Настоятельно рекомендуется перечислить в документирующих комментариях все исключения, выбрасываемые методом с пояснением об условиях возникновения этих исключений, кроме исключений — потомков ArgumentException, возникающих из-за неверных входных параметров.
  • В описании рекомендуется давать ссылки на другие типы и члены класса с помощью тега <see cref="xxx"/>
  • В документирующих комментариях следует описать краткий (в одно предложение) раздел summary, и при необходимости дальнейшие пояснения — в раздел remarks. Не следует писать раздел remarks без раздела summary. Если содержимое раздела summary вместе с тегами занимает менее 120 символов, следует разместить его в одну строку:
   /// <summary> Отослать сообщение </summary>
   /// <remarks>
   /// Отсылает сообщение адресату, указанному в <see cref="From" />
   /// </remarks>   
   /// <exception cref="RecipientNotSpecifiedException">
   /// Выбрасывается, если не указан адресат
   /// </exception>
   public void SendMessage()
   {
      ...

Это позволит видеть содержимое таких разделов summary даже в свернутом виде. Аналогичного правила следует придерживаться при написании тегов param.

  • Документирующий комментарий не должен "прилипать" к предыдущему члену типа, то есть должен быть отделен от него хотя бы одной пустой строкой. Это справедливо и для комментариев к элементам перечисления.

Именование

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

Выбор имен

  • Для именования старайтесь использовать правильные, полные английские слова. Лучше уточните написание в словаре, у коллег, напишите доступный комментарий на русском языке, но не называйте класс Nakladnaya. Помните, что в английском прилагательные идут всегда впереди существительного.
  • Не используйте сокращения без необходимости. Visual Studio предоставляет средства для быстрого набора доступных идентификаторов.
  • Не используйте слишком длинные составные имена. Вместо этого лучше разгруппировать классы в несколько пространств имен.
  • Очень не рекомендуется использовать в качестве имен зарезервированные и ключевые слова 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.
  • Допускается именовать локальные переменные и параметры короткими, даже однобуквенными именами, если в методе их мало и их смысл в контексте данного метода однозначен и интуитивно понятен, например:
 decimal CalculateOrderAmount(Order o, Employee e)
 {
     decimal amount = 0;
 
     foreach (OrderItem item in o.Items)
         amount += item.Goods.Price * item.Quantity;
 
     amount *= (1.0m - e.Discount); 
     return amount;
 }
  • Для счетчиков циклов for используйте имена i, j, k, если не требуется уточнения и акцентирования (например, в случае нескольких вложенных циклов).
  • Для именования исключения в обработчиках исключений используйте имя e, если оно не занято.
  • Для именования параметра - аргументов события в делегатах и обработчиках события используйте имя args:
private void TextBox1_MouseClick(object sender, MouseEventArgs args)
 
{    ...

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

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

  • SomeIdentifier // pascal case (P)
  • someIdentifier // camel case (c)
  • SOME_IDENTIFIER// upper case (U)
  • cpChar // венгерская нотация

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

Что именуем 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 P P _c Не рекомендуется делать поля, кроме private (за исключением static readonly)
Константа U U U U Все константы в UPPER_CASE!
Статическое поле P P P _c
Перечисление и его элементы P P P P Не нужно суффикса Enum!
Делегат, используемый в объявлении события P P P P Суффикс EventHandler
Событие P P P P в залоге doing и done
Локальная переменная c
Параметр метода c



Правила в текстовом виде:

  • Не используйте венгерскую нотацию
  • Не используйте символ подчеркивания для отделения частей составных идентификаторов (some_identifier), за исключением обработчиков событий (описано ниже).
  • Для именования пространств, классов, свойств, методов, событий используется PascalCase: CustIS.Universal.TestProjects
  • Для именования параметров методов, локальных переменных используется camelCase
  • Для именования приватных полей класса используется camelCase c префиксом "_": _department.
  • Неприватные поля делать вообще не рекомендуется, но если все-таки сделали, используйте PascalCase нотацию, это позволит при необходимости легко изменить поле на свойство.
  • Константы именуются в UPPER_CASE.
  • Для именования интерфейсов используется PascalCase с префиксом "I" (например: IComparable).
  • В именах методов – инициализаторов событий используется префикс "On"
  • В обработчиках событий используется название вида <ИсточникСобытия>_<НазваниеСобытия> (например: _applyButton_Click). Это имя метода автоматически предлагается Visual Studio.
  • Типы-делегаты, используемые в объявлении событий, следует именовать с использованием суффикса EventHandler. Другие делегаты рекомендуется называть с использованием суффикса Callback
  • Аргументы событий следует наследовать от EventArgs и именовать с суффиксом EventArgs.
  • Исключения именуются с суфиксом Exception
  • Классы атрибутов именуются с суффиксом Attribute
  • Кроме упомянутых выше случаев, использования префиксов/суффиксов, не отражающих предметную область лучше избегать (а вместо этого, активно использовать пространства имен). Исключением может являться случай, когда есть большое количество однородных классов, которые по каким-то причинам сложно именовать без префикса (например, если без префикса идентификаторы будут совпадать с ключевыми словами или с параллельной иерархией классов).


Пример допустимых имен: Department, Employee, UserSettings, ApplicationSettings (суффиксы отражают реальную предметную область, фактически это определяющее смысл класса существительное); PlusExpression, SelectExpression, JoinExpression, UnionExpression (сложно обойтись без суффикса);


Пример недопустимых имен: clsEmployee, CEmployee, EmployeeKindEnum (префиксы/суффиксы отражают характеристику типа, а не его функциональное назначение).

  • При написании аббревиатур длиной больше двух символов заглавной рекомендуется делать только первую букву, если аббревиатура стоит не в конце слова: XmlValue, но не XMLValue; EnableSSL, но не EnableSsl. При написании аббревиатур из двух символов обе рекомендуется делать заглавными: AbstractUIElement.
  • Используйте следующее написание стандартных аббревиатур (не все из них согласуются с предыдущим правилом): UI, Db, Xml, Id.
Примечание
Можно предложить следующее эмпирическое правило для написания двухбуквенных аббревиатур:
  • Обе буквы пишутся заглавными, если образованы от двух слов (например, UI = User Interface)
  • Заглавной пишется только первая буква, если обе буквы взяты из одного слова, — фактически, аббревиатура является сокращением слова (например, Db = Database)

Типовые конструкции

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

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

Скачать контракты для MS Visual Studio 2012 можно здесь

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

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

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

Работа со строками

  • Избегайте многократной конкатенации строк, например в циклах или в часто вызываемых методах. Используйте StringBuilder.
  • Не используйте StringBuilder по мелочам, особенно если это ухудшит читаемость по сравнению с простой конкатенацией.
  • Активно используйте возможности string.Format. Это позволит вынести строку в ресурсы и упростит формирование строки.
  • При разборе (парсинге) числовых строк и дат всегда указывайте IFormatProvider и явно задавайте в них разделители дат, времени, десятичную точку и т.п. Исключение — если данные приходят напрямую из пользовательского интерфейса и, соответственно, могут зависеть от региональных настроек конкретного компьютера.
  • Аналогичные правила — для преобразования чисел и дат в строки.
  • Для проверки строки на пустоту не используйте сравнение с "" или string.Empty. Вместо этого проверьте string.IsNullOrEmpy(s).

Работа с исключениями

  • Не используйте коды возврата ошибок, возвращаемые признаки успешности выполнения и т.п. С большой вероятностью клиентский код не будет их проверять. Вместо этого выбросьте подходящий тип исключения.
  • Перехватывайте только те исключения, для которых вы можете предоставить нетривиальную обработку.
  • Перехват всех исключений (catch (Exception)) без последующего проброса наверх (throw) допустим только на самом верхнем уровне (функции потока, точки входа в приложение и т.п.)
  • Для каждого потока должна существовать точка перехвата всех исключений с минимальной обработкой (журналирование, сообщение пользователю). В противном случае необработанное исключение молча обрушит приложение.
  • Если в ответ на пойманное исключение вы выбрасываете новое, по возможности передавайте в конструктор нового исключения ссылку на исходное (innerException).
  • Если вы поймали исключение и собираетесь перебросить его выше в неизменном виде, используйте конструкцию throw; не указывая экземпляр исключения (инструкция throw e; в данном случае усечет информацию о стеке вызовов)

Использование утверждений (Assertions)

В некоторых случаях в коде делаются предположения, проверить и обработать каждое из которых достаточно сложно, и при этом подразумевается, что они истинны и "иначе быть никак не должно". В этих случаях полезно использовать метод Assert из класса Microsoft.Contracts.Contract. Не следует использовать assertions в случае, если вы имеете дело с данными, пришедшими извне. Если эти данные некорректны, следует использовать обычную проверку аргументов (Contract.Requires). Assertions должны декларировать утверждения, вытекающие из предполагаемого условия корректности самой программы. Например, класс стека может декларировать и проверять утверждение, что непосредственно после добавления элемента он доступен по свойству Top. Или другой пример: после разбора строки конкретным регулярным выражением, имеющем 3 группы, перед обращением к группе 2 по индексу, может быть проверено утверждение, что групп действительно 3.

Строковые сообщения и литералы

  • Постарайтесь не использовать непосредственно в коде строковых значений, которые будут выдаваться пользователю - лучше вынести их в файл ресурсов. Visual Studio сгенерирует удобную обертку для доступа к этим строкам.
  • Постарайтесь не использовать непосредственно в коде "волшебных значений", особенно если они встречаются в коде несколько раз. Вынесите их в приватную константу в классе. Это позволит существенно упростить как изменение значения, так и преобразование его в свойство, при необходимости.

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

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

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

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

Приведение типов

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

В остальных случаях используйте cast operator: (Employee)person.

  • Для проверки приводимости типов используйте оператор is:
   if (person is Employee)
   {
       ((Employee)person).DoWork();
   }

Создание объектов заданного типа

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

Коллекции

  • По возможности используйте generic-коллекции: List<string> вместо нетипизированного ArrayList, Dictionary<long, Employee> вместо нетипизированного Hashtable.
  • На участках кода, где возможна работа с большими коллекциями и важна производительность, создавайте коллекwии с указанием размера (capacity). Это позволит сэкономить на многократных перераспределениях памяти при наполнении коллекции.

События

  • Перед вызовом делегата проверяйте его значение на null
  • Если дополнительная информация в событии не нужна, передайте в параметр EventArgs значение EventArgs.Empty
    if (Saving != null)
    {
        Saving(this, EventArgs.Empty);
    }
  • Если ваше событие не требует передачи дополнительной информации, используйте для его объявления делегат System.EventHandler.
  • Если ваше событие требует передачи дополнительной информации, объявите класс-потомк EventArgs и используйте для объявления события делегат EventHandler<T>.

Свойства и обеспечивающие приватные поля

  • Самый типичный случай - когда свойство реализуется на базе приватного поля. В этом случае разместите объявление обеспечивающего приватного поля сразу за описанием свойства. Для свойства напишите документирующий комментарий.
    /// <summary> Цвет текста </summary>
    public Color ForegroundColor
    {
        get { return _foregroundColor; }
        set 
        { 
          _foregroundColor = value; 
          OnPropertyChanged('ForegroundColor');
        }
    }
 
    private Color _foregroundColor = DefaultForegroundColor;
  • Кроме того по возможности нужно использовать авто-свойства типа
public Color ForegroundColor { get; private set; }

Лямбда-выражения

  • Если лямбда-выражение имеет только один входной параметр, скобки можно не ставить, во всех остальных случаях они обязательны.
p => p.ToString()
  • Пробелы ставятся с обеих сторон оператора =>
(x, y) => x == y
  • Многострочное тело лямбда-выражения должно начинаться с новой строки
    p => 
    {
        var someVariable = p.Descendants("text").FirstOrDefault();
        return someVariable
            ? someVariable.ToString()
            : string.Empty;
    }

Исключения

  • public исключения помечайте атрибутом [Serializable]. Это позволит легко переносить из через границы доменов приложений.
  • По возможности не стройте логику на исключениях. Если есть возможность вызывать метод, возвращающий признак успешности завершения работы (например, TryParse), вызывайте его, проверяя возвращаемое значение, вместо вызова метода, потенциально бросающего исключение (например, Parse) и последующей обработки исключения. При разработке собственных классов также старайтесь, наряду с методами, бросающими исключение, предусматривать методы, возвращающие признак успешности завершения работы (называя его TryXXX).
  • Если вам часто приходится вызывать конструктор собственного исключения вместе со string.Format, подумайте о создании перегруженного конструктора, принимающего строку форматирования и список аргументов, помеченный модификатором params.
  • Старайтесь не создавать чрезмерного количества собственных классов исключений - с большой вероятностью перехватывающий их код не будет различать их, и это окажется лишней работой.
  • Если вы перехватываете исключение и выбрасываете вместо него новое - передавайте исходное исключение в конструктор нового как innerException. В этом случае при возникновении необработанного исключения будет легче проследить причины его появления.

Условные операторы

  • Старайтесь не писать многострочных условий в операторе if. Вместо этого постарайтесь выделить осмысленные части условия с помощью введения промежуточной переменной или метода.

Выбор сигнатуры и типа возвращаемого значения

При выборе сигнатуры и типа возвращаемого значения метода часто встает вопрос: какой тип коллекции выбрать: T[], List<T>, IList<T> или что-то другое. Следует иметь в виду, что выбор более общего типа для параметра дает больше свободы при модификации клиентов данного метода, но ограничивает возможности по изменению тела метода. А выбор более конкретного типа для параметра дает больше возможностей по изменению тела методов, ограничивая клиентов. Тип возвращаемого значения метода ведет себя обратным образом.

Рефакторинг

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

  • переформатирование кода (особенно при помощи автоматических средств)
  • замена табуляций на пробелы или наоборот
  • расстановка модификатора readonly для приватных полей классов
  • замена интерфейсов IList на IEnumerable для типизации параметров методов
  • замена вызовов одних валидаторов (или каких-то других вспомогательных методов) на другие полностью эквивалентные по функциональности
  • массовое добавление или наоборот устранение this. для обращения к членам класса

Что же относится к рефакторингу:

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

В связи с выше изложенным, при внесении исправлений в существующий код нужно придерживаться следующих правил:

  • следует уважать и по возможности сохранять авторский стиль форматирования:
    • если объем исправлений мал и код не удовлетворяет текущим правилам форматирования, то исправления должны быть внесены в стиле форматирования существующего кода
    • если объем исправлений достаточно велик, то новые куски могут быть отформатированы в соответствии с текущими представлениями о правильном стиле, а форматирование остальных частей должно быть оставлено как есть
    • весь файл может быть переформатирован только в случае почти полного его переписывания (т.е. почти полной замены старой реализации на новую)
  • исправление warning-ов ReSharper-а не может считаться "выполнением рефакторинга"
    • это допустимо выполнять (устранять warning-и ReSharper-а) только при условии необходимости внесения в данный файл существенных исправлений логики (мелкий патчинг, который выражается в правке одной-пяти строк кода не может считаться существенным исправлением)