Целью создания этого списка правил является попытка установить стандарты написания кода на C#, которые были бы удобными и практичными одновременно. Само собой, мы практикуем то, что создали. Эти правила являются одним из тех стандартов, которые лежат в основе нашей ежедневной работы в AvivaSolutions. Не все эти правила имеют четкое обоснование. Некоторые из них просто приняты у нас в качестве стандартов.
Статический анализатор кода VisualStudio (который также известен как FxComp) и StyleCop могут автоматически применять многие из правил кодирования и оформления путем анализа скомпилированных сборок. Вы можете сконфигурировать их таким образом, чтобы анализ производился во время компиляции или был неотъемлемой частью непрерывной или ежедневной сборки. Этот документ просто добавляет дополнительные правила и рекомендации, но его вспомогательный сайт www.csharpcodingguidelines.com предоставляет список правил анализа кода, необходимых в зависимости от того, с какой базой кода вы работаете.
Зачем мне это использовать?
Некоторые воспринимают стандарты написания кода как некие ограничения, которые ущемляют свободу творчества. Но тем не менее такой подход оправдан и проверен в течение многих лет. Почему? Потому, что не каждый разработчик знает, что:
- На то, чтобы разобраться в коде уходит в 10 раз больше времени, чем на то, чтобы его изменить
- Не каждый разработчик знает о тонкостях использования основных конструкций в C#
- Не каждый знает о том, каких соглашений .NET Framework следует придерживаться, например, при использовании
IDisposable
или LINQ с его отложенным исполнением - Не каждый знает, как частные решения какой-либо задачи могут повлиять на производительность, безопасность, поддержку нескольких языков и т.д.
- Не каждый разработчик сможет понять красивый, но абстрактный код другого разработчика
Базовые принципы
Имеется много неочевидных вещей, с которыми я сталкивался в моей работе в качестве консультанта и которые заслуживают упоминания как минимум в одной рекомендации по написанию кода. К сожалению, объем этого руководства должен находиться в разумных пределах. Однако, чтобы ни подумали некоторые начинающие разработчики, если я что-то не упомянул в этом документе, это не значит, что это не заслуживает внимания.
С учетом вышесказанного вам следует понимать, что данный документ не в силах охватить все ситуации, с которыми вы можете столкнуться. В любом спорном случае вам следует ссылаться на базовые принципы, которые применимы в любой ситуации вне зависимости от контекста. К ним относятся:
- The Principle of Least Surprise. «Правило наименьшего удивления». Вы должны выбирать наиболее очевидное решение для своих задач, чтобы не сбить с толку других разработчиков и сделать код более понятным
- Keep It Simple Stupid (или KISS). Дословно – «Делай это проще, тупица». Этот принцип гласит, что для решения поставленных задач необходимо выбирать наиболее простое решение
- You Ain’t Gonne Need It (или YAGNI). «Вам это не понадобится». Он гласит: «Работайте над решением текущих задач, не пишите код только потому, что думаете, будто он пригодится вам в дальнейшем (вы можете предсказывать будущее?)»
- Don’t Repeat Yourself (или DRY). «Не повторяйся». Этот принцип призывает вас воздержаться от дублирования кода и при этом не забыть об «эвристическом правиле трех»
Несмотря на то, что код может выглядеть очень неплохо, посмотрите, понятен ли он для других разработчиков, имеет ли он то поведение, которое от него следует ожидать, не пытается ли он решить те задачи, которые, возможно, возникнут в будущем. Если это не так, то стоит задуматься о рафакторинге.
Как мне начать?
- Попросите всех разработчиков внимательно прочитать этот документ хотя бы один раз. Это должно дать им понимание того, какие принципы в нем содержатся
- Убедитесь, что всегда имеются под рукой несколько печатных копий кратких ссылок на данное руководство
- Включите наиболее значимые правила в ваш Project Checklist, на соответствие оставшимся правилам делайте проверку во время Peer Review
- Решите, какие правила статического анализа применимы к вашему проекту и напишите их где-то. Например, опубликуйте их на вашем командном TFS сайте или создайте стандартный набор правил Visual Studio
- Пополните настраиваемый словарь анализа кода терминами, именами и понятиями, специфичными для вашей компании или области деятельности. Если вы этого не сделаете, статический анализатор будет выводить предупреждения на конструкции, которые не будут включены в его внутренний словарь
- Настройте VisualStudio выполнять проверку на следование выбранным правилам статического анализа в качестве части релизной сборки. Тогда они не будут служить помехой при разработке и отладке, но могут быть запущены при переключении на релизную конфигурацию
- Добавьте в чеклист проекта пункты проверки на следование рекомендациям, чтобы быть уверенным, что весь новый код им соответствует, или используйте соответствующую политику чекинов, если вы хотите, чтобы любой код проверялся на соответствие правилам перед отправкой в репозиторий
- ReSharper имеет интеллектуальный инспектор кода, который при некоторой настройке уже поддерживает многие требования данного руководства. Он будет автоматически выделять любой код, который не соответствует правилам именования (например, стиль именования паскаль или верблюжья нотация), находить мертвый код и делать любые другие проверки. Одного клика мыши (или сочетания горячих клавиш) будет достаточно для того, чтоб исправить это
- ReSharper, кроме всего прочего, имеет окно File Structure, которое дает общее представление о членах вашего класса или интерфейса и позволяет вам легко перемещать их с помощью простого перетаскивания
- Используя GhostDoc, вы можете генерировать XML комментарии, используя сочетания горячих клавиш. Вся прелесть заключается в том, что это позволяет точно следовать стилю MSDN документации. Тем не менее, не злоупотребляйте этим инструментом и используйте его только в качестве стартового
Почему вы создали это?
Идея пришла в 2002 году, когда Вику Харторгу (Philips Medical System) и мне была поставлена задача написания стандартов кодирования для C# 1.0. С того времени я регулярно добавлял, удалял и изменял правила, основываясь на опыте, отзывах коллег и новых плагинах, представленных в Visual Studio 2010. Кроме того, после прочтения книги Роберта Мартина «Чистый код: Создание, анализ и рефакторинг» я загорелся его идеями и решил включить некоторые из них в качестве правил. Хочу заметить, что этот документ ни в коем случае не является заменой его книги. Я искренне рекомендую прочесть его книгу, чтобы твердо усвоить принципы, лежащие в основе его рекомендаций. К тому же я решил дополнить принципы написания кода некоторыми принципами проектирования. Они являются слишком важными, чтобы их игнорировать, и имеют большое влияние на достижение высокого качества кода.
Эти рекомендации являются стандартами?
В этом документе не утверждается, что в проектах необходимо строго соблюдать эти рекомендации. Также в нем не утверждается, что эти рекомендации более важны, чем остальные. Однако мы рекомендуем, чтобы вы сами решили, какие рекомендации важны, какие отклонения от стандарта можно допустить в проекте, кто будет консультантом в случае, если возникнут сомнения, и какую верстку использовать для исходного кода. Очевидно, именно вы должны принять эти решения, прежде чем приступить к реальной работе.
Чтобы помочь вам в выборе, я присвоил степень важности каждой рекомендации:
— Правило, которым вы никогда не должны пренебрегать и которое применимо ко всем ситуациям.
— Настоятельно рекомендуется соблюдать это правило.
— Рекомендуется соблюдать это правило, но оно применимо не ко всем ситуациям.
В заключение хочу сказать, что нет необходимости в том, чтобы весь созданный код соответствовал стандартам. Однако, если есть возможность модифицировать шаблоны, используемые для генерации кода, постарайтесь сделать так, чтобы они создавали код, максимально соответствующий этим рекомендациям.
Обратная связь и отказ от ответственности
Этот документ был составлен во многом благодаря большому вкладу членов сообщества, статьям в блогах, онлайн дискуссиям и многим годам разработки на С#. Если у вас есть вопросы, замечания и предложения пишите мне на адрес dennis.doomen@avivasolutions.nl или в твиттер http://twitter.com/ddoomen. Я буду пытаться регулярно пересматривать и переопубликовывать этот документ в соответствии с новыми идеями, опытом и замечаниями.
Хочу отметить, что эти рекомендации лишь отражают мое видение правильного кода на C#. Aviva Solutions не несет ответственности за любые прямые или косвенные убытки, вызванные применением стандартов, описанных в данном документе.
Разрешается копировать, адаптировать и распространять этот документ и краткие ссылки на это руководство в некоммерческих целях и для внутреннего использования. Но вы не можете распространять этот документ, публиковать или распространять любые адаптированные копии данного документа в коммерческих целях без предварительного получения письменного разрешения от Aviva Solutions.
Рекомендации по проектированию классов
AV1000 Класс или интерфейс должны иметь единственное предназначение
Класс или интерфейс должен иметь единственное предназначение в рамках системы, в которой он используется. Как правило, класс служит одной из целей: либо он описывает тип, например, email или ISBN (международный стандартный книжный номер), либо представляет из себя абстракцию некоторой бизнес-логики, либо он описывает структуру данных, либо отвечает за взаимодействие между другими классами. Он никогда не должен в себе комбинировать эти задачи. Это правило известно как Принцип единой ответственности, один из принципов SOLID.
Совет: Класс со словом «And» в названии — это явное нарушение данного правила.
Совет: Для взаимодействия между классами используйте паттерны проектирования. Если вам не удается применить ни один из паттернов к классу, возможно, он берет на себя слишком большую ответственность.
Примечание: Если вы создаете класс, который описывает тип, вы можете значительно упростить его использование, если сделаете его неизменяемым.
AV1001 Создавайте новые экземпляры класса с помощью конструктора таким образом, чтобы в результате вы получили полностью готовый к использованию объект .
Созданный объект не должен нуждаться в установке дополнительных свойств перед использованием в каких бы целях его не планировалось бы применять. При этом если конструктор нуждается в более чем трех параметрах (что нарушает правило AV1561), возможно, что класс берет на себя слишком большую ответственность (нарушение правила AV1000).
AV1003 Интерфейс должен быть небольшим и должен быть сфокусирован на решении одной задачи
Интерфейс должен иметь имя, которое ясно описывает его предназначение или роль, которую он выполняет в системе. Не объединяйте слабо связанные элементы в один интерфейс только потому, что они относятся к одному классу. Формируйте интерфейсы на основании функциональности, за которую отвечают вызываемые методы или на основе конкретной задачи, которую этот интерфейс выполняет. Это правило более известно как Принцип сегрегации интерфейса.
AV1004 Используйте интерфейс, а не базовый класс, чтобы поддерживать несколько реализаций
Если вы хотите выставить точку расширения вашего класса, выставляйте его в качестве интерфейса, а не базового класса. Вам не захочется заставлять пользователей этой точки расширения делать свои реализации на основе базового класса, который может вести себя нежелательным образом. Впрочем, для их удобства вы можете создать реализацию по умолчанию (абстрактный класс), которая может служить в качестве отправной точки.
AV1005 Используйте интерфейс для реализации слабой связанности между классами
Интерфейсы – это отличный инструмент для реализации слабой связанности между классами.
- Они помогают избежать двунаправленной связанности
- Они упрощают замену одной реализации другой
- Они позволяют заменить недоступный внешний сервис или ресурс временной заглушкой для использования в нерабочем окружении
- Он позволяет заменить текущую реализацию фиктивной при модульном тестировании
- Используя фреймворк для внедрения инъекции зависимостей, вы можете собрать в одном месте логику выбора класса в зависимости от запрашиваемого интерфейса
AV1008 Избегайте статических классов
За исключением статических классов, которые используются для создания методов расширений, статические классы очень часто приводят к плохому коду. К тому же их очень сложно, если вообще возможно, тестировать в изоляции до тех пор, пока вы не прибегнете к каким-либо очень изощренным инструментам.
Примечание: Если вам действительно необходим статический класс, пометьте его как
static
. В этом случае компилятор запретит создание экземпляров этого класса и инициализирует ваш класс перед первым обращением к нему. Это избавит вас от необходимости использовать приватный конструктор. AV1010 Не скрывайте унаследованные элементы за ключевым словом new
Это не только противоречит полиморфизму – одному из самых важных принципов объектно-ориентированного программирования, но и делает дочерние классы трудными для понимания. Рассмотрим следующие два класса:
public class Book
{
public virtual void Print()
{
Console.WriteLine("Printing Book");
}
}
public class PocketBook : Book
{
public new void Print()
{
Console.WriteLine("Printing PocketBook");
}
}
В этом случае класс будет иметь не то поведение, которое вы будете ожидать от него при его использовании:
PocketBook PocketBook = new PocketBook ();
pocketBook.Print (); // Выведет "Printing PocketBook"
((Book)pocketBook).Print(); // Выведет "Printing Book"
Здесь показано, что метод
Print
имеет различное поведение в зависимости от того, вызывается ли он через ссылку на базовый класс или был вызван в качестве метода производного класса.AV1011 Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом
Другими словами, поведение наследуемых классов не должно противоречить поведению, заданному базовым классом, то есть поведение наследуемых классов должно быть ожидаемым для кода, использующего переменную базового типа. Наиболее известным примером нарушения этого правила является исключение
NotImplementedExeption
, когда оно возникает при переопределении метода базового класса. Примечание: Этот принцип также известен как Принцип подстановки Барбары Лисков, один из принципов SOLID.
AV1013 Не ссылайтесь на производные классы из базового класса
Наличие зависимостей в родительском классе от его дочерних классов нарушает принципы объектно-ориентированного программирования и не дает возможности другим разработчикам наследоваться от вашего базового класса.
AV1014 Объект должен обладать ограниченным знанием о других объектах, которые не имеют непосредственного отношения к этому объекту
Если ваш код напоминает код, который приведен ниже, то вы нарушаете Закон Деметры.
someObject.SomeProperty.GetChild().Foo();
Объект не должен открывать доступ к классам, от которых он зависит, потому что объекты-пользователи могут неправильно использовать свойства и методы для получения доступа к объектам, находящимся за ним. Делая так, вы позволяете вызываемому коду объединиться в одно целое с классом, который вы используйте. Таким образом, вы ограничиваете возможность замены одной реализации на другую в будущем.
Примечание: Использование класса, реализующего текучий интерфейс (Fluent Interface), может показаться нарушением данного правила. Но вызываемые методы просто передают контекст вызова следующему звену. Таким образом, это не вызывает противоречий.
Исключение: При использовании инверсии управления и фреймворков инъекции зависимостей часто требуется, чтобы зависимости выставлялись в качестве публичных свойств. До тех пор пока эти свойства не используются ни для чего другого, кроме как реализации инъекции зависимостей, я бы не стал рассматривать это как нарушение правила.
AV1020 Избегайте двунаправленной зависимости
Двунаправленная зависимость означает, что два класса знают о публичных методах друг друга или зависят от внутреннего поведения друг друга. Рефакторинг или замена одного из этих двух классов требуют изменений в обоих классах и могут повлечь за собой много непредвиденной работы. Наиболее очевидное решение – это создание интерфейса для одного из этих классов и использование инъекции зависимостей.
Исключение: Доменные модели (Domain Model), применяемые в проектировании на основе предметной области (Domain Driven Design), могут использовать двунаправленные зависимости, описывающие ассоциации из реального мира. В таких случаях я стараюсь удостовериться, что они действительно необходимы, и по мере возможности пытаюсь их избегать.
AV1025 Классы должны иметь состояние и поведение
Если в вашем репозитории находится множество классов, которые служат только для описания данных, то, скорей всего, у вас есть несколько классов (статических), содержащих в себе много логики обработки этих данных (смотрите правило AV1008). Используйте принципы объектно-ориентированного программирования согласно рекомендациям в этом разделе, переместите вашу логику обработки данных к тем данным, которые ей используются.
Исключение: Единственным исключением из этого правила являются классы, используемые для передачи данных между подсистемами приложения, также называемые Data Transfer Objects, или классы, служащие оберткой для параметров метода.
Рекомендации по проектированию членов класса
AV1100 Свойства класса должны иметь возможность быть установленными в любом порядке
Свойства не должны зависеть от других свойств. Другими словами, не должно быть разницы в том, какое свойство мы устанавливаем в первую очередь. Например, сначала
DataSouse
, затем DataMember
или наоборот. AV1105 Используйте метод вместо свойства
Используйте методы вместо свойств, если:
- Производится более дорогостоящая работа, чем настройка значения поля
- Если свойство представляет из себя конвертацию. Например,
Object.ToString
метод - Если свойство возвращает различные значения для каждого вызова, даже если аргументы при этом не изменяются. Например, метод
NewGuid
будет каждый раз возвращать новое значение - Если использование свойства вызывает побочный эффект. Например, изменение внутреннего состояния, которое не имеет прямого отношения к свойству (это нарушает command-query responsibility segregation (CQRS))
Исключение: Заполнение внутреннего кэша или реализация lazy-loading являются хорошими исключениями из этого правила.
AV1110 Не используйте взаимоисключающие свойства
Если у вас имеются свойства, которые не могут быть использованы в одно и то же время, то это значит, что они представляют из себя две взаимоисключающие концепции. Даже если эти концепции могут иметь некоторую общую логику и состояние, то очевидно, что они имеют различные правила, которые не сочетаются друг с другом.
Нарушение этого правила часто можно встретить в доменной модели, когда свойства инкапсулируют в себе всевозможные виды условной логики, содержащей взаимоисключающие правила. Это зачастую вызывает эффект волны и обслуживание такого кода становится более трудоемким.
AV1115 Метод или свойство должны иметь единственное предназначение
Так же, как и класс (смотрите правило AV1000), каждый метод должен иметь одну зону ответственности.
AV1125 Не выставляйте объекты, описывающие состояние, посредством статических членов
Объекты с состоянием (stateful object) – это объекты, которые содержат в себе множество свойств и логики, которую эти свойства инкапсулируют. Если вы выставите такой объект через статическое свойство или метод другого объекта, то будут плохо поддаваться рефакторингу и юнит-тестированию классы, которые зависят от объекта с состоянием. В общем случае, использование описанной выше конструкции – отличный пример нарушения множества рекомендаций, описанных в этой главе.
Классическим примером служит свойство
HttpContext.Current
в ASP.NET. Многие смотрят на класс HttpContext
как на источник большого количества грязного кода. По факту, одно из правил тестирования – изолируйте уродливые вещи (Isolate the Ugly Stuff) — часто относится к этому классу.AV1130 Возвращайте
IEnumerable<T>
или ICollection<T>
вместо какой-либо конкретной коллекции Если вы не хотите, чтобы пользователи могли изменять коллекцию, не возвращайте массив, лист или другой класс коллекции напрямую. Вместо этого возвращайте
IEnumerable<T>
или, если пользователю требуется знать количество элементов в коллекции, ICollection<T>
.Заметка: Если вы используете .Net 4.5, вы также можете применять
IReadOnlyCollection<T>
, IReadOnlyList<T>
или IReadOnlyDictionary<TKey, TValue>
.AV1135 Свойства, методы или аргументы, которые представляют из себя строку или коллекцию, никогда не должны быть равны
null
Возвращение
null
как результат выполнения метода, может быть неожиданностью для пользователя. Всегда возвращайте пустую коллекцию или пустую строку вместо нулевой ссылки. Кроме всего прочего, это избавит вас от необходимости засорять ваш код дополнительными проверками на null
или, что еще хуже, использовать string.IsNullOrEmpty()
.AV1137 Определяйте параметры настолько специфичными, насколько это возможно
Если элемент класса в качестве параметров требует часть данных другого класса, определяйте типы данных этих параметров как можно более конкретными и не принимайте в качестве параметра весь объект целиком. Например, рассмотрим метод, который в качестве параметра требует передать строку подключения, описанную в некоем центральном интерфейсе
IConfiguration
. Вместо того, чтобы в качестве параметра принимать весь объект, реализующий этот интерфейс, передайте только строку подключения. Это не только позволит вам уменьшить количество зависимостей в коде, но и улучшит его сопровождаемость в отдаленной перспективе.AV1140 Используйте типы, характерные для вашей предметной области, вместо примитивов
Вместо использования строк, целых и дробных чисел для представления таких специфичных типов, как ISBN (международный стандартный книжный номер), адрес электронной почты или денежной суммы, создавайте объекты на каждый тип, которые будут включать в себя как сами данные, так и правила валидации, которые будут к ним применяться. Делая так, вам удастся избежать множественных реализаций одних и тех же бизнес-правил. Это улучшит сопровождаемость вашего кода и уменьшит количество багов.
Различные рекомендации по проектированию
AV1200 Генерируйте исключение вместо возвращения статусного сообщения
Кодовая база, которая использует возвращаемое статусное сообщение для определения завершилась ли операция успешно или нет, зачастую имеет вложенные
if
выражения, разбросанные по всему коду. Зачастую пользователи забывают проверить возвращаемое значение. Структурированная обработка исключений была введена для того, чтобы позволить вам генерировать исключения и отлавливать или заменять их на более высоком уровне. В большинстве систем является довольно распространенной практикой генерировать исключения всякий раз, когда происходит неожиданная ситуация.AV1202 Обеспечьте полное и осмысленное сообщение об исключении
Сообщение должно объяснять, что привело к исключению и ясно описывать, что нужно сделать, чтобы избежать его в дальнейшем.
AV1205 Генерируйте настолько специфичное исключение, насколько это возможно
Например, если метод принял в качестве входного параметра
null
, следует сгенерировать ArgumentNullException
вместо ArgumentException
— его базового типа.AV1210 Не игнорируйте ошибку путем обработки общих исключений
Не игнорируйте ошибки путем обработки общих исключений, таких как
Exception
, SystemException
и другие в коде приложения. Только обработчик ошибок самого верхнего уровня должен отлавливать общие исключения с целью логирования и корректного завершения работы приложения.AV1215 Обрабатывайте исключения в асинхронном коде должным образом
Когда вы генерируете или обрабатываете исключения в коде, который использует
async/await
или Task
, помните о следующих двух правилах:- Исключения, которые возникают в пределах блоков
async/await
и внутриTask
, распространяются на задачу, которая ожидает выполнение этих блоков - Исключения, которые возникли в коде, предшествующем
async/await
иTask
, распространяются на вызывающий код
AV1220 Всегда проверяйте делегат обработчика события на
null
Событие, которое не имеет подписок, равно
null
. Таким образом, перед тем, как оно будет вызвано, убедитесь, что список делегатов, представляющих это событие, не равен null
. Кроме того, чтобы избежать конфликтов при изменении из параллельных потоков, используйте временную переменную, чтобы избежать одновременного изменения.event EventHandler<NotifyEventArgs> Notify;
void RaiseNotifyEvent(NotifyEventArgs args)
{
EventHandler<NotifyEventArgs> handlers = Notify;
if (handlers != null)
{
handlers(this, args);
}
}
Подсказка: Вы можете сделать так, чтобы список делегатов не был совсем пустым. Просто объявите пустой делегат так, как показано ниже:
event EventHandler<NotifyEventArgs> Notify = delegate {};
AV1225 Для обработки каждого события используйте защищенный виртуальный метод
Выполнение этой рекомендации позволит производным классам обрабатывать событие базового класса путем переопределения защищенного метода. Название защищенного виртуального метода должно быть таким же, как название события, но с префиксом
On
. Например, защищенный виртуальный метод для события с названием TimeChanged
должен быть назван OnTimeChanged
.Примечание: От производных классов, которые переопределяют защищенный виртуальный метод, не требуется вызывать реализацию базового класса. Базовый класс должен продолжать свою работу корректно, даже если его реализация не вызвана.
AV1230 Использование событий уведомления об изменении свойств
Событие уведомления об изменении свойства должно иметь название наподобие
PropertyChanged
, где Property
должно быть изменено на название свойства, с которым связано это событие.Примечание: Если ваш класс имеет множество свойств, которые требуют соответствующих событий, попробуйте реализовать вместо этого интерфейс
INotifyPropertyChanged
. Он часто используется в паттернах Presentation Model и Model-View-ViewModel.AV1235 Не отправляйте
null
в качестве аргумента при вызове события Зачастую обработчик событий используется для обработки схожих событий от множества отправителей. В таком случае передаваемый аргумент используется для того, чтобы передать контекст вызова события. Всегда отправляйте ссылку на контекст (обычно
this
) при вызове события. Кроме того, не отправляйте null
при вызове события, если оно не имеет данных. Если событие не имеет данных, отправьте EventArgs.Empty
вместо null
.Исключение: Для статических событий передаваемый аргумент должен быть
null
.AV1240 Используйте общие ограничения, если возможно
Вместо приведения и преобразования типа из конкретного в общий и наоборот используйте ключевое слово
where
или оператор as
, чтобы привести объект к конкретному типу. Например: class SomeClass
{}
// Неправильно
class MyClass<T>
{
void SomeMethod(T t)
{
object temp = t;
SomeClass obj = (SomeClass) temp;
}
}
// Правильно
class MyClass<T> where T : SomeClass
{
void SomeMethod(T t)
{
SomeClass obj = t;
}
}
AV1250 Вычисляйте результат LINQ-запроса до того, как вернуть его
Посмотрите на следующий код:
public IEnumerable<GoldMember> GetGoldMemberCustomers()
{
const decimal GoldMemberThresholdInEuro = 1000000;
var q = from customer in db.Customers
where customer.Balance > GoldMemberThresholdInEuro
select new GoldMember(customer.Name, customer.Balance);
return q;
}
Поскольку LINQ-запросы используют отложенное выполнение, возвращение
q
, как это ни странно, вернет древо выражения, представляющее вышеуказанный запрос. Всякий раз, когда пользователь вычисляет результат, используя foreach
или что-то похожее, весь запрос выполняется заново, создавая каждый раз новые экземпляры GoldMember
. Как следствие, вы не сможете использовать оператор ==
, чтобы сравнить различные экземпляры GoldMember
. Вместо этого всегда явно вычисляйте результат LINQ-запроса, используя ToList()
, ToArray()
или схожие методы.Рекомендации по улучшению сопровождаемости кода
AV1500 В методе не должно быть более 7 объявлений
Метод, который включает в себя более 7 объявлений, скорей всего делает слишком много или берет на себя слишком большую ответственность. Кроме того, человеческая память требует, чтобы метод был коротким. Она не в состоянии удерживать в себе одновременно большее количество вещей, чтобы точно проанализировать и понять, что делает тот или иной кусок кода. Разделите метод на несколько маленьких, имеющих четкое предназначение, и дайте им имена, которые будут точно указывать на то, что они делают. При этом обратите внимание на то, чтобы алгоритм работы этой части программы оставался ясен для понимания.
AV1501 Создавайте все члены класса
private
, а типы internal
по умолчанию Чтобы принять более взвешенное решение о том, какие элементы должны быть доступны другим классам, в первую очередь как можно больше ограничьте их область видимости. Затем тщательно подумайте, какие члены или типы действительно стоит сделать public.
AV1502 Избегайте двойного отрицания
Несмотря на то, что такое свойство, как
customer.HasNoOrder
имеет право на существование, избегайте его использования с отрицанием. Например:bool hasOrders = !customer.HasNoOrders;
Двойное отрицание более сложно для понимания, чем простое выражение, и люди склонны путаться в нем.
AV1505 Наименование сборки в ее названии должно идти после наименования ее пространства имен
Все DLL должны именоваться в соответствии с паттерном
<Company>.<Component>.dll
, где <Company>
— это название вашей фирмы, а <Component>
— наименование одного или более пространства имен, разделенных точками. Например: AvivaSolutions.Web.Controls.dll
В качестве примера можно привести объединение группы классов в пространстве имен
AvivaSolutions.Web.Binding
, которое включает в себя некая сборка. Согласно данной рекомендации эта сборка должна быть названа AvivaSolutions.Web.Binding.dll
. Исключение: Если вы решите связать классы из различных несвязанных пространств имен в одну сборку, добавьте суффикс Core к ее названию. Однако не используйте этот суффикс в названиях пространств имен. Например:
AvivaSolutions.Consulting.Core.dll
.AV1506 Называйте файлы с исходным кодом в соответствии с тем типом данных, который он содержит
Используйте нотацию паскаль для именования файлов и не используйте подчеркивания.
AV1507 Ограничивайте содержимое файла с исходным кодом одним типом данных
Исключение: Вложенные типы, по понятным причинам, должны быть частью того же самого файла.
AV1508 Наименование файла с исходным кодом, который содержит частичный тип данных, должно отражать назначение этой части
Когда используются частичные типы и идет разделение частей на файлы, имя каждого файла должно быть логически разделено на две части. Первая часть – название типа. Вторая – роль, которую данный фрагмент играет в типе. Например:
// В файле MyClass.cs
public partial class MyClass
{...}
// В файле MyClass.Designer.cs
public partial class MyClass
{...}
AV1510 Используйте
using
вместо указания полной ссылки на тип из другого пространства имен Не используйте полную ссылку на тип из другого пространства имен в целях предотвращения конфликтов именования. Например, не делайте так:
var list = new System.Collections.Generic.List<string>();
Лучше сделать так:
using System.Collections.Generic;
var list = new List<string>();
Если вам необходимо избежать конфликтов именования, используйте директиву
using
для создания псевдонима пространства имен или типа:using Label = System.Web.UI.WebControls.Label;
AV1515 Не используйте «магические» числа
Не используйте литеральные значения, числа или строки в вашем коде ни для чего другого, кроме как для объявления констант. Для примера:
public class Whatever
{
public static readonly Color PapayaWhip = new Color(0xFFEFD5);
public cons tint MaxNumberOfWheels = 18;
}
Строки, предназначенные для логирования или трассировки, являются исключением из этого правила. Литеральные значения допускается использовать только тогда, когда их смысл ясен из контекста и их не планируется изменять. Например:
mean = (a + b) / 2; // среднее арифметическое
WhaitMilliseconds(waitTimeInSeconds * 1000); // тут тоже все понятно
Если значение одной константы зависит от значения другой, укажите это в своем коде.
public class SomeSpecialContainer
{
public const int MaxItems = 32;
public const int HighWaterMark = 3 * MaxItems / 4; // 75%
}
Заметка: Перечисления часто могут использоваться в качестве хранилища символьных констант.
AV1520 Используйте
var
только тогда, когда тип переменной очевиден Используйте
var
только в том случае, если переменной присваивается результат LINQ-запроса или если тип переменной очевиден и использование var
повысит читаемость кода. Например, так делать не стоит:var i = 3; // Что за тип? int? uint? float?
var myfoo = MyFatoryMethod.Create(“arg”); // Не понятно, какой тип имеет
// класс или интерфейс. Кроме того,
// тяжело изменять код, который работает
// c этой переменной, если исходный класс
// вам недоступен
Вместо этого используйте
var
как в примерах ниже:var q = from order in orders where order.Items > 10 and order.TotalValue > 1000;
var repository = new RepositoryFactory.Get<IOrderRepository>();
var list = new ReadOnlyCollection<string>();
Во всех трех примерах тип присваиваемых переменным значений очевиден. Для получения более подробной информации об использовании var читайте статью Ерика Липперта «Использование и злоупотребления неявной типизацией».
AV1521 Объявляйте и инициализируйте переменные как можно позже
Избегайте стиля языков C и VisualBasic, когда все переменные объявляются в начале блока. Объявляйте и инициализируйте каждую переменную только тогда, когда она необходима.
AV1522 Присваивайте значение каждой переменной в отдельном объявлении
Никогда не делайте так:
var result = someField = GetSomeMethod();
AV1523 Предпочитайте инициализаторы объектов и коллекций раздельной установке свойств и раздельному добавлению новых объектов в коллекцию
Вместо такой конструкции:
var startInfo = new ProcessStartInfo(“myapp.exe”);
startInfo.StandardOutput = Console.Output;
startInfo.UseShellExecute = true;
Используйте инициализатор объекта:
var startInfo = new ProcessStartInfo(“myapp.exe”)
{
StandardOutput = Console.Output,
UseShellExecute = true
};
Вместо создания коллекции — таким образом:
var countries = new List<string>();
countries.Add(“Netherlands”);
countries.Add(“United States”);
Используйте инициализатор коллекции:
var countries = new List<string> { “Netherlands”, “United States” };
AV1525 Не производите явного сравнения с
true
или false
Сравнение логического значения с
true
или false
– это, как правило, плохой стиль программирования. В качестве примера:while (condition == false) // неправильно, плохой стиль
while (condition != true) // тоже неправильно
while (((condition == true) == true) == true) // где ты остановишься?
while (condition) // OK
AV1530 Не изменяйте переменную цикла
for
или foreach
внутри тела цикла Обновление переменной цикла внутри тела цикла ведет к тому, что код становится запутанным. Особенно, если переменная изменяется более чем в одном месте. Это правило также относится к циклу
foreach
, хотя после окончания итерации енумератор обнаружит изменение коллекции и выдаст исключение.for (int index = 0; index < 10; ++index)
{
if (some condition)
{
index = 11; // Неправильно! Вместо этого используйте ‘break’ или ‘continue’.
}
}
AV1532 Избегайте вложенных циклов
Методы, содержащие вложенные циклы, более сложны для понимания, чем те, которые содержат только один цикл. По факту, в большинстве случаев циклы могут быть заменены гораздо меньшим по размеру LINQ-запросом, который использует ключевое слово
from
два раза и более для объединения данных.AV1535 Всегда используйте конструкции
if
, else
, while
, for
, foreach
и case
с фигурными скобками Пожалуйста, обратите внимание, что это также поможет избежать возможной путаницы с конструкциями вроде этой:
if (b1) if (b2) Foo(); else Bar(); // к какому ‘if’ относится ‘else’?
Лучше сделать так:
if (b1)
{
if (b2)
{
Foo();
}
else
{
Bar();
}
}
AV1536 Всегда используйте блок
default
в конце конструкции switch/case
Если блок
default
будет пуст, добавьте поясняющий комментарий. Кроме того, если этот блок не должен быть достижимым, сгенерируйте при его вызове InvalidOperationException
, чтобы обнаружить будущие изменения, при которых ни один из блоков case
не будет достигнут. Следование этой рекомендации позволит вам писать более чистый код, потому что все сценарии выполнения уже были продуманы.void Foo(string answer)
{
switch (answer)
{
case "no":
Console.WriteLine("You answered with No");
break;
case "yes":
Console.WriteLine("You answered with Yes");
break;
default:
// Not supposed to end up here.
throw new InvalidOperationException("Unexpected answer " + answer);
}
}
AV1537 Заканчивайте каждый блок
if-else-if
объявлением else
Например:
void Foo(string answer)
{
if (answer == "no")
{
Console.WriteLine("Вы ответили Нет");
}
else if (answer == "yes")
{
Console.WriteLine("Вы ответили Да");
}
else
{
// Что должно случиться, когда этот блок выполнится? Игнорировать это?
// Если нет, то сгенерировать исключение InvalidOperationException.
}
}
AV1540 Старайтесь избегать нескольких объявлений
return
Один вход — одна точка выхода, так звучит этот принцип. Он позволяет поддерживать понятным ход выполнения метода. При этом если метод очень маленький и соответствует рекомендации AV1500, тогда несколько объявлений
return
могут быть актуальными и улучшат читаемость кода. Например, если метод возвращает логическое значение, удобней использовать два объявления return
вместо логической переменной, которую вернет метод и которой будут присваиваться значения по ходу его выполнения.AV1545 Не используйте блок
if-else
вместо простого (условного) присваивания Выражайте свои намерения прямо. Например, вместо этого:
bool pos;
if (val > 0)
{
pos = true;
}
else
{
pos = false;
}
Делайте так:
bool pos = (val > 0); // инициализация
Вместо:
string result;
if (someString != null)
{
result = someString;
}
else
{
result = “Unavailable”;
}
Пишите:
return someString ?? “Unavailable”;
AV1547 Инкапсулируйте сложное выражение в методе или свойстве
Рассмотрим следующий пример:
if (member.HidesBaseClassMember && (member.NodeType != NodeType.InstanceInitializer))
{
// что-то делаем
}
Чтобы понять, что делает этот код, вам придется вникать в его детали и предвидеть все варианты его выполнения. Конечно, вы можете добавить поясняющий комментарий перед этим кодом, но лучше замените сложное выражение методом, название которого будет говорить само за себя.
if (NonConstructorMemberUsesNewKeyword(member))
{
// что-то делаем
}
private bool NonConstructorMemberUsesNewKeyword(Member member)
{
return
(member.HidesBaseClassMember &&
(member.NodeType != NodeType.InstanceInitializer)
}
Если вам потребуется изменить этот метод, вам все равно придется разбираться в том, как он работает. Но теперь гораздо легче понять код, который его вызывает.
AV1551 Вызывайте наиболее перегруженный метод из других перегрузок
Данное правило применимо только к тем методам, которые перегружены между собой необязательными аргументами. Посмотрите на пример ниже:
public class MyString
{
private string someText;
public MyString(string text)
{
this.someText = text;
}
public int IndexOf(string phrase)
{
return IndexOf(phrase, 0, someText.Length);
}
public int IndexOf(string phrase, int startIndex)
{
return IndexOf(phrase, startIndex, someText.Length - startIndex );
}
public virtual int IndexOf(string phrase, int startIndex, int count)
{
return someText.IndexOf(phrase, startIndex, count);
}
}
Класс
MyString
обеспечивает три перегрузки метода IndexOf
, при этом две их них просто вызывают другую с большим количеством параметров. Заметьте, что это правило применимо к конструкторам класса. Реализуйте наиболее перегруженный конструктор и вызывайте его из других перегрузок, используя оператор this()
. Также следует отметить, что параметры с одними и теми же именами должны следовать в одном и том же порядке во всех перегрузках.Важно: Если вы хотите, чтобы поведение классов можно было менять с помощью переопределения данных методов, то объявите наиболее перегруженный метод как
protected virtual
, который вызывается всеми перегрузками.AV1553 Используйте необязательные аргументы только для того, чтобы заменять перегрузки
Единственная допустимая причина для использования необязательных аргументов C# 4.0 – это замена примера из правила AV1551 одиночным методом наподобие этого:
public virtual int IndexOf(string phrase, int startIndex = 0, int count = 0)
{
return someText.IndexOf(phrase, startIndex, count);
}
Если необязательный параметр является ссылочным типом, то он может иметь в качестве значения по умолчанию только
null
. Но, как нам известно, string
, list
и collections
никогда не должны быть равны null
(согласно правилу AV1135). Поэтому вы должны использовать вместо этого перегруженный метод.Примечание: Компилятор копирует значение необязательных параметров в место вызова. Поэтому, изменение значения по умолчанию для необязательных параметров должно сопровождаться рекомпиляцией вызывающего кода.
Примечание: Когда метод интерфейса определяет необязательный параметр, его значение по умолчанию не рассматривается во время разрешения перегрузки до тех пор, пока вы не вызовите реализацию этого метода через интерфейс. См. статью Эрика Липперта для получения дополнительной интформации.
AV1555 Избегайте использования именованных аргументов
Именованные аргументы C# 4.0 были созданы для того, чтобы облегчить вызов COM компонентов, которые известны тем, что могут предлагать тонны необязательных параметров. Если вам нужны именованные аргументы, чтобы улучшить читаемость вызова для метода, скорее всего, этот метод делает слишком много и он должен быть подвергнут рефакторингу.
Единственное исключение, где именованные аргументы улучшают читаемость, это ситуация, когда конструктор объекта вызывается, как в примере ниже:
Person person = new Person
(
firstName: "John",
lastName: "Smith",
dateOfBirth: new DateTime(1970, 1, 1)
);
AV1561 Не допускайте, чтобы метод или конструктор принимал более трех параметров
Если ваш метод или конструктор принимает более чем три параметра, используйте структуру или класс для их инкапсуляции их в соответствии с паттерном спецификация. В общем случае, чем меньше число параметров, тем легче понять метод. К тому же юнит-тестирование метода с множеством параметров требует множество сценариев для тестирования.
AV1562 Не используйте
ref
и out
в параметрах Они делают код менее понятным и создают предпосылки для ошибок. Вместо этого возвращайте составные объекты в качестве результата выполнения функции.
AV1564 Не создавайте методы, которые принимают в качестве параметра логическое значение
Посмотрите на следующий метод:
public Customer CreateCustomer(bool platinumLevel) {}
На первый взгляд все выглядит замечательно, но когда вы будете использовать этот метод, смысл логической переменной полностью потеряет свою ясность:
Customer customer = CreateCustomer(true);
Обычно, если метод принимает булевый флаг в качестве параметра, то он делает более, чем одну вещь и нуждается в рефакторинге для разделения на два или более метода. Альтернативным решением является замена флага перечислением.
AV1568 Не используйте параметры в качестве временных переменных
Никогда не используйте параметр в качестве внутренней переменной. Даже если тип параметра может совпадать с тем типом, который вам требуется, то название, как правило, не будет отражать цели временной переменной.
AV1570 Всегда проверяйте результат, возвращаемый оператором
as
Если вы используйте оператор
as
чтобы привести объект к определенному интерфейсу, то всегда проверяйте возвращаемый им результат на null
. Невыполнение этого требования может привести к исключению NullReferenceException
на гораздо поздней стадии выполнении программы, если объект не реализует требуемый интерфейс.AV1575 Не оставляйте закомментированные участки кода
Никогда не отправляйте в репозиторий закомментированный код. Вместо этого используйте систему трекинга задач, чтобы следить за тем, какая работа должна быть сделана. Никто впоследствии не догадается, для чего предназначен тот или иной блок закомментированного кода. Он был временно закомментирован для тестирования? Он был скопирован в качестве примера? Должен ли я удалить его?
Рекомендации по именованию
AV1701 Используйте американский английский язык
Все члены классов, параметры и переменные должны иметь название с использованием слов из американского английского языка.
- Выбирайте легкое, читаемое, предпочтительно грамматически правильное имя. Например,
HorizontalAlignment
более читаемое наименование, чемAlignmentHorizontal
- Предпочитайте читаемость краткости. Имя свойства
CanScrollHorizontally
лучше, чемScrollableX
(отсылка к оси Х ни о чем не говорит) - Избегайте использования имен, которые конфликтуют с ключевыми словами языка программирования
Исключение: В большинстве проектов вы можете использовать слова и фразы из предметной области, характерной для данного приложения, а также имена, специфичные для вашей компании. Статический анализатор кода Visual Studio будет проводить анализ всего кода, так что вам может потребоваться добавить эти термины в настраиваемый словарь анализа кода.
AV1702 Для каждого элемента языка используйте соответствующую нотацию
Элемент языка | Нотация | Пример |
---|---|---|
Класс, структура | Паскаль | AppDomain |
Интерфейс | Паскаль | IBusinessService |
Перечисление (тип) | Паскаль | ErrorLevel |
Перечисление (значение) | Паскаль | FatalError |
Событие | Паскаль | Click |
Приватное поле | Верблюжья нотация | listItem |
Защищенное поле | Паскаль | MainPanel |
Константное поле | Паскаль | MaximumItems |
Константная локальная переменная | Верблюжья нотация | maximumItems |
Read-only статическое поле | Паскаль | RedValue |
Переменная | Верблюжья нотация | listOfValues |
Метод | Паскаль | ToString |
Пространство имен | Паскаль | System.Drawing |
Параметр | Верблюжья нотация | typeName |
Параметры типа | Паскаль | TView |
Свойство | Паскаль | BackColor |
AV1704 Не включайте числа в наименования переменных, параметров и типов
В большинстве случаев только лень может послужить причиной отсутствия ясного и говорящего самого за себя имени.
AV1705 Не используйте префиксы в названиях полей
Например, не используйте
g_
или s_
чтобы различить между собой статические и нестатические поля. Обычно, если в методе трудно отличить локальные переменные от полей класса, то данный метод слишком громоздок. Вот примеры неправильных наименований: _currentUser
, mUserName
, m_loginTime
.AV1706 Не используйте аббревиатуры
Например, используйте
OnButtonClick
вместо OnBtnClick
. Избегайте использования одиночных символов в названиях переменных, таких как «i» и «q». Вместо этого используйте полные слова, такие как «index» и «query».Исключение: Использование общеизвестной аббревиатуры или общепринятого сокращения из вашей предметной области может служить исключением из этого правила. Например, использование
UI
вместо UserInterface
.AV1707 Называйте члены класса, параметры и переменные в соответствии с их назначением, а не типом
- Используйте наименование, которое указывает на функцию, которую выполняет член класса. Например, название
GetLength
лучшеGetInt
- Не используйте такие термины, как
Enum
,Class
илиStruct
в именах - Переменные, ссылающиеся на коллекции, должны иметь название во множественном числе
AV1708 Именуйте типы, используя словосочетания из существительных или прилагательных
Плохие примеры:
SearchExamination
(страница для поиска результатов проверок), Common
(отсутствует существительное в конце, название не объясняет предназначение) и SiteSecurity
(хотя с технической точки зрения все нормально, название ничего не говорит о предназначении). Хорошими примерами являются: BusinessBinder
, SmartTextBox
и EditableSingleCustomer
.Не включайте в наименования классов такие термины, как
Utility
или Helper
. Классы с такими именами обычно являются статическими и спроектированы без учета принципов объектно-ориентированного программирования (см. также правило AV1008).AV1709 При именовании параметров универсальных типов используйте описательные имена
- Всегда добавляйте приставку «Т» к описательным именам параметров типа
- Всегда используйте описательные имена, если только имя, состоящее из одной буквы, не является полностью понятным без пояснений. В этом случае используйте букву «Т» в качестве имени параметра типа
- Рекомендуется в имени параметра типа указывать ограничения, накладываемые на параметр типа. Например, параметр, предназначенный только для
ISession
, может быть названTSession
AV1710 Не повторяйте имя класса или перечисления в названиях их членов
class Employee
{
// Плохо!
static GetEmployee() {}
DeleteEmployee() {}
// Правильно
static Get() {...}
Delete() {...}
// Тоже верно.
AddNewJob() {...}
RegisterForMeeting() {...}
}
AV1711 Давайте элементам такие названия, которые схожи с элементами связанных с ними классов .NET Framework
.NET разработчики уже привыкли к паттернам именования, которые используются в .NET Framework. Таким образом, следование этим паттернам поможет им быстрее разобраться в вашем коде. Например, если вы определяете класс, который реализует коллекцию, то назовите методы удаления элемента, его добавления и получения количества элементов такими именами, как
Add
, Remove
и Count
вместо AddItem
, Delete
, или NumberOfItems
.AV1712 Избегайте коротких имен или имен, которые можно спутать с другими наименованиями
Несмотря на то, что с технической точки зрения следующее выражение может выглядеть корректно, оно легко может ввести в заблуждение того, кто с ним столкнется
bool b001 = (lo == l0) ? (I1 == 11) : (lOl != 101);
AV1715 Не ленитесь давать подходящие названия свойствам
- Именуйте свойства с использованием существительных, словосочетаний с существительными или, в крайнем случае, с использованием словосочетаний с прилагательными
- Называйте свойство, которое имеет логический тип, используя утвердительные фразы. Например,
CanSeek
вместоCantSeek
- В наименованиях свойств, которые имеют логический тип, попробуйте использовать приставки
Is
,Has
,Can
,Allows
илиSupport
- Попробуйте дать свойству такое же название, как и у его типа. Когда вы создали свойство, которое имеет тип перечисления, название свойства может быть таким же, как название типа перечисления. Например, если у вас есть перечисление
CacheLevel
, то свойство, возвращающее одно из его значений, также должно иметь названиеCacheLevel
AV1720 Именуйте методы, используя связку глагол-объект
Называйте методы с использованием связки глагол-объект. Хороший пример –
ShowDialog
. Хорошее имя должно давать подсказку, что делает этот метод и, если возможно, почему. Также не используйте слово And
в названии метода. Это говорит о том, что метод делает более чем одну вещь, что является нарушением принципа единой ответственности (AV1115).AV1725 В названиях пространств имен используйте имена собственные, названия модулей (слоев), глаголы и слова, описывающие особенности данного пространства имен
Например, наименования следующих пространств имен могут служить хорошим примером:
AvivaSolutions.Commerce.Web
NHibernate.Extensibility
Microsoft.ServiceModel.WebApi
Microsoft.VisualStudio.Debugging
FluentAssertion.Primitives
CaliburnMicro.Extensions
Примечание: Никогда не допускайте, чтобы в названиях пространств имен содержались названия типов, но если это существительное в его множественной форме, например
Collections
, то это обычно допустимо.AV1735 Используйте глагол или словосочетание с глаголом в названии события
Именуйте событие глаголом или словосочетанием с глаголом. Например:
Click
, Deleted
, Closing
, Minimizing
, и Arriving
. Объявление события Search
может выглядеть так, как представлено ниже:public event EventHandler<SearchArgs> Search;
AV1737 Используйте
–ing
и –ed
для событий, которые должны случиться перед и после какого-либо другого события Например, событие, которое предшествует закрытию окна, должно называться
Closing
, а событие, которое возникает после его закрытия, — Closed
. Не используйте приставки Before
и After
или какие-либо суффиксы для идентификации таких событий.Предположим, вы хотите определить события, которые связаны с процессом удаления некоторого объекта. Дайте событиям имена
Deleting
и Deleted
и избегайте таких наименований, как BeginDelete
и EndDelete
. Именуйте события так, как написано ниже:Deleting
: Произойдет непосредственно перед удалением объектаDelete
: Произойдет, когда объект должен быть удален обработчиком событияDeleted
: Произойдет, когда объект будет уже удален
AV1738 Используйте приставку
On
в названии обработчика события Хорошей практикой является добавлять приставку
On
к названию метода, который обрабатывает событие. Например, если метод обрабатывает событие Closing
, то название должно быть OnClosing
.AV1739 Используйте символ подчеркивание для параметров лямбда-выражений, которые не имеют значения
Если вы используете лямбда-выражение, например, чтобы подписаться на событие, и текущие параметры события не имеют значения, используйте следующее условное обозначение, чтобы указать это более конкретно.
button.Click += (_, __) => HandleClick();
AV1745 Именуйте группы методов расширений в классе с использованием суффикса
Extentions
Если название метода расширения конфликтует с другими элементом или методом расширения, вы должны добавить префикс в виде названия класса к вызову. Их добавление в связанный класс с суффиксом
Extensions
улучшит читаемость.AV1755 Добавляйте суффиксы
Async
или TaskAsync
к названиям асинхронных методов Общая рекомендация для методов, которые возвращают
Task
или Task<TResult>
, — это добавлять к ним суффикс Async
. Однако если метод с таким названием уже существует, используйте вместо этого суффикс TaskAsync
.Рекомендации по повышению производительности
AV1800 Используйте
Any()
, чтобы проверить IEnbmerable<T>
на пустоту Если метод или другой элемент возвращает
IEnumerable<T>
или другой класс коллекции, который не предоставляет свойство Count
, используйте метод расширения Any()
вместо Count()
, чтобы проверить коллекцию на пустоту. Если вы используете Count()
, то вы рискуете снизить производительность, т.к. это приведет к итерации всей коллекции (например, в случае с IQueryable<T>
выполнится запрос к данным).Примечание: Если вы возвращаете
IEnumerable<T>
, чтобы предотвратить изменение возвращаемой коллекции, как было рекомендовано в правиле AV1130, и вы работаете с .NET 4.5 и выше, попробуйте использовать новые read-only классы.AV1820 Используйте
async
только для долговременных и низкоинтенсивных задачИспользование
async
не запустит автоматически что-нибудь в рабочем потоке, как это делает Task.Run
. Async
, просто добавляет необходимую логику, которая служит для того, чтобы разрешить высвобождать текущий поток и вернуть результат на тот же поток после завершения асинхронной операции. Другими словами, используйте async
только для операций, связанных с I/O.AV1825 Используйте
Task.Run
для высокоинтенсивных задачЕсли вам нужно выполнить операцию, связанную с выделением дополнительных ресурсов процессора, используйте
Task.Run
, чтобы выгрузить работу на поток из пула потоков. Просто не забывайте о том, что вам придется вручную возвращать результат в ваш основной поток.AV1830 Избегайте использования
await/async
с Task.Wait
await
не заблокирует текущий поток, а просто проинформирует компилятор о необходимости построения машины состояний. Однако Task.Wait
заблокирует поток и даже может привести к взаимным блокировкам (см. AV1835). AV1835 Опасайтесь взаимной блокировки
async/await
в однопоточном окружении Рассмотрим следующий асинхронный метод:
private async Task<string> GetDataAsync()
{
var result = await MyWebService.GetDataAsync();
return result.ToString();
}
Затем вызовете его в методе контроллера ASP.NET MVC следующим образом:
public ActionResult ActionAsync()
{
var data = GetDataAsync().Result;
return View(data);
}
Здесь вы получите взаимную блокировку. Почему? Потому что геттер свойства
Result
будет блокировать поток до тех пор, пока операция async
не будет завершена, но поскольку метод async
будет автоматически возвращать результат на оригинальный поток, а ASP.NET использует однопоточный контекст синхронизации, они будут продолжать ждать друг друга. Похожая проблема также может возникнуть с WPF, Silverlight или с C#/XAML приложениями Windows Store. Вы можете узнать об этом больше здесь.Рекомендации по использованию фреймворка
AV2201 Используйте псевдонимы типов C# вместо типов из пространства имен
System
Например, используйте
object
вместо Object
, string
вместо String
и int
вместо Int32
. Эти псевдонимы были введены для того, чтобы сделать примитивные типы первоклассными членами языка C#, так что используйте их должным образом.Исключение: При ссылке на статические элементы таких типов обычно принято использовать полное CLS имя, например,
Int32.Parse()
вместо int.Parse()
.AV2205 Тщательно задавайте названия свойств, переменных или полей, ссылающихся на локализованные ресурсы
Рекомендации в этом разделе применимы к локализуемым ресурсам, таким как сообщения об ошибках и текст меню.
- Используйте нотацию паскаль для ключей ресурсов
- Предпочитайте описательные названия коротким. Старайтесь делать их лаконичными, но не потеряйте читаемость
- Используйте только буквенно-цифровые знаки при именовании ресурсов
AV2207 Не оставляйте в коде строки, которые должны быть изменены во время развертывания приложения
Например, строки подключения, адреса серверов и т.д. Используйте файлы ресурсов, свойство
ConnectionStrings
класса ConfigurationManager
или класс Settings
, генерируемый Visual Studio. Поддерживайте актуальные значения настроек через app.config
или web.config
(а не в каком-либо другом месте).AV2210 Осуществляйте сборку с наивысшим уровнем предупреждений
Сконфигурируйте свое рабочее окружение таким образом, чтобы использовать уровень предупреждений 4 для компилятора C# и включите опцию «Treat warnings as errors» (считать предупреждения за ошибки). Это позволит компилировать код с наивысшим уровнем качества из возможного.
AV2215 Тщательно заполняйте атрибуты в файле
AssemblyInfo.cs
Удостоверьтесь, что атрибуты для названия компании, описания, предупреждения об авторском праве и т.д. заполнены. Одним из способов добиться того, чтобы номер версии и другие поля, общие для всех сборок, были всегда одинаковыми, является перемещение соответствующих атрибутов из
AssemblyInfo.cs
в файл SolutionInfo.cs
, который совместно используется всеми проектами внутри решения.AV2220 Избегайте использования LINQ для простых выражений
Вместо:
var query = from item in items where item.Length > 0;
Лучше воспользоваться методом из пространства имен
System.Linq
: var query = items.Where(i => i.Length > 0);
К тому же LINQ-запросы должны быть разбиты на несколько строк для читаемости. Таким образом, второе выражение выглядит более читаемо.
AV2221 Используйте лямбда-выражения вместо анонимных методов
Лямбда-выражения служат более красивой альтернативой анонимным методам. Таким образом, вместо:
Customer c = Array.Find(customers, delegate(Customer c)
{
return c.Name == “Tom”;
});
используйте лямбда-выражение:
Customer c = Array.Find(customers, c => c.Name == “Tom”);
или даже лучше это:
var customer = customers.Where(c => c.Name == “Tom”);
AV2230 Используйте ключевое слово
dynamic
только при работе с объектами этого типа Ключевое слово
dynamic
было введено для работы с динамическими языками. Их использование создает серьезный затор производительности (performance bottleneck), поскольку компилятор вынужден сгенерировать некоторое количество дополнительного кода.Используйте
dynamic
только при обращении к членам динамически созданного экземпляра класса (при использовании класса Activator
) в качестве альтернативы Type.GetProperty()
и Type.GetMethod()
или при работе с типами COM Iterop.AV2235 Старайтесь использовать
async/await
вместо Task
Использование новых ключевых слов C# 5.0 упрощает создание и чтение кода, что, в свою очередь, упрощает его поддержку. Даже если вам требуется создавать многочисленные асинхронные операции. Например, вместо того, чтобы делать так:
public Task<Data> GetDataAsync()
{
return MyWebService.FetchDataAsync()
.ContinueWith(t => new Data (t.Result));
}
объявляйте метод следующим образом:
public async Task<Data> GetDataAsync()
{
var result = await MyWebService.FetchDataAsync();
return new Data (result);
}
Рекомендации по созданию документации
AV2301 Пишите комментарии и документацию на американском английском
AV2305 Документируйте все
public
, protected
и internal
типы и члены Документирование вашего кода позволит Visual Studio выводить подсказки, когда ваш класс будет использоваться где-нибудь еще. Кроме этого, когда вы хорошо документируете ваши классы, вы можете генерировать документацию к вашему коду, которая выглядит профессионально.
AV2306 При написании XML документации помните о другом разработчике
При написании документации XML помните о другом разработчике. Может быть, у него/нее не будет доступа к исходному коду и нужно попытаться более полно объяснить, как можно использовать ваш тип.
AV2307 Используйте MSDN стиль написания документации
Следуйте стилю онлайн справки MSDN, чтобы помочь другому разработчику быстрее разобраться в вашей документации.
Подсказка: GhostDoc с помощью сочетания горячих клавиш может генерировать xml комментарии для создания документации.
AV2310 Избегайте инлайновых комментариев
Если вы испытываете необходимость в том, чтобы пояснить тот или иной участок кода с помощью комментария, то, скорей всего, необходимо вынести этот код в отдельный метод и дать ему наименование, которое будет ясно говорить о его предназначении.
AV2316 Пишите комментарии только для того, чтобы объяснить комплексные решения и алгоритмы
Старайтесь, чтобы ваши комментарии отвечали на вопросы почему и что, а не как. Избегайте разъяснения блока кода словами, вместо этого помогите понять тому, кто будет читать ваш код, почему вы выбрали это решение или алгоритм и чего вы пытаетесь этим достичь. В случае выбора альтернативного решения, если возможно, также объясните, почему простое решение привело к проблемам.
AV2318 Не используйте комментарии для отслеживания работы, которая должна быть сделана позднее
Добавление к блоку кода комментария ToDo или какого-либо другого для отслеживания работы, которая должна быть сделана, может показаться хорошим решением. Но на самом деле такие комментарии никому не нужны. Используйте систему трекинга задач, такую как Team Foundation Server, чтобы отслеживать недоработки.
Рекомендации по оформлению
AV2400 Используйте общие правила оформления
- Держите длину строк в пределах 130 символов
- В качестве отступов используйте 4 пробела и не используйте табуляцию
- Вставляйте один пробел между выражениями (например
if
), а также ключевыми словами. Не используйте пробелы после(
и перед)
. Например:
if (condition == null)
- Добавляйте пробел перед и после операторов
(
например+
,-
,==
) - Всегда используйте конструкции
if
,else
,do
,while
,for
иforeach
с парными фигурными скобками, даже если без этого можно обойтись - Открывайте и закрывайте парные скобки всегда в новой строке
- Не устанавливайте значение каждого свойства объекта в новой строке после инициализации этого объекта. Используйте следующий формат:
var dto = new ConsumerDto() { Id = 123, Name = “Microsoft”, PartnerShip = PartnerShip.Gold, };
- Не разделяйте объявление лямбда-выражения на несколько строк. Используйте формат, как показано ниже:
methodThatTakesAnAction.Do(x => { // какие-то действия };
- Объявляйте LINQ-запрос в одной строке или объявляйте каждое ключевое слово этого запроса в новой строке, как показано ниже:
var query = from product in products where product.Price > 10 select product;
или
var query = from product in products where product.Price > 10 select product;
- Начинайте LINQ-запрос с объявления всех выражений
from
и не перемешивайте их выражениямиwhere
- Добавляйте скобки вокруг каждого сравнения в условном выражении, но не добавляйте их вокруг одиночного выражения. Например:
if (!string.IsNullOrEmpty(str) && (str != “new”))
- Добавляйте пустую строку между многострочными выражениями, членами класса, после закрытия парных скобок, после несвязанных друг с другом блоков кода, перед и после ключевого слова
#region
и между директивамиusing
, если пространства имен относятся к различным компаниям
AV2402 Располагайте и группируйте пространства имен в соответствии с названием компании
// Пространства имен Microsoft первые
using System;
using System.Collections;
using System.XML;
// Другие пространства имен идут в алфавитном порядке
using AvivaSolutions.Business;
using AvivaSolutions.Standard;
using Telerik.WebControls;
using Telerik.Ajax;
AV2406 Располагайте члены класса в строго определенном порядке
- Приватные поля и константы (в
#region
) - Публичные константы
- Публичные read-only статические поля
- Фабричные методы
- Конструкторы и финализаторы
- События
- Публичные свойства
- Прочие методы и приватные свойства в порядке их вызова
Сохранение общего порядка позволяет другим членам команды легче ориентироваться в вашем коде. В общем случае, чтение файла с исходным кодом должно идти сверху вниз, как если бы вы читали книгу. Это предотвращает ситуацию, когда приходится просматривать файл вверх и вниз в поисках нужного фрагмента.
AV2407 Будьте осторожны с использованием ключевого слова
#region
Ключевое слово
#region
может быть полезно, но оно также может скрывать основное предназначение класса. Следовательно, используйте #region
только для:- Приватных полей и констант (предпочтительно в Private Definitions region)
- Вложенных классов
- Реализаций интерфейса (только если реализация интерфейса не является основной целью этого класса)
Важные ресурсы
Сайт компании
Это документ является частью усилий, направленных на то, чтобы ежедневная работа C# разработчиков шла на профессиональном уровне. Поэтому я размещаю эти рекомендации на сайте CodePlex. Вы легко сможете найти их по ссылке www.csharpcodingguidelines.com.
В дополнение к самой новой версии этого документа вы найдете там:
- Список кратких ссылок на данное руководство
- Набор правил для Visual Studio 2010/2012 для различных типов систем
- Набор правил для ReSharper, которые соответствуют рекомендациям из главы 10
- Площадку для обсуждения качества написания кода на C#
Полезные ссылки
В дополнение ко многим ссылкам, указанным в этом документе, я хотел бы рекомендовать следующие книги, статьи и сайты для всех, кто интересуется качеством программного обеспечения.
Code Complete: A Practical Handbook of Software Construction (Steve McConnel). Это одна из лучших книг, которые я когда-либо читал. Она в деталях повествует обо всех аспектах разработки программного обеспечения. Даже несмотря на то, что оригинал этой книги был написан в 2004, вы будете удивлены, когда увидите, насколько актуальны вещи, описанные в ней. Если вы хотите знать серьезность написанных выше слов, я написал ревью на эту книгу в 2009.
The Art of Agile Development (James Shore). Еще одно замечательное всеохватывающее путешествие через многие практики, проповедуемые такими методологиями, как Scrum и экстремальное программирование. Если вы хотите быстро ознакомиться с этими методологиями, обязательно прочитайте книгу Джеймса.
Applying Domain Driven-Design and Patterns: With Examples in C# and .NET (Jimmy Nilsson). Книга, с которой начался мой интерес к предметно-ориентированному проектированию (DDD) и разработке через тестирование (TDD). Это одна из тех книг, которую бы мне следовало прочитать на несколько лет раньше, чтобы избежать многих своих ошибок.
Jeremy D. Miller’s Blog. Несмотря на то, что он больше не ведет этот блог, в последние пару лет им были написаны превосходные статьи о разработке через тестирование, паттернах и принципах проектирования. Я многому научился на его идеях и примерах, взятых из реальной жизни.
LINQ Framework Design Guidelines. Набор правил и рекомендаций, которых вам следует придерживаться при создании своей собственной реализации интерфейса IQueryable.
Best Practices for c# async/await. Источник и обоснование нескольких новых рекомендаций, описанных в этом документе. Автором является Джон Вагнер.
Комментарии (36)
novar
01.12.2015 14:36+1В «AV1220 Всегда проверяйте делегат обработчика события на null» неверная подсказка. Указанный метод «чтобы список делегатов не был совсем пустым» не поможет когда сначала подписались и потом отписались. После отписки последнего подписчика опять будет null и без проверки всё равно не обойтись.
novar
01.12.2015 14:41+1В «AV1553 Используйте необязательные аргументы только для того, чтобы заменять перегрузки» судя по всему опечатка в ссылке «согласно правилу AV1235». Вместо AV1235 должно быть AV1135.
capslocky
01.12.2015 15:09AV1535 Всегда используйте конструкции if, else, while, for, foreach и case с фигурными скобками
Да, да и еще раз да! Без скобок эти операторы создают хорошую почву для багов, плюс ухудшается читаемость кода.
AV1540 Старайтесь избегать нескольких объявлений return
Достаточно спорная рекомендация. Несколько return это нормально, только надо располагать их как можно раньше в методе.
AV1575 Не оставляйте закомментированные участки кода
Не соглашусь, нередко закомментированный код — лучшее решение. Однако нужно всегда комментировать, почему тот или иной код закомментирован.Shablonarium
02.12.2015 16:21Возражение! Кусок кода с return невозможно просто так взять и вынести в отдельный метод.
Shablonarium
02.12.2015 16:30Возражение 2! Закомментированный кусок кода будет лучше себя чувствовать в объятиях средств системы версионирования кода, либо воспарив метаинформациею в систему таск-трекинга.
kekekeks
01.12.2015 16:02+5AV1130 Возвращайте IEnumerable или ICollection вместо какой-либо конкретной коллекции И ловите проблемы с memory-traffic из-за того что компилятор не может использовать value-type enumerator при перечислении, да.
sergey_russkih89
01.12.2015 16:08-1Если вы не хотите, чтобы пользователи могли изменять коллекцию
kekekeks
01.12.2015 16:10+3sergey_russkih89
01.12.2015 16:13В этом же правиле внизу
Заметка: Если вы используете .Net 4.5, вы также можете применять IReadOnlyCollection, IReadOnlyList или IReadOnlyDictionary<TKey, TValue>
kekekeks
01.12.2015 16:14+3На странице по ссылке можно заметить, что AsReadOnly был доступен в .NET 2.0.
WeslomPo
01.12.2015 16:18AV1025 — на этом построен целый паттерн разработки, успешно функционирующий во множестве современных игр. Component Based подход же. Компоненты не должны содержать методов и какой либо логики, обработка данных происходит в специальной системе которая её и реализует.
AV1200 — есть вещи, где бросая исключения налево и направо можно превратить код в огромную проверку этих самых исключений во множестве мест, когда как можно их все заменить одним статусным сообщением. Например ошибку ввода пользователем неправильных или нежелательных данных, которую тут же и вернуть пользователю по стеку. Может я конечно не прав, но я вообще не очень люблю исключения бросать и обрабатывать, мне эта система кажется плохо продуманной и чуждой.
Ну и еще не согласен с AV1540, AV1575 и частично AV1130, а также форматированием пробелами, скобками на следующей строке и т.д. (фломастеры)
dyadyaSerezha
02.12.2015 00:28Комплексное выражение — сложное выражение! Чего ж огород городить.
Опциональный параметр — необязательный…
В целом, советы грамотные, но таких есть вагон и маленькая тележка. Почему вдруг предлагается следовать именно советам от какой-то Авивы?sergey_russkih89
02.12.2015 17:29Спасибо! Поправил
При составлении этих рекомендаций автор в первую очередь опирался на личный опыт и свое видение правильного кода. Как вы могли заметить, некоторые из них несколько субъективны. Решение, каким стандартам следует следовать, должны принимать именно вы исходя из ситуации
dymanoid
02.12.2015 02:17Комбинация правил StyleCop и студийного CodeAnalyzer'а. В общем и целом разумно, но пара вещей, действительно, требует обдумывания.
Fireball222
02.12.2015 12:13AV1135
А чем в общем случае null строка в качестве результата хуже, чем пустая строка? Если пустая строка символизирует что-то определенное (например то, что что-нибудь не найдено), то уж лучше null, а еще лучше использовать AV1140 с null object или метод с out параметром.sergey_russkih89
02.12.2015 17:40В случае с null можно получить NullReferenceException. Использование паттерна Null Object может быть хорошей альтернативой
Fireball222
02.12.2015 18:18+1NullReferenceException хотя бы виден. А string.Empty ничем не отличается от любой другой строки: без вдумчивого чтения метода догадаться, что его нужно определенным образом обрабатывать, шансов мало.
sergey_russkih89
02.12.2015 19:16Речь не идет об исключительной ситуации. Речь о ситуации, когда возвращаемый результат может быть пустым. Например, метод вернул пустой список пользователей.
Эта рекомендация напрямую соотносится с «Правилом наименьшего удивления» (The Principle of Least Surprise). Если метод возвращает строку то, как ни странно, он и должен всегда возвращать строку. Если коллекцию — результатом выполнения всегда должна быть коллекция. Код должен иметь то поведение, которое от него ожидаютFireball222
02.12.2015 22:26С коллекцией все ясно, я про нее ничего и не писал.
Я писал про то, что если возвращать пустую строку в качестве «null object», то это точно не про принцип наименьшего удивления. Нулевая ссылка гораздо понятнее и привычнее.sergey_russkih89
03.12.2015 07:33Если речь идет именно о возвращении null вместо пустой строки в качестве Null Object, то я соглашусь
sergey_russkih89
03.12.2015 07:39Хотел бы еще раз подчеркнуть, что такое поведение должно быть ожидаемым. Собственно, это главный критерий
Bonart
07.12.2015 00:07AV1250 Вычисляйте результат LINQ-запроса до того, как вернуть его
… и получайте N вместо 1 по памяти на пустом месте.
AV1025 Классы должны иметь состояние и поведение
Это прямо противоречит принципу единственной ответственности.
AV1540 Старайтесь избегать нескольких объявлений return
Неужели дополнительные уровни вложенности if else читаются лучше? Цикломатическая сложность с этим не согласна.
AV1702 Для каждого элемента языка используйте соответствующую нотацию
И как в вашем случае отличить присваивание локальной переменной (низкий уровень ответственности) или присваивания полю(высокий уровень ответственности)?
Всегда используйте конструкции if, else, do, while, for и foreach с парными фигурными скобками, даже если без этого можно обойтись
В окружении парных скобок особенно элегантно смотрится одиночный return (а также break, continue, throw...)
AV2406 Располагайте члены класса в строго определенном порядке
Приватные поля и константы (в #region)
Публичные константы
Публичные read-only статические поля
Фабричные методы
Конструкторы и финализаторы
События
Публичные свойства
Прочие методы и приватные свойства в порядке их вызова
Наиболее важные вещи для класса — его входы и выходы, то есть реализуемые интерфейсы и зависимости.
Отсюда логично вперед ставить публичные члены, причем на первое место из них конструктор (или фабричный метод, если публичного конструктора нет). А вот поля и т.п. нужны только тем, кому интересны потроха класса
novar
«AV1520 Используйте var только тогда, когда тип переменной очевиден» ссылается на статью Липперта где написано практически обратное: НЕ использовать «var» только в редких случаях где совершенно необходимо знать точный тип.
sergey_russkih89
Не вводите в заблуждение, в статье Ерика Липперта именно это и написано
novar
Мой перевод:
Из перечисленных пунктов только один рекомендует точные типы. Остальные варианты — в пользу var. Ну и собственно вся статья на 90% о том, что var использовать почти всегда хорошо.
return_true
Да чего тут спорить, 2015 год на дворе? Все уже давно используют var везде, смысл опираться на рекомендации из 2011? Даже в С++ народ привыкает а auto.
sergey_russkih89
В статье не утверждается, что
var
нужно использовать в 90% случаев, и она не противоречит правилу AV1520. Да, статья про неявную типизацию, но на этом все.В первую очередь мы пишем код для людей. И этот код должен быть понятным. Следовательно, использовать
var
нужно тогда, когда тип нам не важен и мы можем им пренебречь в пользу читаемости. Неудобно, когда приходится заглядывать в функцию, чтобы понять, какой тип присваивается переменной по результату выполнения этой функции. Неявная типизация — это хорошо, но злоупотреблять не стоит.VenomBlood
При чем тут var и неявная типизация? Var к неявной типизации через анонимные типы притягивается как лифчик на слона, в остальном — это о другом.
return_true
Серьёзно? Единственный способ понять тип, скрываемый за var это посмотреть на сигнатуру метода? Пора использовать возможности вашего редактора:
sergey_russkih89
Не проще ли, когда для этого достаточно взгляда?
Razaz
Как-то так…
sergey_russkih89
Рекомендация AV1520 гласит, что в этом случае лучше использовать var т.к. тип переменной очевиден из инициализатора. Даже пример приведен. Липперт этот случай также упомянул в своей статье. Не утрируйте.
Razaz
В текущих реалиях я придерживаюсь мнения что var надо использовать везде, где можно.
Этот код стайл не учитывает еще всякие штуки как JetBrains.Annotations.
Плюс Async не используется для имен экшенов контроллера в MVC.
У нас сейчас взят за основу вот этот гайдлайг для веб проектов: https://github.com/aspnet/Home/wiki/Engineering-guidelines. Может добавите чего оттуда :)
sergey_russkih89
Спасибо, обязательно почитаю. Насчет добавить — не имею морального права. Я всего лишь переводчик, а не автор. По результатам обсуждения этой статьи обязательно напишу автору замечания. И если он их примет, внесу изменения
Bonart
А людей в 95% случаев конкретный тип не интересует.
Мне вполне достаточно, что тип A такой же, как и у B.
Напротив, явное указание типа в данном случае добавит шума в коде и нередко заставит сделать лишнюю правку при рефакторинге.