TG Telegram Group Link
Channel: .NET Разработчик
Back to Bottom
День 2272. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 49. Разработчики программного обеспечения любят инструменты, но дурак с инструментами — это вооруженный дурак


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

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

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

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

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

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

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

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

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

Его можно применить к параметру в сигнатуре метода, чтобы его значением во время выполнения было имя вызывающего метода:
public void SayMyName(
[CallerMemberName] string? methodName = null) =>
Console.WriteLine($"Моё имя {methodName ?? "NULL"}!");

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

1. Прямой вызов
public void DirectCall()
{
Console.WriteLine("Прямой вызов:");
SayMyName();
}

Вывод:
Прямой вызов:
Моё имя DirectCall!

Мы не указываем значение параметра methodName в SayMyName, поэтому по умолчанию используется имя вызывающего метода: DirectCall.

2. Явная передача параметра
public void DirectCallWithName()
{
Console.WriteLine("Прямой вызов с параметром:");
SayMyName("Уолтер Уайт");
}

Вывод:
Прямой вызов с параметром:
Моё имя Уолтер Уайт!

Важно отметить, что компилятор устанавливает значение methodName, только если оно не обозначено явно. При этом явная передача null
SayMyName(null);

приведёт к тому, что значение methodName будет null.

3. Вызов через Action
public void CallViaAction()
{
Console.WriteLine("Вызов через Action:");
Action<int> action = (_) => SayMyName();

var calls = new List<int> { 1 };
calls.ForEach(s => action(s));
}

Вывод:
Вызов через Action:
Моё имя CallViaAction!

Здесь интереснее: атрибут CallerMemberName распознаёт имя метода, содержащего общее выражение. Здесь синтаксически вызывающим является метод ForEach, но он игнорируется, поскольку фактический вызов происходит в методе CallViaAction. Во время компиляции, поскольку методу SayMyName не передаётся значение, оно автоматически заполняется именем родительского метода. Затем метод ForEach вызывает SayMyName, но methodName уже определён во время компиляции.

4. Лямбда-выражения
То же происходит и при использовании лямбда-выражений:
public void CallViaLambda()
{
Console.WriteLine("Вызов через лямбду:");

void lambdaCall() => SayMyName();
lambdaCall();
}

Вывод:
Вызов через лямбду:
Моё имя CallViaLambda!


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

Источник:
https://www.code4it.dev/csharptips/callermembername-attribute/
День 2274. #ЗаметкиНаПолях
Подробный Обзор CallerMemberName. Окончание
Начало

5. Тип dynamic
Что, если мы вызовем метод SayMyName на экземпляре типа dynamic? Допустим класс CallerMemberNameTests содержит метод SayMyName (определённый в предыдущем посте):
public void CallViaDynamic()
{
Console.WriteLine("Вызов через dynamic:");

dynamic obj = new CallerMemberNameTests();
obj.SayMyName();
}

В этом случае атрибут не работает, и мы получим NULL:
Вызов через dynamic:
Моё имя NULL!

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

6. Обработчики событий
Мы определяем события, но их обработчики выполняются не напрямую:
public void CallViaEvent()
{
Console.WriteLine("Вызов через события:");
var source = new MyEventClass();
source.MyEvent += (sender, e) => SayMyName();
source.TriggerEvent();
}

public class MyEventClass
{
public event EventHandler MyEvent;
public void TriggerEvent() =>
MyEvent?.Invoke(this, EventArgs.Empty);
}

Вывод:
Вызов через события:
Моё имя CallViaEventHandler!

Опять же, всё сводится к тому, как метод генерируется во время компиляции: даже если фактическое выполнение не прямое, во время компиляции параметр инициализируется методом CallViaEvent.

7. Конструктор класса
Наконец, что происходит при вызове из конструктора?
public CallerMemberNameTests()
{
Console.WriteLine("Вызов из конструктора:");
SayMyName();
}

Мы можем рассматривать конструкторы как особый вид методов, но какое у него имя?
Вызов из конструктора 
Моё имя .ctor!

Фактическое имя метода — .ctor! Независимо от имени класса, конструктор считается методом с этим внутренним именем.

Источник: https://www.code4it.dev/csharptips/callermembername-attribute/
День 2275. #SystemDesign101
Как Улучшить Производительность API?


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

2. Асинхронное ведение журнала
Синхронное логирование пишет на диск при каждом вызове и может замедлить работу системы. Асинхронное логирование сначала отправляет логи в буфер без блокировки и немедленно возвращает управление. Логи будут периодически сбрасываться на диск. Это значительно снижает накладные расходы на ввод-вывод.

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

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

5. Пул соединений
При доступе к ресурсам нам требуется загружать данные из БД. Открытие и закрытие соединений с БД добавляет значительные накладные расходы. Поэтому мы должны подключаться к базе через открытых соединений. Пул отвечает за управление жизненным циклом соединения.

Источник: https://github.com/ByteByteGoHq/system-design-101
День 2276. #ЗаметкиНаПолях
Разбираем Курсорную Пагинацию. Начало
Пагинация (разбиение на страницы) имеет решающее значение для эффективной доставки больших наборов данных. Хотя офсетная пагинация (offset pagination) широко используется, курсорная пагинация (cursor pagination) предлагает некоторые интересные преимущества для определённых сценариев. Она особенно ценна для каналов в реальном времени, интерфейсов бесконечной прокрутки и API, где производительность имеет значение в масштабе, например, для журналов активности или потоков событий, где пользователи часто просматривают большие наборы данных. Рассмотрим детали реализации и обсудим, где каждый подход имеет наибольший смысл.

Создадим простое хранилище записей пользователя с помощью следующего объекта:
public record UserNote (
Guid Id,
Guid UserId,
string? Note,
DateOnly Date
);


Традиционный подход: Офсетная пагинация
Офсетная пагинация использует Skip и Take. Мы пропускаем определённое количество строк и берём фиксированное количество строк. Обычно они преобразуются в OFFSET и LIMIT в запросах SQL:
var query = dbContext.UserNotes
.OrderByDescending(x => x.Date)
.ThenByDescending(x => x.Id);

// При офсетной пагинации мы обычно сначала считаем общее количество элементов
var total = await query
.CountAsync(cancellationToken);
var pages = (int)Math.Ceiling(total / (double)pageSize);

var query = dbContext.UserNotes
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);

Заметьте, что мы сортируем результаты по дате и Id в обратном порядке. Это обеспечивает единообразие результатов при разбиении на страницы. Вот сгенерированный SQL (в PostgreSql) для офсетной пагинации:
// запрос общего количества
SELECT count(*)::int FROM user_notes AS u;

// запрос данных
SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
ORDER BY u.date DESC, u.id DESC
LIMIT @pageSize OFFSET @offset;


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

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

Источник:
https://www.milanjovanovic.tech/blog/understanding-cursor-pagination-and-why-its-so-fast-deep-dive
День 2277. #ЗаметкиНаПолях
Разбираем Курсорную Пагинацию. Продолжение

Начало

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

Используем поля Date и Id для создания курсора для нашей таблицы UserNotes. Курсор представляет собой композицию этих двух полей, что позволяет нам эффективно выполнять пагинацию:
var query = dbContext.UserNotes.AsQueryable();

if (date != null && lastId != null)
{
// Используем курсор для извлечения части результатов
// При прямой сортировке нужно заменить > на <
query = query
.Where(x => x.Date < date ||
(x.Date == date && x.Id <= lastId));
}

// Извлекаем элементы на 1 больше
var items = await query
.OrderByDescending(x => x.Date)
.ThenByDescending(x => x.Id)
.Take(limit + 1)
.ToListAsync(cancellationToken);

// Определяем данные для следующей страницы
var hasMore = items.Count > limit;
DateOnly? nextDate =
hasMore ? items[^1].Date : null;
Guid? nextId =
hasMore ? items[^1].Id : null;

// Удаляем «лишний» результат, если он есть
if (hasMore)
items.RemoveAt(items.Count - 1);

Порядок сортировки такой же, как в примере офсетной пагинации. Однако порядок сортировки имеет решающее значение для согласованных результатов при курсорной пагинации. Поскольку Date не является уникальным значением в нашей таблице, мы используем поле Id для обработки границ страниц. Это гарантирует, что мы не пропустим и не продублируем элементы при пагинации. Вот сгенерированный SQL (PostgreSql) для курсорной пагинации:
SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
WHERE u.date < @date OR (u.date = @date AND u.id <= @lastId)
ORDER BY u.date DESC, u.id DESC
LIMIT @limit;

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

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

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

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

Источник:
https://www.milanjovanovic.tech/blog/understanding-cursor-pagination-and-why-its-so-fast-deep-dive
День 2278. #ЗаметкиНаПолях
Разбираем Курсорную Пагинацию. Окончание

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

Улучшаем курсорную пагинацию
Для ускорения пагинации можно добавить индекс:
CREATE INDEX idx_user_notes_date_id ON user_notes (date DESC, id DESC);

Индекс создаётся в обратном порядке, чтобы соответствовать порядку в запросах. Однако, если выполнить план запроса, мы увидим, что индекс используется, но запрос может выполняться даже дольше, чем без него:
EXPLAIN ANALYZE SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
WHERE u.date < @date OR (u.date = @date AND u.id <= @lastId)
ORDER BY u.date DESC, u.id DESC
LIMIT 1000;

Возможно, размер данных слишком мал, чтобы получить преимущество от индекса. Но мы можем использовать один трюк – сравнение кортежей:
SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
WHERE (u.date, u.id) <= (@date, @lastId)
ORDER BY u.date DESC, u.id DESC
LIMIT 1000;

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

У провайдера EF для Postgres есть метод EF.Functions.LessThanOrEqual, который принимает ValueTuple в качестве аргумента. Мы можем использовать его для создания сравнения кортежей:
query = query.Where(x => EF.Functions.LessThanOrEqual(
ValueTuple.Create(x.Date, x.Id),
ValueTuple.Create(date, lastId)));


Кодирование курсора
Мы можем закодировать курсор, используемый для извлечения следующего набора результатов. Клиенты получат курсор в виде строки Base64. Им не нужно знать внутреннюю структуру курсора:
using System.Buffers.Text;
using System.Text;
using System.Text.Json;

public record Cursor(DateOnly Date, Guid LastId)
{
public string Encode() =>
Base64Url.EncodeToString(
Encoding.UTF8.GetBytes(
JsonSerializer.Serialize(this)));

public static Cursor? Decode(string? cursor)
{
if (string.IsNullOrWhiteSpace(cursor))
return null;

try
{
return JsonSerializer.Deserialize<Cursor>(
Encoding.UTF8.GetString(
Base64Url.DecodeFromChars(cursor)));
}
catch
{
return null;
}
}
}

Вот пример использования:
var cursor = new Cursor(new DateOnly(2025, 4, 26),
Guid.Parse("019500f9-8b41-74cf-ab12-25a48d4d4ab4"));

var encoded = cursor.Encode();
// eyJEYXRlIjoiMjAyNS0wMi0xNSIsIkxhc3RJZCI6IjAxOTUwMGY5LThiNDEtNzRjZi1hYjEyLTI1YTQ4ZDRkNGFiNCJ9

var decoded = Cursor.Decode(encoded);


Источник: https://www.milanjovanovic.tech/blog/understanding-cursor-pagination-and-why-its-so-fast-deep-dive
День 2279. #УрокиРазработки
Уроки 50 Лет Разработки ПО

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


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

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

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

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

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

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 6.
День 2280. #TipsAndTricks
Удаляем Пустые Папки в PowerShell
Вот простой скрипт в PowerShell, который рекурсивно удаляет пустые папки:
$rootFolder = 'C:\Temp'
Get-ChildItem $rootFolder -Recurse -Directory -Force |
Sort-Object -Property FullName -Descending |
Where-Object { $($_ | Get-ChildItem -Force | Select-Object -First 1).Count -eq 0 } |
Remove-Item


Логика следующая:
1. Получить все каталоги рекурсивно, использовать -Force для получения скрытых папок;
2. Сортировать их в порядке убывания, так как мы хотим сначала удалить самые глубокие папки;
3. Проверить, пуста ли папка;
4. Удалить папку.

Источник: https://www.meziantou.net/remove-empty-folders-using-powershell.htm
День 2281. #SystemDesign101
Версии Протоколов HTTP


HTTP 1.0 был окончательно доработан и полностью задокументирован в 1996 году. Каждый запрос к одному и тому же серверу требует отдельного TCP-соединения.

HTTP 1.1 был опубликован в 1997 году. TCP-соединение можно оставлять открытым для повторного использования (постоянное соединение), но это не решало проблемы HOL-блокировки* (head-of-line).
*HOL-блокировка возникает, когда количество разрешённых параллельных запросов в браузере исчерпано, последующие запросы должны ждать завершения предыдущих.
Также введены:
- Статус продолжения: чтобы избежать отклонения запроса сервером, клиент может сначала отправить только заголовки запроса и проверить, получит ли он код статуса продолжения (100);
- Новые HTTP-методы: PUT, PATCH, DELETE, CONNECT, TRACE и OPTIONS

HTTP 2.0 был опубликован в 2015 году. Он решает проблему HOL с помощью мультиплексирования запросов, что устраняет HOL-блокировки на уровне приложений, но они всё ещё существуют на транспортном (TCP) уровне.
Как вы можете видеть на схеме, HTTP 2.0 представил концепцию «потоков» HTTP: абстракцию, которая позволяет мультиплексировать различные обмены HTTP в одно и то же TCP-соединение. Каждый поток не обязательно должен быть отправлен по порядку.
Также добавлены:
- Приоритетность запросов: возможность установить числовой приоритет в пакете запросов, например, получить CSS перед JS.
- Автоматическое сжатие GZip.
- Серверный push: сервер пытается предсказать ресурсы, которые будут запрошены в ближайшее время и проактивно отправляет эти ресурсы в кэш клиента.

Первый черновик HTTP 3.0 был опубликован в 2020 году. Он использует QUIC вместо TCP для базового транспортного протокола, тем самым устраняя HOL-блокировку на транспортном уровне.
QUIC (Quick UDP Internet Connections) разработан для обеспечения гораздо меньшей задержки для HTTP-соединений. Как и HTTP/2, это мультиплексный протокол, но HTTP/2 работает по одному TCP-соединению, поэтому обнаружение потери пакетов и повторная передача, обрабатываемые на уровне TCP, могут блокировать все потоки. QUIC запускает несколько потоков по UDP и реализует обнаружение потери пакетов и повторную передачу независимо для каждого потока, так что в случае возникновения ошибки блокируется только поток с данными в этом пакете.
По состоянию на октябрь 2022 около 26% сайтов используют HTTP 3.

Источник: https://github.com/ByteByteGoHq/system-design-101
День 2282. #Testing
Тестирование Характеристик
Большинство типов тестирования проверяют правильность. Мы тестируем код, чтобы увидеть, что он делает то, что мы хотим, чтобы он делал. Это предполагает, что мы знаем, что он должен делать. А что, если мы этого не знаем?

К сожалению, это бывает часто. Нужно вносить изменения в код, но мы недостаточно знаем о том, что он делает. Вот небольшой пример:
public class Parser
{
public static string FormatText(string text)
{
var result = new StringBuilder();
for (int n = 0; n < text.Length; ++n)
{
int c = text[n];
if (c == '<')
{
while (n < text.Length && text[n] != '/' && text[n] != '>')
n++;
if (n < text.Length && text[n] == '/')
n += 4;
else
n++;
}
if (n < text.Length)
result.Append(text[n]);
}
return result.ToString();
}
}

Что делает этот код? Кажется, удаляет HTML-теги из текста, но логика странная и, скорее всего, неправильная. Этот крошечный пример показывает, как сложно читать плохо написанный код. Мы можем использовать тестирование. Но вместо того, чтобы пытаться выяснить, является ли код правильным, мы можем попытаться охарактеризовать его поведение, чтобы понять, что он на самом деле делает. Начнем с простого теста. Создадим тест и назовём его «x». «X», потому что мы не знаем, что будет делать метод FormatText. И мы даже не зададим ожидаемого значения, т.к. на данный момент мы не знаем, каким будет поведение:
[TestMethod]
public void x()
{
Assert.AreEqual(null, Parser.FormatText("text"));
}

Тест упадёт, но мы хотя бы узнали, что метод выдаст в виде результата. Теперь мы можем поставить результат вместо ожидаемого значения и заставить тест проходить. А также зададим тесту имя, которое отражает наше понимание того, что делает код:
[TestMethod]
public void DoesNotChangePlainText()
{
Assert.AreEqual("text", Parser.FormatText("text"));
}

Мы не разобрались в коде, а пишем тесты. Какую ценность они могут иметь? Большую. Когда мы пишем тесты характеристик, мы накапливаем знания о том, что на самом деле делает код. Это особенно полезно, когда мы хотим его отрефакторить. Мы можем запустить наши тесты и сразу узнать, изменили ли мы поведение. Вот другой тест. Он проходит:
[TestMethod]
public void RemovesTextBetweenAngleBrackets()
{
Assert.AreEqual("", Parser.FormatText("<>"));
}

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

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

Мы можем рассматривать тесты характеристик как описания того, что у нас есть, а не как утверждения о правильности. Можно периодически пересматривать тесты, чтобы ужесточить их условия, когда мы решаем, каким должно быть поведение. Самое сложное — разорвать зависимости вокруг фрагмента кода, чтобы иметь возможность проверить его в тестовой среде. Как только вы это сделаете, останется только поинтересоваться, что будет делать код в определённых условиях и запустить тест, чтобы найти фактическое значение. Часто вы будете пересматривать названия тестов, поскольку будете больше понимать код, который проверяете. Начните с теста с именем «x».

Источник: https://michaelfeathers.silvrback.com/characterization-testing
День 2283. #УрокиРазработки
10 Уроков Качества Разработки ПО. Начало


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

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

Вот некоторые внутренние признаки качества:
- Читаемость
Что делает код и как он работает?
- Тестируемость
Делает ли он то, что должен?
- Изоляция
Могу ли я изменить это, не сломав что-то еще?
- Обнаруживаемость
Где в системе определено нужное поведение?
- Прослеживаемость
Почему было принято это решение (в коде, архитектуре или дизайне)?

2. Делайте код автоматически тестируемым
Знакомо ли вам то чувство, когда вы изменяете код в кодовой базе, с которой вы не на 100% знакомы, но вы полностью уверены, что автоматизированные тесты вас поддержат? Классные ощущения, правда? Теперь представьте себе похожую кодовую базу, но без такого тестового покрытия. Звучит пугающе.

А что, если вы знаете кодовую базу вдоль и поперёк? Достаточно ли вы уверены, чтобы вносить изменения, не рискуя получить катастрофическую ошибку?

Для тех, кто не чувствует себя уверенно, пришло время инвестировать в автоматизированное тестирование. Начните с создания временной страховочной сетки, используя стратегию тестирования характеристик. Затем начните проектировать код для поддержки подхода «сначала тест». Относитесь к своим тестам как к первоклассному коду. Кодовая база, где 40–50% кода является тестами – это здорово.

3. Не терпите нестабильности в автоматизированном тестировании
Каждый разработчик, который пытался создать набор автоматизированных end-to-end тестов, сталкивался с кошмаром их нестабильности. Да, вы можете гордиться тем, что у вас есть солидное количество тестов. Но если вы не можете положиться на эти тесты из-за их нестабильности, это может стать серьёзной головной болью. Определить, провалился ли тест из-за реальной проблемы в коде базе или просто из-за нестабильности, сложно, особенно когда сбой не связан с конкретным изменением кода.

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

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

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

Источник:
https://www.dennisdoomen.com/2025/03/10-quality-lessons.html
День 2284. #УрокиРазработки
10 Уроков Качества Разработки ПО. Продолжение

Начало

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

Вот несколько рекомендаций:
- Код должен читаться как книга — методы упорядочены по порядку выполнения, а не по порядку видимости.
- Весь код в методе или функции должен находиться на одном уровне абстракции.
- Методы должны стараться не выходить за рамки 15 строк кода.
- Имена методов должны быть функциональными и объяснять их назначение, а не реализацию.
- Документация кода должна описывать, почему существует метод, а не как он работает.
- Встроенные комментарии допустимы, если они предоставляют дополнительный контекст.
- return либо в самом начале, либо в самом конце, но не в середине.
- Избегайте булевых параметров, используйте перечисления.
- Не вводите абстракции только ради внедрения зависимостей.
- Группируйте код по функциональности, а не по технической структуре для большей ясности.
- Обеспечьте достаточное покрытие тестами: 80% — хорошо, 90% — отлично, 95% — слишком много.
- Не тестируйте слишком подробно; тестируйте внешний интерфейс, но не детали реализации.

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

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

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

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

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

Источник:
https://www.dennisdoomen.com/2025/03/10-quality-lessons.html
День 2285. #УрокиРазработки
10 Уроков Качества Разработки ПО. Окончание

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

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

Помимо этого, конвейер — это больше, чем просто ряд задач. Он развивается вместе с кодовой базой, может стать довольно сложным и, следовательно, должен быть простым для понимания и рефакторинга при необходимости. Слишком много компаний пытаются придерживаться конвейеров YAML, обходя ограничения, втискивая скрипты PowerShell или Bash вместе с файлами YAML — хотя есть гораздо лучшие альтернативы. Уже не говоря о необходимости перехода с одного движка сборки на другой, например, с Azure DevOps Pipeline на GitHub Actions. Можно использовать Nuke и C# Это обеспечивает доступ к возможностям навигации, рефакторинга и отладки и позволяет тестировать весь конвейер из локальной среды разработки.

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

9. Берегите историю версий
Представьте, что вы пытаетесь понять, почему кто-то увеличил таймаут для выполнения SQL-запроса до 60 секунд. Нет ничего более разочаровывающего, чем найти PR или описание коммита, в котором указано что-то вроде «Увеличен таймаут SQL». Такое описание сообщает, что было сделано, но не объясняет проблему, которую решал разработчик. Аналогично PR, содержащий один коммит, в котором смешан рефакторинг со значимыми изменениями, скорее всего, вызовет столько же разочарования.

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

10. Не бойтесь отклонять PR, если его слишком сложно просмотреть
Вас когда-нибудь просили просмотреть PR с более чем 100 файлами? Вы прокручиваете код, чувствуете себя подавленным и оставляете комментарий типа «вроде норм». Не бойтесь отклонять огромные PR. Качественный обзор кода становится невозможным при таком количестве изменений.

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

Помните, что меньше PR = лучше обзор = более качественный код.

См. также «Мастерство Пулл-Реквестов»

Источник:
https://www.dennisdoomen.com/2025/03/10-quality-lessons.html
День 2286. #ЧтоНовенького
Новинки GitHub Copilot в Visual Studio 17.14 Preview 3
Взаимодействие с GitHub Copilot в Visual Studio постоянно обновляется, чтобы предоставить вам последние достижения в разработке с использованием ИИ. Новые функции и улучшения были недавно выпущены в Visual Studio версии 17.14 Preview 3.

1. Пошаговое руководство для начала работы
Если вы новичок в GitHub Copilot и ищете быстрый, пошаговый способ начать работу, эта функция для вас. Откройте раскрывающейся список GitHub Copilot справа вверху и выберите GitHub Copilot Walkthrough (Пошаговое руководство GitHub Copilot). Руководство откроется в новой вкладке IDE.

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

2. Улучшенный доступ к моделям
Теперь стало проще, чем когда-либо, получить доступ к последним моделям, таким как Claude 3.7, из Visual Studio. Вместо необходимости настраивать параметры, теперь вы можете включить любую из доступных моделей с github.com напрямую через Visual Studio. Когда вы выбираете модель в Copilot Chat, вам будет предложено включить выбранную вами модель.

3. Сопоставление кода
То, как и куда Copilot вставляет свои предложения кода в ваш код, также получило обновление в этом выпуске с улучшениями того, что в Microsoft называют Code Mapping (Сопоставление кода).

4. Предложения следующего редактирования
Функция Next Edit Suggestions (Предложения следующего редактирования) помогает разработчикам, предоставляя контекстные предложения для редактирования последующего кода на основе предыдущих изменений. Вы нажимаете Tab, и курсор переходит в следующее место, где встречается подобный код.

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

Видео с демонстрацией новых возможностей тут.

Источник: https://devblogs.microsoft.com/visualstudio/github-copilot-highlights-in-visual-studio-17-14-preview-3-available-now/
День 2287. #ЗаметкиНаПолях
5 Основных Ошибок при Создании API. Начало

Создание API кажется простым… пока не придётся их поддерживать. Прекрасные API рушились просто из-за мелких решений, принятых на раннем этапе, — вещей, которых вы даже не замечаете, пока API не окажется под давлением реального мира. Разберём 5 самых распространённых ошибок при создании API и как вы можете избежать попадания в эти ловушки.

1. Плохая или отсутствующая проверка ввода
Ошибка: Вера в то, что клиенты всегда будут отправлять допустимые данные.
Реальность: Если вы не проверяете ввод должным образом, API становится уязвимым для ошибок, сбоев и даже угроз безопасности.
Плохо:
app.MapPost("/users", async (UserDto user) =>
{
// Подразумеваем, что имя и email
// всегда предоставляются…
var newUser = new User {
Name = user.Name,
Email = user.Email };
await db.Users.AddAsync(newUser);
await db.SaveChangesAsync();
return Results.Ok();
});

Замечание: не размещайте логику доступа к БД непосредственно в контроллерах (конечных точках).

Проблемы:
- Нет проверки на пустые поля.
- Нет проверки формата email.
- Не применяются бизнес-правила (например, минимальная/максимальная длина).

Лучше:
app.MapPost("/users", async (UserDto user) =>
{
if (string.IsNullOrWhiteSpace(user.Name) || string.IsNullOrWhiteSpace(user.Email))
return Results.BadRequest(
"Имя и email обязательны.");

if (!user.Email.Contains("@"))
return Results.BadRequest(
"Неверный формат email.");

var newUser = new User {
Name = user.Name.Trim(),
Email = user.Email.Trim() };

});

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

Почему важно:
- Защищает целостность БД.
- Упрощает отладку на стороне клиента («BadRequest» вместо «ошибки 500»).
- Уберегает бэкенд от загадочных ошибок в будущем.

2. Отсутствие версионирования API
Ошибка: Выпуск API без версий, потому что «добавим позже».
Реальность: Нужно будет изменить API. И когда вы это сделаете, вы пожалеете, что не запланировали версионирование.
Плохо:
app.MapGet("/products", () =>
{
// возврат продуктов
});

Проблема: Когда структура вашего ответа изменится, старые клиенты сломаются.

Лучше:
app.MapGroup("/api/v1")
.MapGet("/products", () =>
{
// возврат продуктов версии 1
});

app.MapGroup("/api/v2")
.MapGet("/products", () =>
{
// возврат продуктов версии 2
});

Или используйте .RequireHost() для разных поддоменов.

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

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

Источник:
https://thecodeman.net/posts/building-apis-top-5-mistakes
День 2288. #ЗаметкиНаПолях
5 Основных Ошибок при Создании API. Продолжение
Начало

3. Вводящие в заблуждение или неверные коды состояния
Ошибка: Возврат 200 OK для всего — даже когда что-то не так.
Реальность: Коды состояния HTTP существуют по определенной причине: для передачи намерения.
Плохо:
return Results.Ok("User not found.");

Проблема: Клиент видит 200 OK. Но пользователя не существует. Это сбивает с толку! Теперь клиенту нужно разобрать строку сообщения, чтобы понять, что не так. Это плохая практика.
Лучше:
var user = await db.Users.FindAsync(id);
if (user is null)
return Results.NotFound(
$"Пользователь с id={id} не найден.");

return Results.Ok(user);

Используйте:
- 400 BadRequest → Плохие входные данные
- 401 Unauthorized → Необходим вход в систему
- 403 Forbidden → Доступ запрещён (нет прав)
- 404 Not Found → Ресурс не найден
- 500 Internal Server Error → Что-то сломалось на сервере.

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

4. Лишние данные в моделях ответа
Ошибка: Возврат целых сущностей БД или гигантских вложенных моделей.
Реальность: Клиентам обычно нужен небольшой фрагмент данных, а не вся схема вашей БД.
Плохо:
app.MapGet("/orders", async (DbContext db) =>
{
var orders = await db.Orders.ToListAsync();
return Results.Ok(orders);
});

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

Лучше:
app.MapGet("/orders", async (DbContext db) =>
{
var orders = await db.Orders
.Select(o => new
{
o.Id,
o.CustomerName,
o.TotalAmount,
o.OrderDate
})
.ToListAsync();

return Results.Ok(orders);
});

Почему важно:
- Вы контролируете, какие именно данные покидают ваш сервер.
- Меньше и быстрее ответы = более довольные пользователи и лучше SEO.
- Меньше риска безопасности, если ваша модель изменится позже.

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

Источник:
https://thecodeman.net/posts/building-apis-top-5-mistakes
День 2289. #ЗаметкиНаПолях
5 Основных Ошибок при Создании API. Окончание

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

5. Отсутствие централизованной обработки ошибок
Ошибка: Хаотичное использование блоков try-catch по всему коду или, что ещё хуже оставление исключений необработанными.
Реальность: Нужно одно место для аккуратной обработки непредвиденных ошибок.
Плохо:
try
{
var user = await db.Users.FindAsync(id);
return Results.Ok(user);
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}

Проблемы:
- Дублирование кода.
- Несогласованные ответы с ошибками.
- Трудности с правильным логированием.

Лучше (использование промежуточного ПО обработки ошибок):
app.UseExceptionHandler(app =>
{
app.Run(async ctx =>
{
ctx.Response.StatusCode = 500;
ctx.Response.ContentType = "application/json";
await ctx.Response.WriteAsJsonAsync(new
{
Error = "Что-то пошло не так."
});
});
});

Ещё лучше – использование ProblemDetails (application/problem+json) в .NET 9 автоматически через ответ Problem().

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

Кроме того:
Не рекомендуется использовать исключения в качестве основного способа обработки ошибок. Вместо этого, если вы создаёте новые API сегодня, рассмотрите шаблон Result (например, Result, OneOf и т.д.). Так вы явно возвращаете результаты успеха/неудачи, вообще не полагаясь на исключения. Исключения должны использоваться для действительно неожиданных случаев, а не для обычных ошибок проверки. Тем не менее, если ваш проект (или ваша команда) уже использует исключения, лучшим решением будет централизовать их обработку.

Вот простая обработка ошибок в стиле Result:
public record Result<T>(
bool IsSuccess,
T? Value,
string? ErrorMessage
);

app.MapGet("/users/{id}",
async (Guid id, DbContext db) =>
{
var user = await db.Users.FindAsync(id);
if (user is null)
return Results.NotFound(
new Result<User>(
false,
null,
"Пользователь не найден"));

return Results.Ok(
new Result<User>(true, user, null));
});


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

Источник: https://thecodeman.net/posts/building-apis-top-5-mistakes
День 2290. #ЧтоНовенького
Поддержка Валидации в Минимальных API

В превью 3 .NET 10 доступна поддержка валидации в минимальных API. Эта функция позволяет вам требовать валидацию данных, отправляемых на конечные точки API. Когда валидация включена, среда выполнения ASP.NET Core выполнит любые проверки, определённые для параметров запроса, заголовка и маршрута, а также для тела запроса. Валидации можно определить с помощью атрибутов из пространства имён System.ComponentModel.DataAnnotations.

Разработчики также могут добавлять кастомную валидацию с помощью:
- создания пользовательских реализаций ValidationAttribute (для валидации отдельных свойств);
- реализации интерфейса IValidatableObject для сложной логики проверки (задействующей несколько свойств модели).
Если валидация не проходит, среда выполнения возвращает ответ 400 Bad Request с подробностями ошибок валидации.

Чтобы включить встроенную поддержку валидации для минимальных API, вызовите метод расширения AddValidation, чтобы зарегистрировать требуемые сервисы в контейнере зависимостей приложения:
builder.Services.AddValidation();

Вам также нужно установить свойство InterceptorsNamespaces в файле проекта следующим образом:
<PropertyGroup>
<!-- Включаем генерацию перехватчиков для атрибутов валидации -->
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Validation.Generated</InterceptorsNamespaces>
</PropertyGroup>

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

Валидация может быть отключена для определённых конечных точек с помощью метода расширения DisableValidation:
app.MapPost("/products",
([EvenNumber(ErrorMessage = "ID должен быть чётным")]
int productId,
[Required] string name)
=> TypedResults.Ok(productId))
.DisableValidation();


Атрибуты валидации могут добавляться как к отдельным параметрам конечной точки, так и к свойствам модели (если класс модели является параметром конечной точки).

Источник: https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview3/aspnetcore.md#validation-support-in-minimal-apis
День 2291. #TipsAndTricks
Скрипт PowerShell для Переименования Проектов .NET
Переименовать проект .NET — утомительное занятие. Вам придётся переименовать файлы и папки, а также заменить содержимое в файлах, например пространство имён или путь в файлах .sln.

Следующий скрипт PowerShell, переименует файлы и папки и заменит содержимое в файлах:
$ErrorActionPreference = "Stop"

$rootFolder = Resolve-Path -Path "."
$oldName = "SampleRazorPages"
$newName = "SampleWebApp"

# Переименовываем файлы и папки
foreach ($item in Get-ChildItem -LiteralPath $rootFolder -Recurse | Sort-Object -Property FullName -Descending) {
$itemNewName = $item.Name.Replace($oldName, $newName)
if ($item.Name -ne $itemNewName) {
Rename-Item -LiteralPath $item.FullName -NewName $itemNewName
}
}

# Заменяем содержимое в файлах
foreach ($item in Get-ChildItem $rootFolder -Recurse -Include "*.cmd", "*.cs", "*.csproj", "*.json", "*.md", "*.proj", "*.props", "*.ps1", "*.sln", "*.slnx", "*.targets", "*.txt", "*.vb", "*.vbproj", "*.xaml", "*.xml", "*.xproj", "*.yml", "*.yaml") {
$content = Get-Content -LiteralPath $item.FullName
if ($content) {
$newContent = $content.Replace($oldName, $newName)
Set-Content -LiteralPath $item.FullName -Value $newContent
}
}


Источник: https://www.meziantou.net/powershell-script-to-rename-dotnet-projects.htm
HTML Embed Code:
2025/07/07 18:43:29
Back to Top