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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2149. #ЗаметкиНаПолях
Гибридный Кэш в
ASP.NET Core 9. Окончание
Начало

Используем HybridCache
Допустим, у нас есть API, предоставляющий информацию о продукте. Часто используемые данные будут кэшироваться для повышения производительности:
1. L1 (в памяти) - для обслуживания быстрых чтений из локального кэша.
2. L2 (Redis) - для обеспечения согласованности данных в распределённых экземплярах.
public class ProductService(
HybridCache cache)
{
public async Task<List<Product>>
GetProductsByCategoryAsync(
string category,
CancellationToken ct = default)
{
var key = $"products:category:{category}";

return await cache.GetOrCreateAsync(
key,
async token =>
await FetchFromDB(category, token),
ct
);
}
}


Как это работает
1. Метод проверяет, существует ли key в HybridCache. Если да, возвращаются кэшированные данные (из L1, если доступно; в противном случае из L2).
2. Если данные отсутствуют в кэшах L1 и L2, вызывается делегат (FetchFromDB) для извлечения данных из базы данных.
3. После извлечения данных они сохраняются в кэшах L1 и L2 с указанными политиками истечения срока действия (HybridCacheEntryOptions - их можно задать для всех элементов при регистрации кэша, либо в перегруженной версии GetOrCreateAsync).
4. Метод возвращает список продуктов либо из кэша, либо после извлечения из базы данных.

Удаление данных из кэша
Можно использовать метод RemoveAsync:
public async Task RemoveFromCache(
string category,
CancellationToken ct = default)
{
var key = $"products:category:{category}";
await cache.RemoveAsync(key, ct);
}

Запись удаляется из кэшей L1 и L2. Если ключ не существует, метод не делает ничего.

Добавление элементов с тегами
При сохранении записей в кэше вы можете назначать теги для их логической группировки. Для этого используется перегрузка GetOrCreateAsync:
// …
return await cache.GetOrCreateAsync(
key,
async token =>
await FetchFromDB(category, token),
new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(10),
LocalCacheExpiration = TimeSpan.FromMinutes(5),
Tags = new List<string> { $"category:{category}" }
},
ct
);

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

Чтобы удалить все элементы, имеющие определённый тэг, используется метод RemoveByTagAsync:
public async Task InvalidateByTag(
string tag,
CancellationToken ct = default
)
{
await _cache.RemoveByTagAsync(tag, ct);
}


Источник: https://thecodeman.net/posts/hybrid-cache-in-aspnet-core
День 2150. #ЧтоНовенького #NET10
Заглядываем в .NET 10
9я версия .NET вышла всего месяц назад, а уже появляются предложения того, что может появиться через год в юбилейной 10й версии.

1. «Левое объединение» в LINQ
В LINQ есть оператор Join, который чаще всего в SQL преобразуется во что-то вроде INNER JOIN. В LINQ нет встроенного оператора для LEFT JOIN, но вы можете достичь того же результата, используя комбинацию GroupJoin, SelectMany и DefaultIfEmpty:
var query =
_dbctx.Table1
.GroupJoin(
_dbctx.Table2,
t1 => t1.T2Id,
t2 => t2.Id,
(t1, t2) => new { t1, t2 }
)
.SelectMany(
t => t.t2.DefaultIfEmpty(),
(t, t2) => new { t1 = @t.t1, t2 }
);

Имейте в виду, если вы хотите объединить несколько столбцов, они должны иметь одинаковые имена и типы данных.

Теперь появилось предложение добавить оператор LeftJoin в .NET 10. Тогда запрос выше будет выглядеть так:
var query = _dbctx.Table1
.LeftJoin(
_dbctx.Table2,
t1 => t1.T2Id,
t2 => t2.Id,
(t1, t2) => new { t1, t2 }
);

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

2. Неблокирующий старт фоновых сервисов
Небольшое изменение в способе запуска BackgroundService в .NET 10. Вы могли заметить, что запуск фонового сервиса блокирует запуск приложения до тех пор, пока не будет запущен сервис. Это происходит из-за того, что метод StartAsync вызывается синхронно в реализации IHostedService. Вы можете использовать следующий трюк, чтобы обойти это:
public class MyService : BackgroundService
{
private IHostApplicationLifetime _lt;

public MyService(IHostApplicationLifetime lt)
{
_lt = lt;
}

protected override async Task
ExecuteAsync(CancellationToken ct)
{
await Task.Yield();
// Вся работа здесь
}
}

Или обернуть всё в Task.Run. Но это не очень хорошо и нелегко обнаружить. В .NET 10 это поведение будет заменено на реально асинхронное, и приложение не будет блокироваться при запуске.

3. Новый компаратор строк
Представьте, что вы хотите отсортировать следующие две строки: Windows 10 и Windows 7. Если рассматривать их исключительно как строки, Windows 10 будет идти перед Windows 7, потому что символ 1 идет перед 7. Но есть контексты, в которых вам нужно рассматривать некоторую часть строки как число и сортировать строки соответствующим образом. Вот тут и пригодится новый StringComparer. Он позволит вам сортировать строки как числа, поэтому Windows 10 будет идти после Windows 7.
var list = new List<string> { "Windows 10", "Windows 7" };
list.Sort(StringComparer.NumericOrdering);

Другой пример, упомянутый в предложении, - сортировка IP адресов.

Источники:
-
https://steven-giesel.com/blogPost/82ebfd51-23c0-44b7-9602-628d1aba5f3c/linq-might-get-a-left-join-operator-in-net-10
-
https://steven-giesel.com/blogPost/3a8c29a3-750a-40f0-aa43-c236b855813e/some-news-about-net-10-backgroundservices-and-new-string-comparer
День 2151. #ЗаметкиНаПолях
Публичные API в Модульных Монолитах. Начало

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

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

Когда модулю A что-то нужно от модуля B, у вас есть три варианта:
1) Позволить модулю A читать напрямую из базы данных модуля B,
2) Позволить модулю A получать доступ к внутренним сервисам модуля B,
3) Создать публичный API, который явно определяет, что может делать модуль A.

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

Публичные API служат следующим критически важным целям:
1) Определение контракта: они явно указывают, что могут и не могут делать другие модули,
2) Управление зависимостями: они заставляют вас думать о зависимостях модулей,
3) Управление изменениями: они предоставляют стабильный интерфейс, допуская при этом внутренние изменения.

Вот практический пример. Представьте, что у вас есть модули Orders и Shipping.

Этого мы хотели бы избежать:
public class ShippingService
{
// Прямой доступ к БД
private OrdersDbContext _ordersDb;

public async Task ShipOrder(string id)
{
// Напрямую обращаемся к БД другого модуля
var order = await _ordersDb.Orders
.Include(o => o.Lines)
.FirstOrDefaultAsync(o => o.Id == id);

// Что, если модуль Orders изменит схему данных?
}
}


А вот, чего бы мы хотели добиться вместо этого:
public class ShippingService
{
// Доступ к публичному API
private IOrdersModule _orders;

public async Task ShipOrder(string id)
{
// Используем публичный API
var order = await
_orders.GetShippingOrder(id);

// Модуль Orders может изменять внутреннюю
// структуру, сохраняя внешний контракт
}
}


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

Источник:
https://www.milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths
День 2152. #ЗаметкиНаПолях
Внутренние и Публичные API в Модульных Монолитах. Окончание

Начало

Раскрытие информации
Самая сложная часть проектирования публичных API — решить, что раскрывать:
1) Начните с того, что нет ничего публичного,
2) Открывайте только то, что другим модулям действительно нужно,
3) Проектируйте API вокруг вариантов использования, а не данных.

Вот как это выглядит на практике:
public interface IOrdersModule
{
// Не раскрываем CRUD-операции
// Раскрываем варианты использования
Task<ShippingOrder> GetShippingOrder(string id);
Task<PaymentOrder> GetPaymentOrder(string id);
Task<OrderSummary> GetOrderSummary(string id);
}


Защита данных модуля
Также нужно защитить данные модуля. Вот несколько советов.

1. Отдельные схемы: каждый модуль получает свою схему БД.
CREATE SCHEMA Orders;
CREATE SCHEMA Shipping;

-- Каждый модуль имеет доступ только к своей схеме
CREATE USER OrdersUser WITH DEFAULT_SCHEMA = Orders;
GRANT SELECT, INSERT, UPDATE, DELETE ON SCHEMA Orders TO OrdersUser;

-- … Аналогично для схемы Shipping

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

2. Различные строки подключения: каждый модуль получает своего пользователя БД с соответствующей строкой подключения.
builder.Services.AddDbContext<OrdersDbContext>(
opts =>
opts.UseSqlServer(
builder.Configuration
.GetConnectionString("OrdersConnection")));

builder.Services.AddDbContext<ShippingDbContext>(
opts =>
opts.UseSqlServer(
builder.Configuration
.GetConnectionString("ShippingConnection")));

См. также «Использование Нескольких Контекстов EF Core»

3. Модели для чтения: специальная модель только для чтения для использования из других модулей.
internal class Order
{
// Полная внутренняя модель
}

public class ShippingOrder
{
// Публичное DTO для модуля Shipping
public string Id { get; init; }
public Address ShippingAddress { get; init; }
public List<ShippingItem> Items { get; init; }
}


Итого
Открытые API в модульных монолитах предназначены не для предотвращения связанности, а для её контроля. Каждый открытый API — это контракт, который гласит: «Да, эти модули связаны, и именно так они зависят друг от друга».
Цель не в том, чтобы устранить зависимости между модулями. Цель в том, чтобы сделать их явными, контролируемыми и поддерживаемыми.

Источник: https://www.milanjovanovic.tech/blog/internal-vs-public-apis-in-modular-monoliths
День 2153. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 36. Как бы сильно на вас ни давили, не берите на себя обязательства, которые не сможете выполнить


Люди редко со всей серьёзностью относятся к обязательствам, которые им пришлось взять на себя под принуждением. Они также не берут на себя серьёзных обязательств, которые кто-то другой берет на себя от их имени без консультаций и переговоров. Если сотруднику навязать невыполнимое обязательство, это вызовет у него стресс, а не приведёт к чуду. Более того, люди могут даже снизить свои старания, зная, что всё равно не смогут достичь поставленной цели. Зачем убиваться, если дело и так обречено на провал?

Обязательство — это обещание выполнить некое действие или часть работы на определённом уровне за определённое время. Управление обязательствами — это компонент управления проектами. Идея не брать на себя обязательств, которые невозможно выполнить, — вопрос личной этики. Она также отражает правильное управление проектами и людьми.

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

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

В жизни случается всякое
Люди берут на себя обязательства из лучших побуждений. Потом что-то происходит, и всё меняется. Может появиться новое задание или новая возможность, на которые приходится отвлекаться, или объём работы может превышать предполагаемый. Человек может просто потерять интерес к первоначальному обязательству и позволить ему отойти на второй план, надеясь, что никто не заметит. Но это замечают.

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 5.
День 2154. #ЧтоНовенького
Бесплатный GitHub Copilot в Visual Studio
Если вы ещё не слышали, GitHub анонсировал бесплатный план Copilot! И вы уже можете начать использовать Copilot прямо сейчас в Visual Studio и VS Code.

Copilot предлагает инструменты на базе ИИ, такие как умная отладка, сообщения о коммитах, сгенерированные ИИ, размещение точек останова с помощью ИИ, ИИ-завершения кода, чат и многое другое. Разработчики увидели увеличение продуктивности до 25% при использовании Copilot.

Чтобы попробовать, войдите в свой аккаунт на GitHub, и перейдите в настройки Copilot.

Бесплатная версия включает:
- 2000 интеллектуальных автодополнений кода в месяц: контекстно-зависимые предложения кода, которые черпают контекст из ваших проектов GitHub и рабочего пространства.
- 50 сообщений в чате Copilot в месяц: попросите Copilot помочь вам понять код, выполнить рефакторинг, написать тесты для класса или отладить проблему.
- Выбор модели ИИ Claude 3.5 Sonnet или OpenAI GPT-4o.
- Внесение изменений в несколько файлов с помощью Copilot Edits.
- Поддержка экосистемы Copilot Extensions: доступ к сторонним агентам, разработанным для таких задач, как запросы к Stack Overflow или поиск с помощью Perplexity.

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

Источник: https://devblogs.microsoft.com/visualstudio/github-copilot-free-is-here-in-visual-studio/
День 2155. #ЗаметкиНаПолях
3 Основных Способа Управления Транзакциями в EF Core. Начало

Транзакции помогают безопасно обновлять связанные данные, не оставляя состояние БД несогласованным. Рассмотрим, как EF Core предоставляет механизм упаковки операций с БД в транзакции.

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

Вот основные способы работы с транзакциями в EF Core:
1. Автоматически. Метод SaveChangesAsync() класса DbContext фиксирует все изменения в транзакции.
2. Вручную. Использование команд begin, commit и rollback для транзакций.
3. Стратегия выполнения транзакций. Может иметь несколько вызовов SaveChangesAsync, но они не фиксируются, пока транзакция не фиксирует их с помощью метода transaction.CommitAsync().

1. Автоматические транзакции
Предположим, что нам нужно добавить несколько зданий.
csharp 
public record Building(int Id, string Name, string Address);

using var ctx = new ApplicationDbContext();
var bld1 = new
Building(1, "First", "1st Street, 42");

ctx.Buildings.Add(bld1);

// Транзакция используется автоматически
ctx.SaveChangesAsync();

var bld2 = new
Building(2, "Second", "2nd Street, 69");

ctx.Buildings.Add(bld2);

// Транзакция используется автоматически
ctx.SaveChangesAsync();


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

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

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

2. Ручные транзакции
Здесь каждый вызов SaveChangesAsync рассматривается как завершение одной операции. Несколько вызовов заключены в транзакцию, которая начинается с Database.BeginTransaction. После успешного выполнения всех операций метод transaction.Commit() фиксирует все изменения как одну транзакцию.
public class BuildingRepo: IBuildingRepo
{
private AppDbContext _context;
public BuildingRepo(AppDbContext ctx)
{
_ctx = ctx;
}

public async Task<bool> CreateAsync(
List<Building> buildings)
{
using (var trans = ctx.Database.BeginTransaction())
{
try
{
foreach (var bld in buildings)
{
_ctx.Buildings.Add(bld);
_ctx.SaveChangesAsync();
}

// Если всё ОК, фиксируем транзакцию
trans.Commit();
return true;
}
catch (Exception ex)
{
// В случае ошибки, откатываем
trans.Rollback();

// Обработка исключения…
return false;
}
}
}
}


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

Минусы
- Усложняет код из-за дополнительного кода обработки транзакций.

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

Источник:
https://blog.elmah.io/3-essential-techniques-for-managing-transactions-in-ef-core/
День 2156. #ЗаметкиНаПолях
3 Основных Способа Управления Транзакциями в EF Core. Окончание

Начало

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

Добавим к зданиям различные помещения в зданиях:
public class Unit
{
public int Id { get; set; }
public string Number { get; set; }
// Office, Apartment, Storage, и т.п.
public UnitType Type { get; set; }
public int Floor { get; set; }

public int BuildingId { get; set; }
public Building Building { get; set; }
}

Создадим DTO:
public record UnitDto(
string Number, UnitType Type, int Floor);

public class BuildingDto
{
public string Name { get; set; }
public string Address { get; set; }

public List<UnitDto> Units { get; set; }
= new List<UnitDto>();
}

И, наконец, сценарий обработки:
public class BuildingRepo: IBuildingRepo
{
private AppDbContext _ctx;

public BuildingRepo(AppDbContext ctx)
{
_ctx = ctx;
}

public async Task<bool> CreateAsync(BuildingDto input)
{
// Создаём стратегию
var strategy = _ctx.Database
.CreateExecutionStrategy();

return await strategy.ExecuteAsync(async () =>
{
// Запускаем транзакцию
using (var trans =
await _ctx.Database.BeginTransactionAsync())
{
try
{
var bld = new Building()
{
Name = input.Name,
Address = input.Address
};

// Сохраняем Building
_ctx.Buildings.Add(bld);
await _ctx.SaveChangesAsync();

foreach (var unit in input.Units)
{
_ctx.Add(new Unit()
{
// Добавляем внешний ключ здания
BuildingId = bld.Id,
Number = unit.Number,
Type = unit.Type,
Floor = unit.Floor
});
}

await _ctx.SaveChangesAsync();
await trans.CommitAsync();
return true;
}
catch (Exception ex)
{
// Откатываем, транзакцию при ошибке
await trans.RollbackAsync();
// Выбрасываем исключение,
// чтобы стратегия его обработала
throw;
}
}
}
});
}

Весь блок работает как транзакция. При временном сбое стратегия выполнения повторяет всю операцию в ExecuteAsync.

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

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

Источник: https://blog.elmah.io/3-essential-techniques-for-managing-transactions-in-ef-core/
День 2157. #ЧтоНовенького
Используем Testing.Platform в .NET 9

.NET 9.0 представляет новую платформу тестирования Testing.Platform. Сегодня рассмотрим, что это и как её использовать.

Testing.Platform, как предполагается, станет заменой VSTest в качестве основной платформы для запуска тестов. С появлением .NET Core VSTest был скорректирован только для того, чтобы «заставить его работать», но не получил надлежащего редизайна с нуля. Теперь это изменилось, и команды Microsoft перестраивают среду тестирования для достижения следующих целей в будущем:
- Переносимость. Тесты должны выполняться везде одинаково, будь то разные компьютеры, агенты CI или в облаке.
- Поддержка NativeAOT-компиляции.
- Производительность. В Microsoft утверждают, что видят 30% увеличение производительности только за счёт миграции на Testing.Platform.
- Безопасность. Особенно когда речь идет о внешних зависимостях, которые должны быть сведены к минимуму.

Testing.Platform — это консольное приложение. Основная идея – всё, что вы можете сделать с консольным приложением, вы можете сделать с выводом Testing.Platform: запускать, отлаживать, даже использовать HotReload и видеть вывод тестов во время его работы. В то же время сохраняется обратная совместимость, так что можно запускать dotnet test для выполнения тестов, даже если вы уже перешли на Testing.Platform.

Перенос тестов XUnit на Testing.Platform
Допустим, у вас есть тесты XUnit в приложении. Типичный файл проекта csproj для этого будет выглядеть примерно так:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>

<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
<PackageReference Include="xunit" Version="2.5.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>

</ItemGroup>

<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
</Project>

Чтобы использовать Testing.Platform, изменим файл:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>

<IsTestProject>true</IsTestProject>
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
</PropertyGroup>

</Project>

По сути, нужно:
- Обновить версию платформы на net9.0
- Добавить декларацию UseMicrosoftTestingPlatformRunner
- Использовать недавно вышедший XUnit v3

Теперь, если вы выполните dotnet run в папке проекта, вы увидите что-то вроде следующего:
xUnit.net v3 Microsoft.Testing.Platform Runner v1.0.0 (64-bit .NET 9.0.0)

Test run summary: Passed! - bin\Debug\net9.0\XUnit.Tests.dll (net9.0|x64)
total: 2
failed: 0
succeeded: 2
skipped: 0
duration: 276ms

Тот же вывод вы увидите, выполнив dotnet test, но это может занять больше времени. Вот и весь переход.

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

Visual Studio, и Rider уже поддерживают запуск тестов на Testing.Platform в своих последних версиях.

Источник:
https://dateo-software.de/blog/testing-platform
Practical debugging for dotnet developers eBook.pdf
34.2 MB
День 2158. #Книги
Практика Отладки для .NET-разработчиков

Даже с внедрением ИИ в процесс разработки программного обеспечения отладка остается важной частью нашей работы. Умение эффективно исследовать сложные проблемы и быстро находить решения — важный навык, которым нужно овладеть. Хорошая новость в том, что этому навыку можно научиться. Помочь вам в этом может книга «Practical Debugging for .NET Developers» («Практика Отладки для .NET-разработчиков») Майкла Шпильта, которая теперь доступна бесплатно!

В книге рассматриваются следующие темы:
- Расширенные методы отладки с Visual Studio
- .NET Core и .NET Framework на Windows, Linux и Mac
- Проблемы с производительностью
- Утечки памяти и проблемы с нехваткой памяти
- Низкая производительность ASP.NET и неудачные запросы
- Отладка стороннего кода
- Отладка производственного кода в облаке
- Сбои и зависания

Вот фрагмент из введения:
«Лучшие инженеры-программисты, которых я знаю, отлично справляются с отладкой. Они находят решения сложных проблем там, где никто другой не может. Они достигают этого, используя правильные инструменты, зная, что искать, и имея глубокое понимание как своей собственной области, так и экосистемы .NET. Более того, они проводят систематическое расследование, используя проверенные методологии отладки. Отладка — это не искусство, это то, чему можно научить, и эта книга делает именно это. Эта книга — о совершенствовании ваших навыков отладки, экономии вашего времени и повышении вашей производительности.»

Книга вышла в 2020м году, но большинство информации в ней до сих пор актуально. Неплохой подарок от Майкла на новогодние праздники.

Источник: https://bartwullems.blogspot.com/2024/12/free-ebook-practical-debugging-for-net.html
День 2159. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 37. Не ждите, что без обучения и освоения передовых практик продуктивность повысится как по волшебству. Начало

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

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

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

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

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

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

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

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

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

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

Урок 37. Не ждите, что без обучения и освоения передовых практик продуктивность повысится как по волшебству. Окончание

Начало

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

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

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

В многочисленных публикациях по разработке ПО отмечается десятикратный и более разброс в продуктивности между лучшими и худшими исполнителями. Однако в недавнем отчёте Билла Николса (Bill Nichols) из Software Engineering Institute утверждается, что это миф, и индивидуальные особенности могут увеличить продуктивность разработчиков максимум в два раза в любой конкретной деятельности.

Нет сомнений, что квалифицированные и талантливые люди и команды работают более продуктивно. Неудивительно, что, согласно отчету The Standish Group (2015), проекты, укомплектованные «одаренными» командами, использующими методы Agile-разработки, оказались более успешными, чем проекты, реализуемые низкоквалифицированными командами. При этом интересно отметить, что небольшие проекты имели более высокий уровень успеха, чем крупные. Возможно, это объясняется тем, что небольшую команду проще укомплектовать высококвалифицированными специалистами. Но не каждый может позволить себе нанимать лучших из лучших.

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 5.
День 2161. #ЗаметкиНаПолях
Планировка Фоновых Задач с Помощью Quartz. Начало

Большинству приложений ASP.NET Core необходимо обрабатывать фоновые задачи. Хотя существует множество способов их реализации, Quartz.NET выделяется своими надёжными возможностями планирования, настройкой хранения заданий и богатой функциональностью. Основы Quartz в этом посте. Здесь рассмотрим некоторые продвинутые настройки. Извините за многокода.

Настройка Телеметрии Quartz в ASP.NET Core
Сначала установим пакеты Quartz:
Install-Package Quartz.Extensions.Hosting
Install-Package Quartz.Serialization.Json

# Пока в предрелизе
Install-Package OpenTelemetry.Instrumentation.Quartz

Также потребуются некоторые пакеты OpenTelemetry.

Затем настроим сервисы Quartz и инструментарий OpenTelemetry:
builder.Services.AddQuartz();
builder.Services.AddQuartzHostedService(opts =>
{
opts.WaitForJobsToComplete = true;
});

builder.Services.AddOpenTelemetry()
.WithTracing(trc =>
{
trc
.AddHttpClientInstrumentation()
.AddAspNetCoreInstrumentation()
.AddQuartzInstrumentation();
})
.UseOtlpExporter();


Определение и планирование заданий
Все задания реализуют IJob и выполняются как сервисы с ограниченной областью действия, поэтому при необходимости можно внедрять зависимости. Quartz позволяет передавать данные заданию с помощью словаря JobDataMap. Рекомендуется использовать только примитивные типы для данных задания, чтобы избежать проблем с сериализацией и использовать MergedJobDataMap для извлечения данных задания.

Создадим задание для email-напоминания:
public class EmailJob( 
IEmailService emailSvc) : IJob
{
public const string Name = nameof(EmailJob);

public async Task Execute(
IJobExecutionContext ctx)
{
var data = ctx.MergedJobDataMap;

try
{
await emailSvc.SendAsync(
data.GetString("userId"),
data.GetString("message"));
}
catch (Exception ex)
{
// … обработка … и throw,
// чтобы Quartz попробовал снова
throw;
}
}
}

Замечание: JobDataMap не строго типизирован. Поэтому рекомендуется:
- Использовать константы для ключей;
- Проверять данные в методе Execute;
- Создавать сервисы-обёртки для планирования заданий.

Вот как запланировать одноразовые напоминания:
public record Reminder(
string UserId,
string Message,
DateTime ScheduleTime
);

app.MapPost("/api/reminders/schedule", async (
ISchedulerFactory schFactory,
Reminder rem) =>
{
var scheduler = await schFactory.GetScheduler();
var jobData = new JobDataMap
{
{ "userId", rem.UserId },
{ "message", rem.Message }
};

var group = "email-reminders";

var job = JobBuilder.Create<EmailJob>()
.WithIdentity(
$"reminder-{Guid.NewGuid()}", group)
.SetJobData(jobData)
.Build();

var trigger = TriggerBuilder.Create()
.WithIdentity(
$"trigger-{Guid.NewGuid()}", group)
.StartAt(rem.ScheduleTime)
.Build();

await scheduler.ScheduleJob(job, trigger);

return Results.Ok(new
{
scheduled = true,
at = rem.ScheduleTime
});
})
.WithName("ScheduleReminder")
.WithOpenApi();

Конечная точка планирует одноразовые напоминания по email с помощью Quartz. Она создаёт задание с данными пользователя, устанавливает триггер на указанное время и планирует их. EmailJob получает уникальный идентификатор в группе "email-reminders".
Пример запроса:
POST /api/reminders/schedule
{
"userId": "user42",
"message": "Meeting!",
"scheduleTime": "2024-12-31T23:00:00"
}

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

Источник:
https://www.milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-in-dotnet-advanced-concepts
День 2162. #ЗаметкиНаПолях
Планировка Фоновых Задач с Помощью Quartz. Окончание

Начало

Планирование повторяющихся заданий
Для повторяющихся фоновых заданий можно использовать расписания cron:
public record RecurringReminder(
string UserId,
string Message,
string CronExpression
);

app.MapPost("/api/recurring/schedule", async (
ISchedulerFactory schFactory,
RecurringReminder rem) =>
{
var scheduler = await schFactory.GetScheduler();
var jobData = new JobDataMap
{
{ "userId", rem.UserId },
{ "message", rem.Message }
};

var group = "recurring-reminders";

var job = JobBuilder.Create<EmailJob>()
.WithIdentity(
$"recurring-{Guid.NewGuid()}", group)
.SetJobData(jobData)
.Build();

var trigger = TriggerBuilder.Create()
.WithIdentity(
$"rectrigger-{Guid.NewGuid()}", group)
.WithCronSchedule(rem.CronExpression)
.Build();

await scheduler.ScheduleJob(job, trigger);

return Results.Ok(new {
scheduled = true,
cron = rem.CronExpression });
})
.WithName("RecurringReminder")
.WithOpenApi();

Триггеры Cron более эффективны, чем простые. Они позволяют определять сложные расписания, например «каждый будний день в 10 утра»:
POST /api/recurring/schedule
{
"userId": "user69",
"message": "Daily",
"cronExpression": "0 0 10 ? * MON-FRI"
}


Настройка хранения заданий
По умолчанию Quartz использует хранилище в памяти, т.е. задания теряются при перезапуске приложения. Для постоянного хранения поддерживается несколько провайдеров БД. Вот как настроить постоянное хранилище с надлежащей изоляцией схемы:
builder.Services.AddQuartz(opts =>
{
opts.AddJob<EmailJob>(c => c
.StoreDurably()
.WithIdentity(EmailJob.Name));

opts.UsePersistentStore(o =>
{
o.UsePostgres(cfg =>
{
cfg.ConnectionString = "…";
cfg.TablePrefix = "scheduler.qrtz_";
},
dataSourceName: "reminders"); // Имя БД

o.UseNewtonsoftJsonSerializer();
o.UseProperties = true;
});
});

Замечания:
- TablePrefix помогает организовать таблицы Quartz - в данном случае, помещая их в отдельную схему scheduler;
- Нужно будет запустить соответствующие скрипты БД для создания этих таблиц. Каждый поставщик БД имеет свои скрипты настройки.

Долгосрочные Задания
Мы настроили EmailJob с StoreDurably. Это позволяет определить задание 1 раз и повторно использовать его с разными триггерами:
public async Task ScheduleReminder(
string userId, string msg, DateTime dt)
{
var scheduler = await schFactory.GetScheduler();

// Извлекаем задание по его имени
var jobKey = new JobKey(EmailJob.Name);

var trigger = TriggerBuilder.Create()
// Используем задание по ссылке
.ForJob(jobKey)
.WithIdentity($"trigger-{Guid.NewGuid()}")
.UsingJobData("userId", userId)
.UsingJobData("message", msg)
.StartAt(dt)
.Build();
// Передаём только триггер
await scheduler.ScheduleJob(trigger);
}

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

Источник: https://www.milanjovanovic.tech/blog/scheduling-background-jobs-with-quartz-in-dotnet-advanced-concepts
День 2163.
Дорогие подписчики, с новым 2025м годом!


.NET появился в начале века,
Стал любимой платформой для каждого человека.
От Windows Forms до ASP.NET,
Он покорил разработчиков свет.

C# и VB.NET были языки его,
На них кодить просто и легко.
WPF и WCF — его дети,
Служили верой и правдой всем на свете.

С каждым годом он рос и крепчал,
Новые фичи в себя включал.
.NET Core пришел на смену,
Кроссплатформенность — его арена.

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

(с) Copilot, естественно. Я бы так не написал 😄
День 2164. #Оффтоп
Никому Больше не Нравится Название .NET Core


Автор оригинала: Стивен Клири

Я подумал, что будет неплохо написать об истории терминологии «.NET Core». Вот каким я запомнил термин «.NET Core». Это не гарантирует правильности и, конечно, не гарантирует полноты! Я что-то упустил? Не стесняйтесь сообщить в комментариях!

.NET Compact Framework
Я думаю, что впервые услышал термин «.NET Core» в отношении .NET Compact Framework. Это было давно, когда .NET Framework 3.5 был новинкой. Точнее это был «.NET Framework 3.5 SP1» — Service Pack 1. Да, и у .NET раньше были пакеты обновлений.

Я не уверен, является ли «.NET Core» правильным термином для обозначения NetCF или нет. Насколько мне известно, это был неофициальный термин, который был применён, поскольку .NET Compact Framework содержал небольшое подмножество огромного .NET Framework, предназначенное для работы на встраиваемых устройствах. Следовательно, это был «основной» (core), минимальный, фреймворк.

Silverlight, WinRT, Windows Phone, Windows Store и Universal Windows Platform
Прошу прощенья у всех, кого передёрнуло от одного прочтения названия этого раздела…

Все эти технологии имели какое-то отношение к «.NET Core» как к термину. Все они были минимальными подфреймворками полной .NET Framework. И по крайней мере некоторые из них имели схожую терминологию для своих сред выполнения (например, Silverlight работал на «Core CLR»). Мой мозг милостиво забыл грязные подробности. Я только смутно помню, что платформа Windows Phone отличалась от платформы для приложений в Windows Store, а Silverlight стал ещё более урезанным для работы на Windows Phone.

Это было не самое весёлое время для разработчика библиотек.

netcore как моникер целевой платформы
Моникеры целевой платформы (Target framework moniker - TFM) очень важны для разработчиков библиотек.

Термин «.NET Core» официально вошёл в пространство NuGet как TFM, но не для того, что мы называем .NET Core сегодня. Он был для приложений Windows Store (и приложений WinRT) и ненадолго распространился на приложения UWP.

Да, проекты Windows Store ещё в Windows 8.0 (а также WinRT) использовали идентификатор TFM netcore45. Windows 8.1 использовал netcore451. Когда приложения Windows Store были заменены приложениями универсальной платформы Windows (UWP), они недолго использовали netcore50, прежде чем перейти на uap10.0.
Поэтому, если (современной) команде .NET Core вдруг понадобится TFM, они обнаружат, что netcoreNN недоступен. Я предполагаю, что именно поэтому был выбран моникер netcoreappNN (после краткого использования на dnxcore50 и aspnetcore50). Так что .NET Core TFM для старых версий (1.0-3.1) – это с netcoreapp1.0 до netcoreapp3.1. Это была просто досадная историческая случайность.

Больше никакого .NET Core
Команда .NET отказалась от «Core». Теперь у нас единый .NET — большое облегчение для авторов библиотек!

С версии 5 даже TFM изменились: современный .NET использует net9.0 и подобные, опустив прозвище «core» даже из TFM.

Я все ещё время от времени называю .NET как «.NET Core», особенно в разговорах, где упоминаются как .NET Framework, так и нового .NET. Например, я буду говорить «миграция на .NET Core», а не просто «миграция на .NET» для проекта, который в настоящее время нацелен на .NET Framework. Однако правильный термин больше не «.NET Core», а просто «.NET».

Источник: https://blog.stephencleary.com/2024/12/netcore.html
День 2165. #Оффтоп #AI
ИИ не Заменит Команду Инженеров. Начало
Если позволите, я тоже немного отдохну. Поэтому вот вам лёгкий лонгрид на праздники.

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

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

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

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

ПО — индустрия обучения. Вы не можете научиться быть инженером-программистом, читая книги. Вы можете учиться только делая… и делая, и ещё делая. Неважно, какое у вас образование, большая часть обучения происходит на работе… и никогда не заканчивается!

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

Что значит быть «сеньором»?
Об этом есть целая серия постов на канале. Ищите по тэгу #КакСтатьСеньором

По поводу сроков есть очень много споров:
«7 лет?! Пфф, мне потребовалось 2 года!»
«Меня повысили до сеньора меньше, чем за 5 лет!»

Молодцы! Да, в 7 годах нет ничего волшебного. Но для того, чтобы стать опытным инженером, способным сплотить команду, требуются время и опыт. Более того, нужна практика.

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

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

Что подводит нас к вопросу об ИИ.

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

Источник:
https://stackoverflow.blog/2024/12/31/generative-ai-is-not-going-to-build-your-engineering-team-for-you
День 2166. #Оффтоп #AI
ИИ не Заменит Команду Инженеров. Продолжение

1. ПО - индустрия обучения

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

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

Писать код легко
Написание кода — самая лёгкая часть разработки ПО, и с каждым днём становится проще. Сложная часть — это эксплуатация, понимание, расширение и управление им на протяжении всего его жизненного цикла.

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

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

Текущая волна инструментов ИИ сделала многое, чтобы помочь нам быстро генерировать много кода. Простые части становятся ещё проще. Но она не сделала ничего, чтобы помочь в работе по управлению, пониманию или эксплуатации этого кода. Т.е. ИИ только усложнил сложную работу.

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

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

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

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

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

Источник:
https://stackoverflow.blog/2024/12/31/generative-ai-is-not-going-to-build-your-engineering-team-for-you
День 2167. #Оффтоп #AI
ИИ не Заменит Команду Инженеров. Продолжение

1. ПО – индустрия обучения
2. Просто писать код, сложно писать хороший код

Как инженеры на самом деле используют ИИ
Вы можете сгенерировать много кода очень быстро, но вы не можете доверять тому, что получится. Однако есть некоторые примеры использования, в которых ИИ неизменно блистает:
1. Часто проще попросить ChatGPT сгенерировать пример кода с использованием незнакомых API, чем читать документацию по API.
2. Создание кода, который раздражает или утомителен для написания, но при этом имеет узкую область действия и легко объясняется. Чем более предсказуем сценарий, тем лучше эти инструменты пишут код за вас.
3. Написание небольших функций, чтобы делать что-то на незнакомых языках или в незнакомых сценариях. Если у вас есть фрагмент кода Python и вы хотите то же самое на Java, но вы не знаете Java, ИИ поможет.

Но, помните: вероятность того, что результат полностью выдуман, составляет 50/50. Всегда придется предполагать, что результаты неверны, пока вы не проверите их вручную.

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

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

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

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

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

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

Каждому нанятому инженеру требуется время на разгон, прежде чем он сможет внести свой вклад, независимо от его уровня. Сколько времени? Зависит от чистоты и организованности кодовой базы, прошлого опыта работы с вашими инструментами и технологиями и многого другого, но, скорее всего, 6-9 месяцев. Да, разгон будет дольше для джуна, и это потребует больше инвестиций от команды. Но не критически больше. Джун должен начать приносить пользу в течение примерно того же периода времени, и джуны развиваются гораздо быстрее, чем более опытные работники.

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

Источник:
https://stackoverflow.blog/2024/12/31/generative-ai-is-not-going-to-build-your-engineering-team-for-you
День 2168. #Оффтоп #AI
ИИ не Заменит Команду Инженеров. Продолжение

1. ПО - индустрия обучения
2. Просто писать код, сложно писать хороший код
3. ИИ похож на джуна

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

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

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

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

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

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

Здоровая команда включает людей разных уровней
Вы не будете укомплектовывать команду по разработке продукта шестью экспертами по БД и одним мобильным разработчиком. Также нельзя включать в неё 6 сеньоров и 1 джуна. Существует не так много высокоуровневой архитектуры и планирования, не так много важных решений, которые нужно принять. Сеньоры будут проводить большую часть времени, выполняя работу, которая кажется им скучной и повторяющейся, поэтому будут оверинжинирить решения и/или экономить на них — иногда в одно и то же время. Они будут хронически недодокументировать и недоинвестировать в работу, которая делает системы простыми и управляемыми.

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

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

Источник:
https://stackoverflow.blog/2024/12/31/generative-ai-is-not-going-to-build-your-engineering-team-for-you