Михаил Смирнов
msmirnov@msmirnov.ru
www.msmirnov.ru
Цель документа
Документ является соглашением по оформлению и написанию кода на языке C#. В документе приведены основные правила оформления кода и приемы, используемые при написании программ.
Цели документа:
предоставить общие правила, позволяющие сохранить единый стиль написания кода, облегчив тем самым его понимание всеми участниками команды;
ввести базовые правила написания программ, что позволит повысить предсказуемость выполнения программ, а также избежать ошибок при написании программ новыми участниками команды, не знакомыми с внутренними стандартами разработки.
Стили именования
Pascal case – первая буква каждого слова в имени идентификатора начинается с верхнего регистра.
Пример: TheCategory;
Camel case – первая буква первого слова в идентификаторе в нижнем регистре, все первые буквы последующих слов – в верхнем.
Пример: theCategory;
UpperCase – стиль используется только для сокращений, все буквы в имени идентификатора в верхнем регистре.
Пример: ID;
Hungarian notation – перед именем идентификатора пишется его тип в сокращенной форме.
Пример: strFirstName, iCurrentYear.
Правила именования идентификаторов
Общие правила именования идентификаторов
При именовании идентификаторов не используются аббревиатуры или сокращения, если только они не являются общепринятыми.
Пример: GetWindow(), а не GetWin();
Если имя идентификатора включает в себя сокращение – сокращение пишется в upper case. Исключение - когда имя идентификатора должно быть указано в camel case и сокращение стоит в начале имени идентификатора. В этом случае сокращение пишется в нижнем регистре.
Пример:
PPCAccount (PPC – сокращение от pay per click) для pascal case,
ppcAccount для camel case.
Использование верхнего и нижнего регистра в именах
Запрещается создавать два различных namespace’а, функции, типа или свойства с одинаковыми именами, отличающиеся только регистром. Запрещается создавать функции с именами параметров, отличающимися только регистром. Ниже приведены примеры НЕправильных названий.
Пример:
KeywordManager и Keywordmanager;
KeywordManager.Keyword и KeywordManager.KEYWORD;
int id {get, set} и int ID {get, set};
findByID(int id) и FindByID(int id);
void MyFunction(string s, string S).
Правила именования классов
Следует избегать имен классов, совпадающих с именами классов .NET Framework;
Для классов используется стиль именования pascal case;
Для классов, унаследованных от CollectionBase используется суффикс Collection, перед которым указывается тип объектов, для которых используется коллекция.
Пример: UserCollection, CompanyCollection;
В качестве имен классов используются имена существительные;
Имя класса не должно совпадать с именем namespace’а.
Пример: namespace Debugging, класс Debug;
Если класс представляет собой сущность, хранимую в базе данных – имя класса соответствует имени таблицы. В этом случае имя класса – это название сущности в единственном числе, имя таблицы – во множественном числе.
Пример: таблица Users, класс User;
При создании классов потомков их имена состоят из имени базового класса и суффикса класса потомка, если суффиксов несколько – они разделяются символом подчеркивания.
Пример:
базовый класс Figure,
потомок FigureCircle;
Имена файлов, в которых находятся классы, совпадают с именами классов. Для именования файлов используется стиль pascal case.
Правила именования интерфейсов
Имена интерфейсов начинаются с буквы I, после которой следует название интерфейса в pascal case.
Пример: IDisposable.
Правила именования generic’ов
Generic’и обозначаются буквой T, если generic’ов несколько их имена начинаются с буквы T.
Пример: GetItems<T>(int parentID)
Правила именования функций
Для именования функций используется стиль pascal case;
Функции объявляются согласно следующему шаблону:
<�Модификатор доступа> [Другие модификаторы] <�Тип> <�Название функции>();
Пример: protected abstract void HelloWorld();
Имена функций должны давать четкое представление о том, какое действие эта функция выполняет. Имя функции начинается с глагола, указывающего на то, какое действие она выполняет;
Большие функции, не умещающиеся на одном экране, делятся на несколько private функций меньшего размера, имена таких вспомогательных функций состоят из имени основной (большой) функции и существительного, глагола или фразы, которые уточняют действие вспомогательной функций, разделенные подчеркиванием. Основная и вспомогательная функции объединяются в регионы. Вспомогательные функции вызываются только из основной функции.
Пример:
основная функция – CheckProduct,
вспомогательные функции – CheckProduct_Price,
CheckProduct_Url,
CheckProduct_SearchTerm.
Правила именования параметров функций
Для именования параметров используется стиль camel case;
Имена параметров должны давать четкое представление о том для чего используется параметр, и какое значение следует передать при вызове функции.
Пример:
public void EncodeString(string sourceString, ref string encodedString),
а не public void EncodeString(string string1, ref string string2).
В том случае, когда это не препятствует понимаю кода, в качестве имени параметра функции используется имя соответствующего параметру класса. Для коллекций и массивов используется имя объектов, содержащихся в коллекции или массиве.
Пример:
UserFactory.Create(Company company);
CheckUsers(UserCollection users);
Имена параметров не должны совпадать с именами членов класса, если этого не удается избежать, то для разрешения конфликтов используется ключевое слово this.
Пример:
public void CreateUser(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
В именах параметров не используется венгерская нотация.
Правила именования свойств
Для именования свойств используется стиль pascal case;
Свойства объявляются согласно следующему шаблону:
<�Модификатор доступа> [Другие модификаторы] <�Тип> <�Название свойства>;
Пример: public static User CurrentUser { get; }
В том случае, когда это не препятствует понимаю кода, в качестве имени свойства используется имя соответствующего свойству класса. Для коллекций и массивов используется имя объектов, содержащихся в коллекции или массиве.
Пример:
public User User { get; set; }
public UserCollection Users { get; set; }
Название свойства типа bool должно представлять из себя вопрос, требующий ответа да или нет.
Примеры названий: “CanDownload”, “HasKeywords”, “IsChecked”, “NeedsUpdate”.
Правила именования полей
Для именования полей, доступных вне класса, используется стиль pascal case, для private полей - camel case;
Поля объявляются согласно следующему шаблону:
<�Модификатор доступа> [Другие модификаторы] <�Тип> <�Название поля>;
Пример: private static User currentUser = null;
В том случае, когда это не препятствует понимаю кода, в качестве имени поля используется имя соответствующего полю класса. Для коллекций и массивов используется имя объектов, содержащихся в коллекции или массиве.
Пример:
public User User = new User();
public UserCollection Users = new UserCollection();
Название поля типа bool должно представлять из себя вопрос, требующий ответа да или нет.
Примеры названий: “CanDownload”, “HasKeywords”, “IsChecked”, “NeedsUpdate”.
Правила именования переменных
Для именования переменных используется стиль camel case;
Переменные объявляются согласно следующему шаблону:
<�Тип> <�Название поля>;
Пример: int userID = null;
В циклах foreach имя переменной назначается как имя массива в единственном числе.
Пример: foreach(Campaign newCampaign in NewCampaigns).
Правила именования констант
Для именования констант используется стиль pascal case.
Правила именования enum’ов
Для именования enum’ов и их значений используется стиль pascal case;
Имена enum’ов указываются в единственном числе
Имена, как правило, состоят из имени сущности, к которой относится enum и названия содержимого enum’а (status, type, state).
Пример: KeywordStatus, ConnectionState, TaskType.
Правила именования exception’ов
Для exception’ов используется стиль pascal case;
Имена классов для создаваемых custom exception’ов заканчиваются суффиком Exception;
В качестве имени объекта исключения внтури catch, для исключений типа Exception, используется имя “ex”.
Пример: catch(Exception ex).
Правила именования control’ов в asp.net
Для именования control’ов используется венгерская нотация (стиль Hungarian notation). См. Приложение 1: Префиксы, используемые для именования контролов в asp.net.
Форматирование кода
Используются стандартные настройки форматирования Visual Studio;
В одном файле не объявляется больше одного namespace’а и одного класса (исключение – небольшие вспомогательные private классы);
Фигурные скобки размещаются всегда на отдельной строке;
В условии if-else всегда используются фигурные скобки;
Размер tab’а – 4;
Использование строк длиннее 100 символов не желательно. При необходимости инструкция переносится на другую строку. При переносе части кода на другую строку вторая и последующая строки сдвигаются вправо на один символ табуляции;
Каждая переменная объявляется на отдельной строке;
Все подключения namespace’ов (using) размещаются в начале файла, системные namespace’ы объявляются над custom namespace’ами;
Если для свойства существует соответствующее поле (например, при загрузке по требованию), то поле объявляется над свойством.
Пример:
private User user;
public User User { get; set; }
Если set или get свойства состоит из одной операции – весь set или get размещается на одной строке.
Пример:
Public User
{
get { return user; }
}
Функции, поля и свойства группируются внутри класса по своему назначению. Такие группы объединяются в регионы;
Комментирование кода
Все комментарии должны быть на русском языке;
Для функций, классов, enum’ов, свойств и полей комментарии необходимо указывать в таком виде, чтобы по ним можно было автоматически сгенерировать документацию. Для этого используются стандартные tag’и такие как <summary>,
и <return>.
Для функций создающих exception’ы – возможные исключения необходимо указывать в tag’ах ;
Для включения в документацию примеров использования необходимо применять tag’и , и
Для ссылок в документации необходимо использовать tag’и и .
При использовании в тексте комментариев символов, использующихся в xml как спецсимволы, необходимо использовать tag CDATA.
Комментарии к заголовкам функций, свойств, полей, интерфейсов и прочего необходимо указывать всегда;
Для функций, выполняющих сложные алгоритмы, не очевидные для восприятия, необходимо указывать подробные комментарии не только к заголовку функции, но и самому алгоритму с пояснением каждого шага выполнения алгоритма;
В случае внесения изменений в критические участки кода, ядро системы, либо когда сложно проследить последствия, которые может повлечь такое изменение, необходимо указывать подробный комментарий о том кто внес изменение, когда и по какой причине;
В том случае если необходимо временно добавить заплатку, без которой система не может работать, но заплатку в дальнейшем планируется убрать – необходимо добавлять ключевое слово “//TODO: ”, после которого указывается когда и что должно быть исправлено. Кроме этого необходимо указывать подробный комментарий о том, для чего предназначено временное исправление, кто его внес и когда;
При разработке кода, изменения которого могут повлечь за собой сбой в других частях системы, при этом связь между этими двумя частями программы неочевидна и ошибка не будет показана на этапе компиляции, либо если сам код неочевидным образом зависит от других частей системы – необходимо указывать подробное описания взаимосвязей.
Конфигурация
В конфигурационном файле ключи необходимо группировать по назначению. Перед началом каждой такой группы в комментариях необходимо указывать открывающий tag с названием группы, в конце – закрывающий tag.
Пример:
Для ключей, хранящих boolean значения, value может быть равно только true или false, а не 0/1 или yes/no.
Переменные и типы
Свойства необходимо использовать только тогда, когда это имеет смысл. Если при получении и сохранении значений никакая дополнительная логика не участвует – вместо свойства необходимо использовать поле (исключение – когда класс bind’ится на asp.net страницах, т.к. стандартный механизм bind’а имеет доступ только к public свойствам);
Необходимо использовать максимально простые типы данных. Так, например, необходимо использовать int, а не long, если известно, что для хранимых значений будет достаточно типа int;
Константы необходимо использовать только для простых типов данных;
Для сложных типов вместо констант необходимо использовать readonly поля;
Boxing и unboxing value типов необходимо использовать только когда это действительно необходимо;
При задании значений нецелых типов, значения должны содержать как минимум одну цифру до точки и одну после;
Необходимо использовать именования типов C#, а не .NET common type system (CTS).
Пример: int userID = -1; , а не Int32 userID=-1;
Модификаторы доступа необходимо указывать всегда. Не смотря на то, что по умолчанию назначается модификатор доступа private, поле модификатора не остается пустым – модификатор private необходимо указывать явным образом.
Пример: private User CreateUser(string firstName, string lastName);
Модификаторы доступа (private, protected, internal и public) необходимо указывать в зависимости от того, где требуется доступность соответствующего поля, свойства, функции, класса или конструктора. Модификатор public необходимо указывать только тогда, когда необходим доступ к полю из других проектов, internal – когда необходим доступ из других классов внутри одного проекта, protected – для предоставления доступа классам – потомкам, во всех остальных случаях используется private, т.е. доступ ограничивается самим классом;
Поля и переменные инициализируются при их объявлении, когда это возможно.
Пример: private int userID = -1;
private string firstName = “”;
private string lastName = “”;
Когда для создания класса необходимо передать параметры, используемые при его инициализации, на конструктор по умолчанию (MyClass() { }) необходимо накладывать модификатор доступа private, чтобы избежать создания клиентами неинициализированного объекта.
Пример:
private User()
{ }
public User(int userID)
{ }
Вместо использования “magic numbers” для идентификаторов статусов, состояний и т.п. необходимо указывать константы или enum’ы. Идентификаторы состояний в виде чисел использовать нельзя.
Пример не правильного использования: public GetUserByStatus(int statusID);
Пример правильного использования: public GetUserByStatus(UserStatus userStatus);
Тип object необходимо использовать только когда это действительно необходимо, в большинстве случаев вместо него используются generic’и. Вместо Hashtable необходимо использовать Dictionary<>, вместо ArrayList используется List<>;
В том случае если в get’е или set’е какого-либо свойства выполняются сложные вычисления, если операция, выполняемая в get или set является преобразованием, имеет побочный эффект или долго выполняется – свойство должно быть заменено функциями;
Свойство не должно менять своего значения от вызова к вызову, если состояние объекта не изменяется. Если результат при новом вызове может быть другим при том же состоянии объекта, вместо свойства необходимо использовать функции;
Внутри get’а и set’а не должно быть обращений к коду, не связанному напрямую с получением или сохранением значения свойства, т.к. такие действия могут быть не очевидны для клиентов, использующих свойство;
Все настройки, влияющие на работу приложения, нельзя указывать жестко в коде, а необходимо выносить в config. Если есть возможность прописать значение по умолчанию – они должны быть прописаны, если значение по умолчанию не может быть задано и соответствующий ключ не прописан в конфиге – необходимо создавать исключение.
Функции
Функции, возвращающие массив, всегда должны возвращать массив. Если нет данных – функции возвращают пустой массив, но не null. Это же касается коллекций;
В функциях никогда нельзя использовать больше 7-ми параметров. Если параметров больше – они объединяются в класс.
Управление выполнением программы
При использовании foreach по коллекции объектов – сама коллекция никогда нельзя модифицировать (новые элементы не добавляются, существующие не удаляются);
Если задачу можно решить, используя рекурсию и используя циклы, предпочтение необходимо отдавать использованию циклов. Рекурсия необходимо применять только тогда, когда решение с использованием циклов сложнее, чем при использовании рекурсии;
Тернарные операции необходимо использовать только для простых проверок. В тех случаях, когда проверка сложная и включает в себя несколько условий – используются if/else;
В aspx файлах нельзя использовать код. Т.е. tag’и <%= “C# code” %> использовать нельзя. Это связано с тем, что при компиляции такой код не проверяется;
Сложные проверки, состоящие из множества условий, необходимо разбивать на несколько простых. Для сохранения промежуточных результатов необходимо использовать boolean переменные;
Классы, реализующие интерфейс IDisposable, создаются в директиве using.
Пример: using(SqlConnection sqlConnection = new SqlConnection) { }.
События, делегаты, потоки
Перед вызовом делегатов и событий всегда необходимо выполнять проверку на null;
При создании простых event’ов необходимо использовать стандартные классы EventHandler и EventArgs;
При создании сложных event’ов для передачи аргументов необходимо использовать классы потомки EventArgs;
Для блокировок при написании многопоточных приложений необходимо использовать оператор lock, а не класс Monitor.
Exception’ы и их обработка
Блоки try-catch нельзя использовать для управления ходом работы программы, а только для обработки непредвиденных ошибок;
При прокидывании исключений выше по StackTrace’у, необходимо использовать оператор “throw;”, а НЕ “throw ex;”;
Custom exception’ы необходимо наследовать от класса Exception (а не ApplicationException или какого-то другого);
Исключения необходимо создавать всякий раз, когда функция не может быть выполнена – переданы неверные параметры при вызове функции, нет доступа к базе данных, не известные идентификаторы и т.п.;
Все исключения должны быть записаны в log или показаны пользователю системы. Пустые секции catch использовать нельзя;
При записи информации об ошибке, как правило, пишется StackTrace.
Приложение 1: Префиксы, используемые для именования контролов в asp.net.
Button – btn,
CheckBox – cb,
DropDownList – ddl,
HiddenField – hf,
HyperLink – hl,
Image – img,
ImageButton – ibtn
Label – l,
LinkButton – lbtn
ListBox – lb,
Literal – lt,
Panel – pnl
PlaceHolder – ph,
RadioButton – rb,
TextBox – tb,
Table – tbl,
Validator – val,
ValidationSummary – vals,
AdRotator – ar,
BulletList – bl,
Calendar – cld,
CheckBoxList – cbl,
FileUpload – fup,
ImageMap – im,
Localize – loc,
MultiView – mv,
RadioButtonList – rbl,
Substitution – sbs
View – v,
Wizard – wiz,
Xml – xml.
Приложение 2: Сводная таблица правил именования
Идентификатор
|
Регистр
|
Пример
|
Класс
|
Pascal
|
User
|
Локальная переменная
|
Camel
|
user
|
Интерфейс
|
Pascal
|
IDisposable
|
Generic
|
Pascal
|
T, TKey, TValue
|
Public функция
|
Pascal
|
Authenticate
|
Private функция
|
Pascal
|
Authenticate
|
Параметр функции
|
Camel
|
userID
|
Public свойство
|
Pascal
|
FirstName
|
Private свойство
|
Pascal
|
FirstName
|
Public поле
|
Pascal
|
FirstName
|
Private поле
|
Camel
|
firstName
|
Enum
|
Pascal
|
UserStatus
|
Значение enum’а
|
Pascal
|
Active
|
Exception
|
Pascal
|
UserAuthenticationException
|
Event
|
Pascal
|
StatusChanged
|
Namespace
|
Pascal
|
UserManager
|
|