Skip to content

CODE STYLE

centuriin edited this page Oct 20, 2022 · 22 revisions

Содержание

  • Мы используем новый стиль пространства имён без скобок.
    namespace MatrixBLL;
    
    public class Matrix { }
  • Порядок подключения using'ов и их группировка (группы разделяются пустой строкой).
    • Стандартные
    • В алфавитном порядке
    using System;
    using System.Collections;
    
    using MatrixBLL;
    
    using XUnit;

  • Проекты должны начинаться с названия приложения QuickMaths, а модули должны разделяться ..
    Например QuickMaths.MatrixBLL, QuickMaths.MatrixBLL.Tests.

  • Мы пишем xml-комментарии ко всем публичным членам. О комментариях тут.
  • Если метод уже был покрыт xml комметариями мы указываем тег <inheritdoc />.
    /// <inheritdoc />
    public override string ToString() { }
  • Мы пишем документацию на русском указывая атрибут xml:lang = "ru".
    /// <summary xml:lang = "ru">
    /// Возвращает результат математической функции. 
    /// </summary>
    /// <returns xml:lang = "ru"> Результат типа <see cref="double"/>. </returns>
    public double Calculate();

  • Использовать стиль Олмана для фигурных скобок.
    public void M1()
    {
       //Some works
    }
  • Инициализировать объект построчно.
    var person = new Person()
    {
        Age = 3,
        Name = "name"
    }

  • Все имена, исключая переменные в лямбда-выражениях должны иметь осмысленное название.
  • Использовать PaskalCase в именование class, record, struct, interface, enum, event, delegate, а также в названиях свойств и методов.
    public struct SomeStruct() { }
    public record SomeRecord() { }
    public class SomeClass() { }
    public interface ISomeInterface { }
    public enum SomeEnum { }
    public event SomeHandler SomeEvent { }
    public void SomeMethod() { }
    public int SomeProp { get; set; }
  • Использовать camelCase в именовании переменных.
    var person = new Person();
    var personAge = 4;
  • Поля.
    Ключевое слово Пример
    private private string _name;
    protected protected string name;
    private static private string s_name;
    const private const string SOME_CONST;
  • Интерфейсы начинаются с I.
    public interface IFunction { }
  • Использовать приставку is при именовании bool переменных/полей, когда это возможно.
    bool isCalculated;
  • Асинхронные методы с приставкой Async в конце имени.
    public async Task<int> MethodNameAsync() { }

  • Избегать this, где это не необходимо.
  • Добавлять ?, если элемент может быть null или если метод может возвращать null.
    public Node? plusWay {get; set; }
    public IFunction? Derivative() { } 
  • Использование _, когда метод возвращает значение, но оно нам не нужно.
    _ = int.TryParse(str, out int value)
    _ = Task.Run(() => action())
  • В имена шаблонных классов добавлять {T} соответственно их шаблону.
    // Название файла для этого класса: MyTemplateClass{T1,T2}.cs
    public class MyTemplateClass<T1,T2> { }
  • Пробелы в логических блоках.
    if () { }
    for (int i = 0; i < 5; i++) { }
  • Каждое выражение в LINQ запросе с новой строки.
    var data = list.Where()
       .Select()
       .OrderBy();
  • Входные параметры в методах/конструкторах с новой строки, если они сложные или длинные.
    public Car(Engine engine
       Transmission transmission
       List<Wheel> wheels)
    {
        //...
    }
    //------
    var car = new Car(new Engine(),
        new Transmission()
        new List<Wheel>());
  • Явное указание модификаторов доступа у всех членов.
  • Использовать var, если очевидно какой тип находится в правой части выражения.
    var name = "Johan";
    var person = new Person();
    var person = GetPersonFromDB();
    //но
    Person person = SomeMethod();
  • Использовать nameof, если это возможно.
    public Node(IFunction data) => Data = data ?? throw new ArgumentNullException(nameof(data));
  • Порядок элементов в классе (структуре, записи) определяется по типу.
    • Константы.
    • Поля.
    • Конструктор (в порядке увеличения аргументов).
    • Делегаты.
    • События.
    • Свойства.
    • Методы.
    • Индексаторы.
    • Операторы.
  • В рамках типа элементы в классе (структуре, записи) располагаются по модификаторам доступа.
    • private
    • protected
    • internal
    • public
  • В рамках модификаторов доступа члены группируются по ключевому слову static и находятся выше остальных.
    private static void M1() { }
    private void M1() { }
    
    public static void M1() { }
    public void M1() { }
  • В рамках модификаторов доступа также рекомендуется группировать члены по типу значений или по типу возвращаемого значения.
    Кроме случаев когда два элемента тесно связаны друг с другом по смыслу.
    private string _name;
    private string _lastName;
    private int _age;
    private int _height;
    
    
    private void M1() { }
    private void M2() { }
    private int M3() { }
    private int M4() { }
  • Переопределенные базовые методы пишутся после всех остальных методов и находятся в следующем порядке.
    • Equals (cначала стандартный, потом кастомный).
    • GetHashCode.
    • ToString.
  • Каждый блок элементов сгруппированный по типу разделять двумя пустыми строками от другого типа.
    private string _name;
    private int _age;
    
    
    public Person() { }
    public Person(strin name) { }
    
    
    public int MyProp { get; set; }
    public int MyProp { get; set; }
  • Использование свойств вместо публичных полей, использование автоматических свойств, где это возможно.
    public int MyProp { get; set; }

  • Использовать тернарный опретор, если логика условия простая.
    bool isLessThanZero = value < 0 ? true : false;
  • Сравнение булевых условий без ==.
    if (isLessThanZero) { }
    //or
    if (!isLessThanZero) { }
  • Использовать && и || вместо & и |.
  • Использовать ??, если это возможно.
  • Использовать Pattern matching, где это возможно.
  • Проверки на null в условиях выполнять с помощью опраторов is и is not.
    if (text is not null) return;
  • Переходить на новую строку в сложных условиях.
    if (isCondition1 && isCondition2 ||
        isCondition3 && isCondition2 ) return;
  • Грамотно расставлять порядок проверок для лучшей читабельности.
    //Например
    if (0 <= count && count <= 100) return;
    //А не
    if (count >= 0 && count <= 100)

  • Использование лямбда выражений вместо фигурных скобок, если тело метода/свойства занимает одну строку, кроме конструкторов.
    public Person(string name)
    {
         Name = name ?? throw new ArgumentNullException(nameof(name));
    }
    public int NameLenght => _name.Length;
    public override string ToString() => _name;
  • Переменные в лябмдах.
    • Если запрос короткий и нет запутанной логики с множеством переменных, то достаточно x или любой другой буквы.
    • Если запрос имеет сложную логику то имена лучше давать со смысловой нагрузкой.
  • Использование _, когда переменная не имеет значения.
    _ = task.ContinueWith(_ => myAction());
    //В отличие от 
    _ = task.ContinueWith(t => t.Result);

  • Проверять все входные данные в публичных методах на валидность.
    public Matrix GetRow(long indexRow)
    {
        if (indexRow < 0 || indexRow >= RowsCount)
            throw new IndexOutOfRangeException($"Индекс {nameof(indexRow)} находится вне границ.");
        //
    }
    public Node(IFunction data) => Data = data ?? throw new ArgumentNullException(nameof(data));
  • Всегда проверять объект на null с помощью ?, если объект может иметь состояние null.
    tree?.Node?.ToString();

  • Мы используем xUnit тесты.
  • Мы используем FluentAssertions.
  • Мы используем Moq.
  • Каждый публичный член класса должен быть покрыт Unit тестами.
  • Каждый тестовый проект должен иметь приставку Tests.
    Например для QuickMaths.MatrixBLL -> QuickMaths.MatrixBLL.Tests.
  • Каждый тестируемый класс должен иметь приставку Tests.
    Например для Matrix.cs -> Matrix.Tests.cs.
  • На тесты распространяются правила именования.
  • Тесты пишем по принцу ААА (Arrange-Act-Assert) и явно указываем это комментариями.
     public void CanBeCreated()
     {
         // Arrange
         var size = 3;
         var table = new decimal[3, 3];
    
         // Act
         var exeption1 = Record.Exception(() => new Matrix(size, size));
         var exeption2 = Record.Exception(() => new Matrix(table));
    
         // Assert
         exeption1.Should().BeNull();
         exeption2.Should().BeNull();
      }
  • //Arrange Можно упускать в тестах, когда это не нужно, например в тестировании конструкторов.
  • В категориях желательно указывать тип тестируемого члена. Например: [Trait("Category", "Constructors")], [Trait("Category", "Properties")] и т.п.
  • Тесты элементов класса объединяются в регионы (region) по типу.
    #region Конструкторы
    
    [Fact(DisplayName = "Cannot be created when table is missing.")]
    [Trait("Category", "Constructors")]
    public void CanNotBeCreatedWhenTableIsMissing()
    {
        // Act
        
        // Assert
    }
    #endregion
    
    #region Свойства
    
    [Fact(DisplayName = "Can get rows count.")]
    [Trait("Category", "Properties")]
    public void CanGetRowsCount()
    {
        // Arrange 
    
        // Act
    
        // Assert
    }
    #endregion
  • Открывающий регион должен быть отделен от тест-метода пустой строкой, закрывающий пишется без отступов.
    #region Свойства
    
    [Fact(DisplayName = "Can get rows count.")]
    [Trait("Category", "Properties")]
    public void CanGetRowsCount()
    {
        // Arrange 
    
        // Act
    
        // Assert
    }
    #endregion
  • Теории с большим объемом данных создаются с помощью TheoryData<> и могут выноситься в специальные классы, например Matrix.Tests.Generator.cs.
    Подробнее здесь и здесь.
    //Тест
    [Theory(DisplayName = "Can get row as matrix if index is valid.")]
    [Trait("Category", "Methods")]
    [MemberData(nameof(MatrixTestData.GetRowFromMatrixData), MemberType = typeof(MatrixTestData))]
    public void CanGetRowAsMatrix(Matrix matrix, long index, Matrix expectedMatrix)
    {
        // Arrange
    
        // Act
    
        // Assert
    }
    #endregion
    
    //Тестовые данные в `MatrixTestData`
    public static TheoryData<Matrix, long, Matrix> GetRowFromMatrixData
    {
        get
        {
            var data = new TheoryData<Matrix, long, Matrix>();
    
            data.Add(_matrix4, 0, new Matrix(new decimal[,] { { -5,  5,  5 } }));
            data.Add(_matrix4, 1, new Matrix(new decimal[,] { {  5, -5,  5 } }));
            data.Add(_matrix4, 2, new Matrix(new decimal[,] { {  5,  5, -5 } }));
    
            //Throws error data
            data.Add(_matrix4, -1, new Matrix(0,0));
            data.Add(_matrix4,  3, new Matrix(0,0));
    
            return data;
        }
    }
  • Методы или свойства генераторов данных для тестов должны оканчиваться на Data.