Violet Tape некоторые мысли о разработке на платформе .Net

24Июл/114

Правила оформления кода C# v1.0

Волею судеб я попал в "девственные леса" организации, где не было разработок на C# и надо поставить все процессы с нуля. Систему контроля версий, процесс работы, сбор требований и учет работы. Правила оформления кода. Как раз первую версию такого документа я представляю на ваш суд. Я руководствуюсь исключительно своими предпочтениями в оформлении кода, которые позволяют мне быстро и эффективно ориентироваться по коду приложения.

Здесь отражено только форматирование кода, нет указаний на code smell или какие-либо еще недочеты в самом коде.

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

Краткое содержание:

  • Переменные
  • Использование VAR
  • Именование методов
  • Обрамление кода
  • Комментарии
  • Оформление блоков кода
  • Свойства
  • Логические операции
  • Организация класса

Переменные

Использовать значимые имена переменных написанных на правильном английском. Транслитерация названий переменных не допускается.

Хорошо:

private int letters;
private int result;

Плохо:

private int rezultat;
private int oshibka;

Применение венгерской нотации не применяется ни в каких участках кода, ни в GUI, ни в DataAccess, ни в доменной части.

Хорошо:

private int letters;
private string result;

Плохо:

private int iRezultat;
private ComboBox cbItems;

В именах переменных не допускается применение знака подчеркивания. Каждое новое слово пишется с прописной буквы.

Хорошо:

private int lettersToUpdate;
private string lastName;

Плохо:

private int rows_in_table;
private string last_Name;

Переменные не могут начинаться со знака подчеркивания.

Хорошо:

private int lettersToUpdate;
private string lastName;

Плохо:

private int _rows;
private string _lastName;

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

Хорошо:

public static int Letters;
public int Letters;
protected int Letters;
internal int Letters;

Плохо:

public static int letters;
public static int LETTERS;
public static int LeTTeRs;

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

Хорошо:

public static int Letters;

for (var i = 0; i < names.Count; i++) {
   ...
}
names.Where(s => s.Length > 3).Select(s => s);

Плохо:

private int n;
private List<string> l;

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

Использование var

Переменные с одновременным присваиванием в теле методов должны быть объявлены как var. Данная техника направлена на выбор осмысленных имен разработчиком, чтобы тип не был подсказкой к значению, так как это приводит порой к созданию «программистских ребусов».

Хорошо:

var errorMessage = new StringBuilder();

Плохо:

StringBuilder sbError = new StringBuilder();

List<string> list = new List<string>();

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

Именование методов

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

Хорошо:

private int GetErrorMessages() {
   …
}

Плохо:

private int get_Error_Messages() {
   …
}

private void save_OnClick(…) {
   …
}

Имена методов должны быть написаны на английском языке. Транслитерация не допускается.

Хорошо:

private int GetStockRates () {
   …
}

Плохо:

private int GetKotirovki () {
   …
}

Именование методов при возвращении булевого значения не должно быть с отрицательным значением.

Хорошо:

private bool IsValidUser () {
   …
}

Плохо:

private bool IsUserDoNotHaveErrors () {
   …
}

Данные правила направлены на согласованность методов с самим фреймворком, и легкость чтения и понимания.

Обрамление кода

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

Плохо:

#region public fields

public static int Letters;

#endregion

//======== public fields =========================================

public static int Letters;

//======== end public fields =========================================

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

Комментарии

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

Плохо:

//        public Foo() {
//            Letters = 2;
//        }

Не стоит комментировать каждую строку и писать очевидные вещи.

Плохо:

/// <summary>
/// Отображение данных
/// </summary>
public interface IDataViewer
{
    /// <summary>
    /// Всегда один
    /// </summary>
    bool IsSingle { get; }

    /// <summary>
    /// Сервер
    /// </summary>
    string HostName { get; set; }

    /// <summary>
    /// База данных
    /// </summary>
    string Database { get; set; }

    /// <summary>
    /// Загрузка данных
    /// </summary>
    /// <returns>Результат загрузки</returns>
    bool LoadData();
}

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

Оформление блоков кода

Фигурная открывающая скобка всегда стоит на той же строке, что и конструкция языка.

Хорошо:

namespace N {
    internal interface I {
        void foo();
    }

    internal class C {}
}

Плохо:

namespace N
{
    internal interface I
    {
        void foo();
    }

    internal class C
    {}
}

Такой подход позволяет записывать код более компактно и боле четко разграничивать методы визуально.

Свойства

Автосвойства должны быть записаны в одну строку.

Хорошо:

public int Age { get; set; }
public string Login { get; set; }

Плохо:

public int Age {
    get;
    set;
}
public string Login {
    get;
    set;
}

В остальном правила такие же, как и для полей класса.

Логические операции

Длина строки с логическими операциями не должна превышать 80-120 символов. Перенос операций производится с логическим знаком.

Хорошо:

if(boolValue && number >= 0) {
   ...
}

if(string.IsNullOrWhiteSpace(bigCompositeCaption)
   && bigCompositeCaption.StartsWith("x00")
   && bigCompositeCaption.Length > somePositiveNumber) {
     ...
}

Плохо:

if(string.IsNullOrWhiteSpace(bigCompositeCaption) &&
   bigCompositeCaption.StartsWith("x00") &&
   bigCompositeCaption.Length > somePositiveNumber) {
     ...
}

if(string.IsNullOrWhiteSpace(bigCompositeCaption) && bigCompositeCaption.StartsWith("x00") && bigCompositeCaption.Length > somePositiveNumber) {
    ...
}

Форматирование тернарной операции должно быть  на несколько строк.

Хорошо:

return boolValue
    ? somePositiveNumber
    : anotherNegativeNumber;

Плохо:

return boolValue ? somePositiveNumber : anotherNegativeNumber;

Исключение может быть сделано только для результатов в 1-3 символа.

Хорошо:

return boolValue ? 1 : -1;

return boolValue ? “Y” : “N”;

Унарная операция ?? так же должна быть с переносом на новую строку, если для записи выражения требуется более ~60 символов.

 Хорошо:

return someVeryLongNamedNullable() ?? “N/A”;

return someVeryLongNamedNullable()
	?? new SomeVeryLongNamedClass();

Плохо:

return someVeryLongNamedNullable() ?? new SomeVeryLongNamedClass();

Строка начинающаяся со знака логической операции показывает, что это все еще идет условие, а не первый оператор при выполнении условия. Сравните:

if(string.IsNullOrWhiteSpace(bigCompositeCaption) &&
   bigCompositeCaption.StartsWith("x00") &&
   bigCompositeCaption.Length > somePositiveNumber)
   bigCompositeCaption.Trim();

if(string.IsNullOrWhiteSpace(bigCompositeCaption)
   && bigCompositeCaption.StartsWith("x00")
   && bigCompositeCaption.Length > somePositiveNumber)
   bigCompositeCaption.Trim();

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

 

Hard'n'heavy!

Комментарии (4) Пинги (0)
  1. Не поддерживаю оформление блоков кода. Привык писать так, как у вас в «Плохо». Мне кажется читабельность в этом случае как раз лучше. Ваш стиль как помнится заимствован из C++. Также не вижу ничего плохого в знаке подчеркивания в именовании переменной. Не вызовет ли ваше ограничение путаницу в параметризированных конструкторах? Остального стараюсь придерживаться примерно в таком же виде как и у вас, но не всегда получается)

    P.S.: Думаю стоит еще оформить какое-то правило о использовании ключевого слова this

    • Да, это всегда самая спорная часть оформления кода. Надо наверно будет привести побольше кусок кода, оформленный 2мя способами. При этом оба куска сделать сначала чистым текстом, а потом картинкой с размытием и посмотреть как что будет видно. Очень интересный эксперимент, займусь в ближайшие дни.

  2. С чем не согласен.

    Венгерская нотация и в частности знак нижнего подчеркивания «_» private полей в некоторых случаях добавит больше наглядности. Для примера, наличие в классе static полей на ряду с экземплярными. Да, можно попытаться решить всё именами, но не так наглядно будет. Можно так же решить через this и полное имя статичного члена соответственно, но будет громоздко.

    var не панацея от всех бед и использоваться должно только тогда, когда тип ясен из присваиваемой части выражения. Например:
    var userList = new List(); // вся ясно и понятно
    var partners = GetPartners(); // предположим что будет что-то на вроде IEnumerable что не факт, ибо в некоторых библиотеках есть еще всеми «любимые» унаследованные с первого фреймворка *Collection
    var registringAbonents = GetRegistringAbonents(); // тут совсем всё плохо, ибо понять что-же всё таки будет на выходе сложно: то ли IEnumerable то ли IEnumerable, а может и еще хуже RegistringAbonentsCollection
    и мой любимый вариант, сравни:
    var userUpdateProcedure = Procedures.UserUpdate;
    userUpdateProcedure.UserId = userId;
    userUpdateProcedure.Name = newName;
    userUpdateProcedure.CompanyId = newCompany.Id;
    userUpdateProcedure.Prepare();

    var cmd = Procedures.UserUpdate;
    cmd.UserId = userId;
    cmd.Name = newName;
    cmd.CompanyId = newCompany.Id;
    cmd.Prepare();

    UpdateBaseUserDataProcedure cmd = Procedures.UserUpdate;
    cmd.UserId = userId;
    cmd.Name = newName;
    cmd.CompanyId = newCompany.Id;
    cmd.Prepare();

    что более читабельно?

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

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

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

    а вообще, тут ни слова об инизиализаторах, разделением пустыми строками, симметрии условных операторов, ранних возвратах, кастомных атрибутах и опциональных параметрах(возможно что-то забыл)

    • эх, видимо я окончательно испорчен R# и IntelliSense, раз мне первые варианты (а конкретно второй вариант) с варами приятнее и понятнее. Наверно потому что я вообще не помню какие методы у какого класса есть, а полагаюсь только на подсказки студии. Так что что есть явное указание типа, что нет, я буду смотреть только в подсказку при выборе членов/методов класса.

      Так, я все же подготовлю завтра две картинки с разными стилями скобок с гаусовым размытием. ;)

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

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


Оставить комментарий


Нет обратных ссылок на эту запись.