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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 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
В каком порядке выполняется промежуточное ПО в конвейере ASP.NET Core при обработке запроса?
#Quiz #ASPNET
Anonymous Quiz
76%
в порядке регистрации
7%
в порядке обратном регистрации
2%
в произвольном порядке
15%
в зависимости от типа промежуточного ПО
👍1
День 1979. #ЧтоНовенького
Новинки
ASP.NET Core 9 Preview 5
Недавно Microsoft выпустили 5й превью .NET 9, добавив значительные улучшения в ASP.NET Core.

1. MapStaticAssets
Основным улучшением в этом выпуске является оптимизация доставки статических веб-ресурсов. Новый API MapStaticAssets предназначен для замены UseStaticFiles в большинстве случаев (кроме обслуживания внешних ресурсов). MapStaticAssets оптимизирован для ресурсов, известных на момент сборки и публикации. Он сжимает ресурсы с помощью gzip и brotli, уменьшая их размер и ускоряя время загрузки для пользователей.
MapStaticAssets также настраивает заголовки ETag на основе содержимого, гарантируя, что браузер загружает файлы только в случае изменения их содержимого. Это упрощает процесс для разработчиков, поскольку новые библиотеки или ресурсы JS/CSS автоматически оптимизируются и обслуживаются быстрее, что особенно полезно для мобильных пользователей с ограниченной пропускной способностью.
Утверждается, что даже приложения, использующие сжатие на стороне сервера, могут извлечь выгоду из MapStaticAssets, поскольку он допускает более высокие степени сжатия, замедляя при этом процесс сборки. Однако использование сжатия Brotli может уменьшить, например, bootstrap.min.css со 163КБ до 17,5КБ.

2. Повторные подключения к серверу в Blazor
Для работы серверных приложений Blazor требуется подключение в режиме реального времени. Новые изменения вводят экспоненциальную стратегию отсрочки попыток повторного подключения:
- Когда пользователь возвращается к приложению с отключенным каналом, попытка повторного подключения выполняется немедленно. Это улучшает взаимодействие с пользователем при возвращении к приложению на вкладке браузера, которая перешла в спящий режим.
- Если попытка повторного подключения достигает сервера, но сервер уже разорвал канал, обновление страницы происходит автоматически. Это избавляет пользователя от необходимости вручную обновлять страницу.
- По умолчанию первые несколько попыток повторного подключения происходят быстро друг за другом, прежде чем между попытками вводятся задержки. Размер задержек можно настраивать.
- Также улучшен UI повторных подключений.

3. Определение режима рендеринга компонента во время выполнения
Класс ComponentBase теперь включает свойство Platform, которое вскоре будет переименовано в RendererInfo, со свойствами Name и IsInteractive. Эти свойства помогают разработчикам понять, где работает их компонент и является ли он интерактивным. Новое свойство AssignedRenderMode также предоставляет информацию о том, как компонент будет отображаться после пререндеринга.

4. Упрощена сериализация состояния аутентификации в Blazor
Если вы начали с шаблона проекта веб-приложения Blazor и выбрали параметр «Индивидуальные учетные записи», всё работает хорошо. Но требуется написать много кода, если вы пытаетесь добавить аутентификацию в существующий проект. Новые API, AddAuthenticationStateSerialization и AddAuthenticationStateDeserialization, упрощают этот процесс, добавляя необходимые сервисы для сериализации и десериализации состояния аутентификации и расширяя возможности приложения для доступа к состоянию аутентификации.

5. Шаблон приложения .NET MAUI Blazor
Этот шаблон упрощает создание приложений, предназначенных для Android, iOS, Mac, Windows и Интернета, одновременно обеспечивая максимальное повторное использование кода.
Ключевые особенности шаблона:
- Возможность выбрать режим интерактивного рендеринга Blazor для веб-приложения.
- Автоматическое создание соответствующих проектов, включая веб-приложение Blazor и гибридное приложение .NET MAUI Blazor.
- Созданные проекты используют общую библиотеку классов Razor (RCL) для поддержки компонентов пользовательского интерфейса Razor.
- Включен пример кода, который демонстрирует, как использовать внедрение зависимостей для предоставления различных реализаций интерфейса для гибридного приложения Blazor и веб-приложения Blazor.

Источники:
-
https://www.infoq.com/news/2024/06/asp-net-core-9-preview5/
-
https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0
👍8
День 1980. #ЗаметкиНаПолях
ReadOnly-Коллекция не Является Неизменяемой

Коллекция «только для чтения» не означает, что она не изменяема. Сегодня посмотрим, как можно изменить ReadOnlyCollection.

ReadOnlyCollection, которая существует со времен .NET Framework 2.0, — это хороший способ сообщить: «Вот коллекция, которую вы не можете изменить». С точки зрения реализации это всего лишь оболочка поверх коллекции, которая запрещает такие операции, как добавление, удаление или очистку. Но это не означает, что её элементы не могут изменяться.

ReadOnlyCollection не является неизменяемым представлением, это, по сути, просто представление коллекции или списка. Тем не менее, если исходный список изменится, изменится и ReadOnlyCollection:
var numbers = new List<int> { 1, 2 };
var asReadOnly = numbers.AsReadOnly();
var readOnly = new ReadOnlyCollection<int>(numbers);

Console.WriteLine($"List: {numbers.Count}");
Console.WriteLine($"AsReadOnly: {asReadOnly.Count}");
Console.WriteLine($"ReadOnly: {readOnly.Count}");

Вывод довольно очевиден:
List: 2
AsReadOnly: 2
ReadOnly: 2

Но что, если мы добавим элемент в исходный список?
numbers.Add(3);

Получим:
List: 3
AsReadOnly: 3
ReadOnly: 3

Как и представление в базе данных, наше представление списка «только для чтения» обновляется. Это плохо? Нет, потому что имеет некоторые преимущества. Самое главное - базовая операция по созданию ReadOnlyCollection очень дёшева. Она занимает O(1) времени, поскольку аллокации памяти не требуется.

Если мы хотим иметь реальную неизменяемость, нужно использовать другие типы, например, ImmutableList. Если мы проведём тот же тест, что и выше, мы получим то, что ожидаем от неизменяемого типа:
var immutable = numbers.ToImmutableList();
Console.WriteLine($"List: {numbers.Count}");
Console.WriteLine($"Immutable: {immutable.Count}");

numbers.Add(4);

Console.WriteLine($"List: {numbers.Count}");
Console.WriteLine($"Immutable: {immutable.Count}");

Вывод:
List: 3
Immutable: 3
List: 4
Immutable: 3

Мы видим, что длина остаётся прежней. Недостаток очевиден: нам нужно скопировать все элементы из исходного списка в неизменяемый список за O(n).

Итого
ReadOnly-коллекции не являются неизменяемыми. Это просто представление коллекции, доступное только для чтения. Вы как потребитель не можете их изменить, но это не значит, что изначальный создатель/владелец списка не может этого сделать. Если вам действительно нужно текущее состояние, которое не может измениться, используйте ImmutableArray/ImmutableList или Frozen-коллекции.

Источник: https://steven-giesel.com/blogPost/c20eb758-a611-4f98-9ddf-b9e2b83fcac9/readonlycollection-is-not-an-immutable-collection
👍39
День 1981. #ProjectManagement
Настоящий 10х-Разработчик Делает Всю Команду Лучше

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

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

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

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

Это требует времени и энергии сотрудников. Не отвлекает ли это их от основных должностных обязанностей?

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

Вот некоторые конкретные способы, с помощью которых разработчики (и их менеджеры) могут построить культуру общего обучения:
- Если вы менеджер или старший сотрудник, подавайте пример, уделяя приоритетное внимание собственному обучению. Как бы вы ни были заняты, старайтесь уделять регулярное время изучению и отработке новых навыков. Сообщайте подчинённым, что вы делали и что вы узнали, чтобы они осознали, что постоянное обучение приветствуется в организации.
- Убедитесь, что сотрудники уделяют время обучению. Предоставьте командам возможность выделять время для обучения. Иначе им придётся посвящать личное время обучению, а не делать обучение неотъемлемой частью вашей организационной культуры и повседневной работы.
- Делитесь ошибками с коллегами. Любая ошибка — это возможность учиться — не только для того, кто допустил ошибку, но и для остальных участников проекта. А когда старшие разработчики и менеджеры бесстрашно признают свои ошибки, перспектива становится менее пугающей для младших разработчиков.
- Предоставьте учебные ресурсы. Это ключ к развитию культуры обучения.

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

Источник: https://stackoverflow.blog/2024/06/19/the-real-10x-developer-makes-their-whole-team-better/
👍16👎1
День 1982. #ЧтоНовенького #CSharp13
ReadOnlySet<T> в .NET 9

Очередная, шестая, превью версия .NET 9 представит новый тип ReadOnlySet<T>. Это множество только для чтения, аналогичное ReadOnlyCollection<T>. Посмотрим, как это работает и зачем оно добавлено.

IReadOnlySet недостаточно
У нас уже есть интерфейс IReadOnlySet<T>. Например, его реализует FrozenSet. Почему его недостаточно? Рассмотрим List<T> и IReadOnlyList<T>, которые страдают от той же проблемы:
var list = new List<int> { 1, 2, 3 };
IReadOnlyList<int> readOnlyList = list;

// Вы можете привести это обратно к List<T> и изменить
var list2 = (List<int>)readOnlyList;
list2.Add(4);

Console.WriteLine($"List: {list.Count}");
Console.WriteLine($"ReadOnlyList: {readOnlyList.Count}");
Console.WriteLine($"List2: {list2.Count}");

Вывод (внезапно):
List: 4
ReadOnlyList: 4
List2: 4

Конечно, потребителям вашего API не следует этого делать, но вы не можете этого предотвратить. Вот почему у нас есть AsReadOnly:
List<int> list = [1, 2, 3];
var readOnlyList = list.AsReadOnly();

// Вы не можете привести это к List<T>
var list2 = (List<int>)readOnlyList; // Исключение

AsReadOnly возвращает ReadOnlyCollection<T>, поэтому приведение её к изменяемому списку невозможно.

Той же проблемой страдает и IReadOnlySet<T>. Именно для этого создан ReadOnlySet<T>:
var set = new HashSet<int> { 1, 2, 3 };
var readonlySet = new ReadOnlySet<int>(set);


Неправильный обходной путь
Если попытаться реализовать собственный способ создания множества только для чтения, пришлось бы создать что-то вроде ImmutableHashSet<T> или FrozenSet.
var set = new HashSet<int> { 1, 2, 3 };
var readOnly = set.ToFrozenSet(); // или ToImmutableHashSet()

Хотя технически это работает, у этого две основные проблемы:
1. Неизменяемые коллекции, такие как ImmutableHashSet<T> или FrozenSet, должны cкопировать всю коллекцию в свою память, чтобы гарантировать неизменяемость. Это может быть пустой тратой памяти и циклов процессора.
2. Это не совсем множество только для чтения. «Только для чтения» и «неизменяемый» — это два разных понятия, как мы разобрали недавно.

Подробнее о предложении новинки тут.

Источник: https://steven-giesel.com/blogPost/f368c7d3-488e-4bea-92b4-abf176353fa3/readonlysett-in-net-9
👍11
День 1983. #УрокиРазработки
Уроки 50 Лет Разработки ПО

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

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

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

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

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

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

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

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

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