.NET Разработчик
6.6K subscribers
446 photos
4 videos
14 files
2.15K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2113.
.NET Conf 2024 Уже Сегодня!

С 12–14 ноября пройдёт .NET Conf 2024, которая обещает быть познавательной и увлекательной. На конференции будут продемонстрированы последние достижения платформы .NET, проекты с открытым кодом и инструменты для разработчиков.

Вы можете смотреть и участвовать в .NET Conf, зайдя на сайт: https://dotnetconf.net/ Там вы сможете присоединиться к прямой трансляции на YouTube или Twitch. Весь контент записывается и впоследствии будет доступен на канале dotnet в YouTube. Исходный код демонстраций также будет доступен на GitHub.

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

Первый взгляд на .NET 9 от команды .NET
Конференция .NET начнётся с основного доклада членов и руководителей команды .NET, которые покажут самые крутые новые функции выпуска .NET 9. Затем вас ждёт целый день живых презентаций от людей, создававших .NET 9, которые подробно расскажут о функциях .NET Aspire, C#, ASP.NET Core, Blazor, .NET MAUI и других.

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

Подробное расписание смотрите на https://www.dotnetconf.net/agenda.

Из интересного сегодня:
19:00 – 20:00 мск – Открытие конференции.
20:00 – 20:45 мск – Что нового в .NET Aspire
21:30 – 22:15 мск – Что нового в C# 13
22:15 – 23:00 мск – Что нового в ASP.NET Core и Blazor
23:00 – 23:45 мск – Что нового в .NET MAUI
23:45 – 00:30 мск – Что нового в рантайме, библиотеках и SDK.
00:30 – 01:15 мск – Улучшения производительности в .NET 9. (Таубу на его обзор выделили всего 45 минут, ха-ха!)
01:45 – 02:15 мск – OpenAPI в .NET 9
02:15 – 02:45 мск – Создаём гибридные приложения в .NET MAUI
02:45 – 03:15 мск – Что нового в Visual Studio 2022

Источник: https://devblogs.microsoft.com/dotnet/get-ready-for-dotnet-conf-2024/
👍13
День 2114. #ЗаметкиНаПолях
Функциональное Программирование в C#. Продолжение

Начало

2. Ошибки как значения
Обработка ошибок в C# часто выглядит так:
public class UserService
{
public User CreateUser(string email, string password)
{
if (string.IsNullOrEmpty(email))
throw new ArgumentException("Email is required")
if (password.Length < 8)
throw new ArgumentException("Password too short");
if (_userRepo.EmailExists(email))
throw new DuplicateEmailException(email);

// Create user...
}
}

Проблема в том, что:
- исключения дорогие,
- вызывающий код часто забывает обрабатывать исключения,
- сигнатура метода лжёт — она утверждает, что возвращает User, но может выдать исключение.

Мы можем сделать ошибки явными, используя библиотеку OneOf. Она предоставляет дискриминируемые объединения для C#, используя пользовательский тип OneOf<T0, ... Tn>.
public class UserService
{
public OneOf<
User,
ValidationError,
DuplicateEmailError>
CreateUser(string email, string password)
{
if (string.IsNullOrEmpty(email))
return new ValidationError("Email обязателен");
if (password.Length < 8)
return new ValidationError("Пароль короткий");
if (_userRepo.EmailExists(email))
return new DuplicateEmailError(email);

return new User(email, password);
}
}

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

Вот как это использовать:
var result = userService.CreateUser(email, password);
result.Switch(
user => SendWelcomeEmail(user),
valError => HandleError(valError),
dupError => HandleError(dupError)
);


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

Источник:
https://www.milanjovanovic.tech/blog/functional-programming-in-csharp-the-practical-parts
👍20👎8
День 2115. #ЗаметкиНаПолях
Функциональное Программирование в C#. Продолжение

Начало
Продолжение

3. Монадическое связывание
Монада — это контейнер для значений, например List<T>, IEnumerable<T> или Task<T>. Особенностью её является то, что вы можете объединять операции с содержащимися значениями, не имея дела с контейнером напрямую. Такое объединение называется монадическим связыванием. Вы ежедневно используете монадическое связывание в LINQ, не осознавая этого. Оно позволяет объединять операции, преобразующие данные.

Select преобразует значения:
var numbers = new[] { 1, 2, 3, 4 };
var doubled = numbers.Select(x => x * 2);

Операции, возвращающие множество значений, используют SelectMany:
var folders = new[] { "docs", "photos" };
var files = folders.SelectMany(
f => Directory.GetFiles(f));

Популярным примером применения монад на практике является шаблон Result, который обеспечивает простой способ объединения операций, которые могут завершиться неудачей.

4. Чистые функции
Чистые функции предсказуемы: они зависят только от своих входных данных и ничего не меняют в системе. Никаких вызовов базы данных, никаких запросов API, никакого глобального состояния. Это ограничение упрощает их понимание, тестирование и отладку.
Не чистая функция – использует общее состояние:
public class PriceCalculator
{
private decimal _tax;
private List<Discount> _discounts;

public decimal CalculatePrice(Order order)
{
var price = order.Items.Sum(i => i.Price);

foreach (var d in _discounts)
price -= d.Calculate(price);

return price * (1 + _tax);
}
}

А вот пример чистой функции:
public static class PriceCalculator
{
public static decimal CalculatePrice(
Order order,
decimal tax,
IReadOnlyList<Discount> discounts)
{
var price = order.Items.Sum(i => i.Price);

var discounted = discounts.Aggregate(
price,
(pr, disc) => pr - disc.Calculate(pr));

return discounted * (1 + tax);
}
}

Чистые функции потокобезопасны, просты в тестировании и просты в обосновании, поскольку все зависимости явные.

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

Источник:
https://www.milanjovanovic.tech/blog/functional-programming-in-csharp-the-practical-parts
👍15
День 2116. #ЗаметкиНаПолях
Функциональное Программирование в C#. Окончание

Части 1, 2, 3-4

5. Неизменяемость
Неизменяемые объекты не могут быть изменены после создания. Вместо этого создаются новые экземпляры для каждого изменения. Это простое ограничение устраняет целые категории ошибок: состояния гонки, случайные модификации и несогласованное состояние. Вот пример изменяемого типа:
public class Order
{
public List<OrderItem> Items { get; set; }
public decimal Total { get; set; }
public OrderStatus Status { get; set; }

public void AddItem(OrderItem item)
{
Items.Add(item);
Total += item.Price;
// Проблемы потокобезопасности
// Можно изменять отправленные заказы
// Total и Items могут не совпадать
}
}

Сделаем тип неизменяемым:
public record Order
{
public ImmutableList<OrderItem>
Items { get; init; }
public OrderStatus
Status { get; init; }
public decimal Total =>
Items.Sum(x => x.Price);

public Order AddItem(OrderItem item)
{
if (Status != OrderStatus.Created)
throw new InvalidOperationException(
"Нельзя изменять отправленный заказ");

return this with
{
Items = Items.Add(item)
};
}
}

Неизменяемая версия:
- по умолчанию потокобезопасна,
- делает недопустимые состояния невозможными,
- держит данные и вычисления согласованными,
- делает изменения явными и отслеживаемыми.

Итого
Функциональное программирование — не просто написание «более чистого» кода. Эти паттерны фундаментально меняют то, как вы управляете сложностью:
1. Переносите ошибки на время компиляции, выявляйте проблемы до запуска кода.
2. Делайте недопустимые состояния невозможными, не полагайтесь на документацию или соглашения.
3. Делайте счастливый путь очевидным. Объект должно быть легко использовать правильно и сложно использовать неправильно.

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

Источник: https://www.milanjovanovic.tech/blog/functional-programming-in-csharp-the-practical-parts
👍10
День 2117. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 32. Если вы не контролируете риски проекта, то они будут контролировать вас. Начало


Риск — это условие или событие, которое может нанести вред проекту. Это потенциальная проблема, которая ещё не возникла. Цель управления рисками — обеспечить успех проекта, несмотря на возможные негативные последствия рисков, с которыми он сталкивается.

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

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

1. Мозговой штурм
Каждый из участников может поделиться точкой зрения и опытом. Также следует изучить любые допущения, в том числе использовавшиеся при составлении оценок, поскольку ничем не подкрепленные допущения могут содержать риск. Многие из предполагаемых рисков, выявляемых в ходе таких встреч, действительно отражают реалии текущего проекта. Это не риски — это проблемы. А с существующими проблемами нужно бороться намного энергичнее, чем с потенциальными рисками.

2. Исследование списка рисков из публикаций
Ещё одна стратегия — начать с изучения обширного списка рисков, полученного из книг и статей по разработке ПО. Просмотр длинного списка потенциальных рисков немного пугает, подобно чтению перечня всех побочек лекарства. Не все риски применимы к вашему проекту, но их списки могут предупредить о возможностях, о которых вы не догадывались. Риски можно разделить на категории:
- требования и область применения;
- проектирование и реализация;
- организация и персонал;
- управление и планирование;
- заказчик;
- аутсорсинг и подрядчики;
- среда и процесс разработки;
- технология;
- юридические и нормативные требования.

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.
👍6
День 2118. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 32. Если вы не контролируете риски проекта, то они будут контролировать вас. Окончание

Начало

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

2. Оценка рисков и расстановка приоритетов. Подумайте, какой ущерб каждое из условий может нанести проекту. Следует учитывать два аспекта:
- Какова вероятность (от 0 до 1) превращения риска в реальную проблему?
- Какое влияние (от 1 до 10) проблема может оказать на проект, если материализуется?
Умножение вероятности на воздействие дает оценку воздействия каждого риска. Отсортируйте список по убыванию, чтобы элементы с наибольшей угрозой располагались вверху. Это поможет сосредоточить внимание на наиболее важных пунктах. Опытные руководители проектов постоянно держат в центре своего внимания 10 (или около того) наиболее важных рисков.

3. Выбор стратегии. Варианты реагирования на каждый риск:
1) Просто принять риск. Да, он может материализоваться и иметь некоторые негативные последствия, но вы решаете не предпринимать никаких действий, а просто ждать и смотреть, что произойдёт. Иногда нет иного выбора. Подумайте о разработке плана мероприятий на случай непредвиденных обстоятельств.
2) Предотвратить риск путем изменения направления, например, выбрав другие технологии или деловых партнеров, представляющих меньшую опасность.
3) Передать то, что имеет отношение к риску, какой-либо другой стороне, чтобы больше не беспокоиться по этому поводу.
4) Попытаться снизить риск, чтобы уменьшить его воздействие.

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.
👍6
День 2119. #ЗаметкиНаПолях
Копируем Образ docker в Другой Реестр
Перемещение образа docker из одного реестра в другой может быть полезным для миграции образов между различными средами, например, между промежуточной и рабочей.

Простейшим способом будет использование следующих команд:
docker pull myregistry1.com/image:tag
docker tag myregistry1.com/image:tag myregistry2.com/image:tag
docker push myregistry2.com/image:tag


Однако здесь есть недостатки:
1. Сохранение только одной архитектуры. Если образ является мультиплатформенным, вы потеряете другие платформы.
2. Производительность: он подтянет все слои в движок docker, даже если в другом реестре уже есть некоторые из них.

Недавно docker представил новую функцию под названием buildx, которая представляет собой плагин CLI, расширяющий команду docker полной поддержкой функций, предоставляемых набором инструментов Moby BuildKit builder. Одной из функций buildx является возможность копировать образы между реестрами.
docker buildx imagetools create --tag "myregistry2.com/image:tag" "myregistry1.com/image:tag"


Другое решение — один из инструментов, использующих API реестра docker для копирования образа: skopeo или regctl.
regctl image copy source_image:tag target_image:tag


skopeo copy --all docker://source_image:tag docker://target_image:tag


Источник:
https://www.meziantou.net/how-to-copy-a-docker-image-from-one-registry-to-another.htm
👍12
День 2120. #ЧтоНовенького
Заполнение БД в EF Core 9

В EF 9 появились методы UseSeeding и UseAsyncSeeding, которые предоставляют удобный способ заполнения базы данных начальными данными. Эти методы предоставляют одно чёткое место, где может быть размещён весь код заполнения данных. Более того, код внутри методов UseSeeding и UseAsyncSeeding защищён механизмом блокировки миграции для предотвращения проблем с параллелизмом.

Новые методы заполнения вызываются как часть операции EnsureCreated, Migrate и команды dotnet ef database update, даже если нет никаких изменений модели и не были применены миграции. Настройку методов можно произвести в OnConfiguring:
protected override void OnConfiguring(
DbContextOptionsBuilder builder)
=> builder
.UseSqlServer(…)
.UseAsyncSeeding(async (ctx, _, ct) =>
{
var testBlog = await ctx
.Set<Blog>()
.FirstOrDefaultAsync(b =>
b.Url == "https://test.com", ct);

if (testBlog == null)
{
ctx.Set<Blog>()
.Add(new Blog { Url = "https://test.com" });

await ctx.SaveChangesAsync(ct);
}
});


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

Источник: https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding
👍18
День 2121. #ЗаметкиНаПолях
Используем Оператор Неявного Приведения для Сокращения Кода

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

Поэтому их можно сделать строго типизированными:
public class CacheKey
{
private readonly string _key;

public CacheKey()
{
_key = $"{Prefix}-{Key}-{Version}";
}
public required string Prefix { get; init; }
public required string Key { get; init; }
public required int Version { get; init; }

public string GetKey() => _key;
}

Тогда получить значение из кэша можно так:
var value = 
await db.StringGetAsync(сacheKey.GetKey());


Метод StringGetAsync принимает строку, поэтому нужно вызвать метод GetKey, чтобы получить строковое представление CacheKey. Но было бы здорово обойтись без вызова GetKey.

Оператор неявного приведения
Обновим класс CacheKey, добавив оператор неявного приведения:
public class CacheKey
{
private readonly string _key;

public CacheKey()
{
_key = $"{Prefix}-{Key}-{Version}";
}

public required string Prefix { get; init; }
public required string Key { get; init; }
public required int Version {get; init; }

public static implicit operator
string(CacheKey key) => key._key;
}

Теперь переменная типа CacheKey будет неявно приведена к строке, и мы можем получить значение кэша по ключу без вызова GetKey:
var value =
await db.StringGetAsync(cacheKey);


Источник: https://josef.codes/use-the-implicit-operator-to-reduce-noise-in-your-code/
👍36
День 2122. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 6: Связывание. Начало

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

Связанные токены отмены
Связанные токены отмены позволяют вашему коду создавать связанный CancellationTokenSource, который отменяется другим токеном отмены, в дополнение к тому, что сам может запросить отмену:
async Task DoAsync(
CancellationToken ct)
{
using var cts =
CancellationTokenSource
.CreateLinkedTokenSource(ct);
var task = DoOtherAsync(cts.Token);
… // Что-то делаем
… // возможно вызываем cts.Cancel()
await task;
}

Метод DoAsync принимает токен ct — «внешний» токен отмены. Затем он создает CTS, который связан с этим внешним токеном. Когда он вызывает DoOtherAsync, он передает «внутренний» токен из этого связанного CTS.

Если внешний токен когда-либо отменяется, то связанный cts и его внутренний токен (cts.Token) также отменяются. Более того, метод DoAsync имеет возможность явно отменить связанный CTS — в этом случае будет отменён только внутренний токен отмены, оставив внешний отмены неизменным.

То же самое можно сделать с помощью регистраций:
async Task DoAsync(
CancellationToken ct)
{
using var cts = new CancellationTokenSource();
using var reg = ct.Register(cts.Cancel);
var task = DoOtherAsync(cts.Token);
… // Что-то делаем
… // возможно вызываем cts.Cancel()
await task;
}

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

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

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

Источник:
https://blog.stephencleary.com/2024/10/cancellation-6-linking.html
👍8
День 2123. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 6: Связывание. Продолжение

Начало

Варианты использования
Внешний токен и внутренний источник отмены могут на самом деле представлять что угодно; связанные токены отмены полезны, когда вам нужно отменить код, если «A или B».

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

Одним из естественных мест, где используется этот тип кода, является Polly. Polly позволит передать внешний токен, который находится под вашим контролем. Затем он передаёт другой токен отмены вашему делегату выполнения; этот внутренний токен контролируется Polly. Конвейеры Polly (например, тайм-аут) могут отменить внутренний токен для отмены вашего делегата. Естественно, если ваш код отменяет внешний токен, переданный Polly, это также перейдет во внутренний токен. То есть, они связаны:
async Task ExecuteRetryTimeoutAsync(
CancellationToken ct)
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();

await pipeline.ExecuteAsync(async token =>
{
/* ваш код тут */
}, ct);
}

ExecuteRetryTimeoutAsync принимает внешний токен ct и передаёт его Polly. Затем Polly создаёт связанный внутренний токен (который включает поведение конвейера, такое как тайм-аут), и передаёт внутренний токен (token) вашему делегату.

Делегаты, которые вы передаёте Polly, должны следить за токеном, который они получают от Polly, а не за какими-либо другими! Это может оказаться ловушкой, когда вы добавляете конвейеры Polly в существующий код, например, при добавлении тайм-аутов в этот код:
async Task ExecuteAsync(CancellationToken ct)
{
for (int i = 0; i != 10; ++i)
await Task.Delay(1000, ct);
}

Частая ошибка – забыть обновить использованный токен:
async Task ExecuteWithTimeoutAsync(
CancellationToken ct)
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();

await pipeline.ExecuteAsync(async token =>
{
// ПЛОХОЙ КОД!!!
for (int i = 0; i != 10; ++i)
await Task.Delay(1000, ct);
}, ct);
}

Здесь делегат по-прежнему следит за внешним токеном отмены ct, а должен следить за токеном token:
async Task ExecuteWithTimeoutAsync(
CancellationToken ct)
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();

await pipeline.ExecuteAsync(async token =>
{
for (int i = 0; i != 10; ++i)
await Task.Delay(1000, token);
}, ct);
}


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

Источник:
https://blog.stephencleary.com/2024/10/cancellation-6-linking.html
👍8
День 2124. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 6: Связывание. Окончание

Начало
Продолжение

Не используйте OperationCanceledException.CancellationToken
Рассмотрим снова исходный пример:
async Task DoAsync(
CancellationToken ct)
{
using var cts =
CancellationTokenSource
.CreateLinkedTokenSource(ct);
var task = DoOtherAsync(cts.Token);
… // Что-то делаем
… // возможно вызываем cts.Cancel()
await task;
}

Предположим, код вызывает DoAsync и реагирует на отмену:
async Task MainAsync()
{
using var cts =
new CancellationTokenSource();
cts.CancelAfter(2000);

try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException ex)
// ПЛОХОЙ КОД!!!
when (ex.CancellationToken == cts.Token)
{
Console.WriteLine("Timeout!");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}

Замысел кода — сделать что-то особенное, если код отменяется из-за этого конкретного источника отмены. К сожалению, этот код проблематичен в реальном мире; DoAsync может использовать связанный источник токена отмены, в этом случае OperationCanceledException.CancellationToken не будет соответствовать cts.Token, даже если он был источником отмены!

Поэтому не используйте OperationCanceledException.CancellationToken. Правильное решение — проверять, запросил ли этот источник отмену:
async Task MainAsync()
{

catch (OperationCanceledException)
when (cts.IsCancellationRequested)
{
Console.WriteLine("Timeout!");
}

}


То же касается и проверки связанных источников отмены:
async Task DoAsync(
CancellationToken ct)
{
using var cts =
CancellationTokenSource
.CreateLinkedTokenSource(ct);
cts.CancelAfter(1000);

try
{
await DoOtherAsync(cts.Token);
}
catch (OperationCanceledException ex)
// ПЛОХОЙ КОД!!!
when (ex.CancellationToken == cts.Token)
{
… // делаем что-то при таймауте
throw;
}
}

С этим кодом та же проблема! Возможно, что DoAsync может сам использовать связанный токен отмены (или будет использовать в будущем). Решение — не использовать OperationCanceledException.CancellationToken:
async Task DoOtherAsync(
CancellationToken ct)
{

catch (OperationCanceledException ex)
when (cts.IsCancellationRequested)
{
… // делаем что-то при таймауте
throw;
}
}


Итого
В большинстве случаев не приходится использовать связанные токены отмены, но они бывают полезны. Вот, что следует запомнить:
1. Освобождайте (dispose) источники токенов отмены, включая связанные источники токенов отмены.
2. Не используйте OperationCanceledException.CancellationToken; вместо этого используйте IsCancellationRequested.
3. Для любого кода, который имеет несколько токенов в области действия, будьте внимательны к тому, на какой из них вы реагируете.

Источник: https://blog.stephencleary.com/2024/10/cancellation-6-linking.html
👍9
Что выведет код с картинки в первом комментарии (при стандартных настройках культуры)?
#Quiz #CSharp
Anonymous Quiz
12%
23.11.2024 11.23.2024
40%
23.11.2024 11/23/2024
5%
23/11/2024 11/23/2024
13%
2024-11-23 11/23/2024
23%
23.11.2024 00:00:00 11/23/2024
7%
23.11.2024 00:00:00 11.23.2024
👍13👎2
День 2125. #УрокиРазработки
Уроки 50 Лет Разработки ПО


Урок 33. Клиент не всегда прав
Утверждение «Клиент всегда прав» подразумевает, что если клиент что-то просит, то вы обязаны дать ему это.

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

Быть «неправым»
Вот несколько примеров, когда покупатель ПО может быть не прав.
1. Конфликт запросов
Два клиента требуют противоречивых решений проблемы. Они не могут быть правы оба. Но какие-то рассуждения привели их к таким запросам. Мы должны понять, на чём основываются запросы, чтобы оценить, какой из них теснее связан с достижением бизнес-целей проекта.

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

3. Заместитель представителя
Клиент может представить требования от имени класса пользователей, к которому он не принадлежит. Его понимание ожиданий от системы может быть устаревшим или неполным. Например, если в роли представителя сообщества пользователей при обсуждении требований выступает руководитель. Он может не знать всех подробностей повседневной работы простых пользователей. Его опыт может устареть.

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

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

6. У кого больше полномочий, тот и устанавливает требования
Клиенты иногда настаивают, чтобы их требования получили наивысший приоритет из-за их организационного статуса или другого влиятельного положения в проекте. Эта настойчивость может стать проблемой, если люди запрашивают функциональность, которая не будет использоваться настолько часто, чтобы оправдать её преимущество перед другими возможностями.

7. Изменения не бесплатны
Типичный пример, когда клиент не всегда прав, — когда он просит добавить новые возможности или внести другие изменения, но ожидает, что цена и дата готовности останутся прежними. Клиенты относятся к этому так: «Изменения должны вноситься бесплатно; просто сделайте это». Это больше похоже на шутку, но такое случается в реальной жизни.

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.
👍3
День 2126. #Холивар
Композиция или наследование

Возникли у нас как-то с коллегой разногласия по поводу реализации одной функциональности. Лучшие практики объектно-ориентированного проектирования советуют нам предпочитать композицию наследованию. Но всегда ли это верно?

Коротко о домене. Есть у нас сервис поиска поставщиков электронных деталей, и сайт, на котором его можно осуществлять по подписке. А ещё есть сервис, который мы предоставляем производителям этих деталей. Заключается он в том, что мы даём им доступ к результатам нашего поиска «на их сайте». Чтобы на их сайте покупатель мог легко найти, какие поставщики их деталей есть рядом. Да, в наше время уже 99,9% читателей подумали бы, что мы предоставляем API, но нет. В смысле, API тоже есть, но поскольку сервису уже почти 20 лет и не у каждого производителя есть отдел разработки, который мог бы прикрутить этот API к их сайту, есть у нас и другая опция. Мы создаём страницы с функциональностью (поиск, результаты, возможность заказа и т.п.) и подгоняем их под внешний вид сайта производителя.

К сути. Конечно, каждый наш клиент хочет, чтобы функциональность максимально соответствовала его сайту. И стандартная реализация не всегда подходит. Кто-то хочет разную логику поиска: «с начала», «содержит», «равно». Кто-то разные регионы: по умолчанию результаты делятся на Америка, Европа и Азия, но некоторые просят поделить по-другому. И так далее, там настроек выше крыши. Большинство из них легко сохраняется в JSON, но не все. Например, кто-то хочет, чтоб на странице результатов поиска повторялась форма поиска, а кто-то, чтоб результаты определённого поставщика показывались сверху. Тут уже не избежать кастомной бизнес-логики.

Мы сошлись на том, что должен быть базовый класс со стандартным поведением, реализующий интерфейс. Вот его упрощённый код. Только методы, каждый из которых реализует нужный кусок функциональности (допустим, форматирует вывод):
puclic class SearchResults : ISearchResults
{
public Header GetHeader(…) { … }
public Rows GetRows(…) { … }
public Footer GetFooter(…) { … }
}

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

Так вот, моя реализация предполагала, что спец-класс просто наследовал бы от базового и переопределял нужную функциональность:
puclic class CustomSearchResults
: SearchResults, ISearchResults
{
public override Header GetHeader(…)
{
// кастомная логика
}
}

Остальные методы автоматически наследовались бы. Коллега настаивал на предпочтении композиции наследованию, и что мы должны делать декоратор базового класса. Как-то так:
puclic class CustomSearchResults 
: ISearchResults
{
private SearchResults _base;
public CustomSearchResults(SearchResults sr)
{
_base = sr;
}
public Header GetHeader(…)
{
// кастомная логика
}

public Caption GetRows(…)
=> _base.GetRows(…);
public Caption GetFooter(…)
=> _base.GetFooter(…);
}

По мне так в декораторе куча лишней логики. Мы обязаны реализовать все методы, даже если мы просто вызываем метод декорируемого класса. С другой стороны – это «правильная» композиция вместо «неправильного» наследования.

Так мы к согласию и не пришли. А вы что скажете? Может есть вообще какой-то третий, «более лучший», вариант?
👍2
День 2127. #ЗаметкиНаПолях
Проверяем Утверждения в Коде

Иногда нам приходится делать предположения в коде. Например, что какое-то свойство или переменная имеет определённое значение. Либо у нас есть определённое представление о значении, но мы не уверены на 100%, верно ли оно. Сегодня познакомимся с Debug.Assert.

Сразу оговоримся, есть TDD, где вы можете записать свои предположения в виде тестов https://t.iss.one/NetDeveloperDiary/2471. Сегодня не об этом. Иногда у вас есть предположения об определённых характеристиках данных или среды, которые вы не хотите тестировать по какой-то причине (например, потому что вы думаете, что переменная никогда не должна иметь это значение в этом пути кода). Так что рассматривайте это как дополнительный инструмент разработки.

Debug.Assert
Рассмотрим следующий код:
return view
.Where(s => s.TimeSignal is not null)
.GroupBy(v => v.SomeField)
.Select(v =>
{
var signals = v.ToList();
Debug.Assert(signals.Count == 2);
return new Model
{
Id = v.Key,
First =
CreateFromSignal(signals[0].TimeSignal),
Last =
CreateFromSignal(signals[1].TimeSignal),
};
})
.ToArray();

Код анализирует коллекцию объектов представления, группирует их по SomeField, а затем создаёт новый объект Model для каждой группы. Мы ожидаем, что каждая группа должна иметь ровно два элемента. Если это не так, то что-то не так с данными, и мы хотим об этом узнать. И, конечно, мы можем написать тест для этого, но означает ли это, что производственная/тестовая база данных также должна будет удовлетворять этому предположению? Может быть, а может и нет.

Отличительная особенность Debug.Assert, как следует из названия, в том, что он работает только в режиме отладки. Поэтому вы можете усеять свой код этими утверждениями, и они будут работать только в режиме отладки (надеюсь, в проде у вас код работает в режиме RELEASE). Если условие не выполняется, вы получите исключение.

Мне нравится такой подход, чтобы записывать некоторые из моих предположений в код. Это как небольшая заметка для меня и моих коллег. Если мы точно знаем, что предположение должно быть верным в 100% случаев, тогда мы утверждаем это тем или иным способом. В противном случае удаляем Debug.Assert и позволяем коду работать. Часто я запускаю свой код локально с включенным режимом DEBUG.

Microsoft тоже использует эту технику повсеместно: https://grep.app/search?current=2&q=Debug.Assert&case=true&filter[lang][0]=C%23

Trace.Assert
Также есть Trace.Assert, который похож на Debug.Assert, но выполняется, когда в проекте определён символ условной компиляции TRACE.

В проектах Visual Studio и Rider по умолчанию символ условной компиляции DEBUG определяется для отладочных сборок, а TRACE — для всех сборок. Однако, если вы его удалите, либо создадите новую конфигурацию без него, то Trace.Assert не будет выполняться.

Источник: https://steven-giesel.com/blogPost/0c8009eb-0bb6-4716-ae6c-fcb3de4a26e6/how-to-assert-assumptions-in-code-that-might-not-be-true
👍2
День 2128. #Книги
«Предметно-ориентированное проектирование (DDD): структуризация сложных программных систем» (Эванс Э. — СПб.: ООО «Диалектика», 2020).

«Библия DDD», и я наконец-то её осилил. Ох! Ни с одной другой книгой у меня не возникало таких трудностей. Даже Рихтера в начале карьеры я прочитал не с первого, так со второго раза. Но тут я принимался, бросал, продолжал, понимал, что забыл, о чём читал раньше, начинал сначала, снова бросал, читал другие книги, снова возвращался к этой, снова понимал, что всё забыл, пролистывал начало по диагонали и продолжал… В общем, книга у меня уже года три, и последние полтора я пытался её осилить.

Дело даже не в сложности темы, а скорей в подаче. Среди отзывов в начале книги встретился такой: «Книгу легко читать. У Эрика в запасе много интересных историй, и языком он владеет хорошо». Но, то ли моих умственных способностей недостаточно, то ли «легко читать» - это не об этой книге. Во-первых, на мой взгляд, очень сложные для понимания примеры. Да, автор описывает реальные проекты, в которых он участвовал. Но всё-таки, ПО для компании грузоперевозок с грузами, видами контейнеров, маршрутизаторами, маршрутами, участками пути и манипуляциями с грузами – не самый очевидный домен. А уж финансовая программа с займами, долями участия, графиками платежей и начислений и прочими синдицированными кредитами – тут вообще, если с этой сферой не знаком, разобраться по тексту крайне сложно.

А во-вторых, это перевод. Я занимался переводами нескольких книг, и по себе знаю, что такое «русифицировать» какой-то английский термин. Иногда подходящих слов в русском языке просто нет, а часто англицизм настолько вошёл в обиход, что не использовать его – значит специально вводить читателя в заблуждение. Но НАСТОЛЬКО академический перевод вроде бы не самой академической темы я вижу впервые. Я лично сквозь текст просто продирался, как через джунгли, размахивая мачете. Вроде все слова по отдельности знаю, но какая мысль заложена в предложение – решительно не понятно. То ли переводчик привык работать с научными текстами, то ли издательство посчитало, что тема книги подойдёт лишь людям с научными степенями. Но, например, что словосочетание «деловые регламенты» - это то же самое, что «бизнес-правила», я понял далеко не сразу.

Показательна фраза в сноске, которая должна пояснить (sic!) смысл перевода выражения «алгоритмическая часть программы»: «Это словосочетание входит и в название книги “Tackling Complexity at the Heart of Software”, которое можно по смыслу перевести как “систематическое упрощение кода алгоритмически сложных систем”». ЧТО??? Да мне английская фраза гораздо понятнее, чем перевод, хотя, согласен, её не просто перевести дословно.

Попробуйте сами догадаться, что означают следующие понятия:
1. Регламентное правило
2. Объекты-показатели
3. Уровень прикладных операций
4. Техническая архитектурная среда

Убеждён, что «рефакторингу» удалось остаться в тексте книги «рефакторингом» исключительно по причине того, что это название книги Мартина Фаулера.

Несмотря на всё это, книга довольно полезная. И хорошо раскрывает особенности DDD, лучшие практики и подходы к проектированию программных систем. Читать именно её или найти какой-то другой источник, решать вам.
👍18
День 2129. #Оффтоп
Почему Разработчики Любят Чистый Код, но Ненавидят Писать Документацию? Начало

В Developer Coefficient, исследовании, заказанном финтех-гигантом Stripe, разработчики сообщили, что они тратят более 17 часов в неделю на задачи по обслуживанию, такие как отладка и рефакторинг — работа, классифицируемая ими как «мытарство».

Опрос разработчиков 2024 на StackOverflow выявил множество тех же проблем и жалоб. Наибольшим разочарованием, с большим отрывом, был технический долг. И наоборот, больше всего разработчиков радовало улучшение качества их кода и среды разработки. И заглядывая в будущее, две области, в которых разработчики чувствовали, что они получат наибольшую выгоду от инструментов GenAI, — это тестирование и документирование.

В какой степени отличная документация помогает сократить «мытарства» и технический долг, которые приводят к разочарованию и выгоранию разработчиков? И в какой степени она может поддерживать то, что делает их счастливыми, например, качество кода?

Действительно ли документация помогает?
Есть эмпирические доказательства того, что хорошая документация оказывает положительное влияние на такие работы, как рефакторинг или отладка. Мета-исследование более 60 научных работ по качеству ПО и документации показало, что преимущества отражаются во многих аспектах: сокращение продолжительности задачи, улучшение качества кода, более высокая производительность и т.п. И исследования показывают, что документация часто занимает 11% рабочего времени разработчиков.

В исследовании PLOS ONE 2023 года была разработана модель для проверки того, какие методы окажут положительное или отрицательное влияние на процесс рефакторинга. Авторы пишут, что «документация помогает в адаптации новых членов команды и обеспечивает согласованность методов рефакторинга во всей команде».

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

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

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

Ещё одна важная проблема - документация часто рассматривается как ненужные накладные расходы. Разработчики полагают, что код должен быть понятным сам по себе или что документация замедляет процесс разработки. Но это затрудняет онбординг новых членов команды и увеличивает время на задачи по обслуживанию из-за трат времени на понимание плохо документированного кода.

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

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

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

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

Источник:
https://stackoverflow.blog/2024/11/11/developers-hate-documentation-ai-generated-toil-work/
👍6