[Из песочницы] Рекомендации по написанию кода на C# от Aviva Solutions

Представляю вашему вниманию перевод документа «Coding Guidelines for C# 3.0, 4.0 and 5.0».

Целью создания этого списка правил является попытка установить стандарты написания кода на 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. Кроме того, после прочтения книги Роберта Мартина «Чистый код: Создание, анализ и рефакторинг» я загорелся его идеями и решил включить некоторые из них в качестве правил. Хочу заметить, что этот документ ни в коем случае не является заменой его книги. Я искренне рекомендую прочесть его книгу, чтобы твердо усвоить принципы, лежащие в основе его рекомендаций. К тому же я решил дополнить принципы написания кода некоторыми принципами проектирования. Они являются слишком важными, чтобы их игнорировать, и имеют большое влияние на достижение высокого качества кода.

Эти рекомендации являются стандартами?


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

Чтобы помочь вам в выборе, я присвоил степень важности каждой рекомендации:

0a2fb872c6da4ee8b7244b77a4438892.png — Правило, которым вы никогда не должны пренебрегать и которое применимо ко всем ситуациям.
6ce09bdd83374fd2a684eb9c49663eed.png — Настоятельно рекомендуется соблюдать это правило.
8d93aa4f255b4216a52c3276b3cc7c75.png — Рекомендуется соблюдать это правило, но оно применимо не ко всем ситуациям.

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

Обратная связь и отказ от ответственности


Этот документ был составлен во многом благодаря большому вкладу членов сообщества, статьям в блогах, онлайн дискуссиям и многим годам разработки на С#. Если у вас есть вопросы, замечания и предложения пишите мне на адрес dennis.doomen@avivasolutions.nl или в твиттер http://twitter.com/ddoomen. Я буду пытаться регулярно пересматривать и переопубликовывать этот документ в соответствии с новыми идеями, опытом и замечаниями.

Хочу отметить, что эти рекомендации лишь отражают мое видение правильного кода на C#. Aviva Solutions не несет ответственности за любые прямые или косвенные убытки, вызванные применением стандартов, описанных в данном документе.
Разрешается копировать, адаптировать и распространять этот документ и краткие ссылки на это руководство в некоммерческих целях и для внутреннего использования. Но вы не можете распространять этот документ, публиковать или распространять любые адаптированные копии данного документа в коммерческих целях без предварительного получения письменного разрешения от Aviva Solutions.

Рекомендации по проектированию классов


AV1000 Класс или интерфейс должны иметь единственное предназначение 0a2fb872c6da4ee8b7244b77a4438892.png

Класс или интерфейс должен иметь единственное предназначение в рамках системы, в которой он используется. Как правило, класс служит одной из целей: либо он описывает тип, например, email или ISBN (международный стандартный книжный номер), либо представляет из себя абстракцию некоторой бизнес-логики, либо он описывает структуру данных, либо отвечает за взаимодействие между другими классами. Он никогда не должен в себе комбинировать эти задачи. Это правило известно как Принцип единой ответственности, один из принципов SOLID.

Совет: Класс со словом «And» в названии — это явное нарушение данного правила.
Совет: Для взаимодействия между классами используйте паттерны проектирования. Если вам не удается применить ни один из паттернов к классу, возможно, он берет на себя слишком большую ответственность.
Примечание: Если вы создаете класс, который описывает тип, вы можете значительно упростить его использование, если сделаете его неизменяемым.

AV1001 Создавайте новые экземпляры класса с помощью конструктора таким образом, чтобы в результате вы получили полностью готовый к использованию объект 8d93aa4f255b4216a52c3276b3cc7c75.png.

Созданный объект не должен нуждаться в установке дополнительных свойств перед использованием в каких бы целях его не планировалось бы применять. При этом если конструктор нуждается в более чем трех параметрах (что нарушает правило AV1561), возможно, что класс берет на себя слишком большую ответственность (нарушение правила AV1000).

AV1003 Интерфейс должен быть небольшим и должен быть сфокусирован на решении одной задачи 6ce09bdd83374fd2a684eb9c49663eed.png

Интерфейс должен иметь имя, которое ясно описывает его предназначение или роль, которую он выполняет в системе. Не объединяйте слабо связанные элементы в один интерфейс только потому, что они относятся к одному классу. Формируйте интерфейсы на основании функциональности, за которую отвечают вызываемые методы или на основе конкретной задачи, которую этот интерфейс выполняет. Это правило более известно как Принцип сегрегации интерфейса.

AV1004 Используйте интерфейс, а не базовый класс, чтобы поддерживать несколько реализаций 8d93aa4f255b4216a52c3276b3cc7c75.png

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

AV1005 Используйте интерфейс для реализации слабой связанности между классами 6ce09bdd83374fd2a684eb9c49663eed.png

Интерфейсы — это отличный инструмент для реализации слабой связанности между классами.

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


AV1008 Избегайте статических классов 8d93aa4f255b4216a52c3276b3cc7c75.png

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

Примечание: Если вам действительно необходим статический класс, пометьте его как static. В этом случае компилятор запретит создание экземпляров этого класса и инициализирует ваш класс перед первым обращением к нему. Это избавит вас от необходимости использовать приватный конструктор.

AV1010 Не скрывайте унаследованные элементы за ключевым словом new 0a2fb872c6da4ee8b7244b77a4438892.png

Это не только противоречит полиморфизму — одному из самых важных принципов объектно-ориентированного программирования, но и делает дочерние классы трудными для понимания. Рассмотрим следующие два класса:

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 Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом 6ce09bdd83374fd2a684eb9c49663eed.png

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

Примечание: Этот принцип также известен как Принцип подстановки Барбары Лисков, один из принципов SOLID.

AV1013 Не ссылайтесь на производные классы из базового класса 0a2fb872c6da4ee8b7244b77a4438892.png

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

AV1014 Объект должен обладать ограниченным знанием о других объектах, которые не имеют непосредственного отношения к этому объекту 6ce09bdd83374fd2a684eb9c49663eed.png

Если ваш код напоминает код, который приведен ниже, то вы нарушаете Закон Деметры.

someObject.SomeProperty.GetChild().Foo();


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

Примечание: Использование класса, реализующего текучий интерфейс (Fluent Interface), может показаться нарушением данного правила. Но вызываемые методы просто передают контекст вызова следующему звену. Таким образом, это не вызывает противоречий.
Исключение: При использовании инверсии управления и фреймворков инъекции зависимостей часто требуется, чтобы зависимости выставлялись в качестве публичных свойств. До тех пор пока эти свойства не используются ни для чего другого, кроме как реализации инъекции зависимостей, я бы не стал рассматривать это как нарушение правила.

AV1020 Избегайте двунаправленной зависимости 0a2fb872c6da4ee8b7244b77a4438892.png

Двунаправленная зависимость означает, что два класса знают о публичных методах друг друга или зависят от внутреннего поведения друг друга. Рефакторинг или замена одного из этих двух классов требуют изменений в обоих классах и могут повлечь за собой много непредвиденной работы. Наиболее очевидное решение — это создание интерфейса для одного из этих классов и использование инъекции зависимостей.

Исключение: Доменные модели (Domain Model), применяемые в проектировании на основе предметной области (Domain Driven Design), могут использовать двунаправленные зависимости, описывающие ассоциации из реального мира. В таких случаях я стараюсь удостовериться, что они действительно необходимы, и по мере возможности пытаюсь их избегать.

AV1025 Классы должны иметь состояние и поведение 0a2fb872c6da4ee8b7244b77a4438892.png

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

Исключение: Единственным исключением из этого правила являются классы, используемые для передачи данных между подсистемами приложения, также называемые Data Transfer Objects, или классы, служащие оберткой для параметров метода.

Рекомендации по проектированию членов класса


AV1100 Свойства класса должны иметь возможность быть установленными в любом порядке 0a2fb872c6da4ee8b7244b77a4438892.png

Свойства не должны зависеть от других свойств. Другими словами, не должно быть разницы в том, какое свойство мы устанавливаем в первую очередь. Например, сначала DataSouse, затем DataMember или наоборот.

AV1105 Используйте метод вместо свойства 8d93aa4f255b4216a52c3276b3cc7c75.png

Используйте методы вместо свойств, если:

  • Производится более дорогостоящая работа, чем настройка значения поля
  • Если свойство представляет из себя конвертацию. Например, Object.ToString метод
  • Если свойство возвращает различные значения для каждого вызова, даже если аргументы при этом не изменяются. Например, метод NewGuid будет каждый раз возвращать новое значение
  • Если использование свойства вызывает побочный эффект. Например, изменение внутреннего состояния, которое не имеет прямого отношения к свойству (это нарушает command-query responsibility segregation (CQRS))


Исключение: Заполнение внутреннего кэша или реализация lazy-loading являются хорошими исключениями из этого правила.

AV1110 Не используйте взаимоисключающие свойства 0a2fb872c6da4ee8b7244b77a4438892.png

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

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

AV1115 Метод или свойство должны иметь единственное предназначение 0a2fb872c6da4ee8b7244b77a4438892.png

Так же, как и класс (смотрите правило AV1000), каждый метод должен иметь одну зону ответственности.

AV1125 Не выставляйте объекты, описывающие состояние, посредством статических членов 6ce09bdd83374fd2a684eb9c49663eed.png

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

Классическим примером служит свойство HttpContext.Current в ASP.NET. Многие смотрят на класс HttpContext как на источник большого количества грязного кода. По факту, одно из правил тестирования — изолируйте уродливые вещи (Isolate the Ugly Stuff) — часто относится к этому классу.

AV1130 Возвращайте IEnumerable или ICollection вместо какой-либо конкретной коллекции 6ce09bdd83374fd2a684eb9c49663eed.png

Если вы не хотите, чтобы пользователи могли изменять коллекцию, не возвращайте массив, лист или другой класс коллекции напрямую. Вместо этого возвращайте IEnumerable или, если пользователю требуется знать количество элементов в коллекции, ICollection.

Заметка: Если вы используете .Net 4.5, вы также можете применять IReadOnlyCollection, IReadOnlyList или IReadOnlyDictionary.

AV1135 Свойства, методы или аргументы, которые представляют из себя строку или коллекцию, никогда не должны быть равны null 0a2fb872c6da4ee8b7244b77a4438892.png

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

AV1137 Определяйте параметры настолько специфичными, насколько это возможно

Если элемент класса в качестве параметров требует часть данных другого класса, определяйте типы данных этих параметров как можно более конкретными и не принимайте в качестве параметра весь объект целиком. Например, рассмотрим метод, который в качестве параметра требует передать строку подключения, описанную в некоем центральном интерфейсе IConfiguration. Вместо того, чтобы в качестве параметра принимать весь объект, реализующий этот интерфейс, передайте только строку подключения. Это не только позволит вам уменьшить количество зависимостей в коде, но и улучшит его сопровождаемость в отдаленной перспективе.

AV1140 Используйте типы, характерные для вашей предметной области, вместо примитивов 8d93aa4f255b4216a52c3276b3cc7c75.png

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

Различные рекомендации по проектированию


AV1200 Генерируйте исключение вместо возвращения статусного сообщения 6ce09bdd83374fd2a684eb9c49663eed.png

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

AV1202 Обеспечьте полное и осмысленное сообщение об исключении 6ce09bdd83374fd2a684eb9c49663eed.png

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

AV1205 Генерируйте настолько специфичное исключение, насколько это возможно 8d93aa4f255b4216a52c3276b3cc7c75.png

Например, если метод принял в качестве входного параметра null, следует сгенерировать ArgumentNullException вместо ArgumentException — его базового типа.

AV1210 Не игнорируйте ошибку путем обработки общих исключений 0a2fb872c6da4ee8b7244b77a4438892.png

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

AV1215 Обрабатывайте исключения в асинхронном коде должным образом 6ce09bdd83374fd2a684eb9c49663eed.png

Когда вы генерируете или обрабатываете исключения в коде, который использует async/await или Task, помните о следующих двух правилах:

  • Исключения, которые возникают в пределах блоков async/awaitи внутри Task, распространяются на задачу, которая ожидает выполнение этих блоков
  • Исключения, которые возникли в коде, предшествующем async/await и Task, распространяются на вызывающий код

AV1220 Всегда проверяйте делегат обработчика события на null 0a2fb872c6da4ee8b7244b77a4438892.png

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

event EventHandler Notify; 

void RaiseNotifyEvent(NotifyEventArgs args) 
{ 
    EventHandler handlers = Notify; 
    if (handlers != null) 
    { 
        handlers(this, args);    
    } 
} 


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

event EventHandler Notify = delegate {}; 


AV1225 Для обработки каждого события используйте защищенный виртуальный метод 6ce09bdd83374fd2a684eb9c49663eed.png

Выполнение этой рекомендации позволит производным классам обрабатывать событие базового класса путем переопределения защищенного метода. Название защищенного виртуального метода должно быть таким же, как название события, но с префиксом On. Например, защищенный виртуальный метод для события с названием TimeChanged должен быть назван OnTimeChanged.

Примечание: От производных классов, которые переопределяют защищенный виртуальный метод, не требуется вызывать реализацию базового класса. Базовый класс должен продолжать свою работу корректно, даже если его реализация не вызвана.

AV1230 Использование событий уведомления об изменении свойств 8d93aa4f255b4216a52c3276b3cc7c75.png

Событие уведомления об изменении свойства должно иметь название наподобие PropertyChanged, где Property должно быть изменено на название свойства, с которым связано это событие.

Примечание: Если ваш класс имеет множество свойств, которые требуют соответствующих событий, попробуйте реализовать вместо этого интерфейс INotifyPropertyChanged. Он часто используется в паттернах Presentation Model и Model-View-ViewModel.

AV1235 Не отправляйте null в качестве аргумента при вызове события 0a2fb872c6da4ee8b7244b77a4438892.png

Зачастую обработчик событий используется для обработки схожих событий от множества отправителей. В таком случае передаваемый аргумент используется для того, чтобы передать контекст вызова события. Всегда отправляйте ссылку на контекст (обычно this) при вызове события. Кроме того, не отправляйте null при вызове события, если оно не имеет данных. Если событие не имеет данных, отправьте EventArgs.Empty вместо null.

Исключение: Для статических событий передаваемый аргумент должен быть null.

AV1240 Используйте общие ограничения, если возможно 6ce09bdd83374fd2a684eb9c49663eed.png

Вместо приведения и преобразования типа из конкретного в общий и наоборот используйте ключевое слово where или оператор as, чтобы привести объект к конкретному типу. Например:

class SomeClass 
{}  

// Неправильно
class MyClass 
{ 
    void SomeMethod(T t) 
    { 
        object temp = t; 
        SomeClass obj = (SomeClass) temp; 
    } 
} 
 
// Правильно
class MyClass where T : SomeClass 
{ 
    void SomeMethod(T t) 
    { 
        SomeClass obj = t; 
    } 
}


AV1250 Вычисляйте результат LINQ-запроса до того, как вернуть его 0a2fb872c6da4ee8b7244b77a4438892.png

Посмотрите на следующий код:

public IEnumerable 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 объявлений 0a2fb872c6da4ee8b7244b77a4438892.png

Метод, который включает в себя более 7 объявлений, скорей всего делает слишком много или берет на себя слишком большую ответственность. Кроме того, человеческая память требует, чтобы метод был коротким. Она не в состоянии удерживать в себе одновременно большее количество вещей, чтобы точно проанализировать и понять, что делает тот или иной кусок кода. Разделите метод на несколько маленьких, имеющих четкое предназначение, и дайте им имена, которые будут точно указывать на то, что они делают. При этом обратите внимание на то, чтобы алгоритм работы этой части программы оставался ясен для понимания.

AV1501 Создавайте все члены класса private, а типы internal по умолчанию 0a2fb872c6da4ee8b7244b77a4438892.png

Чтобы принять более взвешенное решение о том, какие элементы должны быть доступны другим классам, в первую очередь как можно больше ограничьте их область видимости. Затем тщательно подумайте, какие члены или типы действительно стоит сделать public.

AV1502 Избегайте двойного отрицания 6ce09bdd83374fd2a684eb9c49663eed.png

Несмотря на то, что такое свойство, как customer.HasNoOrder имеет право на существование, избегайте его использования с отрицанием. Например:

bool hasOrders = !customer.HasNoOrders;


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

AV1505 Наименование сборки в ее названии должно идти после наименования ее пространства имен 8d93aa4f255b4216a52c3276b3cc7c75.png

Все DLL должны именоваться в соответствии с паттерном ..dll, где  — это название вашей фирмы, а  — наименование одного или более пространства имен, разделенных точками. Например:

AvivaSolutions.Web.Controls.dll


В качестве примера можно привести объединение группы классов в пространстве имен AvivaSolutions.Web.Binding, которое включает в себя некая сборка. Согласно данной рекомендации эта сборка должна быть названа AvivaSolutions.Web.Binding.dll.

Исключение: Если вы решите связать классы из различных несвязанных пространств имен в одну сборку, добавьте суффикс Core к ее названию. Однако не используйте этот суффикс в названиях пространств имен. Например: AvivaSolutions.Consulting.Core.dll.

AV1506 Называйте файлы с исходным кодо

© Habrahabr.ru