.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День восемьсот тридцать шестой. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
85. Простые системы и закон Галла
Когда вам нужно будет создать новую большую систему, чтобы заменить старую, помните о законе Галла и о пользе эволюционного развития и частой обратной связи.

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

Прежде чем двигаться дальше, важно обратить внимание на первое предложение. Работающая сложная система, и простая система тоже работающая! В любом другом случае мы можем игнорировать этот закон.

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

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

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

Экстремальное программирование
Одна из ключевых ценностей экстремального программирования (XP) - это простота в сочетании с практикой простого дизайна. По сути, практика XP вращается вокруг частой доставки работающего ПО и его изменения по мере необходимости для удовлетворения текущих потребностей клиентов или пользователей. Экстремальное программирование серьезно относится к закону Галла и встраивает его в процесс разработки.

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

Что проще и быстрее построить: монолит или набор микросервисов? Монолит, всегда. Так что сначала подумайте о создании модульного монолита и рассмотрите микросервисы, когда у вас будет простая рабочая система.

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

Источник: https://ardalis.com/simple-systems-galls-law/
День восемьсот тридцать седьмой. #ЗаметкиНаПолях
Статические Методы - Зло? Начало
Использовать статические методы - хорошо или плохо? Попытаюсь описать эволюцию моего отношения к ним.

1. Ух ты, статические методы!
Впервые узнав о статических методах, большинство людей приходят в восторг. Это понятно, потому что у них есть довольно веские преимущества:
- Они удобны - вы можете вызывать их в любое время, не заботясь ни о каких надоедливых зависимостях.
- Они быстрее - статические методы немного быстрее, чем экземплярные, потому что в экземплярных вы также работаете с неявным параметром this. Исключение этого параметра дает небольшой прирост производительности в большинстве языков программирования.

Учитывая эти 2 преимущества, становится понятно, почему люди поначалу тяготеют к использованию статических методов.

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

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

Явные зависимости также помогают сузить область действия этих зависимостей, делая их доступными для определенных классов, а не для всей базы кода. А чем уже сфера использования чего-либо, тем легче это понять.

3. Знакомство с функциональным программированием.
Последнее прозрение приходит, когда вы узнаете о функциональном программировании. Основная идея функционального программирования - избегать скрытых входных и выходных данных, таких как:
- Ссылка на изменяемое состояние,
- Модификация общего состояния (также известная как побочные эффекты),
- Использование исключений для управления ходом программы.

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

Окончание следует…

Источник:
https://enterprisecraftsmanship.com/posts/static-methods-evil/
День восемьсот тридцать восьмой. #ЗаметкиНаПолях
Статические Методы - Зло? Окончание
Начало

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

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

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

Вы же не беспокоитесь об "абстрагировании" вызовов вроде Math.Min(a, b), верно? Вам также не следует беспокоиться об отделении вашего кода от ваших собственных статических методов, которые не сохраняют состояния.

Рассмотрим следующий класс:
public class CustomerService {
public void Process(string cust, string addr)
{
Address a = CreateAddress(addr);
Customer c = CreateCustomer(cust, a);
SaveCustomer(c);
}

}
Обычно мы сделали бы методы CreateAddress, CreateCustomer и SaveCustomer приватными экземплярными методами. Но, их можно сделать статическими:
private static Address CreateAddress(string addr)
=> new Address(addr);

private static Customer
CreateCustomer(string name, Address addr)
=> new Customer(name, addr);

private static void SaveCustomer(Customer c)
{
var repository = new Repository();
repository.Save(c);
}

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

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

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

Источник: https://enterprisecraftsmanship.com/posts/static-methods-evil/
День восемьсот тридцать девятый. #ЗаметкиНаПолях #GC
Топ Вопросов о Памяти в .NET. Продолжение 21-24
Начало 1-4
Продолжение 5-8
Продолжение 9-12
Продолжение 13-16
Продолжение 17-20

21. Как измерить время паузы на сборку мусора?
Каждая пауза на сборку мусора может вызвать нежелательные задержки - будь то паузы, которые заметит пользователь, или более длительная обработка HTTP-запроса. К сожалению, на момент .NET 5 нет поддержки в их измерении. Нет специального счетчика производительности или счетчика событий. И подходящего API сборщика мусора также нет. Но это не значит, что мы не можем это измерить! Лучший способ - собрать трассировку с помощью инструмента PerfView или dotnet trace и просмотреть отчет GC Stats в PerfView.

22. В чём особенность типа ref struct?
Ref struct - это особый тип структуры, представленный в C# 7.2. Он накладывает множество ограничений на поведение структуры с целью избежать попадания экземпляра ref struct в управляемую кучу. Обычная структура может попасть туда, например, при явной или неявной упаковке или если она является полем класса.
Все эти условия запрещены для ref struct. Благодаря этому гарантировано, что она всегда будет находиться в стеке (или регистрах процессора). Это ограничение может быть интересным само по себе: структура данных в стеке не требует многопоточной синхронизации, поскольку по умолчанию к ней нельзя получить доступ из нескольких потоков. И у нас есть гарантия, что она никогда не повлияет на сборщик мусора.
Но наиболее важным результатом этих ограничений является то, что она может содержать управляемый указатель. Или типы, содержащие управляемый указатель (например, Span<T>). Таким образом, это довольно популярный тип, используемый в низкоуровневом программировании.

23. Циклические ссылки замедляют на сборку мусора и влияют на производительность?
Циклические ссылки обычно проблематичны для алгоритмов сборки мусора, использующих подсчёт ссылок. В наивных реализациях объекты, образующие циклическую ссылку, не могут быть собраны, потому что всегда есть по крайней мере один объект, указывающий на другой. Есть более сложные алгоритмы подсчёта ссылок с некоторыми дополнительными накладными расходами для обработки циклических ссылок.
Но .NET GC не использует подсчёт ссылок. Он использует так называемую трассировочную сборку мусора, которая основана на «отслеживании» того, что действительно всё ещё доступно из кода. Такая трассировка является умной - она посещает каждый объект только один раз, даже если между объектами существуют сложные зависимости. Другими словами, циклические ссылки вообще не влияют на производительность сборки мусора в .NET.

24. Чем ValueTask «лучше» Task в смысле потребляемой памяти?
ValueTask - это структура. Таким образом, используя его, мы получаем выгоду от того, что экземпляр не будет размещен в управляемой куче. Это может произойти в «счастливом» синхронном пути асинхронной операции - мы уже знаем результат, поэтому мы можем передать его через структуру, вместо того чтобы выделять Task только для передачи результата. Однако мы не можем гарантировать, что ValueTask никогда не будет упакован, потому что в «несчастливом» асинхронном случае он может быть упакован внутри конечного автомата, отслеживающего операцию.

Продолжение следует…

Источник:
https://dotnetmemoryexpert.com
День восемьсот сороковой. #PerformanceTips
5 Способов Повысить Производительность Кода C# Бесплатно
Разработка программного обеспечения - это поиск компромиссов:
- нормализация против денормализации в реляционных базах данных,
- скорость разработки против качественного кода
и т.п.

Высокопроизводительный код C# обычно требует компромиссов. Разработчики могут пожертвовать удобством сопровождения или безопасностью кода, чтобы код работал быстрее. Но это применимо только к сценариям, в которых уже применяются все шаблоны производительности и лучшие практики, а производительность всё равно требует дальнейшего улучшения.
Существует множество подходов, которые могут помочь разработчикам значительно улучшить производительность приложений, ничем не жертвуя.
Начнём с очевидного:

1. Указывайте ёмкость коллекции
Рассмотрим два почти идентичных метода:
public void NonFixedCapacityTest()
{
var items = new List<decimal>();
for (int i = 0; i < 1000000; i++)
items.Add(i);
}

public void FixedCapacityTest()
{
const int capacity = 1000000;
var items = new List<decimal>(capacity);
for (int i = 0; i < capacity; i++)
items.Add(i);
}

Оба метода выполняют одну и ту же задачу - заполнение коллекции целыми числами с помощью цикла foreach. Единственное отличие состоит в том, что в методе FixedCapacityTest конструктор коллекции инициализируется некоторым числом. Этот простой трюк заставляет метод FixedCapacityTest работать в два раза быстрее, чем NonFixedCapacityTest.

|               Method |      Mean |
|--------------------- |----------:|
| NonFixedCapacityTest | 22.708 ms |
| FixedCapacityTest | 8.418 ms |
Производительность в два-три раза выше, потому что List<T> реализован таким образом, что хранит элементы в массиве, который представляет собой структуру данных фиксированного размера. Когда разработчик создает экземпляр List<T> без указания его ёмкости, выделяется массив ёмкости по умолчанию. Когда массив заполнен, выделяется новый массив большего размера, а значения из старого массива копируются в новый.
Предварительное указание ёмкости устраняет накладные расходы на выделение, копирование и сборку мусора использованных массивов. Разработчики должны всегда указывать ёмкость коллекции, если они заранее знают, сколько элементов будет в неё добавлено.
Параметр ёмкости работает не только с коллекцией List, но и с другими, такими как Dictionary<TKey, TValue>, HashSet<T> и т.п.

Продолжение следует…

Источник:
https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
День восемьсот сорок первый. #PerformanceTips
5 Способов Повысить Производительность Кода C# Бесплатно

2. Используйте структуры вместо классов в некоторых случаях
Разработчикам часто может потребоваться выделить массив или список для хранения десятков тысяч объектов в памяти. Эту задачу можно решить с помощью класса или структуры.
public class PointClass
{
public int X { get; set; }
public int Y { get; set; }
}
public struct PointStruct
{
public int X { get; set; }
public int Y { get; set; }
}

public void ListOfObjectsTest()
{
const int length = 1000000;
var items = new List<PointClass>(length);
for (int i = 0; i < length; i++)
items.Add(new PointClass() { X = i, Y = i });
}

public void ListOfStructsTest()
{
const int length = 1000000;
var items = new List<PointStruct>(length);
for (int i = 0; i < length; i++)
items.Add(new PointStruct() { X = i, Y = i});
}

Как видите, единственная разница между ListOfObjectTest и ListOfStructsTest заключается в том, что первый создаёт экземпляры класса, а второй - экземпляры структур. Код PointClass идентичен коду PointStruct.

|            Method |      Mean |
|------------------ |----------:|
| ListOfObjectsTest | 67.724 ms |
| ListOfStructsTest | 5.136 ms |
Код, использующий структуры, работает в 10-15 раз быстрее, чем код, использующий классы. Такая большая разница во времени, объясняется тем, что в случае классов CLR должна выделить один миллион объектов в управляемой куче и сохранить ссылки на них в коллекции List<T>. В случае структур единственным объектом, размещённым в куче, будет экземпляр коллекции List<T>. Миллион структур будет встроен в этот единственный экземпляр коллекции.

Продолжение следует…

Источник:
https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
День восемьсот сорок второй. #PerformanceTips
5 Способов Повысить Производительность Кода C# Бесплатно
3. Распараллеливание циклов
Часто бывает необходимо перебрать коллекцию с помощью цикла foreach и выполнить некоторую логику для каждого элемента.
public void ForeachTest()
{
var items = Enumerable.Range(0, 100).ToList();
foreach (var item in items)
{
//Симулируем длинную операцию
Thread.Sleep(1);
}
}

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

Производительность можно повысить, начав использовать параллельную версию цикла foreach, которую платформа предоставляет разработчикам.
public void ParallelForeachTest()
{
var items = Enumerable.Range(0, 100).ToList();

Parallel.ForEach(items, (item) =>
{
//Симулируем длинную операцию
Thread.Sleep(1);
});
}

Parallel.Foreach можно использовать на любой коллекции, которая реализует IEnumerable<T> как обычный цикл foreach. Реализация Parallel.Foreach выполнит всю работу по распараллеливанию за вас:
- разобьёт коллекцию на части,
- назначит и выполнит эти части в отдельных потоках.

|              Method |       Mean |
|-------------------- |-----------:|
| ForeachTest | 1,543.9 ms |
| ParallelForeachTest | 199.9 ms |
Надо отметить, что если коллекции небольшие и время выполнения одной итерации быстрое, переход с foreach на Parallel.Foreach может даже ухудшить производительность, особенно если используется синхронизация потоков из-за доступа к общим ресурсам.

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

Продолжение следует…

Источник:
https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
День восемьсот сорок третий. #PerformanceTips
5 Способов Повысить Производительность Кода C# Бесплатно

4. Избегайте неявного линейного поиска
Линейный поиск - это один из простейших алгоритмов поиска, который перебирает все элементы коллекции один за другим, пока не будет найден указанный элемент.

Хотя разработчики обычно не реализуют алгоритм поиска явно, линейный поиск всё же часто вызывает снижение производительности.
public void LinearSearchTest()
{
var ids = Enumerable.Range(0, 10000000);
int idToFind = 9193513;
var exists = ids.Any(u => u == idToFind);
}

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

Разработчикам не обязательно знать, как реализован каждый из методов LINQ. Важно знать основы: в .NET коллекция List<T> базируется на массиве. А когда дело доходит до поиска значения в несортированном массиве, его сложность составляет O(n). Независимо от того, какой метод LINQ используется для поиска значения в массиве (Any, Contains или Where), сложность остается прежней.

Решением этой конкретной проблемы было бы использование структуры данных, подходящей для конкретной задачи. В нашем случае у нас есть идентификаторы, которые всегда уникальны. Это позволяет нам преобразовать коллекцию в HashSet<T>.
public void HashSetTest()
{
var ids = Enumerable.Range(0, 10000000).ToHashSet();
int idToFind = 9193513;
var exists = ids.Contains(idToFind);
}

| Method | Mean |
|----------------- |----------:|
| LinearSearchTest | 63.74 ms |
| HashSetTest | 334.34 ms |
Погодите-ка. HashSetTest оказался гораздо медленнее, чем LinearSearchTest. Это связано с тем, что время тратится и на создание коллекции HashSet<T>, что является трудоёмкой операцией для больших наборов.

Разработчики выиграют от преобразования в HashSet только в том случае, если они планируют часто вызывать на коллекции метод Contains. Если вынести создание коллекции из тестов производительности и измерить только время нахождения элемента, результаты будут кардинально отличаться. Поиск значения в HashSet<T> почти не занимает времени по сравнению с поиском значения в коллекции List<T>.

Продолжение следует…

Источник:
https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
День восемьсот сорок четвёртый. #PerformanceTips
5 Способов Повысить Производительность Кода C# Бесплатно

5. Материализуйте запросы LINQ один раз
При написании запросов LINQ с использованием интерфейсов IEnumerable или IQueryable разработчики могут материализовать запрос (вызвать ToList, ToArray или аналогичные методы) или не делать этого, что позволяет лениво работать с коллекциями. Но иногда возникает необходимость перебрать одну и ту же коллекцию несколько раз. Если запрос не был материализован, повторный перебор коллекции повлияет на производительность.
public void NotMaterializedQueryTest()
{
var elements = Enumerable.Range(0, 50000000);
var filtered =
elements.Where(e => e % 100000 == 0);

foreach (var e in filtered)
{

}

foreach (var e in filtered)
{

}

foreach (var e in filtered)
{

}
}

В этом примере запрос Where не материализуется. Вызов метода Where просто возвращает объект, реализующий интерфейс IEnumerable. Методы GetEnumerator и MoveNext будут вызываться только при итерации по коллекции в цикле foreach.

Вот пример с материализованным запросом:
public void MaterializedQueryTest()
{
var elements = Enumerable.Range(0, 50000000);
var filtered =
elements.Where(e => e % 100000 == 0).ToList();

//остальной код такой же
}

Второй метод из-за материализации запроса с помощью ToList будет работать в 3 раза быстрее первого.
|                   Method |       Mean |
|------------------------- |-----------:|
| NotMaterializedQueryTest | 1,299.6 ms |
| MaterializedQueryTest | 495.5 ms |

Источник: https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
День восемьсот сорок пятый. #ЗаметкиНаПолях #GC
Топ Вопросов о Памяти в .NET. Продолжение 25-27
Начало 1-4
Продолжение 5-8
Продолжение 9-12
Продолжение 13-16
Продолжение 17-20
Продолжение 21-24

25. Что такое интернирование строк?
Интернирование строк - это метод, позволяющий не дублировать экземпляры строк с одинаковым значением, известным во время компиляции. Таким образом, каждый раз, когда вы используете строковый литерал вроде "Hello world!" в разных местах вашего кода C# (в одном проекте/сборке) компилятор распознает это и будет рассматривать как одну «интернированную строку». Такие интернированные строки - это обычные строки, размещённые в управляемой куче. Единственное отличие состоит в том, что JIT знает о них, поэтому при компиляции IL кода, ссылающегося на них, он будет повторно использовать уже выделенную строку вместо создания новой с тем же значением. «Пул Интернированных Строк» (String Intern Pool) - это пул для обработки адресов/экземпляров этих строк, а не какое-либо выделенное пространство для них.
Дедупликация динамически создаваемых строк (со значением, неизвестным во время компиляции) не использует пул, потому что накладные расходы на проверку того, существует ли уже строка с заданным значением, вероятно, сократят преимущество от невыделения новой строки. В настоящее время открыт вопрос о дедупликации строк в фоновом режиме, но она пока не реализована.
Существует потокобезопасный API, с помощью которого вы можете получить доступ к пулу строк, чтобы проверить, интернирована ли строка с заданным значением (string.IsInterned) или принудительно интернировать её (string.Intern).

26. Какие аллокации происходят в следующем методе:
IEnumerable Processing(List input) =>
input
.Where(static x => x > 0)
.Select(static x => x * x);
LINQ - это очень удобный высокоуровневый способ написания декларативного кода. Однако он не оптимизирован для высокопроизводительных горячих путей в вашем коде. Многие операции что-то выделяют в куче, как и в этом случае. Каждый раз, когда вы используете Where или Select, выделяется специальный класс-итератор. Он может комбинироваться с другими. В нашем случае сначала выделяется WhereListIterator (из-за вызова Where), а затем он объединяется в WhereSelectListIterator (из-за вызова Select). Оба экземпляра занимают 152 байта, что явно не так много, если рассматривать один вызов. Но если он используется в горячем пути и вызывается тысячи или миллионы раз, и вы получите мегабайты мусора только из-за этой единственной строчки кода.
Каждая лямбда также выделяет экземпляр Func, но обычно они оптимизируются с помощью кеширования. То есть, хотя первый вызов действительно выделит два экземпляра Func для представления лямбда-выражений в методах Where и Select, они будут повторно использоваться в последующих вызовах.
Примечание: статические лямбда-функции – нововведение в C#9.

27. Приводит ли ToString() на структуре к боксингу?
ToString() у структур имеет реализацию по умолчанию и вызывает боксинг структуры (размещение её в управляемой куче). Если вы хотите избежать ненужного боксинга, вам необходимо переопределить метод ToString().

Окончание следует…

Источник:
https://dotnetmemoryexpert.com
День восемьсот сорок шестой. #ЗаметкиНаПолях
Отмена Операции при Отмене HTTP Запроса
Я уже много раз писал про скоординированную отмену, например, здесь и здесь. И даже про улучшения, которые ожидаются в .NET6. Однако, я, к своему удивлению, упустил момент, что Kestrel может перехватывать событие отмены запроса со стороны пользователя, и передавать отмену операции методу действия контроллера.

Таким образом, можно просто передать CancellationToken в метод контроллера, который запускает длительную операцию:
public MyController {
public async Task<IActionResult>
DoSomethingAsync(CancellationToken token)
{

}
}

Entity Framework и ADO, а также, например, HttpClient, принимают токен отмены, и в случае отмены операции выбрасывают TaskCanceledException. В методе действия контроллера вы можете перехватить и обработать это исключение.

В своём коде вы можете использовать два способа:
- булево свойство IsCancellationRequested, которое вы можете проверять вручную в цикле или между вызовами длительных операций,
- метод ThrowIfCancellationRequested(), выбрасывающий исключение OperationCanceledException, которое вы впоследствии можете обработать.

Необработанные исключения в методах контроллера – это, конечно, плохая идея. Чтобы не засорять код контроллеров их обработкой, можно использовать фильтр исключений, например, такой:
public class OperationCanceledExceptionFilter : 
ExceptionFilterAttribute
{
public OperationCanceledExceptionFilter()
{}

public override void OnException(ExceptionContext context)
{
if(context.Exception is OperationCanceledException)
{
context.ExceptionHandled = true;
context.Result = new StatusCodeResult(400);
}
}
}
Обратите внимание, что мы перехватываем исключение OperationCanceledException. TaskCanceledException является его потомком, поэтому также будет перехвачено.

Также имейте в виду, что пользователь, отменивший запрос, конечно, не увидит никакого сообщения или кода ответа от сервера, поэтому какой именно код ответа будет использован, не играет роли, хотя формально существует код 499 Client Closed Request.

Стоит отметить ещё одну интересную особенность токена отмены. Swagger «понимает» смысл передачи его в метод действия контроллера, поэтому он не включает его в общедоступные параметры метода. Таким образом, в сгенерированной документации API у вас не будет лишних параметров конечных точек.

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

Источники:
-
https://www.youtube.com/watch?v=b5dyPJ3zyRg
-
https://andrewlock.net/using-cancellationtokens-in-asp-net-core-mvc-controllers/
День восемьсот сорок седьмой. #ЗаметкиНаПолях #GC
Топ Вопросов о Памяти в .NET. Окончание 28-32
Предыдущие вопросы: 1-4, 5-8, 9-12, 13-16, 17-20, 21-24, 25-27

28. Что такое хвост распределения задержек (tail latency)?
Хвост распределения задержек - это небольшой процент ответов системы с наибольшим временем отклика. Обычно, рассматривая производительность, например, веб-приложения, нас интересует «среднее время отклика». Оно сообщает нам, каково время отклика для среднестатистического пользователя. Это может быть важным, но также и вводящим в заблуждение. Особенно, если распределение времени отклика является мультимодальным, т.е. когда существуют различные группы времён отклика (например, большинство - очень быстрые, но есть и другая группа очень медленных). Поэтому лучше измерять производительность, наблюдая гистограмму времени отклика, а также следить за хвостом распределения задержек.
Оптимизация хвоста распределения задержек важна, потому что даже если наше приложение работает достаточно хорошо, всё равно будут те несколько процентов пользователей, которые наблюдают действительно медленные ответы. Они могут разочароваться в нашем приложении и больше к нему не возвращаться.
В контексте GC хвост распределения задержек может возникать из-за случайных, но очень длинных пауз на сборку мусора.

29. В чём особенность метода Dispose?
Метод Dispose - это контракт, представленный интерфейсом IDisposable. Реализуя его, мы явно говорим: «экземпляры этого типа владеют некоторыми ресурсами, которые требуют явной очистки путём вызова метода Dispose». Вот и всё! Что делает метод Dispose, полностью зависит от конкретного случая. Важно помнить, что он не имеет ничего общего с GC и управляемой памятью. Это просто метод, который нужно вызвать. Мы можем вызвать его неявно через using и проверить, вызывается ли он, с помощью анализаторов кода. Вызов его ни о чём не сообщает сборщику мусора и не «освобождает память» после объекта. Такой объект, как и любой другой объект .NET, будет обработан сборщиком мусора, когда сборщик мусора обнаружит, что он больше не используется.

30. Можно ли добиться детерминированных пауз на сборку мусора?
Короткий ответ - нет. Мы можем повлиять на время паузы: всё, что снижает нагрузку на память (например, уменьшение количества аллокаций), скорее всего уменьшит время пауз. Некоторые настройки GC тоже могут влиять на них. Но нет гарантии, что время паузы будет детерминированным (например, всегда меньше X миллисекунд). На рынке есть другие сборщики мусора, особенно в мире JVM, которые предоставляют такие гарантии. В .NET их пока нет.

31. Где можно настроить режим GC: серверный или рабочей станции?
Мы можем изменить режим GC только перед запуском приложения, но не во время его выполнения. Для этой цели можно использовать как файл конфигурации, так и переменные среды. Выбор между конкурентным (фоновым) и неконкурентным режимами сборки мусора не зависит от того, работает ли текущий сборщик мусора в режиме рабочей станцией или сервера.

32. Можем ли мы заменить сборщик мусора в .NET?
Да, можем! Начиная с .NET Core 2.0, GC и среда выполнения были разделены. С помощью переменной среды COMPlus_GCName вы можете указать динамическую библиотеку, которая реализует ваш собственный сборщик мусора, и она будет использоваться средой выполнения .NET Core или .NET 5 вместо библиотеки по умолчанию. Однако есть одна проблема: на рынке нет готовых пользовательских сборщиков мусора.

Источник: https://dotnetmemoryexpert.com
День восемьсот сорок восьмой. #BestPractices #CICD
Индикатор Покрытия Кода
Многие используют непрерывную интеграцию или какой-либо конвейер сборки с помощью Azure DevOps, GitHub Actions или другого сервера сборки. Практически любой из конвейеров позволяет вам запускать тесты как часть процесса и откатывать сборку, если тесты терпят неудачу. Некоторые шаги сборки могут анализировать общее покрытие кода (процент от общего количества строк кода в вашем приложении, которые покрыты тестами), и вы можете выбрать откат сборки, если процент упадёт ниже определённого порога. Это один из индикаторов качества сборки, в данном случае индикатором покрытия тестами.

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

Суть процесса непрерывной интеграции заключается в сокращении циклов обратной связи. И когда процесс обнаруживает ошибку или неудавшийся тест, он хорошо служит этой цели. Однако, если ваше текущее покрытие кода составляет 85%, а порог качества - 80%, могут произойти несколько ошибочных коммитов, которые уменьшат покрытие (путем удаления тестов или добавления тонны непроверенного кода). Но, возможно, это просто снизит покрытие до 81%. Затем происходят ещё несколько коммитов, которые в принципе нормальные. Но через пару недель - БАМ! – покрытие снижается ниже лимита после вполне безобидного коммита. Настоящий виновник - более ранние коммиты, которые в реальности серьёзно снизили покрытие, но ещё проходили под лимит. А вы обнаружили проблему намного позже.

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

Источник: https://ardalis.com/tips
День восемьсот сорок девятый. #ЗаметкиНаПолях
Стек или Куча
Тема про сборщик мусора и паттерн Disposable пару дней назад вызвала бурную дискуссию у нас в чате. Сегодня предложу вам не менее острую: ссылочные типы и типы значений, и где они хранятся. Короткий (и неправильный) ответ: ссылочные - в куче, а типы значений - в стеке.

Типы значений в .NET – это bool, byte, sbyte, char, decimal, double, float, int, uint, nint, nuint, long, ulong, short, ushort, struct, enum и кортежи. Все остальные типы ссылочные.

На самом деле, всё немного сложнее. Типы значений могут храниться в куче в некоторых случаях. Вот краткий список правил хранения экземпляров типа значения:
1. переменная в методе – в стеке,
2. параметр метода – в стеке,
3. поле класса – в куче,
4. элемент коллекции – в куче,
5. поле структуры – там, где хранится структура,
6. тип ref struct и его члены – всегда в стеке.

Подробнее в новом видео от Nick Chapsas по ссылке ниже. Кроме того, Ник наглядно показывает, где хранится переменная в том или ином случае, а также, что такое упаковка, и почему она происходит при интерполяции строк.

Источник: https://www.youtube.com/watch?v=jONSIhMST9E
День восемьсот пятьдесят первый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
86. Одна Голова Хорошо, а Две (Зачастую) Лучше
Программирование требует много обдумывания и сосредоточенности, а глубокие мысли требуют уединения. Таков стереотип программиста.

Этот подход «одинокого волка» к программированию уступил место более коллективному подходу, который, я бы сказал, улучшает качество, продуктивность и удовлетворённость работой программистов. Такой подход побуждает разработчиков более тесно сотрудничать друг с другом, а также с не разработчиками: бизнес-аналитиками, системными аналитиками, специалистами по обеспечению качества и пользователями.

Что это значит для разработчиков? Быть опытным технологом уже недостаточно. Вы должны научиться эффективно работать с другими.

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

Я большой поклонник парного программирования. Можно назвать это «экстремальным сотрудничеством». Когда я работаю в паре, мои навыки как разработчика растут. Если я слабее своего партнёра в какой-то области или технологии, я учусь на его или её опыте. Когда я становлюсь сильнее в каком-то аспекте, я глубже понимаю, что я знаю и чего не знаю, когда мне приходится объяснять это партнёру. Мы неизменно добавляем что-то в общую копилку и учимся друг у друга.

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

Парное программирование популярно, но не только среди сторонников гибкой разработки программного обеспечения. Скептики объединения в пары, задают вопрос: «Почему я должен платить двум программистам, чтобы они выполняли работу одного?» Вы действительно не должны. Однако объединение в пары повышает качество, понимание предметной области, технологий и методов (например, трюков IDE) и снижает последствия «риска лотереи» (когда один из ваших опытных разработчиков выигрывает в лотерею и увольняется на следующий день).

Какова долгосрочная ценность изучения нового сочетания клавиш? Как измерить общее улучшение качества продукта в результате объединения в пары? Как измерить вклад вашего партнёра, который не позволил вам пойти по тупиковому пути к решению сложной проблемы? В одном исследовании говорится об увеличении эффективности и скорости на 40%*. Какова ценность снижения последствий «риска лотереи»? Большинство из этих преимуществ трудно измерить.

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

Если вы не уверены, экспериментируйте: сотрудничайте с коллегами. Решите интересную и трудную проблему. Посмотрите, каково это. Попробуйте несколько раз.

*“The Case for Collaborative Programming”, J. T. Nosek - Communications of the ACM, 1998

Источник:
https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Adrian Wible
День восемьсот пятьдесят второй.
Новые Бесплатные Расширения для VS 2019. Начало 1-4
Пока мы ждём выхода новой Visual Studio 2022, большая часть .NET разработчиков используют VS 2019. Сегодня рассмотрим полезные бесплатные расширения для студии. Замечу, что под «новыми» здесь понимаются не только недавно вышедшие, но и недавно обновлённые инструменты.

1. Unit Test Boilerplate Generator от RandomEngy - это бесплатный инструмент со средней оценкой 4,8 из 5. Он поможет создать шаблон модульного теста для класса C#, с mock-объектами для всех зависимостей. Наряду с двумя режимами внедрения зависимостей и поддержкой инфраструктуры тестирования VS, он поддерживает NUnit и xUnit, а для mock-объектов поддерживаются: FakeItEasy, JustMock, Moq + AutoMoq, NSubstitute, Rhino Mocks и
SimpleStubs.

2. Fine Code Coverage от Fortune Ngwenya было скачано почти 18 тысяч раз, получив почти идеальную оценку 4,9. Оно помогает разработчикам визуализировать покрытие кода модульными тестами в .NET Core (.NET 5) и даже в старой .NET Framework. В частности, функция визуализации позволяет программистам:
- Помечать код, для которого есть тесты и для которого их нет
- Помечать тестирующий код
- Получать отчёты о покрытии кода тестами
- Получить отчёт о проблемах на горячих путях
Расширение последний раз было обновлено в конце марта, а последний коммит в репозитории проекта недельной давности, то есть автор старается поддерживать его в актуальном состоянии.

3. Run Coverlet Report от Chris Dexter - еще одна утилита для упрощения демонстрации покрытия кода, цель которой - бесплатно дублировать функциональность Visual Studio Enterprise (прости, Microsoft). Run Coverlet Report основан на превосходных инструментах Coverlet и Report Generator, которые позволяют собирать результаты покрытия кода из модульных тестов. Это расширение запускает оба этих инструмента, а затем отображает файл отчёта в Visual Studio, а также использует вывод Coverlet для подсветки синтаксиса. Coverlet, поддерживаемый .NET Foundation, - это проект с открытым исходным кодом, обеспечивающий кроссплатформенную систему анализа покрытия кода для C#. Report Generator преобразует отчёты о покрытии кода, созданные Visual Studio и многими другими инструментами, в удобочитаемые отчёты в различных форматах. Ещё один инструмент с идеальными 5 из 5.

4. Debug Attach Manager от Viktor Karpach делает одно дело, но делает его хорошо: помогает присоединить отладчик Visual Studio к пулам веб-приложений, службам Windows и другим процессам. Фактически, вы можете искать процессы (например, относящиеся к пулу приложений IIS), а затем присоединяться к ним, автоматически сохраняя выбранные процессы между перезапусками Visual Studio. Вы также можете подключаться к нескольким процессам одновременно. Кстати, расширение работает с версиями VS, начиная аж с 2013, если вдруг у кого ещё остались такие.

Окончание следует …

Источник:
https://visualstudiomagazine.com/articles/2021/05/07/testing-tools.aspx
День восемьсот пятьдесят третий.
Новые Бесплатные Расширения для VS 2019. Окончание 5-9
Начало 1-4

5. SpecFlow представляет собой комплексное предложение профессионального уровня, в котором есть ссылки на документацию, инструкции по началу работы, часто задаваемые вопросы и политика конфиденциальности. При этом оно по-прежнему бесплатное, и имеет впечатляющие 4,9 из 5. SpecFlow помогает группам разработчиков .NET определять, управлять и выполнять автоматизированные приёмочные тесты в виде удобочитаемых бизнес-ориентированных спецификаций. SpecFlow поддерживает концепции разработки Behavior Driven Development (BDD) и Acceptance Test Driven Development (ATDD). Про BDD и SpecFlow, кстати, есть видео у Nick Chapsas.

6. Azure DevOps Test Connector от MadeUpExtensions работает с вышеупомянутым SpecFlow, поддерживая создание тестов Specflow Scenario Outline. Он позволяет программистам использовать атрибуты для связи с тестовыми классами и файлами функций SpecFlow с планами/наборами тестов и кейсами Azure DevOps. Это позволяет инженеру по автоматизации тестирования быстро синхронизировать свои последние изменения в Visual Studio с Azure DevOps, экономя время за счёт автоматического создания и связывания планов тестирования, наборов тестов (как статических, так и основанных на требованиях).

7. «Обожаю работать со сложными регулярными выражениями» – любят говорить… примерно никто из программистов. Хотя, может, это только мой личный опыт, наверняка есть любители. Поэтому, если нечто подобное:
([^e]|e([^s]|s([^\.]|\.([^c]|c([^o]|o([^m]|m([^p]|p([^\.]|\.([^o]|o([^ +s]|s([^\.]|\.([^l]|l([^i]|i([^n]|n([^u]|u[^x])))))))))))))))
для вас как два пальца…, можете пропустить этот пункт. Для всех остальных Regex Editor от Георгия Лосенкова может помочь. Это ещё один нишевый инструмент, который:
- Помогает редактировать и тестировать регулярные выражения.
- Тестировать основные методы класса Regex: Match, Replace, Split.
- Генерировать образцы кода (C# и VB), используя введённые вами данные.
- Рассматривать входные данные как несколько выборок (по одной выборке в строке)
- Подсвечивать шаблоны, найденные группы и замещающие строки
Короче, это regex101.com внутри вашей студии. На данный момент он был установлен более 8 тысяч раз и имеет идеальные 5 из 5.

8. Telerik Testing Framework от известного производителя множества профессиональных платных инструментов .NET. Но это бесплатная среда для веб-тестирования, разработанная, чтобы помочь вам автоматизировать приложения HTML5, AJAX, Silverlight (помните?) и WPF. Среди достоинств инструмента богатый API, поддержка LINQ, абстракция браузера и другие.

9. Недавно было обновлено популярное расширение SonarLint от SonarSource. Оно использует Roslyn, чтобы помочь пользователям находить и исправлять ошибки и проблемы в файлах C#, VB.Net, C, C++ и JavaScript. В связи с ростом количества правил, которые теперь исчисляются сотнями, оно использует сопоставление с образцом и анализ потока данных для облегчения алгоритмов глубокого анализа кода. Оно даже помогает решить любые обнаруженные проблемы. Есть возможность подключения к серверу SonarQube или SonarCloud для совместного использования наборов правил, получения уведомлений о событиях и т.п.

Источник: https://visualstudiomagazine.com/articles/2021/05/07/testing-tools.aspx
День восемьсот пятьдесят четвёртый. #ЗаметкиНаПолях
Валидация Email и URL в .NET
Существует множество совершенно безумных способов проверки email в .NET. Недавно я наткнулся на войну между двумя разработчиками в пул-реквесте. Война велась вокруг «идеального» регулярного выражения для проверки email.

[email protected], что может быть проще?

Посмотрите на этот ответ на Stack Overflow. Регулярное выражение в ответе приведено следующее:
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])

Другой пример можно взять из валидации email в Angular. Тут также используется регулярное выражение:
^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$

Немного другой, но всё равно гигантский шаблон. Итак, учитывая эти параметры (и, вероятно, многие другие), какое же выражение нам выбрать для валидации нашей модели?
public class CreateAccountViewModel
{
[RegularExpression("SoMeCrAzYReGeX")]
public string Email { get; set; }
}

На самом деле, ни то, ни другое. В .NET Core (и в .NET Framework) есть встроенный валидатор email:
[EmailAddress]
public string Email { get; set; }

Красиво и просто, без лишних сложностей. Но тогда возникает вопрос, какое регулярное выражение использует .NET? Вообще-то тут в принципе не используется регулярное выражение!

Логика валидации довольно проста:
1. Значение – строка?
2. Есть ли в строке символ @?
3. Находится ли символ @ в любой позиции, кроме первой или последней?
Всё. Регулярное выражение не требуется!

Это идеальный валидатор? Конечно, нет, и, если он используется в списке рассылки, то письма будут отправляться на адреса, которые не совсем соответствуют спецификации RFC. Однако, такой валидации достаточно, чтобы поймать 99,99% ошибок. Тем более, что, например, при создании учётной записи вы, скорее всего, дополнительно «проверите» email, отправив на него письмо с подтверждением регистрации.

Аналогичный атрибут [Url] для валидации URL, просто проверяет, начинается ли строка с https://, https:// или ftp://.

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

Источник: https://dotnetcoretutorials.com/2021/05/16/validating-an-email-in-a-net-api
День восемьсот пятьдесят пятый. #Оффтоп #КакСтатьСеньором
После некоторого перерыва продолжу серию #КакСтатьСеньором с простыми, но полезными советами.

Написание Кода
1. Legacy-код и следующий разработчик
Вы когда-нибудь смотрели на код и чувствовали, что тут что-то не так? Зачем они так сделали? В этом нет никакого смысла. Я имел счастье работать с устаревшей кодовой базой. С комментариями типа
//Раскомментировать этот код, когда прояснится ситуация с Мухамедом
Что это? Кто такой Мухамед?

То же относится к вам. Подумайте о следующем человеке, который прочитает ваш код. Код покажется ему странным? Обзор кода частично решает эту проблему. Это привело меня к идее контекста: осознавать контекст, в котором работает команда.

Если я забуду код, вернусь к нему позже и не смогу воссоздать контекст, в котором был написан код, я подумаю: «Какой дебил это написал? Тут нет никакого смысла… Ой, это же я написал». И здесь на помощь приходит документация и комментарии.

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

У нас есть конечная точка клиентского API, которую, казалось, никто никогда не использовал. Просто удалим её? В конце концов, это технический долг. А что, если раз в год 10 человек отправляют свои отчёты на эту конечную точку? Как это проверить? Если документации нет, то никак. Мы удалили эту конечную точку. Через несколько месяцев наступило то самое время года. Пользователи не смогли отправить 10 важных отчетов, потому что конечной точки больше не существовала. Люди, знавшие продукт, давно покинули команду. Конечно, теперь в коде есть комментарии, объясняющие, для чего предназначена эта конечная точка.

3. Атомарные коммиты
Если вам придётся откатить ваши изменения (а вам рано или поздно придётся это сделать), что случится с приложением? Атомарен ли ваш коммит? То есть, переводит ли он приложение из одного стабильного состояния в другое (например, проект компилируется и до ваших изменений, и после)?

4. Уверенность в удалении плохого кода
Поначалу мне было очень неловко удалять плохой или устаревший код. Я считал священным то, что когда-то было написано, тем более не мной. Я размышлял так: «Когда это писали, они явно хотели этим что-то сказать». Это традиции и культура против рационального мышления. Отчасти такие мысли подкрепляются случаями, вроде удаления той конечной точки.

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

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

5. Обзоры кода
Обзоры кода великолепны для обучения. Это внешний цикл обратной связи о том, как вы пишете код и как его пишут другие. В чем разница? Один способ лучше другого? Я задавал себе этот вопрос на каждом обзоре: «Почему они сделали это именно так?». Когда я не мог найти подходящего ответа, я приходил поговорить с коллегами.

По прошествии первого месяца я начал замечать ошибки в коде моих коллег (а они замечали мои). Рецензирование стало для меня намного интереснее. Это была игра, которую я с нетерпением ждал. Игра, которая улучшит моё восприятие кода.
Правило: не одобряйте код, пока не поймёте, как он работает.

Источник:
https://neilkakkar.com/things-I-learnt-from-a-senior-dev.html
Автор оригинала – Neil Kakkar
День восемьсот пятьдесят шестой. #юмор
Доброй летней пятницы, дорогие подписчики.