.NET Разработчик @netdeveloperdiary Channel on Telegram

.NET Разработчик

@netdeveloperdiary


Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b

.NET Разработчик (Russian)

Вы когда-нибудь задумывались о том, как стать сертифицированным .NET разработчиком? Если да, то канал ".NET Разработчик" (@netdeveloperdiary) идеально подходит для вас! Здесь вы найдете дневник сертифицированного .NET разработчика, который делится своим опытом, знаниями и советами.

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

Если вы ищете поддержку и мотивацию на пути к сертификации .NET разработчика, то присоединяйтесь к каналу ".NET Разработчик" прямо сейчас! Не упустите возможность стать профессионалом в своей области. А чтобы поддержать канал и его развитие, вы всегда можете воспользоваться ссылками:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b

.NET Разработчик

22 Nov, 05:00


День 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

.NET Разработчик

21 Nov, 05:01


День 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

.NET Разработчик

20 Nov, 05:00


День 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/

.NET Разработчик

19 Nov, 05:00


День 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 == "http://test.com", ct);

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

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


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

Источник: https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding

.NET Разработчик

18 Nov, 05:01


День 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

.NET Разработчик

17 Nov, 05:01


День 2118. #УрокиРазработки
Уроки 50 Лет Разработки ПО

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

Начало

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

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

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

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.

.NET Разработчик

16 Nov, 05:00


День 2117. #УрокиРазработки
Уроки 50 Лет Разработки ПО

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


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

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

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

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

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.

.NET Разработчик

15 Nov, 05:00


День 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

.NET Разработчик

14 Nov, 05:01


День 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

.NET Разработчик

13 Nov, 05:00


День 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

.NET Разработчик

12 Nov, 05:00


День 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/

.NET Разработчик

11 Nov, 05:00


День 2112. #ЗаметкиНаПолях
Функциональное Программирование в C#. Начало

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

В последнее время в C# добавили множество функций из функционального программирования:
- Записи для неизменяемости,
- LINQ для функциональных преобразований,
- Лямбда-выражения для функций первого класса.
Эти функции — не просто синтаксический сахар — они помогают предотвращать ошибки и упрощают понимание кода.

В этой серии рассмотрим практические паттерны, которые можно использовать в проектах на C#.

1. Функции высшего порядка
Функции высшего порядка могут принимать другие функции в качестве параметров или возвращать их в качестве результатов. Они позволяют писать более гибкий и компонуемый код, поскольку вы можете передавать поведение как данные. Типичные примеры — Where и Select из LINQ, которые принимают функции для преобразования данных. Рассмотрим пример валидации:
public class OrderValidator
{
public bool ValidateOrder(Order order)
{
if (order.Items.Count == 0) return false;
if (order.TotalAmount <= 0) return false;
if (order.ShippingAddress == null) return false;
return true;
}
}


Что, если нам нужны:
- разные правила проверки для разных стран.
- повторное использование некоторых проверок,
- по-разному комбинировать проверки.
Вот как функции высшего порядка делают это более гибким:
public static class OrderValidation
{
public static Func<Order, bool>
Create(string country,
decimal minOrder)
{
var baseValidations = CombineValidations(
o => o.Items.Count > 0,
o => o.TotalAmount >= minOrder,
o => o.ShippingAddress != null
);

return country switch
{
"US" => CombineValidations(
baseValidations,
o => IsValidUSAddress(o.ShipAddress)),
"EU" => CombineValidations(
baseValidations,
o => IsValidVATNumber(o.VatNumber)),
_ => baseValidations
};
}

private static Func<Order, bool>
CombineValidations(
params Func<Order, bool>[] validations) =>
order => validations.All(v => v(order));
}

// Использование
var usValidator =
OrderValidation.Create("US", 25.0m);
var euValidator =
OrderValidation.Create("EU", 30.0m);


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

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

Источник:
https://www.milanjovanovic.tech/blog/functional-programming-in-csharp-the-practical-parts

.NET Разработчик

10 Nov, 05:01


День 2111. #УрокиРазработки
Уроки 50 Лет Разработки ПО

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

Начало

Соглашения о приоритетах
Важным аспектом является необходимость согласования относительных приоритетов измерений между командой, клиентами и руководством в начале проекта. Например, график часто представляется как ограничение, хотя на самом деле это драйвер. Чтобы понять разницу, задайте вопрос: «Я понимаю, что вы хотите, чтобы решение было готово к 30 июня. Но что случится, если оно будет готово только к концу июля?» Если в ответ вы услышите, что тогда решение утратит свою ценность или последуют штрафные санкции, то график действительно является ограничением. Но если вам ответят: «Мы бы хотели получить решение к 30 июня, но можем потерпеть и до 31 июля, если придётся», — график является драйвером.

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

Для измерения гибкости используется относительная шкала от 0 до 10. Нулевая точка — начало координат — соответствует отсутствию гибкости, то есть данное измерение является ограничением. Точки в диапазоне от нуля до примерно 4 соответствуют драйверу, то есть в данном измерении проект имеет небольшую гибкость. Любая другая точка с более высоким значением соответствует степени свободы, когда данное измерение предлагает больше гибкости. Соединение точек, нанесенных на оси пяти измерений, дает пятиугольник неправильной формы. Диаграммы гибкости для разных проектов будут иметь разные формы.

В примере диаграммы на картинке время на разработку было ограничено, т.к. компонент должен был быть готов до того, как несколько других приложений, разрабатываемых параллельно, смогли бы его использовать. Поэтому измерение «план» получило нулевую гибкость. Надежность и правильность компонента были очень важны; соответственно, качество было важным фактором успеха. Поэтому «качество» оценено в 2 единицы гибкости. К контрольному сроку должен был быть реализован базовый набор возможностей, затем функциональность постепенно могла расширяться. Поэтому «масштаб» получил оценку гибкости — 4. Была значительная свобода в отношении бюджета и персонала, главное, чтобы все было сделано вовремя. Поэтому эти измерения получили высокие значения гибкости и были оценены как степени свободы.

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

Практическое применение анализа пяти измерений
Этот пятимерный анализ может помочь руководителю проекта решить, как лучше реагировать на меняющиеся условия или реалии проекта, чтобы достичь основных целей. Предположим, что персонал является ограничением. Если понадобится включить новые требования, то единственные параметры, которые потенциально могут измениться, — это масштаб, качество, бюджет и план. В нашем мире за все приходится платить. Диаграмма гибкости может облегчить обсуждение и поиск решения, как действовать дальше. Можно ли сократить или отложить реализацию каких-нибудь других возможностей? Можно ли добавить в процесс разработки ещё одну итерацию и отодвинуть сроки? Должен ли продукт работать идеально в первый же день? Рассмотрение этих пяти аспектов помогает понять приоритеты проекта, а не предполагать, что каждый из них жизненно важен и не подлежит обсуждению.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.

.NET Разработчик

09 Nov, 07:01


День 2110. #УрокиРазработки
Уроки 50 Лет Разработки ПО

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

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

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

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

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

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

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

3. Степень свободы – остальные измерения проекта, которые не являются ни драйвером, ни ограничением. Можно управлять степенями свободы в определённых пределах. Главная задача — так скорректировать степени свободы, чтобы достичь успеха проекта в пределах, устанавливаемых ограничениями. Например, в Agile-разработке масштаб рассматривается как степень свободы, путём регулировки объёма, реализуемого в каждой итерации, так, чтобы уложиться во временные ограничения графика.

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 4.

.NET Разработчик

08 Nov, 05:01


День 2109. #Оффтоп #RegEx
Доброй пятницы, дорогие подписчики.

Сегодня порекомендую вам замечательное видео с канала Мэта Паркера Stand-up Maths.

Сначала, не подглядывая, как вы думаете, что проверяет следующий код:
static bool Check(int n)
{
return !Regex.IsMatch(
new string('1', n),
@"^.?$|^(..+?)\1+$"
);
}


Этот метод проверяет, является ли число простым! Да. Но как вот это ^.?$|^(..+?)\1+$ может выдавать простые числа?

Об этом подробно, а также «Regex 101» - объяснение того, как работают регулярные выражения в принципе, и это выражение в частности - от Мэта Паркера смотрите в его видео.

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

А вообще, подписывайтесь на канал Мэта, если так же, как я, любите всякие математические приколы (не переживайте, там всё объясняется на уровне средней школы).

.NET Разработчик

07 Nov, 05:00


День 2108. #ЗаметкиНаПолях
Не Внедряйте Сторонние Зависимости. Используйте Декораторы. Окончание

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

Причины для снижения связанности
Почему важно отделить код приложения от Polly?

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

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

3. Предположим, что мы не хотим зависеть от какого-то случайного компонента с открытым кодом, но ведь зависимость от Polly безопасна, правда? В долгосрочной перспективе - нет. Пять лет назад была такая же ситуация с Newtonsoft.Json, но затем Microsoft сделали свою System.Text.Json. Теперь есть две конкурирующие библиотеки JSON, и Microsoft использует свою во фреймворках и библиотеках, которые они выпускают.

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

Конфигурация
Поскольку Polly живёт только в корне композиции, вам также нужно будет определить ResiliencePipeline там. Вы можете написать код, который создает пайплайн, где угодно, но было бы естественно сделать его функцией создания в классе ResilientService:
public static ResiliencePipeline CreatePipeline()
{
return new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 4
})
.AddTimeout(TimeSpan.FromSeconds(1))
.Build();
}

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

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

Источник: https://blog.ploeh.dk/2024/09/02/keeping-cross-cutting-concerns-out-of-application-code/

.NET Разработчик

06 Nov, 05:00


День 2107. #ЗаметкиНаПолях
Не Внедряйте Сторонние Зависимости. Используйте Декораторы. Продолжение

Начало

Устойчивый декоратор
Задача в этом примере - сделать зависимость IService более устойчивой. Класс MyApi станет более устойчивым транзитивно, получив более устойчивую зависимость. Поэтому первым шагом рефакторинга является введение устойчивого декоратора:
public class ResilientService(
ResiliencePipeline pipeline,
IService inner) : IService
{
public QueryResult RetrieveMultiple(
QueryByAttribute query)
{
return pipeline.Execute(()
=> inner.RetrieveMultiple(query));
}
}

Как и все декораторы, этот класс содержит экземпляр IService, а также сам реализует этот интерфейс. Вместо внедрения ResiliencePipelineProvider<string> только для вызова GetPipeline для него, он просто получает ResiliencePipeline и сохраняет объект для использования в методе RetrieveMultiple. Обратите внимание, что тут используется первичный конструктор.

Упрощение MyApi
Теперь, нам не нужен код Polly в MyApi:
public class MyApi
{
private IService _svc;

public MyApi(IService svc)
{
_svc = svc;
}

public List<string> GetSomething(
QueryByAttribute query)
{
var result = svc.RetrieveMultiple(query);
return result.Entities
.Cast<string>().ToList();
}
}

Да, в MyApi почти ничего не осталось, но напомню, что у нас упрощённый пример, и GetSomething может быть гораздо длиннее и требовать тестирования. А в нынешнем виде тестирование тривиально:
[Theory]
[InlineData("foo", "bar", "baz")]
public void GetSomething(params string[] expected)
{
var svc = new Mock<IService>();
svc
.Setup(s => s.RetrieveMultiple(
new QueryByAttribute()))
.Returns(new QueryResult(expected));

var sut = new MyApi(svc.Object);

var actual = sut.GetSomething(
new QueryByAttribute());

Assert.Equal(expected, actual);
}

Однако важнее то, что теперь нам не только удалось исключить сторонние зависимости из кода приложения, но и упростить его. Создать устойчивый объект MyApi можно в корне композиции:
var svc = new ResilientService(pipeline, inner);
var myApi = new MyApi(svc);

Так мы отделяем код приложения от сторонних зависимостей. Мы определяем ResilientService в корне композиции и оставляем зависимость от Polly там.

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

Источник:
https://blog.ploeh.dk/2024/09/02/keeping-cross-cutting-concerns-out-of-application-code/

.NET Разработчик

05 Nov, 05:00


День 2106. #ЗаметкиНаПолях
Не Внедряйте Сторонние Зависимости. Используйте Декораторы. Начало

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

Часто можно видеть, как внедрение зависимости (DI) используется для внедрения, скажем, интерфейса журнала или классов пайплайнов Polly для отказоустойчивости в код приложения. Однако, лучшим решением было бы использование Декораторов.
Вот упрощённый пример:
public class MyApi
{
private ResiliencePipeline _pipeline;
private IService _svc;

public MyApi(
ResiliencePipelineProvider<string> prv,
IService service)
{
_pipeline = prv.GetPipeline("retry-pipeline");
_svc = service;
}

public List<string> GetSomething(
QueryByAttribute query)
{
var result =
_pipeline.Execute(() =>
svc.RetrieveMultiple(query));

return result.Entities
.Cast<string>().ToList();
}
}

Как протестировать такой класс? Да, в методе GetSomething всего пара строк, но это упрощённый код, можно предположить, что метод гораздо длиннее.

Класс MyApi связан с Polly, т.к. ResiliencePipeline определён в этой библиотеке. Связанность — главная причина спагетти-кода. Чтобы писать устойчивый код, вы должны осознавать степень связанности. Самый несвязанный код — это код, который вы можете легко удалить. Polly — прекрасная библиотека, и всё сказанное далее скорее относится ко всем сторонним зависимостям.

Также это не означает, что нельзя использовать высококачественные сторонние библиотеки, такие как Polly. Не стоит становиться жертвой синдрома «not invented here».

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

Дело не в том, что мы не хотим иметь эти сторонние зависимости, а в том, что мы хотим переместить их в другое место.

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

Источник:
https://blog.ploeh.dk/2024/09/02/keeping-cross-cutting-concerns-out-of-application-code/