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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1960. #ЗаметкиНаПолях
Что Такое Идемпотентность в Программных Системах? Окончание

Начало

Как достичь идемпотентности?
Паттерн «Исходящие» (Outbox) обеспечивает согласованность, подобную базе данных, между операциями обмена сообщениями (как получением входящего сообщения, так и отправкой исходящих сообщений) и изменениями бизнес-данных в базе. Опираясь на транзакцию БД, мы превращаем гарантию доставки «хотя бы раз» брокера сообщений в гарантию ровно одной обработки.

Для реализации паттерна Outbox логика обработки сообщений разделена на две фазы:
1. Фаза обработки сообщения
Мы не отправляем исходящие сообщения немедленно брокеру сообщений, а храним их в памяти до завершения работы обработчика сообщений. На этом этапе мы сохраняем все накопленные исходящие сообщения в таблицу БД, используя ту же транзакцию, что и для записи бизнес-данных, и Id сообщения в качестве первичного ключа.
2. Фаза отправки
Все исходящие сообщения физически отправляются брокеру сообщений. Если всё идет хорошо, исходящие сообщения отправляются, а входящие обрабатываются. Но здесь ещё возможно возникновение проблемы и отправка не всех сообщений, что вынудит нас повторить попытку. Так возникнут дублирующие сообщения, но так и задумано.

Паттерн «Исходящие» связан с паттерном «Входящие», поэтому при обработке любого повторяющегося сообщения (или повторной попытке обработки сообщения, которое не удалось выполнить на этапе отправки), сначала извлекаются данные из таблицы исходящих сообщений. Если такое сообщение существует, это означает, что оно уже успешно обработано, надо пропустить этап обработки, и перейти к этапу отправки. Если сообщение является дубликатом, и исходящие сообщения уже отправлены, то и этап отправки также можно пропустить. На псевдокоде это выглядит так:
 
var message = PeekMessage();
// проверка на дубликат
var outbox = DB.GetOutboxData(message.Id);
// обработка
if(outbox == null)
{
using(var trans = DB.StartTransaction())
{
var result = ExecuteHandler(message);
outbox = new OutboxData(message.Id, result);
DB.StoreOutboxData(outbox);
trans.Commit();
}
}

// отправка
if(!outbox.IsDispatched)
{
Bus.DispatchMessage(outbox);
DB.SetAsDispatched(message.Id);
}

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

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

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

Источник: https://particular.net/blog/what-does-idempotent-mean
👍14👎2
День 1961. #ЗаметкиНаПолях
Ожидает ли HttpClient всё Тело Ответа?
Если вы вызываете HttpClient.GetAsync или HttpClient.PostAsync, а затем ожидаете результат, ожидается получение только заголовков или всего тела ответа?

Сервер
Создадим простой сервер, который отправляет ответ с заголовком и телом. Тело будет отправляться частями, чтобы мы могли видеть, когда оно будет получено.
app.MapGet("/", async ctx =>
{
await ctx.Response.WriteAsync("1");
await Task.Delay(500);
await ctx.Response.WriteAsync("2");
await Task.Delay(500);
await ctx.Response.WriteAsync("3");
await ctx.Response.CompleteAsync();
});

Таким образом, весь запрос занимает не менее 1 секунды. Напишем простой вызов этой конечной точки.

Клиент
using var client = new HttpClient();

// измерим время до получения ответа
var sw = Stopwatch.StartNew();
await client.GetAsync("https://localhost:5001");
Console.WriteLine(
$"Время отклика: {sw.ElapsedMilliseconds}мс");

Если выполнить этот код, мы увидим, что время ответа составляет около 1 секунды. Таким образом, await ожидает получения всего тела:
Время отклика: 1088мс

Теперь посмотрим, как можно не ждать тела. Используем опцию HttpCompletionOption.ResponseHeadersRead:
var sw = Stopwatch.StartNew();
await client.GetAsync("https://localhost:5001",
HttpCompletionOption.ResponseHeadersRead);
Console.WriteLine(
$"Время отклика: {sw.ElapsedMilliseconds}мс");

Мы добавили флаг в вызов GetAsync. Теперь время отклика значительно меньше:
Время отклика: 78мс

Таким образом await ожидает только получения заголовков, и вы можете исследовать их сразу, как они будут получены. Тело всё ещё принимается в фоновом режиме. Если вы хотите дождаться получения тела, можно использовать метод ReadAsStringAsync:
using var response = 
await client.GetAsync("https://localhost:5001",
HttpCompletionOption.ResponseHeadersRead);
var body = await response.Content.ReadAsStringAsync();

Важно! Мы здесь используем using var response. Объект response продолжит удерживать ресурсы после await, поэтому необходимо удалять его как можно скорее.

Итого
Использование HttpCompletionOption.ResponseHeadersRead даёт выигрыш в производительности и памяти, и вы всё равно можете прочитать тело, если захотите. Но будьте осторожны с объектом ответа, так как он может по-прежнему удерживать ресурсы, что усложняет код. Так что это не должно быть выбором по умолчанию, если в этом нет необходимости.

Источник: https://steven-giesel.com/blogPost/e2c3bcba-4f81-42b0-9b25-060da5e819fa/does-an-httpclient-await-the-header-and-the-body
👍38
День 1962. #УрокиРазработки
Уроки 50 Лет Разработки ПО


Урок 11. Люди не просто «собирают» требования
Когда говорят «сбор требований», можно подумать о сборе цветов или грибов. С требованиями всё не так просто. Они редко существуют в сознании пользователей в сформированном виде, готовом для передачи бизнес-аналитику. Термин «выявление требований» точнее описывает, как разработчики сотрудничают с заинтересованными сторонами, чтобы понять, как те работают, и определить, какие возможности должна предоставлять будущая программная система. Задача бизнес-аналитика во время сбора информации - задавать правильные вопросы, стимулируя обсуждение и побуждать заинтересованные стороны выходить за рамки поверхностных и очевидных решений. При этом он не просто записывает всё, что ему говорят, а выявляет детали, помогая участникам структурировать знания.

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

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

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

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

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

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

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

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

7. Прототипы
Людям трудно представить, каким может быть предлагаемое решение. Прототип делает требования более осязаемыми. Даже простые эскизы UI могут помочь получить наглядное представление. Но создавать прототипы в начале исследования требований рискованно, поскольку люди могут преждевременно зациклиться на конкретном (и, возможно, неидеальном) решении.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍7👎1
День 1963. #Шпаргалка #CSS
Единицы Размеров в CSS. Начало
Поговорим немного о фронтенде. Тем более, что разобраться с UI бывает очень непросто. Многие свойства CSS принимают числа в качестве значений. Кроме того, за ними часто следуют единицы измерения. Что такое px? Чем отличаются em и rem? И как определяются итоговые размеры?

В CSS есть два типа единиц: абсолютные и относительные.

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

1. Длина
- cm – сантиметры (96px/2.54)
- mm – миллиметры (1/10cm)
- Q – четверть-миллиметры (1/40cm)
- in – дюймы (2.54cm = 96px)
- pc – пики (1/6in)
- pt – точки (1/72in)
- px – пиксели (1/96in)

2. Углы
Хороши, например, для направления линейного градиента (linear-gradient) или поворота (rotate) элемента.
- deg – градусы (полный круг – 360deg)
- grad – грады (круг – 400grad)
- rad – радианы (круг - 2π ~ 6.2832rad)
- turn – повороты (круг - 1turn)
Например, rotate(180deg)

3. Время
Тут всё просто
- s – секунды,
- ms - миллисекунды
Например, animation-duration: 2s

4. Разрешение
Количество точек на единицу измерения. Чем меньше точек, тем более «пикселизированным» и размытым будет изображение. Отлично подходит для таргетинга стилей на определенные экраны в медиа-запросах.
- dpi – точек на дюйм
- dpcm – точек на сантиметр
- dppx (или x) – точек на пиксель
Например,
@media (min-resolution: 96dpi) { … }

Спецификация определяет значение infinite для медиа-запросов под экраны без ограничений разрешения.

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

Источник:
https://css-tricks.com/css-length-units/
👍9
День 1964. #Шпаргалка #CSS
Единицы Размеров в CSS. Окончание

Начало. Абсолютные единицы

Относительные единицы
Значение относительной единицы зависит от размера чего-то ещё. Допустим, у нас есть элемент <div> с абсолютным значением высоты 200 пикселей. Эта высота никогда не изменится. Но если задать элементу относительную ширину в 50%, он займёт половину ширины страницы (либо половину ширины своего родителя). Относительное число действует как множитель для вычисления значения, в зависимости от того, относительно какого размера мы считаем.

1. Проценты
% - относительно размера родительского элемента.
Используются, когда нет другого контекста для задания размера.

2. Относительно шрифта
- em – элемент (относительно размера шрифта родительского элемента)
- rem – корневой элемент (относительно размера шрифта элемента <html>)
Аналогично:
- ch и rch – ширина символа (отличается для каждого шрифта и у разных символов, кроме моноширинных шрифтов)
- lh и rlh – высота строки
- cap и rcap – высота заглавной буквы
- ic и ric – ширина иероглифа
- ex и rex – ширина буквы X.
Большинство из этого требуется только для типографии. Однако, хорошей практикой является задавать размер шрифта в пикселях для элемента <html>, а для всех остальных элементов использовать относительные em и rem:
html {
/* Наследуется всеми элементами */
font-size: 18px;
}
.parent {
/* Изменяется при смене размера в `html` */
font-size: 1.1rem;
}
.child {
/* Изменяется при смене размера в `.parent` */
font-size: 0.9em;
}


3. Относительно области просмотра (viewport – видимая часть страницы)
Эти величины всегда считаются относительно размеров страницы.
- vh и vw – высота и ширина (100vh – высота экрана)
- vmin и vmax – минимум и максимум между vh и vw соответственно
- lvh и lvw – «большие» высота и ширина (в полноэкранном режиме)
- lvb и lvi – эквиваленты lvh и lvw для блока и строки
- svh и svw – «маленькие» высота и ширина (когда отображаются UI браузера и экранная клавиатура)
- svb и svi – эквиваленты svh и svw для блока и строки
- dvh и dvw – «динамические» высота и ширина (изменяются, например, при отображении/скрытии экранной клавиатуры)
- dvb и dvi – эквиваленты dvh и dvw для блока и строки
- dvmin и dvmax – минимум и максимум между dvh/dvb и dvw/dvi соответственно.

4. Относительно контейнера
Эти величины считаются в контейнер-запросах относительно размеров соответствующего контейнера.
- cqw – ширина контейнера (100cqw – полная ширина)
- cqh – высота контейнера
- cqi – ширина строкового контейнера
- cqb – ширина блокового контейнера
- cqmin и cqmax – минимум или максимум между cqi и cqb

Например,
.parent {
container-type: inline-size;
}
.child {
width: 100%;

@container (width > 30ch) {
.child {
width: 50cqi;
}
}
}

Когда родительский элемент блока child превысит ширину в 30 символов, он станет занимать вместо полной ширины контейнера только половину его ширины.

Источник: https://css-tricks.com/css-length-units/
👍13
День 1965. #ЧтоНовенького #CSharp13
Полуавтоматические Свойства
Полуавтоматические свойства позволяют вам добавлять логику в методы get и set, не создавая явно приватного поля как для полноценного свойства, а используя ключевое слово field.

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

Автоматические свойства
Автоматические свойства появились очень давно и используются повсеместно:
public class Test
{
public string Name { get; set; }
}

Этот код за кулисами создаёт приватное поле и два метода для получения и задания его значения. Т.е. следующий код эквивалентен коду выше:
csharp 
public class Test
{
private string _name;

public string get_Name()
{
return _name;
}
public void set_Name(string name)
{
_name = name;
}
}

Но что, если мы хотим добавить какую-то логику в метод get или set автосвойства? К примеру, мы хотим, чтобы имя всегда задавалось без лишних пробелов. До сих пор нам ничего не оставалось, кроме как отказаться от автосвойства и использовать полноценное свойство, явно создавая поле для него:
private string _name;

public string Name
{
get
{
return _name;
}
set
{
_name = value.Trim();
}
}

Полуавтоматические свойства
Начиная с С#13 мы сможем использовать для этого ключевое слово field, и не создавать поле явно:
public string Name { 
get => field;
set => field = value.Trim();
}

Интересно, что, даже если вам нужно изменить только set, то придётся реализовать и get (пусть и в таком элементарном виде). То есть, его нельзя оставить в виде get;. Возможно, к релизу это исправят.

Очевидно, что добавление нового ключевого слова может сломать чей-то существующий код. Если в вашем коде использовалось поле с названием field, то в такой ситуации будет не понятно, нужно обращаться к этому полю, либо к неявному полю для свойства Name.
Microsoft годами пыталась избегать ломающих изменений, в частности при вводе новых ключевых слов. Раньше они создавали сложные правила для разрешения этих конфликтов. Но теперь решили пойти другим путём. На примере этой функции они хотят протестировать инструмент «раннего предупреждения» о ломающем изменении. Теперь, когда вы обновляете .NET SDK на новую версию (при этом используя старую версию .NET в проектах), анализатор кода в таких случаях будет выдавать предупреждение, что этот код не будет работать в новой версии языка и предлагать автоматический рефакторинг кода под новую версию.

В этом случае для обращения к полю field из свойства нужно будет добавить @:
private string field;
public string Field {
get => @field;
set => @field = value.Trim();
}

Источник: https://www.youtube.com/watch?v=3jb9Du9pMes
👍31
День 1966. #ЗаметкиНаПолях
Безопасны Мои LINQ Запросы в EF?

Задумывались ли вы когда-нибудь о том, подвержены ли ваши LINQ-запросы в EF атакам с использованием SQL-инъекции? Т.к. вы либо используете данные прямо из текстового поля, либо вставляете всё, что вернул API в базу?

Ну, во-первых, нужно всегда проверять и экранировать вводимые данные. Но, допустим, вы этого не сделали, и …?

Хорошая новость: Entity Framework экранирует ввод. Поэтому, если у вас есть что-то вроде этого:
var userInput = "Jon \"OR 1 = 1";
var evil = await myContext
.People
.Where(p => p.Name === userInput)
.ToListAsync();

Результирующий SQL будет вроде следующего:
SELECT "p"."Id", "p"."Name"
FROM "People" as "p"
WHERE "p"."Name" = 'Jon "OR 1 = 1'

И это хорошо, потому что атака SQL-инъекцией не пройдёт, т.к. OR 1 = 1 будет рассматриваться как строка, а не как команда SQL.

Это не сработает, если вы используете чистые SQL-запросы. Поэтому, такой код:
var userInput = "Jon \"OR 1 = 1";
var evil = await myContext
.People
.FromSqlRaw(
$"SELECT * FROM People WHERE Name = '{userInput}'")
.ToListAsync();

может быть подвержен атакам SQL-инъекцией.

FromSql (до EF Core 7 - FromSqlInterpolated)
FromSql – это более безопасный способ использовать чистый SQL. Метод – часть пакета Microsoft.EntityFrameworkCore.Relational. Вот разница коротко:
var evil = await myContext.
.People
.FromSqlRaw(
$"SELECT * FROM People WHERE Name = {userInput}")
.ToListAsync();

// или
var notSoEvil = await myContext.
.People
.FromSql(
$"SELECT * FROM People WHERE Name = {userInput}")
.ToListAsync();

Хотя они выглядят одинаково, важна сигнатура методов: FromSqlRaw принимает string, а FromSqlFormattableString. FormattableString представляет собой строку с заполнителями для параметров. Так EF знает, что такое userInput, и сможет правильно его экранировать.

Если вы хотите выполнить произвольный SQL-запрос, вы можете использовать ExecuteSql на объекте Database (вместо DbSet<TYourEntity>).

Итого
Используйте FromSql (или FromSqlInterpolated). В некоторых редких случаях могут быть проблемы форматирования интерполированных строк, но они редки. Если вы столкнётесь с ними, можете использовать FromSqlRaw, но вы должны осознавать риски.

Источник: https://steven-giesel.com/blogPost/35ec8819-220a-42bc-9224-a812ec358434/are-my-ef-linq-to-sql-queries-safe
👍32
День 1967. #ЗаметкиНаПолях
Вы Пожалеете, что Использовали Естественные Ключи

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

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

Уникальность
Хороший архитектор ПО должен бросать вызов основополагающим предположениям. Допустим, вы отказались от синтетического ключа. Является ли выбранный естественный ключ (поле или сочетание полей) уникальным? Допустим, название города уникально в пределах региона. Но что, если ПО расширится до использования по всей стране? А если на несколько стран?

Идентичность
Хорошо, для таблицы городов синтетический ключ - лучший выбор, это довольно очевидно. А как насчёт, «естественных» естественных ключей? Примером может быть VIN автомобиля. Это уже определённый код, и он, вероятно, берётся из какой-то базы. А номер паспорта (или аналогичный номер в других странах)?

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

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

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

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

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

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

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

Источник: https://blog.ploeh.dk/2024/06/03/youll-regret-using-natural-keys/
Автор оригинала: Mark Seemann
👍24
День 1968. #ЗаметкиНаПолях
Различия Между Span и Memory в C#

Сегодня разберём особенности типов Span и Memory в .NET, и когда что использовать.

Span<T>
Это ref-структура в .NET, представляющая собой доступ к непрерывной области памяти. Т.е. Span<T> — это всего лишь представление блока памяти, а не способ выделить память.
Span<T> может иметь несколько источников блока памяти:
- массив T[] (или его фрагмент)
- Memory<T>
- неуправляемый указатель
- результат stackalloc

Например:
int[] data = [1, 2, 3, 4, 5, 6];
var span = data.AsSpan().Slice(2, 1);

Span<T> всегда располагается в стеке и хранит только указатель на уже выделенный ссылочный тип и не выделяет новую управляемую память в куче. Span<T> нельзя упаковать или назначить переменным типа Object, Dynamic или интерфейсам. Он также не может быть полем ссылочного типа.

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

Memory<T>
Как и Span<T> представляет собой доступ к непрерывной области памяти, однако является структурой. Memory<T> можно разместить как в управляемой куче, так и в стеке, использовать в качестве поля в классе, а также совместно с await и yield.

Свойство Span возвращает Span<T>, что позволяет использовать Memory<T> как Span<T> в рамках одного метода. В этом смысле Memory<T> иногда называют фабрикой Span’ов.

Рекомендации
- Как правило, следует использовать Span<T> в качестве параметра для синхронного API, когда это возможно.
- Если буфер памяти должен быть доступен только для чтения, можно использовать ReadOnlySpan<T> и ReadOnlyMemory<T>.
- Если метод имеет параметр Memory<T> и возвращает void, не следует использовать этот экземпляр Memory<T> после завершения выполнения метода. Аналогично, если метод возвращает Task, не следует использовать экземпляр после завершения задачи.

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

Span<T> и Memory<T> - это представления блока памяти, которые предназначены для предотвращения копирования памяти или выделения большего количества памяти, чем необходимо, для управляемой кучи.

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

Источник: https://code-maze.com/csharp-differences-between-span-and-memory/
👍26
День 1969. #УрокиРазработки
Уроки 50 Лет Разработки ПО


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

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

2. Пользователи – Менеджер по продвижению продукта – Бизнес-аналитик - Разработчики
Функции основного канала передачи информации о требованиях выполняют один или несколько ключевых представителей пользователей, называемых менеджерами по продвижению продукта (product champions), которые сотрудничают с одним или несколькими бизнес-аналитиками. Менеджеры по продвижению знают предметную область и понимают бизнес-цели проекта. Они взаимодействуют со своими коллегами, собирают требования и отзывы, а также информируют других пользователей о ходе выполнения проекта. Бизнес-аналитик помогает преодолеть разрыв в общении между ними и командой разработчиков.

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍2
День 1970. #Безопасность
Межсайтовая Подделка Запросов (CSRF). Начало

В 2005 г. исследователь в области информационной безопасности Сами Камкар обнаружил уязвимость в популярной в то время социальной сети Myspace. Ему удалось внедрить код JavaScript на страницу своего профиля. Это классическая XSS-атака. Код отправлял HTTP-запрос от имени жертвы, добавляя её в список друзей Камкара. Не прошло и 20 часов, как у него было более миллиона друзей на Myspace.

Название атаки (Cross-Site Request Forgery - межсайтовая подделка запросов), по сути, описывает ее анатомию. Какой-то другой сайт (созданный злоумышленником) принудительно отправляет от имени клиента поддельный запрос. Чаще всего это выглядит так:
1. Пользователь ранее посещал (целевой) сайт и создал состояние – например, выполнил вход или добавил товары в корзину. Это состояние сохраняется, обычно в виде файла cookie (скажем, сеансового файла cookie, а сессия содержит состояние входа в систему или содержимое корзины).
2. Пока браузер открыт и сессия активна, пользователь переходит на другой сайт, контролируемый злоумышленником. Этот сайт делает запрос на целевой сайт, с которым пользователь ранее взаимодействовал, одним из двух способов:
- в случае GET-запроса сайт злоумышленника содержит код JavaScript, выполняющий запрос через скрытый iframe или <img> с адресом целевого сайта в атрибуте href;
- в случае POST-запроса сайт злоумышленника содержит элемент <form> с атрибутом method=POST и адресом целевого сайта в атрибуте action. А страница содержит код JavaScript, который отправляет данные формы.

В итоге целевой сайт получает HTTP-запрос, отправленный браузером пользователя. Запрос содержит сеансовый файл cookie для идентификации пользователя. Поэтому сайт может выполнять действия «от имени» пользователя. GET-запросы обычно не так страшны, поскольку не меняют состояния (в соответствии с лучшими практиками), а вот POST-запросы могут действительно повлиять на приложение.

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

Эту атаку ещё называют "катание на сессии", т.к. сессия пользователя не крадётся, злоумышленник просто использует её "втёмную".

Именно так Сами Камкар завел новых друзей на Myspace: он нашел XSS-уязвимость и внедрил код JavaScript, который выполнял POST-запрос, чтобы «добавить пользователя в друзья».

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

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

Источник: Кристиан Венц “Безопасность
ASP.NET Core”. М.: ДМК Пресс, 2023. Глава 4.
👍21
День 1971. #Безопасность
Межсайтовая Подделка Запросов (CSRF). Окончание

Начало

В случае с CSRF есть два аспекта, которые делают атаку возможной:
1. Злоумышленник может точно предсказать HTTP-запрос. Это сделать несложно, проанализировав настоящий запрос. Данные формы передаются в открытом виде, поэтому их легко подменить, имитировав нужные параметры.
2. Активная сессия. Но файлы cookie клиента автоматически отправляются на сервер, которому они принадлежат. Злоумышленнику не нужно красть идентификатор сессии; он использует сессию пользователя.

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

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

В ASP.NET Core такой механизм уже есть и активирован по умолчанию. ASP.NET Core автоматически добавляет в форму дополнительное скрытое поле, вроде такого:
html 
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8FflGUpl_…U90c" />

А сервер отправляет cookie, например:
 
.AspNetCore.Antiforgery.Za7zYHoQn5w=CfDJ8FflGUpl_…0ueI

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

Никакой дополнительной настройки не требуется, если вы используете форму следующими способами:
- <form method="post">...</form>
- @Html.BeginForm(...)

Механизм защиты от CSRF представляет собой промежуточное ПО, которое автоматически активируется, при использовании стандартных методов в классе Program:
- MVC (AddMvc(), AddControllersWithViews() или MapControllerRoute()),
- Razor Pages (AddRazorPages() или MapRazorPages()),
- Blazor (MapBlazorHub())

Если вы хотите избавиться от токенов (а у вас для этого должна быть очень веская причина, например другое приложение, отправляющее POST-запрос в вашу конечную точку), то либо:
- используйте <!form>...<!/form>;
- деактивируйте CSRF-токен для каждой формы с помощью asp-antiforgery="false".

Токен по умолчанию генерируется и добавляется в любую форму, но не проверяется автоматически. Используя фильтры в ASP.NET Core (атрибуты или глобальные фильтры), можно реализовать 3 варианта:
- AutoValidateAntiForgeryToken – требует (и проверяет) токены для всех HTTP-запросов, меняющих состояние приложения (все, кроме GET, HEAD, OPTIONS и TRACE);
- ValidateAntiForgeryToken – гарантирует, что метод-действие, помеченный этим атрибутом, проверит запрос, независимо от HTTP-метода;
- IgnoreAntiForgeryToken – отключает проверку токенов.

Источник: Кристиан Венц “Безопасность ASP.NET Core”. М.: ДМК Пресс, 2023. Глава 4.
👍15
День 1972. #ЧтоНовенького
Ссылки на Исходный Код в Документации .NET

Microsoft добавили ссылки, соединяющие документацию с исходным кодом для большинства популярных API .NET.

Для API .NET, соответствующих некоторым критериям (включённая ссылка на источник, наличие доступной PDB и размещение в публичном репозитории), ссылки включаются в метаданные определения. При этом по возможности публикуются ссылки на точное место в коде, например, на конкретную перегрузку метода.

Конвейер документации работает с набором файлов DLL и пакетов NuGet. Они обрабатываются различными инструментами для преобразования их содержимого в HTML-страницы, отображаемые в Microsoft Learn. Правильное создание ссылок на исходный код требует понимания взаимосвязи между исходным кодом, двоичными файлами и GitHub, а также того, как связать их вместе с некоторыми существующими API .NET. Поэтому было принято решение пойти по пути существующего инструмента Go to definition (Перейти к определению) в Visual Studio.

Источник
👍31
День 1973. #ЗаметкиНаПолях
Особенности Bulk-Операций в EF Core

Когда вы имеете дело с тысячами или даже миллионами записей, эффективность имеет решающее значение. В EF Core 7 представлены два новых метода: ExecuteUpdate и ExecuteDelete (и их асинхронные перегрузки), предназначенные для упрощения Bulk-операций в БД. Однако они обходят трекер изменений EF Core, что может привести к неожиданному поведению, если вы об этом не знаете.

Трекер Изменений
Когда вы загружаете объекты из БД с помощью EF Core, трекер изменений начинает их отслеживать. Когда вы обновляете свойства, удаляете объекты или добавляете новые, он записывает эти изменения:
using (var context = new AppDbContext())
{
// Загрузка
var pr = context.Products
.FirstOrDefault(p => p.Id == 1);
// Изменение
pr.Price = 99.99;

// Здесь трекер знает, что pr изменён

// Добавление
var newPr = new Product {
Name = "New Gadget", Price = 129.99 };
context.Products.Add(newPr);

// Удаление
context.Products.Remove(pr);

// Сохранение всех изменений в БД
context.SaveChanges();
}

Когда вы вызываете SaveChanges, EF Core использует трекер изменений, чтобы определить, какие команды SQL следует выполнить. Это гарантирует, что БД будет синхронизирована с вашими изменениями.

Bulk-операции и трекер изменений
Bulk-операции не используют трекер изменений. Это решение может показаться нелогичным, но за ним стоит веская причина: производительность. Непосредственно выполняя инструкции SQL в БД, EF Core устраняет накладные расходы на отслеживание изменений отдельных объектов.
using (var context = new AppDbContext())
{
// Увеличиваем цену всех продуктов в категории электроника на 10%
context.Products
.Where(p => p.Category == "Electronics")
.ExecuteUpdate(
s => s.SetProperty(p => p.Price, p => p.Price * 1.10));
// Все объекты типа Product в памяти по-прежнему будут иметь старые цены
}

В этом примере метод ExecuteUpdate эффективно преобразует операцию в одну инструкцию SQL UPDATE:
UPDATE [p]
SET [p].[Price] = [p].[Price] * 1.10
FROM [Products] as [p];

Но, если вы проверите экземпляры Product, которые EF Core уже загрузил в память, вы обнаружите, что их свойства Price не изменились. Это может показаться удивительным, если вы не знаете, как массовые обновления взаимодействуют с системой отслеживания изменений. Это же применимо и к методу ExecuteDelete.

Перехватчики EF Core также не запускаются для операций ExecuteUpdate и ExecuteDelete. Если нужно отслеживать или изменять Bulk-операции, вы можете создать триггеры в БД на обновление или удаление.

Проблема: поддержание согласованности
Если ExecuteUpdate завершается успешно, изменения сразу фиксируются в БД, т.к. Bulk-операции обходят трекер изменений и не участвуют в обычной транзакции, управляемой SaveChanges. Если впоследствии SaveChanges завершится сбоем, вы окажетесь в несогласованном состоянии. Изменения, внесенные с помощью ExecuteUpdate, уже сохранятся. Любые изменения, сделанные «в памяти», потеряются.

Самый надежный способ обеспечить согласованность — обернуть в транзакцию и ExecuteUpdate, и операции, которые приводят к SaveChanges:
using (var context = new AppDbContext())
using (var transaction = context.Database.BeginTransaction())
{
try
{
context.Products
.Where(p => p.Category == "Electronics")
.ExecuteUpdate(
s => s.SetProperty(p => p.Price, p => p.Price * 1.10));

// … другие изменения сущностей
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}

Если SaveChanges завершится неудачно, транзакция будет отменена с отменой изменений, внесенных как ExecuteUpdate, так и любыми другими операциями внутри транзакции. Это сохранит вашу БД в согласованном состоянии.

Источник: https://www.milanjovanovic.tech/blog/what-you-need-to-know-about-ef-core-bulk-updates
👍23
.NET Разработчик pinned «Вы пользуетесь .NET MAUI?»
День 1974. #ЗаметкиНаПолях
Согласованные и Воспроизводимые Сборки с Помощью global.json. Начало

Том, .NET-разработчик, только что завершил реализацию новой функции в приложении ASP.NET Core. На его машине всё работает отлично, и он отправляет код в пул-реквест. Однако в CI-конвейере сборка завершается неудачей из-за ошибки IDE0100. Не понимая причину проблемы, Том просит помощи у своего коллеги Брайана, которому удаётся её воспроизвести.

Под давлением сроков Том и Брайан не тратят время на то, чтобы выяснить, почему ошибка не появляется на машине Тома. Они решают отключить ошибку с помощью директивы #pragma, и PR успешно создаётся. Через несколько минут приложение успешно развёртывается в рабочей среде.

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

Том использует версию 8.0.200 SDK для .NET, а Брайан — версию 8.0.204. Заинтересовавшись новыми возможностями .NET 9, Брайан также установил предварительную версию 9.0.100-preview.4. Конвейер CI, размещенный в Azure DevOps, использует задачу UseDotNet@2 для установки версии SDK 8.x, включая предварительные версии.

Ошибка IDE0100 появилась в тот день, когда Microsoft выпустили SDK версии 8.0.300. В этой версии представлено исправление, изменяющее поведение анализатора Roslyn IDE0100. Том, использующий более раннюю и уязвимую версию .NET 8 SDK, не пострадал. Однако Брайан скомпилировал приложение с помощью SDK предварительной версии .NET 9, которая включала это новое поведение.

Ирония в том, что приложение развёртывается в контейнере, а в качестве базового образа используется mcr.microsoft.com/dotnet/sdk:8.0.100, первой версии пакета SDK для .NET 8, выпущенной в ноябре 2023 года. Эта версия содержит несколько известных уязвимостей и больше не отображается в Docker Hub.

Какие выводы можно сделать из этой истории, вдохновлённой реальными событиями:
1. Команда Тома и Брайана не реализовала механизм, гарантирующий, что версия .NET SDK, используемая для компиляции приложения, одинакова на всех машинах.
2. Их поведение CI может измениться без предварительного уведомления, поскольку Microsoft выпускает новые предварительные версии SDK.
3. Версии SDK, содержащие уже исправленные уязвимости, по-прежнему используются как на машинах разработчиков, так и в производстве.

Этой ситуации можно избежать, явно указав необходимую версию .NET SDK для компиляции приложения. В продолжении рассмотрим, чем отличаются версии SDK и как использовать необходимую версию с помощью файла global.json.

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

Источник:
https://anthonysimmon.com/automate-dotnet-sdk-updates-global-json-renovate/
👍22
День 1975. #ЗаметкиНаПолях
Согласованные и Воспроизводимые Сборки с Помощью global.json. Продолжение

Начало

Понимание версий .NET SDK
Ежегодно выпускается основная версия .NET: 5.0, 6.0, 7.0, 8.0 и скоро 9.0 в ноябре 2024 года. Обычно именно на эту версию разработчики ссылаются в проектах
<TargetFramework>netX.0</TargetFramework>.

Однако пакет SDK для .NET обычно получает ежемесячные обновления, которые могут включать исправления безопасности, новые функции или обновления компонентов, таких как NuGet или MSBuild. Поэтому управление версиями SDK немного отличается от управления версиями среды выполнения. Например, первым SDK для .NET 8 была версия 8.0.100. Эта версия соответствует функциональному диапазону 8.0.1nn. Увеличение цифры сотых в третьем разделе номера версии может указывать на добавление новых функций и, возможно, на критические изменения.

Таким образом, 8.0.101 и 8.0.201 относятся к разным функциональным группам, а 8.0.101 и 8.0.199 — к одной функциональной группе. Изменение цифр nn в 8.0.1nn указывает на то, что новых функций нет, и чаще всего это соответствует исправлениям уязвимостей или ошибок.

Формат версии .NET SDK выглядит так: x.y.znn
Здесь:
- x — основная версия, обычно увеличивающаяся с каждым ежегодным основным выпуском.
- y — это дополнительная версия, которая остаётся 0, начиная с .NET 5.
- z — диапазон функций, который может меняться ежемесячно, указывая на добавление новых функций.
- nn — версия исправления, которая может меняться ежемесячно, указывая на исправления ошибок или уязвимостей.

Файл global.json
Файл global.json позволяет определить, какая версия .NET SDK используется для запуска команд .NET CLI, таких как dotnet build, dotnet run или dotnet test. При отсутствии этого файла используется последняя версия SDK, установленная на машине. В большинстве случаев файл создаётся в корне решения:
{
"sdk": {
"version": "8.0.300",
"rollForward": "latestPatch",
"allowPrerelease": false
}
}

sdk.version - минимальная версия SDK, необходимая в соответствии со стратегией rollForward.

sdk.rollForward - можно ли использовать версию выше, чем sdk.version. Некоторые возможные значения:
- latestPatch (по умолчанию) - допускает использование любой более высокой версии исправления, например, 8.0.300 разрешает 8.0.301, 8.0.399 и т.п.
- latestFeature - позволяет использовать любую более высокую функциональность. 8.0.300 разрешает 8.0.400, 8.0.599 и т.п.
- latestMinor - то же для второстепенной версии. 8.0.300 разрешает 8.1.100, 8.2.199 и т.п.
- latestMajor - допускает любую более старшую основную версию.
- disable указывает, что требуется точная версия.

Рекомендации
- Используйте последнюю доступную версию SDK, которую вы используете. Его можно получить на странице загрузки .NET SDK.
- Разрешите только исправления, чтобы избежать критических изменений.
- Запретите предварительные версии (allowPrerelease = false).

Цель состоит в том, чтобы обеспечить воспроизводимые сборки и идентичное поведение от машины разработчика до производства.

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

Источник:
https://anthonysimmon.com/automate-dotnet-sdk-updates-global-json-renovate/
👍11
День 1976. #ЗаметкиНаПолях
Согласованные и Воспроизводимые Сборки с Помощью global.json. Окончание

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

Настройка CI для использования файла global.json.
Azure DevOps и GitHub Actions позволяют установить пакет SDK для .NET в конвейере CI. Эти задачи можно настроить на использование файла global.json и гарантировать, что версия SDK совпадает с версией компьютера разработчика.
Azure DevOps:
- task: UseDotNet@2
displayName: "Install .NET SDK from global.json"
inputs:
packageType: "sdk"
useGlobalJson: true

GitHub Actions:
 
# Задача попытается найти файл global.json в текущем каталоге
- uses: actions/setup-dotnet@v4

# Также можно указать путь
- uses: actions/setup-dotnet@v4
with:
global-json-file: "./something/global.json"


Обновление версий образов Docker
Давайте посмотрим на эти несколько тегов образов Docker для .NET SDK, среды выполнения .NET и ASP.NET Core:
mcr.microsoft.com/dotnet/sdk:8.0
mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
mcr.microsoft.com/dotnet/sdk:8.0-alpine
mcr.microsoft.com/dotnet/runtime:8.0
mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled
mcr.microsoft.com/dotnet/aspnet:8.0-jammy

Их объединяет то, что они указывают не конкретную версию, а «последнюю версию ветки 8.0». Использование этих тегов может иметь несколько последствий:
- Если вы забудете загрузить последний образ, это может привести к использованию старой версии.
- Может быть выпущена новая функциональная группа, возможно, с критическими изменениями.
- Для ясности и согласованности с файлом global.json желательно указать точную версию .NET SDK. Например, mcr.microsoft.com/dotnet/sdk:8.0.301:
# Сборка приложения
FROM mcr.microsoft.com/dotnet/sdk:8.0.301 AS build
# [...]

# Сборка образа среды выполнения
FROM mcr.microsoft.com/dotnet/aspnet:8.0.3
# [...]


Автоматизация обновления версий .NET SDK
На этом этапе вы можете быть уверены, что разработчики, CI и контейнеры используют одну и ту же версию .NET SDK. Результат сборки, который вы получите, везде будет одинаковым.

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

Для этого вы можете использовать инструменты управления зависимостями, такие как Renovate или Dependabot, чтобы каждая новая версия .NET SDK автоматически создавала пул-реквест для обновления файла global.json и Dockerfiles.

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

Источник: https://anthonysimmon.com/automate-dotnet-sdk-updates-global-json-renovate/
👍13
День 1977. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 13. Две распространённые практики выявления требований — телепатия и ясновидение. Обе не работают

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

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

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

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

Например, требование: «Система должна поддерживать...» Как разработчики компании-подрядчика узнают, какую именно функциональность подразумевает слово «поддерживать»?

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

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

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

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

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍12
День 1978. #Курсы
Изучаем .NET Aspire

Сегодня порекомендую вам обучающее видео (точнее запись недавней трансляции) с канала dotnet. Джефф Фритц рассказывает о .NET Aspire. Если вы не знаете, что это и с чем его едят, вот отличное 2х-часовое руководство по его практическому использованию.

https://youtu.be/8i3FaHChh20
👍4