-
Notifications
You must be signed in to change notification settings - Fork 1
CODE STYLE
centuriin edited this page Oct 20, 2022
·
22 revisions
- Пространства имён
- Создание проектов
- Документация
- Фигурные скобки
- Правила именования
- Правила форматирования
- Pattern-matching
- Лямбда-выражения
- Исключения
- Тесты
- Мы используем новый стиль пространства имён без скобок.
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));
- Порядок элементов в классе (структуре, записи) определяется по типу.
- Константы.
- Поля.
- Конструктор (в порядке увеличения аргументов).
- Делегаты.
- События.
- Свойства.
- Методы.
- Индексаторы.
- Операторы.
- В рамках типа элементы в классе (структуре, записи) располагаются по модификаторам доступа.
privateprotectedinternalpublic
- В рамках модификаторов доступа члены группируются по ключевому слову
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.
- Сервис QuickMaths.CMD
- Сервис QuickMaths.FunctionBLL
- Сервис QuickMaths.General
- Сервис QuickMaths.MatrixBLL