День 1997. #ЗаметкиНаПолях
Добавляем Аудит NuGet в Проекты .NET.
Аудит безопасности для менеджеров пакетов, таких как NuGet, является критически важным процессом для обеспечения безопасности программных проектов. В NuGet есть функция, которая вам поможет. Она может запустить аудит безопасности с помощью команды dotnet restore, которая сверяет ваши зависимости со списком известных уязвимостей из GitHub Advisory Database.
Если обнаружены уязвимости, вы можете обновить затронутые пакеты до более новой версии, содержащей исправления безопасности, или, если обновления недоступны, проверить, как обезопасить код или использовать альтернативный пакет.
Для аудита NuGet требуется .NET SDK 8.0.100 или более поздней версии. Чтобы включить аудит NuGet для ваших проектов .NET, добавьте в файл проекта следующие свойства:
Вы также можете проверить наличие уязвимостей в своих проектах с помощью команды
Если вы пока не можете устранить уязвимость, можно отключить предупреждение для каждого пакета, используя NoWarn в элементе PackageReference. Если ссылка транзитивная, можно добавить прямую ссылку на пакет с уязвимостью:
Если у вас глобально включены WarningsAsErrors в проекте, но вы не хотите такого поведения для уязвимостей, используйте WarningsNotAsErrors:
Источник: https://www.meziantou.net/enable-nuget-auditing-for-your-dotnet-projects.htm
Добавляем Аудит NuGet в Проекты .NET.
Аудит безопасности для менеджеров пакетов, таких как NuGet, является критически важным процессом для обеспечения безопасности программных проектов. В NuGet есть функция, которая вам поможет. Она может запустить аудит безопасности с помощью команды dotnet restore, которая сверяет ваши зависимости со списком известных уязвимостей из GitHub Advisory Database.
Если обнаружены уязвимости, вы можете обновить затронутые пакеты до более новой версии, содержащей исправления безопасности, или, если обновления недоступны, проверить, как обезопасить код или использовать альтернативный пакет.
Для аудита NuGet требуется .NET SDK 8.0.100 или более поздней версии. Чтобы включить аудит NuGet для ваших проектов .NET, добавьте в файл проекта следующие свойства:
<Project>
<PropertyGroup>
<NuGetAudit>true</NuGetAudit>
<!-- Прямые и транзитивные пакеты -->
<NuGetAuditMode>all</NuGetAuditMode>
<!-- Уровень уязвимостей для отчёта -->
<NuGetAuditLevel>low</NuGetAuditLevel>
<!-- Отменяем сборку в CI при обнаружении уязвимостей -->
<WarningsAsErrors Condition="$(ContinuousIntegrationBuild) == 'true' OR '$(Configuration)' == 'Release'">
(WarningsAsErrors);NU1900;NU1901;NU1902;NU1903;NU1904
</WarningsAsErrors>
</PropertyGroup>
</Project>
Вы также можете проверить наличие уязвимостей в своих проектах с помощью команды
dotnet list package --vulnerable.
Если вы пока не можете устранить уязвимость, можно отключить предупреждение для каждого пакета, используя NoWarn в элементе PackageReference. Если ссылка транзитивная, можно добавить прямую ссылку на пакет с уязвимостью:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="System.Formats.Asn1" Version="8.0.0" NoWarn="NU1903" />
<PackageReference Include="System.Text.Json" Version="8.0.1" />
</ItemGroup>
</Project>
Если у вас глобально включены WarningsAsErrors в проекте, но вы не хотите такого поведения для уязвимостей, используйте WarningsNotAsErrors:
<Project>
<PropertyGroup>
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
</PropertyGroup>
<Project>
Источник: https://www.meziantou.net/enable-nuget-auditing-for-your-dotnet-projects.htm
👍17
День 1998. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 16. Не задокументировав и не согласовав содержимое проекта, нельзя узнать, увеличивается ли его объём. Начало
Наверняка всем приходилось работать над проектом, который постепенно разбухал. Но был ли изначально чётко определён его объем?
Призрак разбухания проекта
Разбухание часто называют главной причиной того, почему реализация проекта не укладывается в запланированный график. Но вы не узнаете, имеет ли место разбухание проекта, если нет согласованной точки отсчёта: «Вот что мы намерены сделать в этот период времени».
Объём проекта можно определить как набор возможностей, с реализацией которого в конкретной итерации, сборке или выпуске продукта соглашаются заинтересованные стороны. Всякий запланированный этап работы начинается с базовой функциональности, которую команда должна реализовать на данном этапе. Это является отправной точкой, позволяющей определять изменение объёма.
Люди традиционно считают разбухание проекта чем-то плохим. Оно свидетельствует о том, что выявленные требования были неполными или неточными и это повлекло поток дополнительных требований. В любом крупном проекте невозможно заранее определить все требования, поэтому должны предполагаться изменение и рост количества требований по мере того, как пользователи пробуют ранние версии, получают новые идеи и лучше понимают задачу. Сдерживание изменений из-за боязни разбухания проекта может привести к созданию продуктов, которые не удовлетворяют все потребности клиентов. Но и непрерывное разбухание может навредить.
В Agile-проектах участники намеренно не пытаются выявить объём всего проекта и определяют объём каждой итерации. В список требований постоянно добавляются новые пункты. Владелец продукта расставляет приоритеты для новых требований с учетом ещё не реализованных, чтобы определить, какие из них должны быть реализованы в ближайших итерациях. Это помогает точно выявить потребности пользователей, но может привести к неопределённости времени доставки конечного продукта.
Как документировать объём
Простейший способ — перечислить функции, запланированные для реализации в следующем спринте. Другие способы:
- контекстная диаграмма, которая ничего не говорит о внутреннем устройстве системы, но идентифицирует пользователей, другие программы и устройства, которые подключаются к ней;
- диаграмма вариантов использования, изображающая действующих лиц за границами системы и варианты их взаимодействия с системой;
- диаграмма экосистемы, показывающая взаимосвязь нескольких систем, что позволяет оценить волновой эффект изменений в системах, которые не взаимодействуют напрямую с вашей;
- журнал реализации требований в итерации, содержащий набор требований, которые команда планирует завершить в течение одной итерации;
- карта пользовательских историй, показывающая детали пользовательских историй, определяющие объём всего продукта, итерации, конкретной функции и т.п.;
- дорожная карта функций, помогающая определить несколько уровней наращивания возможностей для каждой функции, а затем описать область применения конкретного выпуска и перечислить уровни расширения отдельных функций, добавленных в этот выпуск;
- дерево функций, визуально разбивающее основные функции на подфункции, которые планировщик может сгруппировать в целях определения объёма каждого цикла разработки;
- список событий, определяющий внешние события, которые будут обрабатываться при каждом выпуске.
В каждом случае цель определения объема состоит в том, чтобы выяснить, какие возможности должна предоставлять конкретная часть проекта. Выявление этих возможностей помогает установить границу области применения и задать точку отсчёта для рассмотрения изменений в этой области в процессе разработки.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
Уроки 50 Лет Разработки ПО
Урок 16. Не задокументировав и не согласовав содержимое проекта, нельзя узнать, увеличивается ли его объём. Начало
Наверняка всем приходилось работать над проектом, который постепенно разбухал. Но был ли изначально чётко определён его объем?
Призрак разбухания проекта
Разбухание часто называют главной причиной того, почему реализация проекта не укладывается в запланированный график. Но вы не узнаете, имеет ли место разбухание проекта, если нет согласованной точки отсчёта: «Вот что мы намерены сделать в этот период времени».
Объём проекта можно определить как набор возможностей, с реализацией которого в конкретной итерации, сборке или выпуске продукта соглашаются заинтересованные стороны. Всякий запланированный этап работы начинается с базовой функциональности, которую команда должна реализовать на данном этапе. Это является отправной точкой, позволяющей определять изменение объёма.
Люди традиционно считают разбухание проекта чем-то плохим. Оно свидетельствует о том, что выявленные требования были неполными или неточными и это повлекло поток дополнительных требований. В любом крупном проекте невозможно заранее определить все требования, поэтому должны предполагаться изменение и рост количества требований по мере того, как пользователи пробуют ранние версии, получают новые идеи и лучше понимают задачу. Сдерживание изменений из-за боязни разбухания проекта может привести к созданию продуктов, которые не удовлетворяют все потребности клиентов. Но и непрерывное разбухание может навредить.
В Agile-проектах участники намеренно не пытаются выявить объём всего проекта и определяют объём каждой итерации. В список требований постоянно добавляются новые пункты. Владелец продукта расставляет приоритеты для новых требований с учетом ещё не реализованных, чтобы определить, какие из них должны быть реализованы в ближайших итерациях. Это помогает точно выявить потребности пользователей, но может привести к неопределённости времени доставки конечного продукта.
Как документировать объём
Простейший способ — перечислить функции, запланированные для реализации в следующем спринте. Другие способы:
- контекстная диаграмма, которая ничего не говорит о внутреннем устройстве системы, но идентифицирует пользователей, другие программы и устройства, которые подключаются к ней;
- диаграмма вариантов использования, изображающая действующих лиц за границами системы и варианты их взаимодействия с системой;
- диаграмма экосистемы, показывающая взаимосвязь нескольких систем, что позволяет оценить волновой эффект изменений в системах, которые не взаимодействуют напрямую с вашей;
- журнал реализации требований в итерации, содержащий набор требований, которые команда планирует завершить в течение одной итерации;
- карта пользовательских историй, показывающая детали пользовательских историй, определяющие объём всего продукта, итерации, конкретной функции и т.п.;
- дорожная карта функций, помогающая определить несколько уровней наращивания возможностей для каждой функции, а затем описать область применения конкретного выпуска и перечислить уровни расширения отдельных функций, добавленных в этот выпуск;
- дерево функций, визуально разбивающее основные функции на подфункции, которые планировщик может сгруппировать в целях определения объёма каждого цикла разработки;
- список событий, определяющий внешние события, которые будут обрабатываться при каждом выпуске.
В каждом случае цель определения объема состоит в том, чтобы выяснить, какие возможности должна предоставлять конкретная часть проекта. Выявление этих возможностей помогает установить границу области применения и задать точку отсчёта для рассмотрения изменений в этой области в процессе разработки.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍10
День 1999. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 16. Не задокументировав и не согласовав содержимое проекта, нельзя узнать, увеличивается ли его объём. Окончание
Начало
Это входит в рамки проекта?
Поскольку объём так или иначе будет меняться, в каждом проекте должен быть определён практический процесс управления изменениями. Простое добавление требований в список без их анализа может только навредить. Формальный процесс внесения изменений не должен начинаться, пока команда не установит базовые границы определённого объёма работы. До этого момента требования и объём проекта динамично меняются. Во время выполнения каждого этапа работы контроль над изменениями должен становиться всё более строгим, чтобы повысить вероятность достижения базовых целей в соответствии с графиком.
Когда кто-то предлагает новое требование, следует задать вопрос: «Вписывается ли оно в объём?» Есть три возможных ответа.
1. Да, требование явно вписывается в рамки проекта. Предлагаемый функционал необходим для достижения целей в текущем цикле разработки. Поэтому мы должны добавить это требование в список.
2. Нет, требование явно выходит за рамки проекта. Оно не способствует достижению бизнес-целей в текущем цикле, поэтому его не нужно принимать в реализацию прямо сейчас, но можно рассмотреть в дальнейшем или отклонить.
3. Требование не вписывается в текущие рамки проекта, но относится к категории желательных. Спонсор проекта должен принять бизнес-решение: следует ли увеличивать объём проекта ради реализации новых возможностей. Если запрошенное изменение представляет определённую ценность для бизнеса, то правильным будет увеличить объём проекта. Но если объём увеличивается, придётся изменить и что-то ещё: сократить другие функции, изменить расписание, увеличить стоимость и т.п.
Расплывчатые требования = расплывчатый объём проекта
Расплывчатое описание требований чревато нечётким пониманием объёма проекта. Из-за двусмысленности одна сторона может думать, что определённая функция явно входит в рамки проекта, а другая – что нет. Расплывчатые термины, такие как «поддержка», «улучшение», «и т. д.», по сути являются неопределёнными.
Например: «Система должна поддерживать документы Microsoft Word». У читателей могут быть очень разные представления о том, какие именно функции подразумеваются под поддержкой. Владелец продукта строит планы на основе своей интерпретации слова «поддерживать», а позже обнаруживает, что клиент, запросивший эту функцию, имел гораздо более широкие ожидания. Это ведёт к разбуханию проекта? Или просто является уточнением первоначального ожидания? Тщательное описание требований перед принятием обязательств помогает избежать таких проблем.
Мудрые подрядчики предусматривают дополнительное время на разрешение непредвиденных обстоятельств, стараясь учесть возможность некоторого разбухания проекта и расплывчатости требований. Однако увеличение цены проекта, вызываемое такой предусмотрительностью, может оттолкнуть потенциального клиента. В контрактах должно быть чётко указано, как следует оценивать увеличение объёма работ и кто будет за это платить. Чем чётче вы определите, что включено, а что нет, тем легче будет впоследствии разрешить споры о реализованных и нереализованных функциях.
Итого
Изменения случаются, но чрезмерные изменения предполагают, что изначально никто не проработал задачу должным образом. Чёткое определение запланированного объёма работ позволяет команде сосредоточиться на создании ценного результата в рамках графика и бюджетных ограничений и принимать важные бизнес-решения, когда поступают запросы на изменение.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
Уроки 50 Лет Разработки ПО
Урок 16. Не задокументировав и не согласовав содержимое проекта, нельзя узнать, увеличивается ли его объём. Окончание
Начало
Это входит в рамки проекта?
Поскольку объём так или иначе будет меняться, в каждом проекте должен быть определён практический процесс управления изменениями. Простое добавление требований в список без их анализа может только навредить. Формальный процесс внесения изменений не должен начинаться, пока команда не установит базовые границы определённого объёма работы. До этого момента требования и объём проекта динамично меняются. Во время выполнения каждого этапа работы контроль над изменениями должен становиться всё более строгим, чтобы повысить вероятность достижения базовых целей в соответствии с графиком.
Когда кто-то предлагает новое требование, следует задать вопрос: «Вписывается ли оно в объём?» Есть три возможных ответа.
1. Да, требование явно вписывается в рамки проекта. Предлагаемый функционал необходим для достижения целей в текущем цикле разработки. Поэтому мы должны добавить это требование в список.
2. Нет, требование явно выходит за рамки проекта. Оно не способствует достижению бизнес-целей в текущем цикле, поэтому его не нужно принимать в реализацию прямо сейчас, но можно рассмотреть в дальнейшем или отклонить.
3. Требование не вписывается в текущие рамки проекта, но относится к категории желательных. Спонсор проекта должен принять бизнес-решение: следует ли увеличивать объём проекта ради реализации новых возможностей. Если запрошенное изменение представляет определённую ценность для бизнеса, то правильным будет увеличить объём проекта. Но если объём увеличивается, придётся изменить и что-то ещё: сократить другие функции, изменить расписание, увеличить стоимость и т.п.
Расплывчатые требования = расплывчатый объём проекта
Расплывчатое описание требований чревато нечётким пониманием объёма проекта. Из-за двусмысленности одна сторона может думать, что определённая функция явно входит в рамки проекта, а другая – что нет. Расплывчатые термины, такие как «поддержка», «улучшение», «и т. д.», по сути являются неопределёнными.
Например: «Система должна поддерживать документы Microsoft Word». У читателей могут быть очень разные представления о том, какие именно функции подразумеваются под поддержкой. Владелец продукта строит планы на основе своей интерпретации слова «поддерживать», а позже обнаруживает, что клиент, запросивший эту функцию, имел гораздо более широкие ожидания. Это ведёт к разбуханию проекта? Или просто является уточнением первоначального ожидания? Тщательное описание требований перед принятием обязательств помогает избежать таких проблем.
Мудрые подрядчики предусматривают дополнительное время на разрешение непредвиденных обстоятельств, стараясь учесть возможность некоторого разбухания проекта и расплывчатости требований. Однако увеличение цены проекта, вызываемое такой предусмотрительностью, может оттолкнуть потенциального клиента. В контрактах должно быть чётко указано, как следует оценивать увеличение объёма работ и кто будет за это платить. Чем чётче вы определите, что включено, а что нет, тем легче будет впоследствии разрешить споры о реализованных и нереализованных функциях.
Итого
Изменения случаются, но чрезмерные изменения предполагают, что изначально никто не проработал задачу должным образом. Чёткое определение запланированного объёма работ позволяет команде сосредоточиться на создании ценного результата в рамках графика и бюджетных ограничений и принимать важные бизнес-решения, когда поступают запросы на изменение.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 2.
👍3
День 2000.
Вот добрался до очередного юбилея. А вас почти 5,2К. Это немного, ноэто честная работа ценю каждого и стараюсь для каждого.
Напоминаю, что вот здесь подборка тегов, по которым можно искать интересующие темы. Кроме того, можно и простым поиском найти интересующую информацию. За 2000 дней много всего скопилось. А если не нашли, не стесняйтесь спросить в нашем чате.
Спасибо, что читаете, ещё больше спасибо, что лайкаете, комментируете и шерите. А если вдруг кто захочет поддержать, можно это сделать на Boosty, Patreon или просто закинуть напивко кофе.
Вот добрался до очередного юбилея. А вас почти 5,2К. Это немного, но
Напоминаю, что вот здесь подборка тегов, по которым можно искать интересующие темы. Кроме того, можно и простым поиском найти интересующую информацию. За 2000 дней много всего скопилось. А если не нашли, не стесняйтесь спросить в нашем чате.
Спасибо, что читаете, ещё больше спасибо, что лайкаете, комментируете и шерите. А если вдруг кто захочет поддержать, можно это сделать на Boosty, Patreon или просто закинуть на
👍83
День 2001. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 3: Обнаружение Отмены. Начало
Нередко возникает желание определить, действительно ли запрос на отмену что-то отменил. Отмена – это скоординированная операция, и иногда коду, запрашивающему отмену, необходимо знать, действительно ли отмена имела место или операция просто завершилась нормально.
В контракте отмены есть способ сообщить об этом: методы, принимающие CancellationToken, по соглашению должны выдавать исключение OperationCanceledException при их отмене. Это верно для всех методов BCL и должно быть так и в вашем коде.
Ответ на отмену
Самый распространённый сценарий обнаружения отмены — не обрабатывать исключение при отмене. Обычно исключение OperationCanceledException просто игнорируется:
Код выше обработает непредвиденные ошибки, но проигнорирует исключения отмены.
Если код должен сделать что-то другое при отмене, можно обработать это в блоке catch:
Однако подумайте, действительно ли вам нужно это делать, потому что это редкий случай, вызывает вопросы по логике кода, а также такой код сложно тестировать.
Окончание следует…
Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
Отмена. Часть 3: Обнаружение Отмены. Начало
Нередко возникает желание определить, действительно ли запрос на отмену что-то отменил. Отмена – это скоординированная операция, и иногда коду, запрашивающему отмену, необходимо знать, действительно ли отмена имела место или операция просто завершилась нормально.
В контракте отмены есть способ сообщить об этом: методы, принимающие CancellationToken, по соглашению должны выдавать исключение OperationCanceledException при их отмене. Это верно для всех методов BCL и должно быть так и в вашем коде.
Ответ на отмену
Самый распространённый сценарий обнаружения отмены — не обрабатывать исключение при отмене. Обычно исключение OperationCanceledException просто игнорируется:
async Task TryDoSomethingAsync()
{
using CancellationTokenSource cts = new();
// Создаём что-то, что может отменять cts
try
{
await DoAsync(cts.Token);
}
catch (Exception ex) when
(ex is not OperationCanceledException)
{
// Обработка исключения
}
}
Код выше обработает непредвиденные ошибки, но проигнорирует исключения отмены.
Если код должен сделать что-то другое при отмене, можно обработать это в блоке catch:
async Task DoSomethingAsync()
{
using CancellationTokenSource cts = new();
// Создаём что-то, что может отменять cts
try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException)
{
// Обработка отмены
}
catch (Exception ex)
{
// Обработка исключения
}
}
Однако подумайте, действительно ли вам нужно это делать, потому что это редкий случай, вызывает вопросы по логике кода, а также такой код сложно тестировать.
Окончание следует…
Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
👍8
День 2002. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 3: Обнаружение Отмены. Окончание
Начало
TaskCanceledException
Существует ещё один тип исключения для отмены: TaskCanceledException. Он выбрасывается некоторыми API вместо OperationCanceledException.
Как правило, рекомендуется не использовать TaskCanceledException. Некоторые API просто вызывают OperationCanceledException, даже если они имеют дело с отменёнными задачами. А поскольку TaskCanceledException является производным от OperationCanceledException, код обработчика исключений отмены может просто перехватывать OperationCanceledException, игнорировать TaskCanceledException, и это будет работать везде.
Внимание: не перехватывайте TaskCanceledException. Вместо этого перехватывайте OperationCanceledException.
OperationCanceledException.CancellationToken
OperationCanceledException имеет свойство CancellationToken. Это токен, вызвавший отмену. Это в том случае, если он установлен. Не все API устанавливают это значение для выбрасываемых ими исключений.
Если вашему коду необходимо определить, отменяет ли операцию он сам или это делает какой-то другой код, у вас может возникнуть соблазн использовать это свойство. Но лучше игнорировать его. Когда используются связанные токены отмены (об этом далее в этой серии), вполне возможно, что токен в этом свойстве на самом деле не является основной причиной отмены:
В коде выше есть проблема: в зависимости от реализации DoAsync возможно, что отмена cts приведёт к тому, что DoAsync выдаст исключение OperationCanceledException, но токен, на который ссылается это исключение, будет отличаться от токена cts.
Если вам действительно необходимо выполнить специальную обработку случая, когда происходит отмена через конкретный токен, попробуйте что-то вроде этого:
Технически, здесь мы проверяем не «привёл ли мой токен к отмене операции», а скорее «произошла ли отмена и запрашивал ли мой токен отмену». Но в подавляющем большинстве случаев такой проверки достаточно.
Внимание: не используйте OperationCanceledException.CancellationToken. Он может работать не так, как вы ожидаете.
Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
Отмена. Часть 3: Обнаружение Отмены. Окончание
Начало
TaskCanceledException
Существует ещё один тип исключения для отмены: TaskCanceledException. Он выбрасывается некоторыми API вместо OperationCanceledException.
Как правило, рекомендуется не использовать TaskCanceledException. Некоторые API просто вызывают OperationCanceledException, даже если они имеют дело с отменёнными задачами. А поскольку TaskCanceledException является производным от OperationCanceledException, код обработчика исключений отмены может просто перехватывать OperationCanceledException, игнорировать TaskCanceledException, и это будет работать везде.
Внимание: не перехватывайте TaskCanceledException. Вместо этого перехватывайте OperationCanceledException.
OperationCanceledException.CancellationToken
OperationCanceledException имеет свойство CancellationToken. Это токен, вызвавший отмену. Это в том случае, если он установлен. Не все API устанавливают это значение для выбрасываемых ими исключений.
Если вашему коду необходимо определить, отменяет ли операцию он сам или это делает какой-то другой код, у вас может возникнуть соблазн использовать это свойство. Но лучше игнорировать его. Когда используются связанные токены отмены (об этом далее в этой серии), вполне возможно, что токен в этом свойстве на самом деле не является основной причиной отмены:
async Task DoSomethingAsync()
{
// Плохой код, не делайте так
using CancellationTokenSource cts = new();
// Создаём что-то, что может отменять cts
try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException ex)
when (ex.CancellationToken == cts.Token)
{
// Обработка отмены «нашим» токеном
}
}
В коде выше есть проблема: в зависимости от реализации DoAsync возможно, что отмена cts приведёт к тому, что DoAsync выдаст исключение OperationCanceledException, но токен, на который ссылается это исключение, будет отличаться от токена cts.
Если вам действительно необходимо выполнить специальную обработку случая, когда происходит отмена через конкретный токен, попробуйте что-то вроде этого:
async Task DoSomethingAsync()
{
using CancellationTokenSource cts = new();
// Создаём что-то, что может отменять cts
try
{
await DoAsync(cts.Token);
}
catch (OperationCanceledException ex)
when (cts.IsCancellationRequested)
{
// Обработка отмены «нашим» токеном
}
}
Технически, здесь мы проверяем не «привёл ли мой токен к отмене операции», а скорее «произошла ли отмена и запрашивал ли мой токен отмену». Но в подавляющем большинстве случаев такой проверки достаточно.
Внимание: не используйте OperationCanceledException.CancellationToken. Он может работать не так, как вы ожидаете.
Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
👍12
День 2003. #ЧтоНовенького #CSharp13
Regex.EnumerateSplits
В 6м превью .NET 9 представлен новый метод класса Regex, который позволяет разбивать входные данные по шаблону без выделения памяти.
Класс Regex предоставляет метод Split, по концепции аналогичный методу String.Split. С помощью String.Split вы предоставляете один или несколько разделителей (символов или строк), и реализация разбивает входной текст по этим разделителям. Вот пример использования String.Split:
который выводит:
Regex предоставляет аналогичный метод Split, но вместо указания разделителя в виде символа или строки он указывается как шаблон регулярного выражения:
Вывод:
Однако Regex.Split принимает в качестве входных данных только строки и не поддерживает входные данные в виде ReadOnlySpan<char>, а также выводит полный набор результатов в виде массива строк, что требует выделения как массива строк для хранения результатов, так и выделения строк для каждого результата. В .NET 9 новый метод EnumerateSplits позволяет выполнять ту же операцию, но с входными данными в виде ReadOnlySpan<char> и без какого-либо выделения памяти. Он принимает ReadOnlySpan<char> и возвращает перечислитель (ValueSplitEnumerator) результатов в виде диапазона (Range). В следующем примере показано использование Regex.EnumerateSplits:
Вывод будет такой же, как в предыдущем примере, но без выделения памяти. Здесь r – диапазон, например, {0..1} для первого результата, и на консоль выводится срез входных данных, соответствующий диапазону (это результат типа ReadOnlySpan<char>, поэтому он помещён в интерполированную строку для использования в Console.WriteLine.
Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview6/libraries.md
Regex.EnumerateSplits
В 6м превью .NET 9 представлен новый метод класса Regex, который позволяет разбивать входные данные по шаблону без выделения памяти.
Класс Regex предоставляет метод Split, по концепции аналогичный методу String.Split. С помощью String.Split вы предоставляете один или несколько разделителей (символов или строк), и реализация разбивает входной текст по этим разделителям. Вот пример использования String.Split:
foreach (var s in "Hello, world! How are you?".Split('w'))
{
Console.WriteLine(s);
}
который выводит:
Hello,
orld! Ho
are you?
Regex предоставляет аналогичный метод Split, но вместо указания разделителя в виде символа или строки он указывается как шаблон регулярного выражения:
foreach (var s in Regex.Split(
"Hello, world! How are you?", "[aeiou]"))
{
Console.WriteLine(s);
}
Вывод:
H
ll
, w
rld! H
w
r
y
?
Однако Regex.Split принимает в качестве входных данных только строки и не поддерживает входные данные в виде ReadOnlySpan<char>, а также выводит полный набор результатов в виде массива строк, что требует выделения как массива строк для хранения результатов, так и выделения строк для каждого результата. В .NET 9 новый метод EnumerateSplits позволяет выполнять ту же операцию, но с входными данными в виде ReadOnlySpan<char> и без какого-либо выделения памяти. Он принимает ReadOnlySpan<char> и возвращает перечислитель (ValueSplitEnumerator) результатов в виде диапазона (Range). В следующем примере показано использование Regex.EnumerateSplits:
ReadOnlySpan<char> input =
"Hello, world! How are you?";
foreach (Range r in
Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"{input[r]}");
}
Вывод будет такой же, как в предыдущем примере, но без выделения памяти. Здесь r – диапазон, например, {0..1} для первого результата, и на консоль выводится срез входных данных, соответствующий диапазону (это результат типа ReadOnlySpan<char>, поэтому он помещён в интерполированную строку для использования в Console.WriteLine.
Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview6/libraries.md
👍21
День 2004. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Проектирование. Начало
Если цель выявления требований состоит в определении характеристик, которыми должны обладать решения, то суть проектирования — в разработке этих решений. Однако между требованиями и проектным решением не чёткая граница, а размытая серая зона. Во время исследования требований полезно предпринять некоторые предварительные действия в области проектирования, например создать прототипы. Пользователи, взаимодействующие с прототипом, часто замечают, что это проясняет их мышление и способствует появлению новых идей, поскольку прототип более осязаем, чем абстрактный список требований.
Суть проектирования ПО в том, чтобы связать требования с фрагментами кода, но переход даже от чётко описанных требований к конкретным элементам проектного решения не является ни простым, ни очевидным. Одни виды деятельности, например проектирование БД, носят систематический и аналитический характер. Другие более органичны: проектное решение выкристаллизовывается постепенно, по мере того как разработчики исследуют переход от задачи к решению. А проектирование UX/UI включает художественно-творческие подходы, основанные на глубоком понимании человеческого фактора.
Виды проектирования ПО
- архитектурное,
- техническое (низкоуровневое),
- проектирование БД,
- проектирование UX/UI.
В каждом есть многочисленные ограничения: требования совместимости с другими продуктами, применимые стандарты, технологические ограничения, бизнес-политики и правила, ограничения по стоимости и ресурсам и т.п. Ограничения усложняют проектирование, сообщая разработчикам, чего они не могут делать, точно так же как требования диктуют, что должна делать реализация.
1. Архитектурное проектирование
Определяет структуру системы и её компоненты, или архитектурные элементы. Элементы состоят из модулей кода, которые могут быть объединены в несколько взаимосвязанных подсистем в крупном продукте. Архитектурное проектирование предусматривает разделение системы на компоненты, определение обязанностей каждого компонента и назначение конкретных требований соответствующим компонентам, а также определение интерфейсов между компонентами.
2. Техническое проектирование
Проработка логической структуры отдельных компонентов (модулей, классов и их методов, сценариев и т. д.) и деталей интерфейсов между модулями, разработка алгоритмов.
3. Проектирование БД
На этом этапе определяются сущности и отношения между ними, а также элементы данных каждой сущности, их типы, свойства и логические связи, определяются CRUD (create, read, update, delete) процедуры.
4. Проектирование UX/UI
Проектирование пользовательского опыта само по себе является обширной дисциплиной. Архитектура пользовательского интерфейса определяет диалоговые элементы (точки взаимодействия пользователя с системой) и пути навигации между ними, повторяющие сценарии решения пользовательских задач. При техническом проектировании UI учитывается специфика взаимодействий пользователя с продуктом, в том числе продумываются эскизы экрана, внешний вид, элементы управления и свойства отдельных текстовых блоков, графики, поля ввода и экраны вывода результатов. Оба этапа проектирования UI — архитектурное и техническое — определяют воспринимаемую пользователем простоту обучения и использования или в совокупности — удобство использования (UX).
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Проектирование. Начало
Если цель выявления требований состоит в определении характеристик, которыми должны обладать решения, то суть проектирования — в разработке этих решений. Однако между требованиями и проектным решением не чёткая граница, а размытая серая зона. Во время исследования требований полезно предпринять некоторые предварительные действия в области проектирования, например создать прототипы. Пользователи, взаимодействующие с прототипом, часто замечают, что это проясняет их мышление и способствует появлению новых идей, поскольку прототип более осязаем, чем абстрактный список требований.
Суть проектирования ПО в том, чтобы связать требования с фрагментами кода, но переход даже от чётко описанных требований к конкретным элементам проектного решения не является ни простым, ни очевидным. Одни виды деятельности, например проектирование БД, носят систематический и аналитический характер. Другие более органичны: проектное решение выкристаллизовывается постепенно, по мере того как разработчики исследуют переход от задачи к решению. А проектирование UX/UI включает художественно-творческие подходы, основанные на глубоком понимании человеческого фактора.
Виды проектирования ПО
- архитектурное,
- техническое (низкоуровневое),
- проектирование БД,
- проектирование UX/UI.
В каждом есть многочисленные ограничения: требования совместимости с другими продуктами, применимые стандарты, технологические ограничения, бизнес-политики и правила, ограничения по стоимости и ресурсам и т.п. Ограничения усложняют проектирование, сообщая разработчикам, чего они не могут делать, точно так же как требования диктуют, что должна делать реализация.
1. Архитектурное проектирование
Определяет структуру системы и её компоненты, или архитектурные элементы. Элементы состоят из модулей кода, которые могут быть объединены в несколько взаимосвязанных подсистем в крупном продукте. Архитектурное проектирование предусматривает разделение системы на компоненты, определение обязанностей каждого компонента и назначение конкретных требований соответствующим компонентам, а также определение интерфейсов между компонентами.
2. Техническое проектирование
Проработка логической структуры отдельных компонентов (модулей, классов и их методов, сценариев и т. д.) и деталей интерфейсов между модулями, разработка алгоритмов.
3. Проектирование БД
На этом этапе определяются сущности и отношения между ними, а также элементы данных каждой сущности, их типы, свойства и логические связи, определяются CRUD (create, read, update, delete) процедуры.
4. Проектирование UX/UI
Проектирование пользовательского опыта само по себе является обширной дисциплиной. Архитектура пользовательского интерфейса определяет диалоговые элементы (точки взаимодействия пользователя с системой) и пути навигации между ними, повторяющие сценарии решения пользовательских задач. При техническом проектировании UI учитывается специфика взаимодействий пользователя с продуктом, в том числе продумываются эскизы экрана, внешний вид, элементы управления и свойства отдельных текстовых блоков, графики, поля ввода и экраны вывода результатов. Оба этапа проектирования UI — архитектурное и техническое — определяют воспринимаемую пользователем простоту обучения и использования или в совокупности — удобство использования (UX).
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍2
День 2004. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Проектирование. Окончание
Начало
У вас хороший проект?
Проектирование предполагает разработку оптимального решения, которое будет соответствовать множеству требований в течение всего срока службы продукта. Проектное решение должно позволять реализовать правильную функциональность и достичь ожидаемых характеристик всех атрибутов качества. Кроме того, проектное решение должно эффективно обеспечивать расширение и изменение как в процессе разработки, так и после выпуска.
Соблюдение принципов, изложенных ниже, способствует уменьшению сложности проектного решения, повышению его надёжности и упрощению понимания, изменения, расширения и повторного использования.
1. Разделение обязанностей
Проект должен быть разделён на модули, независимые друг от друга и имеющие чётко определённые и не пересекающиеся обязанности.
2. Сокрытие информации
Каждый модуль должен скрывать внутренние детали своих данных и алгоритмов от остальной системы. Другие модули должны получать доступ к данным и сервисам модуля только через чётко определённый интерфейс. В этом случае реализацию каждого модуля можно изменить при необходимости, не затрагивая другие модули, которые его вызывают.
3. Слабая связанность
Хорошо продуманный модульный проект демонстрирует слабую связанность между компонентами, поэтому изменение одного модуля требует минимальных изменений других.
4. Сильная связность
Модуль, для которого характерна сильная связность, идеально выполняет одну чётко определённую задачу.
5. Абстракция
Позволяет разработчикам писать код, не зависящий от конкретных деталей реализации, таких как тип ОС или UI. Абстракция упрощает переносимость и повторное использование.
6. Чётко определённые и удобные интерфейсы
Позволяют разработчикам других модулей легко обращаться к сервисам данного модуля, а также упрощают замену модуля, когда это необходимо, поскольку интерфейс, который он предоставляет остальной части системы, остаётся неизменным. Тот же принцип применяется к внешним интерфейсам, которые система предоставляет вовне.
Не спешите переходить от требований сразу к коду. Найдите время, оцените альтернативные варианты проектного решения и выберите наиболее подходящее.
Первые шаги
1. Перечислите приёмы проектирования, в которых особенно преуспела ваша организация. Задокументирована ли информация об этих приёмах? Доступна ли она другим членам команды, чтобы они могли ознакомиться с ними и применять на практике?
2. Определите любые проблемы, отмечавшиеся в команде и связанные с архитектурным, техническим проектированием, проектированием БД и UX.
3. Опишите, как каждая проблема влияет на вашу способность успешно завершать проекты. Как они мешают достижению успеха разработчикам и клиентам? Огрехи в проектном решении могут привести к снижению надёжности системы, что нелегко исправить, низкой производительности, дублированию кода, несоответствиям внутри продукта или между связанными продуктами и к проблемам с удобством использования.
4. Для каждой проблемы, определите основные причины, провоцирующие или усугубляющие её. Вы можете найти несколько основных причин, способствующих появлению одной и той же проблемы, или несколько проблем, обусловленных одной общей причиной.
5. Изучая лучшие практики проектирования, отмечайте, как они могут быть полезны вашей команде.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Проектирование. Окончание
Начало
У вас хороший проект?
Проектирование предполагает разработку оптимального решения, которое будет соответствовать множеству требований в течение всего срока службы продукта. Проектное решение должно позволять реализовать правильную функциональность и достичь ожидаемых характеристик всех атрибутов качества. Кроме того, проектное решение должно эффективно обеспечивать расширение и изменение как в процессе разработки, так и после выпуска.
Соблюдение принципов, изложенных ниже, способствует уменьшению сложности проектного решения, повышению его надёжности и упрощению понимания, изменения, расширения и повторного использования.
1. Разделение обязанностей
Проект должен быть разделён на модули, независимые друг от друга и имеющие чётко определённые и не пересекающиеся обязанности.
2. Сокрытие информации
Каждый модуль должен скрывать внутренние детали своих данных и алгоритмов от остальной системы. Другие модули должны получать доступ к данным и сервисам модуля только через чётко определённый интерфейс. В этом случае реализацию каждого модуля можно изменить при необходимости, не затрагивая другие модули, которые его вызывают.
3. Слабая связанность
Хорошо продуманный модульный проект демонстрирует слабую связанность между компонентами, поэтому изменение одного модуля требует минимальных изменений других.
4. Сильная связность
Модуль, для которого характерна сильная связность, идеально выполняет одну чётко определённую задачу.
5. Абстракция
Позволяет разработчикам писать код, не зависящий от конкретных деталей реализации, таких как тип ОС или UI. Абстракция упрощает переносимость и повторное использование.
6. Чётко определённые и удобные интерфейсы
Позволяют разработчикам других модулей легко обращаться к сервисам данного модуля, а также упрощают замену модуля, когда это необходимо, поскольку интерфейс, который он предоставляет остальной части системы, остаётся неизменным. Тот же принцип применяется к внешним интерфейсам, которые система предоставляет вовне.
Не спешите переходить от требований сразу к коду. Найдите время, оцените альтернативные варианты проектного решения и выберите наиболее подходящее.
Первые шаги
1. Перечислите приёмы проектирования, в которых особенно преуспела ваша организация. Задокументирована ли информация об этих приёмах? Доступна ли она другим членам команды, чтобы они могли ознакомиться с ними и применять на практике?
2. Определите любые проблемы, отмечавшиеся в команде и связанные с архитектурным, техническим проектированием, проектированием БД и UX.
3. Опишите, как каждая проблема влияет на вашу способность успешно завершать проекты. Как они мешают достижению успеха разработчикам и клиентам? Огрехи в проектном решении могут привести к снижению надёжности системы, что нелегко исправить, низкой производительности, дублированию кода, несоответствиям внутри продукта или между связанными продуктами и к проблемам с удобством использования.
4. Для каждой проблемы, определите основные причины, провоцирующие или усугубляющие её. Вы можете найти несколько основных причин, способствующих появлению одной и той же проблемы, или несколько проблем, обусловленных одной общей причиной.
5. Изучая лучшие практики проектирования, отмечайте, как они могут быть полезны вашей команде.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍8
Техника работодателя и работа на ней... #холивар
(С)тянуто из соседнего чатика, надеюсь, там поймут и простят.
(С)тянуто из соседнего чатика, надеюсь, там поймут и простят.
Anonymous Poll
32%
Работодатель обязан предоставить хорошую технику в любом случае
23%
Работодатель обязан предоставить технику хотя бы в офисе (на удалёнке, так и быть, буду на своей)
12%
Если работодатель даст, могу работать на ней, если нет, буду на своей
27%
Если работодатель требует уснавку ВПН, и прочих трекеров, пусть даёт технику
5%
То, что предлагают работодатели, меня не устраивает, работаю только на своей технике
1%
Свой вариант (напишу в комментариях)
День 2006. #HotKeys
Горячие Клавиши Visual Studio
Давным-давно на канале уже были подобные посты, но не грех и повторить. Тем более, что в IDE появились новые функции.
Управляем буфером обмена
В Visual Studio есть буфер обмена, куда можно копировать несколько значений, и вставлять их через Ctrl+Shift+V, однако в Windows есть свой буфер, куда можно копировать как обычно через
Вырезать целую строку, не выделяя весь текст в ней, можно просто поставив курсор на строку и нажав
Редактирование
Навигация
Для ASP.NET:
Рефакторинг
Скрипты
"prop",
"ctor",
Также
Отладка
Какие горячие клавиши вы чаще всего используете? Добавляйте в комментарии.
Горячие Клавиши Visual Studio
Давным-давно на канале уже были подобные посты, но не грех и повторить. Тем более, что в IDE появились новые функции.
Управляем буфером обмена
В Visual Studio есть буфер обмена, куда можно копировать несколько значений, и вставлять их через Ctrl+Shift+V, однако в Windows есть свой буфер, куда можно копировать как обычно через
Ctrl+C
, и вставлять несколько значений в любое приложение с помощью Win+V
. Что удобнее – решать вам.Вырезать целую строку, не выделяя весь текст в ней, можно просто поставив курсор на строку и нажав
Ctrl+L
(хотя у меня работает и Ctrl+X
). В обоих случаях строка будет вырезана в буфер обмена. Просто удалить строку, не добавляя её в буфер обмена можно с помощью Ctrl+Shift+L
.Редактирование
Ctrl+K,C
– закомментировать строку,Ctrl+K,U
– раскомментировать строку,Ctrl+D
– продублировать строку,Ctrl+K,F
– отформатировать выделенный блок,Ctrl+K,D
– отформатировать весь файл,Ctrl+K,E
– очистка кода в файле (по заданным вами правилам, например, удаление ненужных using),Ctrl+K,S
– вставка скрипта (блок if, try, for и т.п.), либо заключить выделенный код в блок.Alt+<стрелки вверх/вниз>
- переместить строку вверх/вниз,Alt+Shift+<стрелки>
(Alt+<выделение мышью>
) - редактирование по колонкам,Ctrl+Alt+<поместить курсор мышью>
- многокурсорный ввод (редактирование в нескольких местах одновременно).Навигация
Ctrl+G
– перейти к линии,Ctrl+K,K
– оставить закладку,Ctrl+K,N
– перейти к следующей закладке,Ctrl+,
(Ctrl+T
) - поиск по коду в проекте,Ctrl+Q
– поиск по инструментам IDE,F12
(Ctrl+щелчок мыши
) – перейти к определению,Ctrl+F12
– перейти к реализации,Alt+F12
– подсказка реализации во всплывающем окне,Shift+F12
– найти использования,Ctrl+Shift+пробел
– подсказка по аргументам метода,Ctrl+[,X
(Ctrl+Alt+L
) – отметить текущий файл в обозревателе решения,Ctrl+;
- поиск в обозревателе решения,Ctrl+M,O
- свернуть все методы,Ctrl+M,P
- развернуть все методы,Ctrl+M,M
– свернуть/развернуть текущий метод.Для ASP.NET:
Ctrl+M,G
– перейти из метода контроллера в представление или наоборот.Рефакторинг
Ctrl+.
(Alt+Enter
) – подсказки рефакторинга,Ctrl+R,R
– переименовать,Ctrl+R,M
– извлечь выделенный код в метод,Ctrl+R,I
- извлечь интерфейс из класса,Ctrl+R,V (Ctrl+R,O)
- реорганизовать параметры метода.Скрипты
"prop",
Tab,Tab
– создать свойство,"ctor",
Tab,Tab
– создать конструктор,Также
Tab,Tab
работает для других популярных ключевых слов (if, for, switch и т.п.)Отладка
F9
– создать точку останова,Ctrl+Alt+B
– окно со всеми точками останова,Ctrl+Shift+F9
– удалить все точки останова.Какие горячие клавиши вы чаще всего используете? Добавляйте в комментарии.
👍29👎1
День 2007. #ЗаметкиНаПолях
Остановка PowerShell Скрипта при Ошибке
По умолчанию PowerShell не останавливает скрипт при возникновении ошибки. Вместо этого он записывает ошибку в поток ошибок и продолжает выполнение скрипта. Вы можете изменить это поведение, установив переменную
PowerShell имеет два разных вида команд: командлеты (cmdlet) и нативные команды. Например,
Если задать
Можно переопределить поведение
Чтобы обойти поведение
Источник: https://www.meziantou.net/stop-the-script-when-an-error-occurs-in-powershell.htm
Остановка PowerShell Скрипта при Ошибке
По умолчанию PowerShell не останавливает скрипт при возникновении ошибки. Вместо этого он записывает ошибку в поток ошибок и продолжает выполнение скрипта. Вы можете изменить это поведение, установив переменную
$ErrorActionPreference
в значение Stop
. Тогда PowerShell остановит скрипт при возникновении ошибки.PowerShell имеет два разных вида команд: командлеты (cmdlet) и нативные команды. Например,
Get-ChildItem
— это командлет, а git
— нативная команда. Начиная с первой версии PowerShell, переменная $ErrorActionPreference управляла поведением только командлетов. В PowerShell 7.3 была введена переменная $PSNativeCommandUseErrorActionPreference для управления поведением нативных команд.Если задать
$ErrorActionPreference
значение Stop
, PowerShell остановит скрипт, когда командлет выдаст ошибку. Если задать $PSNativeCommandUseErrorActionPreference
в значение true
, PowerShell также остановит скрипт, когда нативная команда выдаст ошибку. Хорошей практикой будет установить обе переменные в начале скрипта:# Остановить скрипт при ошибке
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
Можно переопределить поведение
$ErrorActionPreference
для каждого командлета, используя параметр -ErrorAction
. Например, следующая команда не останавливает скрипт, если Get-ChildItem
завершится с ошибкой:Get-ChildItem dummy -ErrorAction SilentlyContinue
Чтобы обойти поведение
$PSNativeCommandUseErrorActionPreference
для определённой нативной команды, вы можете использовать синтаксис & { }
для запуска команды внутри блока скрипта. Внутри блока скрипта вы можете изменить значение $PSNativeCommandUseErrorActionPreference
на false
. Когда блок скрипта завершается, переменная возвращается к своему предыдущему значению.& {
# Изменяем $PSNativeCommandUseErrorActionPreference в этом блоке
$PSNativeCommandUseErrorActionPreference = $false
robocopy.exe dummy1 dummy2
if ($LASTEXITCODE -gt 8) {
throw "robocopy failed with exit code $LASTEXITCODE"
}
}
Источник: https://www.meziantou.net/stop-the-script-when-an-error-occurs-in-powershell.htm
👍17
День 2008. #ЗаметкиНаПолях #Cancellation
Отмена. Часть 4: Проверка отмены
В большинстве случаев получаемый токен отмены просто передаётся на более низкий уровень. Но если нужен отменяемый код на самом низком уровне, есть несколько вариантов.
Обычный шаблон — периодически вызывать ThrowIfCancellationRequested:
В коде выше проверяется токен отмены до начала работы, что является хорошей практикой. Возможно, токен уже отменён к моменту начала выполнения вашей операции.
ThrowIfCancellationRequested проверит, запрошена ли отмена, и если да, то выдаст OperationCanceledException. Ваш код должен просто позволить этому исключению распространиться из метода.
Главный вопрос — как часто проверять. На него нет хорошего ответа; в идеале - несколько раз в секунду, но в общем случае - просто поместите его в лучшее место(а) и запустите несколько тестов, чтобы увидеть, достаточно ли отзывчива отмена.
Как не делать проверку
Существует антишаблон пулинга на предмет отмены в бесконечном цикле:
Код будет периодически проверять токен отмены; и при отмене происходит выход из метода. Этот метод не удовлетворяет контракту отмены, требующему выбрасывать исключение при отмене. Т.е. вызывающий код не может знать, был ли метод выполнен до конца или был отменён. Правильное решение — использовать ThrowIfCancellationRequested, даже для бесконечных циклов (как в предыдущем примере).
Когда проверять
Проверка - подходящий вариант для наблюдения за отменой, если код синхронный, например, код вычислительной операции. Но отмена в .NET применима как к синхронному коду, так и к асинхронному. Фактически, параллельные циклы и PLINQ имеют встроенную поддержку отмены: ParallelOptions.CancellationToken для Parallel и WithCancellation для PLINQ.
Уместно вводить ThrowIfCancellationRequested в асинхронный код, если вы не уверены, будут ли другие методы учитывать токен отмены. Помните, что метод, принимающий CancellationToken может учитывать отмену, но может и проигнорировать его. Поэтому в вашем коде можно добавить проверки отмены между «шагами»:
Хотя вы можете разбросать вызовы ThrowIfCancellationRequested по всему коду, как в примере выше, лучше это делать, только когда тестирование показывает, что код не учитывает отмену. Т.е. предполагайте, что DoStep1Async и DoStep2Async будут учитывать отмену, пока тестирование не докажет обратное.
Также уместно использовать ThrowIfCancellationRequested в определённых точках, где ваш код собирается сделать что-то затратное. Простое добавление туда проверки отмены означает, что вашему коду не придётся выполнять затратную работу, если он всё равно будет отменён.
Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
Отмена. Часть 4: Проверка отмены
В большинстве случаев получаемый токен отмены просто передаётся на более низкий уровень. Но если нужен отменяемый код на самом низком уровне, есть несколько вариантов.
Обычный шаблон — периодически вызывать ThrowIfCancellationRequested:
void DoSomething(CancellationToken ct)
{
while (!done)
{
ct.ThrowIfCancellationRequested();
Thread.Sleep(200); // некоторая работа
}
}
В коде выше проверяется токен отмены до начала работы, что является хорошей практикой. Возможно, токен уже отменён к моменту начала выполнения вашей операции.
ThrowIfCancellationRequested проверит, запрошена ли отмена, и если да, то выдаст OperationCanceledException. Ваш код должен просто позволить этому исключению распространиться из метода.
Главный вопрос — как часто проверять. На него нет хорошего ответа; в идеале - несколько раз в секунду, но в общем случае - просто поместите его в лучшее место(а) и запустите несколько тестов, чтобы увидеть, достаточно ли отзывчива отмена.
Как не делать проверку
Существует антишаблон пулинга на предмет отмены в бесконечном цикле:
void DoSomethingForever(CancellationToken ct)
{
// плохой код, не делайте так
while (!ct.IsCancellationRequested)
Thread.Sleep(200); // некоторая работа
}
Код будет периодически проверять токен отмены; и при отмене происходит выход из метода. Этот метод не удовлетворяет контракту отмены, требующему выбрасывать исключение при отмене. Т.е. вызывающий код не может знать, был ли метод выполнен до конца или был отменён. Правильное решение — использовать ThrowIfCancellationRequested, даже для бесконечных циклов (как в предыдущем примере).
Когда проверять
Проверка - подходящий вариант для наблюдения за отменой, если код синхронный, например, код вычислительной операции. Но отмена в .NET применима как к синхронному коду, так и к асинхронному. Фактически, параллельные циклы и PLINQ имеют встроенную поддержку отмены: ParallelOptions.CancellationToken для Parallel и WithCancellation для PLINQ.
Уместно вводить ThrowIfCancellationRequested в асинхронный код, если вы не уверены, будут ли другие методы учитывать токен отмены. Помните, что метод, принимающий CancellationToken может учитывать отмену, но может и проигнорировать его. Поэтому в вашем коде можно добавить проверки отмены между «шагами»:
async Task DoComplexWorkAsync(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
await DoStep1Async(ct);
ct.ThrowIfCancellationRequested();
await DoStep2Async(ct);
}
Хотя вы можете разбросать вызовы ThrowIfCancellationRequested по всему коду, как в примере выше, лучше это делать, только когда тестирование показывает, что код не учитывает отмену. Т.е. предполагайте, что DoStep1Async и DoStep2Async будут учитывать отмену, пока тестирование не докажет обратное.
Также уместно использовать ThrowIfCancellationRequested в определённых точках, где ваш код собирается сделать что-то затратное. Простое добавление туда проверки отмены означает, что вашему коду не придётся выполнять затратную работу, если он всё равно будет отменён.
Источник: https://blog.stephencleary.com/2022/03/cancellation-3-detecting-cancellation.html
👍29
День 2009. #ЧтоНовенького
Предложение Типов-Объединений в C#
Бывает, что вы хотите сохранить в переменной значение не всегда одного и того же типа. Может потребоваться сохранить один из нескольких связанных типов в зависимости от того, что данные должны представлять в конкретный момент.
Например, приложение может определить клиента и поставщика, которые имеют несколько общих свойств, и может потребоваться выполнить общую операцию над обоими типами, но с учётом их различий.
Обычно здесь выделяют общие члены в абстрактный класс или интерфейс, и делают разных наследников. Но это хорошо, только когда эти типы существуют в первую очередь для цели операции или имеет смысл, чтобы операция отображалась как часть типа. Если типы имеют более широкое назначение, загрязнение их такими методами может быть нежелательным. Кроме того, вы можете не владеть определениями типов, либо у вас слишком много похожих ситуаций и вы можете решить только одну из них через наследование, либо вы решили не пропускать требования конкретной операции в определение данных, тогда единственный простой выбор, который у вас есть, — объявить переменную как object и позволить ей быть чем угодно.
Альтернативой является создание одной и той же логики для обработки обоих типов, и объявление параметра или переменной, которая может содержать любой из типов. Было бы хорошо, если бы язык предоставлял способ объявить тип, который хранит значение одного из ограниченного набора других типов, и при этом сохранял бы безопасность типов. Многие другие языки уже делают это. Обычно такие специальные типы называют discriminated union (дискриминированными/размеченными объединениями) или просто объединениями типов.
Недавно было предложено (точнее уже давно, сейчас предложение просто обновили) добавить в C# 4 типа объединений:
1. Стандартные
Именованное объединение типов, которое объявляет все типы своих членов в одном объявлении (похоже на расширенный enum):
2. Структуры
Аналогично классу, но объединение и типы членов являются структурами.
3. «По требованию» (Ad hoc)
Анонимные объединения типов, объявляемые в любом месте.
4. Пользовательские
Если необходимо объявить тип объединения, который не может быть указан как один из типов выше, можно объявить пользовательский тип объединения.
Но не спешите радоваться, это пока только обновлённое предложение. Так что завезут его в лучшем случае через пару релизов (лет), и то, скорей всего, сначала в виде превью.
Источник: https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md
Предложение Типов-Объединений в C#
Бывает, что вы хотите сохранить в переменной значение не всегда одного и того же типа. Может потребоваться сохранить один из нескольких связанных типов в зависимости от того, что данные должны представлять в конкретный момент.
Например, приложение может определить клиента и поставщика, которые имеют несколько общих свойств, и может потребоваться выполнить общую операцию над обоими типами, но с учётом их различий.
Обычно здесь выделяют общие члены в абстрактный класс или интерфейс, и делают разных наследников. Но это хорошо, только когда эти типы существуют в первую очередь для цели операции или имеет смысл, чтобы операция отображалась как часть типа. Если типы имеют более широкое назначение, загрязнение их такими методами может быть нежелательным. Кроме того, вы можете не владеть определениями типов, либо у вас слишком много похожих ситуаций и вы можете решить только одну из них через наследование, либо вы решили не пропускать требования конкретной операции в определение данных, тогда единственный простой выбор, который у вас есть, — объявить переменную как object и позволить ей быть чем угодно.
Альтернативой является создание одной и той же логики для обработки обоих типов, и объявление параметра или переменной, которая может содержать любой из типов. Было бы хорошо, если бы язык предоставлял способ объявить тип, который хранит значение одного из ограниченного набора других типов, и при этом сохранял бы безопасность типов. Многие другие языки уже делают это. Обычно такие специальные типы называют discriminated union (дискриминированными/размеченными объединениями) или просто объединениями типов.
Недавно было предложено (точнее уже давно, сейчас предложение просто обновили) добавить в C# 4 типа объединений:
1. Стандартные
Именованное объединение типов, которое объявляет все типы своих членов в одном объявлении (похоже на расширенный enum):
union U
{
A(int x, string y);
B(int z);
C;
}
U u = new A(10, "ten");
2. Структуры
Аналогично классу, но объединение и типы членов являются структурами.
union struct U
{
A(int x, string y);
B(int z);
C;
}
U u = new A(10, "ten");
3. «По требованию» (Ad hoc)
Анонимные объединения типов, объявляемые в любом месте.
(A or B or C) u = new A(10, "ten");
4. Пользовательские
Если необходимо объявить тип объединения, который не может быть указан как один из типов выше, можно объявить пользовательский тип объединения.
[Closed]
public class U { … }
public class A(int x, string y) : U { … }
public class B(int z) : U { … }
Но не спешите радоваться, это пока только обновлённое предложение. Так что завезут его в лучшем случае через пару релизов (лет), и то, скорей всего, сначала в виде превью.
Источник: https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md
👍18👎2
День 2010. #ЗаметкиНаПолях #AsyncTips
Конечный Автомат в C# для async/await. Начало
Часто говорят, что ключевые слова async/await приводят к созданию конечного автомата. Но что это значит? Рассмотрим на простом примере:
Здесь несколько вызовов await: для получения ответа, и для чтения содержимого.
Деление метода по границе await
Каждый раз при вызове await, мы знаем, что нам не нужно ничего делать, кроме как ждать результата. Логично при этом просто выйти из метода и вернуться, как только будет получен результат. Компилятор разделит метод по границе await и создаст конечный автомат. Вот упрощённый код:
Это очень упрощенная версия того, что делает компилятор, и она не учитывает важные части, например, как содержимое извлекается из HttpClient. Важно то, что мы синхронно вызываем часть метода до await, а затем используем механизм обратных вызовов (именно поэтому используется ref this) для продолжений метода. Таким образом, как только HTTP-вызов завершается, мы возвращаемся в метод и продолжаем с того места, где остановились (state = 1), когда завершается чтение – продолжаем со state=2.
Окончание следует…
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
Конечный Автомат в C# для async/await. Начало
Часто говорят, что ключевые слова async/await приводят к созданию конечного автомата. Но что это значит? Рассмотрим на простом примере:
async Task<Dto> GetAsync()
{
using var hc = new HttpClient();
hc.BaseAddress = new Uri("…");
var resp = await hc.GetAsync("");
resp.EnsureSuccessStatusCode();
var content =
await resp.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<Dto>(content);
}
public class Dto;
Здесь несколько вызовов await: для получения ответа, и для чтения содержимого.
Деление метода по границе await
Каждый раз при вызове await, мы знаем, что нам не нужно ничего делать, кроме как ждать результата. Логично при этом просто выйти из метода и вернуться, как только будет получен результат. Компилятор разделит метод по границе await и создаст конечный автомат. Вот упрощённый код:
public class GetAllAsync_StateMachine
{
public ContinuationMachine _builder =
ContinuationMachineBuilder.Create();
private int _state = 0;
private HttpClient _hc;
private HttpResponseMessage _resp;
private string _content;
private void MoveNext()
{
switch (_state)
{
case 0:
_hc = new HttpClient();
_hc.BaseAddress = new Uri("…");
_hc.GetAsync("");
_state = 1;
_builder.Continue(ref this);
break;
case 1:
_resp.EnsureSuccessStatusCode();
_resp.Content.ReadAsStringAsync();
_state = 2;
_builder.Continue(ref this);
break;
case 2:
return JsonSerializer.Deserialize<Dto>(_content);
}
}
}
Это очень упрощенная версия того, что делает компилятор, и она не учитывает важные части, например, как содержимое извлекается из HttpClient. Важно то, что мы синхронно вызываем часть метода до await, а затем используем механизм обратных вызовов (именно поэтому используется ref this) для продолжений метода. Таким образом, как только HTTP-вызов завершается, мы возвращаемся в метод и продолжаем с того места, где остановились (state = 1), когда завершается чтение – продолжаем со state=2.
Окончание следует…
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
👍21
День 2011. #ЗаметкиНаПолях #AsyncTips
Конечный Автомат в C# для async/await. Окончание
Начало
Планировщик заданий
Продолжения (обратные вызовы) размещаются в планировщике заданий (TaskScheduler). Он берёт конечный автомат и планирует его выполнение после завершения ожидаемой задачи. Таким образом, TaskScheduler отвечает за продолжения. Здесь есть ещё несколько интересных моментов, таких как контекст синхронизации (SynchronizationContext) и пул потоков (ThreadPool), но это детали.
Итак: при использовании async/await компилятор разделит метод по границам await и создаст конечный автомат. Этот конечный автомат будет запланирован в TaskScheduler для продолжения после завершения ожидаемой задачи.
Где хранится конечный автомат?
В общем случае - в Task или Task<T>. Там хранится текущее состояние (включая продолжения), а также результат ожидаемой задачи. Но, кроме того, объект Task также хранит исключения. Исключения в с async/await-коде немного отличаются от исключений в синхронном коде.
Рассмотрим следующий код:
Компилятор преобразует этот код в следующий:
Важно отметить, что внутри блока catch нет throw. Т.е., если возникнет исключение в асинхронной части метода, оно не будет выброшено, а будет сохранено в объекте Task.
Возможно, теперь вы понимаете, почему async void — плохая идея: исключения будут потеряны. Они будут возникать, но вы не сможете их перехватить или обработать каким-либо образом. То же самое относится к async Task, если вы не ожидаете её. Поэтому:
Исключение перехватывается и сохраняется в объекте Task, не выбрасываясь во внешний код. Но когда вы ожидаете объект Task, вызов await будет преобразован во что-то вроде GetAwaiter().GetResult(), и именно здесь исключение выбрасывается из объекта Task наружу.
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
Конечный Автомат в C# для async/await. Окончание
Начало
Планировщик заданий
Продолжения (обратные вызовы) размещаются в планировщике заданий (TaskScheduler). Он берёт конечный автомат и планирует его выполнение после завершения ожидаемой задачи. Таким образом, TaskScheduler отвечает за продолжения. Здесь есть ещё несколько интересных моментов, таких как контекст синхронизации (SynchronizationContext) и пул потоков (ThreadPool), но это детали.
Итак: при использовании async/await компилятор разделит метод по границам await и создаст конечный автомат. Этот конечный автомат будет запланирован в TaskScheduler для продолжения после завершения ожидаемой задачи.
Где хранится конечный автомат?
В общем случае - в Task или Task<T>. Там хранится текущее состояние (включая продолжения), а также результат ожидаемой задачи. Но, кроме того, объект Task также хранит исключения. Исключения в с async/await-коде немного отличаются от исключений в синхронном коде.
Рассмотрим следующий код:
static async Task ThrowExceptionAsync()
{
await Task.Yield();
throw new Exception("Ex");
}
Компилятор преобразует этот код в следующий:
try
{
YieldAwaitable.YieldAwaiter awaiter;
// Другой код
awaiter.GetResult();
throw new Exception("Ex");
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
}
Важно отметить, что внутри блока catch нет throw. Т.е., если возникнет исключение в асинхронной части метода, оно не будет выброшено, а будет сохранено в объекте Task.
Возможно, теперь вы понимаете, почему async void — плохая идея: исключения будут потеряны. Они будут возникать, но вы не сможете их перехватить или обработать каким-либо образом. То же самое относится к async Task, если вы не ожидаете её. Поэтому:
static async Task ThrowExAsync()
{
throw new Exception("Ex");
await SomethingAsync();
}
// не выбросит исключения
_ = ThrowExAsync();
// не выбросит исключения
ThrowExAsync();
// выбросит исключение
await ThrowExAsync();
Исключение перехватывается и сохраняется в объекте Task, не выбрасываясь во внешний код. Но когда вы ожидаете объект Task, вызов await будет преобразован во что-то вроде GetAwaiter().GetResult(), и именно здесь исключение выбрасывается из объекта Task наружу.
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
👍16👎1
День 2012. #BlazorBasics
Основы Blazor. Создание Компонента Blazor
Приложения Blazor состоят из нескольких слоёв компонентов. Все в Blazor является компонентом. Компонентная архитектура имеет много преимуществ, таких как простое повторное использование кода, изолированное программирование и компоновка, даже в нескольких приложениях Blazor.
Компоненты можно разделить на 3 категории: страницы, компоненты и элементы управления. Технически это всё компоненты Blazor, создающие в сочетании дерево компонентов.
Компонент Blazor состоит из двух основных частей — шаблона и кода взаимодействия. Шаблон использует стандартные веб-технологии, такие как HTML и CSS. Код взаимодействия реализован на C#:
Этот простой компонент содержит шаблон с одним HTML-элементом <h1>. Синтаксис шаблона Blazor основан на синтаксисе Razor. Содержимое элемента <h1> ссылается на свойство Title кода C# в блоке @code. Значение переменной выводится с помощью символа @, за которым следует имя свойства. Символ @ используется везде в шаблоне, когда нужно сослаться на код из раздела @code компонента.
Использование существующих элементов
Идея в том, чтобы сделать компонент как можно меньше, ограничив его одной ответственностью. Например, компонент формы обрабатывает взаимодействие с формой, но не определяет шаблон для каждого отдельного поля в форме.
Некоторые компоненты могут использоваться в нескольких приложениях. Можно создавать компоненты самому, что занимает много времени, либо использовать стороннюю библиотеку для UI компонентов Blazor, вроде Blazorise, Radzen, MudBlazor или Telerik Blazor UI.
Страницы в приложениях Blazor
В Blazor страницы являются компонентами. Единственное, что нужно, чтобы превратить компонент в страницу, — это добавить директиву @page.
В этом примере мы использовали маршрут
Деревья компонентов
Теперь извлечём компонент MyComponent.razor из страницы MyPage.razor:
Мы создали два компонента и сослались на компонент MyComponent в компоненте MyPage. Можно продолжить в том же духе для построения целого дерева компонентов: страница может содержать компоненты Content и Menu. Menu - содержать несколько компонентов MenuItem и т.д.
Передача параметров из родительского компонента в дочерний
Нужно добавить атрибут Parameter, чтобы превратить свойство в параметр, который можно задать извне компонента:
Теперь в родительском компоненте зададим значение для свойства Title:
Источник: https://www.telerik.com/blogs/blazor-basics-creating-blazor-component
Основы Blazor. Создание Компонента Blazor
Приложения Blazor состоят из нескольких слоёв компонентов. Все в Blazor является компонентом. Компонентная архитектура имеет много преимуществ, таких как простое повторное использование кода, изолированное программирование и компоновка, даже в нескольких приложениях Blazor.
Компоненты можно разделить на 3 категории: страницы, компоненты и элементы управления. Технически это всё компоненты Blazor, создающие в сочетании дерево компонентов.
Компонент Blazor состоит из двух основных частей — шаблона и кода взаимодействия. Шаблон использует стандартные веб-технологии, такие как HTML и CSS. Код взаимодействия реализован на C#:
<h1>@Title</h1>
@code {
public string Title { get; set; }
= "Мой компонент";
}
Этот простой компонент содержит шаблон с одним HTML-элементом <h1>. Синтаксис шаблона Blazor основан на синтаксисе Razor. Содержимое элемента <h1> ссылается на свойство Title кода C# в блоке @code. Значение переменной выводится с помощью символа @, за которым следует имя свойства. Символ @ используется везде в шаблоне, когда нужно сослаться на код из раздела @code компонента.
Использование существующих элементов
Идея в том, чтобы сделать компонент как можно меньше, ограничив его одной ответственностью. Например, компонент формы обрабатывает взаимодействие с формой, но не определяет шаблон для каждого отдельного поля в форме.
Некоторые компоненты могут использоваться в нескольких приложениях. Можно создавать компоненты самому, что занимает много времени, либо использовать стороннюю библиотеку для UI компонентов Blazor, вроде Blazorise, Radzen, MudBlazor или Telerik Blazor UI.
Страницы в приложениях Blazor
В Blazor страницы являются компонентами. Единственное, что нужно, чтобы превратить компонент в страницу, — это добавить директиву @page.
@page "/MyPage"
<h1>@Title</h1>
@code {
public string Title { get; set; }
= "Мой компонент";
}
В этом примере мы использовали маршрут
/MyPage
в качестве маршрута, по которому будет доступна страница. Директива @page регистрирует компонент как страницу и добавляет маршрут к поставщику маршрутизации ASP.NET Core.Деревья компонентов
Теперь извлечём компонент MyComponent.razor из страницы MyPage.razor:
@page "/MyPage"
<MyComponent />
<h1>@Title</h1>
@code {
public string Title { get; set; }
= "Мой компонент";
}
Мы создали два компонента и сослались на компонент MyComponent в компоненте MyPage. Можно продолжить в том же духе для построения целого дерева компонентов: страница может содержать компоненты Content и Menu. Menu - содержать несколько компонентов MenuItem и т.д.
Передача параметров из родительского компонента в дочерний
Нужно добавить атрибут Parameter, чтобы превратить свойство в параметр, который можно задать извне компонента:
<h1>@Title</h1>
@code {
[Parameter]
public string Title { get; set; }
}
Теперь в родительском компоненте зададим значение для свойства Title:
@page "/MyPage"
<MyComponent Title="Заголовок" />
Источник: https://www.telerik.com/blogs/blazor-basics-creating-blazor-component
👍9
День 2013. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Начало
В классической книге «Мифический человеко-месяц» Брукс советует: «Планируйте выбросить первую версию — вам всё равно придётся это сделать». Он имеет в виду, что в крупных проектах желательно создавать пилотную систему, чтобы выяснить, как лучше построить основную. Это недешёвое удовольствие, особенно если в систему входят аппаратные компоненты. Но пилотная система может пригодиться, если есть сомнения в технической осуществимости или подходящая стратегия проектирования изначально неясна. Пилотная система также может помочь выявить неизвестные факторы, о существовании которых вы не догадывались.
Вряд ли вы согласитесь создать, а потом выбросить первую версию продукта, но лучше пересмотреть потенциальное проектное решение, прежде чем команда слишком далеко продвинется в разработке. Идея создания максимально простого проекта выглядит привлекательно и действительно ускоряет доставку решения. Быстрая доставка может повысить ценность продукта для покупателя в краткосрочной перспективе, но редко когда оказывается лучшей долгосрочной стратегией, особенно если продукт продолжает развиваться.
Любая задача имеет несколько решений, и редко лучшим оказывается первое из них. Работу по проектированию можно считать выполненной, только если вы придумали хотя бы три решения, отбросили их все, а затем объединили лучшие их части в превосходное четвёртое решение. Иногда только рассмотрев три варианта, начинаешь осознавать, насколько плохо понимаешь проблему.
Есть несколько методов, помогающих разработчикам переходить от первоначальной концепции к эффективному решению. Один из них — прототипирование, помогающее последовательно выполнять техническое проектирование и улучшать пользовательский опыт.
Прототип — это частичное, предварительное или возможное решение. В прототипе вы воплощаете часть системы, проверяя, насколько правильно вы понимаете, как спроектировать решение. Если эксперимент не удался, то вы меняете подход и пробуете снова. Прототип помогает оценить риски и снизить их, особенно если используется новый архитектурный или проектный шаблон, который следует проверить, прежде чем принять его за основу.
Во-первых, определите, что вы собираетесь делать: отказаться от прототипа и разработать реальное решение или превратить его в окончательный продукт. Если второе, вы должны с самого начала создания прототипа придавать ему промышленное качество. Это потребует больше усилий, чем создание чего-то временного, от чего вы откажетесь.
Прежде чем приступить к конкретному решению, Agile-команды иногда создают истории, называемые спайками (spike), предназначенные для исследования технических подходов, устранения неопределённости и снижения риска. В отличие от других пользовательских историй, основной результат спайка — не рабочий код, а знания. В спайки может входить создание технических прототипов, прототипов UI или того и другого в зависимости от искомой информации. Спайк должен иметь чёткую цель, как научный эксперимент. У разработчика есть гипотеза, и для получения доказательств её верности или неверности, подтверждения обоснованности какого-либо подхода или быстрого принятия технического решения реализуется спайк.
Например, задача — сделать авторизацию в приложении. Но разработчики не понимают, какую технологию лучше использовать. И вместо того, чтобы бросаться создавать код, они тормозят разработку и изучают технологии, подбирая наиболее подходящую с учётом всех требований.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Начало
В классической книге «Мифический человеко-месяц» Брукс советует: «Планируйте выбросить первую версию — вам всё равно придётся это сделать». Он имеет в виду, что в крупных проектах желательно создавать пилотную систему, чтобы выяснить, как лучше построить основную. Это недешёвое удовольствие, особенно если в систему входят аппаратные компоненты. Но пилотная система может пригодиться, если есть сомнения в технической осуществимости или подходящая стратегия проектирования изначально неясна. Пилотная система также может помочь выявить неизвестные факторы, о существовании которых вы не догадывались.
Вряд ли вы согласитесь создать, а потом выбросить первую версию продукта, но лучше пересмотреть потенциальное проектное решение, прежде чем команда слишком далеко продвинется в разработке. Идея создания максимально простого проекта выглядит привлекательно и действительно ускоряет доставку решения. Быстрая доставка может повысить ценность продукта для покупателя в краткосрочной перспективе, но редко когда оказывается лучшей долгосрочной стратегией, особенно если продукт продолжает развиваться.
Любая задача имеет несколько решений, и редко лучшим оказывается первое из них. Работу по проектированию можно считать выполненной, только если вы придумали хотя бы три решения, отбросили их все, а затем объединили лучшие их части в превосходное четвёртое решение. Иногда только рассмотрев три варианта, начинаешь осознавать, насколько плохо понимаешь проблему.
Есть несколько методов, помогающих разработчикам переходить от первоначальной концепции к эффективному решению. Один из них — прототипирование, помогающее последовательно выполнять техническое проектирование и улучшать пользовательский опыт.
Прототип — это частичное, предварительное или возможное решение. В прототипе вы воплощаете часть системы, проверяя, насколько правильно вы понимаете, как спроектировать решение. Если эксперимент не удался, то вы меняете подход и пробуете снова. Прототип помогает оценить риски и снизить их, особенно если используется новый архитектурный или проектный шаблон, который следует проверить, прежде чем принять его за основу.
Во-первых, определите, что вы собираетесь делать: отказаться от прототипа и разработать реальное решение или превратить его в окончательный продукт. Если второе, вы должны с самого начала создания прототипа придавать ему промышленное качество. Это потребует больше усилий, чем создание чего-то временного, от чего вы откажетесь.
Прежде чем приступить к конкретному решению, Agile-команды иногда создают истории, называемые спайками (spike), предназначенные для исследования технических подходов, устранения неопределённости и снижения риска. В отличие от других пользовательских историй, основной результат спайка — не рабочий код, а знания. В спайки может входить создание технических прототипов, прототипов UI или того и другого в зависимости от искомой информации. Спайк должен иметь чёткую цель, как научный эксперимент. У разработчика есть гипотеза, и для получения доказательств её верности или неверности, подтверждения обоснованности какого-либо подхода или быстрого принятия технического решения реализуется спайк.
Например, задача — сделать авторизацию в приложении. Но разработчики не понимают, какую технологию лучше использовать. И вместо того, чтобы бросаться создавать код, они тормозят разработку и изучают технологии, подбирая наиболее подходящую с учётом всех требований.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍19
День 2014. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Окончание
Начало
Проверка концепции
Прототипы для проверки концепции, также называемые вертикальными прототипами (реализуется часть функциональности от базовых функций до UI), полезны для проверки предлагаемой архитектуры. Экспериментирование с прототипом для проверки концепции — относительно недорогой способ итерации, даже при том, что требует создания некоего действующего ПО. Такие прототипы полезны для оценки технических аспектов проекта: архитектуры, алгоритмов, структуры БД, системных интерфейсов и взаимодействий. С помощью прототипа можно оценить свойства архитектуры, такие как производительность, безопасность, защищённость и надёжность, а затем постепенно улучшать их.
Макеты
Проектирование UI всегда должно выполняться итеративно. Даже следуя принятым соглашениям о UI, вы должны провести хотя бы неформальное тестирование удобства использования, чтобы выбрать подходящие элементы управления и схемы их размещения, обеспечивающие простоту освоения и использования, а также высокую доступность. Например, A/B-тестирование — подход, при котором вы предоставляете пользователям два альтернативных интерфейса и даете им возможность выбрать наиболее удобный для них. Люди, которые проводят A/B-тестирование, могут наблюдать за поведением пользователей с помощью различных подходов и определять, какой вариант понятнее или быстрее приводит к успешным результатам. Проводить такие эксперименты проще, быстрее и дешевле, находясь на этапе изучения дизайна, чем после доставки решения клиентам, реагируя на их жалобы или на более низкую, чем ожидалось, частоту щелчков на веб-странице.
Как и в случае с требованиями, проектирование UI продвигается успешнее при постепенном уточнении деталей с помощью прототипирования. С этой целью можно создавать макеты, также называемые горизонтальными прототипами, поскольку они представляют собой тонкий слой UI без какой-либо функциональной основы под ним. Макетами могут быть и простые эскизы экрана, и действующие интерфейсы, которые выглядят аутентично, но не выполняют никакой реальной работы. Ценны даже простые бумажные прототипы — их можно быстро создавать и изменять. Вы можете использовать текстовый редактор или даже каталожные карточки, чтобы разместить элементы данных в прямоугольных областях, представляющих потенциальные экраны, посмотреть, как они соотносятся друг с другом, и отметить, какие из них принимают ввод пользователя, а какие отображают результаты. Но при прототипировании UI остерегайтесь следующих ловушек.
1. Не тратьте слишком много времени на совершенствование UI («Может, для этого текста лучше выбрать темно-красный цвет?») до того, как организуете информацию и элементы управления на экране и создадите функциональные макеты.
2. Клиенты или руководители могут подумать, что ПО почти готово, увидев удобный и красивый UI, даже если за ним нет ничего, кроме имитации функциональности. Менее изысканный прототип показывает, что ПО ещё не готово.
3. Не стремитесь подсказывать, как правильно действовать, тем, кто оценивает прототип и пытается выполнить неочевидную для них задачу. Вы не сможете судить об удобстве использования, помогая участникам тестирования изучать и применять прототип.
Если вы не потратили время на итеративное изучение UI и техническое проектирование до реализации, то рискуете создать продукт, который не понравится клиентам. Бездумно спроектированные продукты раздражают клиентов, вынуждают их неэффективно тратить своё время, подрывают их хорошее отношение к вашему продукту и компании и заставляют писать негативные отзывы. Потратив чуть больше времени на проектирование, вы значительно приблизитесь к созданию полезного и удобного решения.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Окончание
Начало
Проверка концепции
Прототипы для проверки концепции, также называемые вертикальными прототипами (реализуется часть функциональности от базовых функций до UI), полезны для проверки предлагаемой архитектуры. Экспериментирование с прототипом для проверки концепции — относительно недорогой способ итерации, даже при том, что требует создания некоего действующего ПО. Такие прототипы полезны для оценки технических аспектов проекта: архитектуры, алгоритмов, структуры БД, системных интерфейсов и взаимодействий. С помощью прототипа можно оценить свойства архитектуры, такие как производительность, безопасность, защищённость и надёжность, а затем постепенно улучшать их.
Макеты
Проектирование UI всегда должно выполняться итеративно. Даже следуя принятым соглашениям о UI, вы должны провести хотя бы неформальное тестирование удобства использования, чтобы выбрать подходящие элементы управления и схемы их размещения, обеспечивающие простоту освоения и использования, а также высокую доступность. Например, A/B-тестирование — подход, при котором вы предоставляете пользователям два альтернативных интерфейса и даете им возможность выбрать наиболее удобный для них. Люди, которые проводят A/B-тестирование, могут наблюдать за поведением пользователей с помощью различных подходов и определять, какой вариант понятнее или быстрее приводит к успешным результатам. Проводить такие эксперименты проще, быстрее и дешевле, находясь на этапе изучения дизайна, чем после доставки решения клиентам, реагируя на их жалобы или на более низкую, чем ожидалось, частоту щелчков на веб-странице.
Как и в случае с требованиями, проектирование UI продвигается успешнее при постепенном уточнении деталей с помощью прототипирования. С этой целью можно создавать макеты, также называемые горизонтальными прототипами, поскольку они представляют собой тонкий слой UI без какой-либо функциональной основы под ним. Макетами могут быть и простые эскизы экрана, и действующие интерфейсы, которые выглядят аутентично, но не выполняют никакой реальной работы. Ценны даже простые бумажные прототипы — их можно быстро создавать и изменять. Вы можете использовать текстовый редактор или даже каталожные карточки, чтобы разместить элементы данных в прямоугольных областях, представляющих потенциальные экраны, посмотреть, как они соотносятся друг с другом, и отметить, какие из них принимают ввод пользователя, а какие отображают результаты. Но при прототипировании UI остерегайтесь следующих ловушек.
1. Не тратьте слишком много времени на совершенствование UI («Может, для этого текста лучше выбрать темно-красный цвет?») до того, как организуете информацию и элементы управления на экране и создадите функциональные макеты.
2. Клиенты или руководители могут подумать, что ПО почти готово, увидев удобный и красивый UI, даже если за ним нет ничего, кроме имитации функциональности. Менее изысканный прототип показывает, что ПО ещё не готово.
3. Не стремитесь подсказывать, как правильно действовать, тем, кто оценивает прототип и пытается выполнить неочевидную для них задачу. Вы не сможете судить об удобстве использования, помогая участникам тестирования изучать и применять прототип.
Если вы не потратили время на итеративное изучение UI и техническое проектирование до реализации, то рискуете создать продукт, который не понравится клиентам. Бездумно спроектированные продукты раздражают клиентов, вынуждают их неэффективно тратить своё время, подрывают их хорошее отношение к вашему продукту и компании и заставляют писать негативные отзывы. Потратив чуть больше времени на проектирование, вы значительно приблизитесь к созданию полезного и удобного решения.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍10
День 2015. #ЗаметкиНаПолях
Статические Абстрактные Члены Интерфейсов в Конфигурации
Статические абстрактные члены интерфейсов появились в C# 11. Основной мотивацией для них является поддержка обобщённой математики. Но они могут быть полезны и в других сценариях. Например, упростить код регистрации и использования типов разделов пользовательской конфигурации.
Например, вы хотите настроить клиент API в appSettings.json:
Хорошей практикой является связать раздел с отдельным типом:
Упростим регистрацию через статические члены интерфейсов. Сначала определим интерфейс:
Все реализации этого интерфейса должны иметь статическое свойство SectionName:
Добавим методы расширения для регистрации секций конфигурации:
Теперь можно регистрировать различные секции конфигурации так:
Источник: https://haacked.com/archive/2024/07/18/better-config-sections/
Статические Абстрактные Члены Интерфейсов в Конфигурации
Статические абстрактные члены интерфейсов появились в C# 11. Основной мотивацией для них является поддержка обобщённой математики. Но они могут быть полезны и в других сценариях. Например, упростить код регистрации и использования типов разделов пользовательской конфигурации.
Например, вы хотите настроить клиент API в appSettings.json:
{
…
"GoogleAPI": {
"ApiKey": "…",
"OrganizationId": "…",
…
}
}
Хорошей практикой является связать раздел с отдельным типом:
public class GoogleOptions {
public string? ApiKey { get; init; }
public string? OrganizationId { get; init; }
…
}
…
// добавляем в Program.cs
builder.Configuration
.Configure<GoogleOptions>(
builder.Configuration.GetSection("GoogleAPI"));
…
// внедряем через DI
public class GoogleClient(
IOptions<GoogleOptions> options) {
// …
}
Упростим регистрацию через статические члены интерфейсов. Сначала определим интерфейс:
public interface IConfigOptions
{
static abstract string SectionName { get; }
}
Все реализации этого интерфейса должны иметь статическое свойство SectionName:
public class GoogleOptions: IConfigOptions {
public static string SectionName => "GoogleAPI";
// остальные члены
}
Добавим методы расширения для регистрации секций конфигурации:
public static class OptionsExtensions {
public static IHostApplicationBuilder
Configure<TOptions>(
this IHostApplicationBuilder builder)
where TOptions : class, IConfigOptions
{
var sect = builder.Configuration
.GetSection(TOptions.SectionName);
builder.Services.Configure<TOptions>(sect);
return builder;
}
}
Теперь можно регистрировать различные секции конфигурации так:
builder.Configure<GoogleOptions>()
.Configure<GitHubOptions>()
.Configure<WeatherOptions>()
Источник: https://haacked.com/archive/2024/07/18/better-config-sections/
👍47