День 1393. #TipsAndTricks
Советы и Инструменты Отладки. Продолжение
Начало
5. Загрузка символов сборки
Для отладки скомпилированных сборок необходимо загрузить файлы символов, сопоставляющие бинарный код со строками кода в текстовых файлах. Последние версии Visual Studio загружают символы автоматически. В более старых версиях можно пройти в Debug > Windows > Modules (Отладка > Окна > Модули), там найти модуль, который хотите отладить, и нажать Load Symbols (Загрузить символы).
Если символы найти не удаётся, возможно нужно включить (или добавить) символьные серверы. В настройках (Tools > Options…) зайдите в Debugging > Symbols (Отладка > Символы).
6. Различные виды точек останова
Есть много способов остановить выполнение кода:
- Точки останова: остановка всегда при выполнении выражения.
- Условные точки останова: остановка при выполнении выражения, когда соблюдается условие.
- Точки останова в данных: остановка, когда значение данных в памяти меняется
- Зависимые точки останова: остановка, только когда другая точка останова достигнута. Полезны в сложном сценарии, таком как отладка многопоточного приложения.
- Остановка при исключении
7. Добавление утверждений
Точки останова полезны для отладки кода, но их не всегда легко использовать. Вы можете использовать утверждения для проверки состояния вашего кода.
Продолжение следует…
Источник: https://www.meziantou.net/debugging-tips-and-tools.htm
Советы и Инструменты Отладки. Продолжение
Начало
5. Загрузка символов сборки
Для отладки скомпилированных сборок необходимо загрузить файлы символов, сопоставляющие бинарный код со строками кода в текстовых файлах. Последние версии Visual Studio загружают символы автоматически. В более старых версиях можно пройти в Debug > Windows > Modules (Отладка > Окна > Модули), там найти модуль, который хотите отладить, и нажать Load Symbols (Загрузить символы).
Если символы найти не удаётся, возможно нужно включить (или добавить) символьные серверы. В настройках (Tools > Options…) зайдите в Debugging > Symbols (Отладка > Символы).
6. Различные виды точек останова
Есть много способов остановить выполнение кода:
- Точки останова: остановка всегда при выполнении выражения.
- Условные точки останова: остановка при выполнении выражения, когда соблюдается условие.
- Точки останова в данных: остановка, когда значение данных в памяти меняется
- Зависимые точки останова: остановка, только когда другая точка останова достигнута. Полезны в сложном сценарии, таком как отладка многопоточного приложения.
- Остановка при исключении
7. Добавление утверждений
Точки останова полезны для отладки кода, но их не всегда легко использовать. Вы можете использовать утверждения для проверки состояния вашего кода.
public void MethodToDebug()Замечание: Debug.Assert доступно только при сборке в режиме отладки.
{
for (int i = 0; i < 100; i++)
{
Debug.Assert(i != 50, "i не должно быть 50");
Console.WriteLine(i);
}
}
Продолжение следует…
Источник: https://www.meziantou.net/debugging-tips-and-tools.htm
👍10
День 1394. #TipsAndTricks
Советы и Инструменты Отладки. Продолжение
Начало 1-4
Продолжение 5-7
8. Наблюдение за приложением без отладчика
Вы можете наблюдать за http-вызовами, используя прокси-сервер, такой как Fiddler.
В браузерах на движке Chromium помимо вкладки Network (Сеть) в инструментах разработчика, есть:
-
-
Также следить за сетевым трафиком можно с помощью WireShark.
Вы можете следить за файловой системой, регистром и активностью процессов/потоков с помощью procmon (Монитор Процессов) или wtrace.
Можно использовать dotnet-counter для быстрого мониторинга таких вещей, как загрузка ЦП или частота исключений, возникающих в вашем приложении .NET Core.
dotnet-trace или PerfView используются для сбора ETW-событий вашего приложения .NET Core. Это позволяет получить много информации о приложении, такой как запросы к базе данных, HTTP-вызовы, DNS-запросы, конфликты блокировок, информация о пуле потоков, сборках мусора, JIT, загрузке сборок и т.д.
Анализатор производительности Windows создаёт графики и таблицы данных для событий трассировки событий Windows (ETW).
Дамп приложения можно сделать с помощью dotnet-dump и проанализировать его позже с помощью Visual Studio. Если вам нужно создать дамп при выполнении определенных условий, вы можете попробовать ProcDump.
Handle или ProcessExplorer помогут перечислить дескрипторы (открытые файлы, ключи реестра, порты, примитивы синхронизации).
Visual Studio предоставляет множество инструментов для отладки приложения. Кроме того, вы можете использовать WinDbg и dotnet-sos. Инструменты Sysinternals также очень полезны.
Окончание следует…
Источник: https://www.meziantou.net/debugging-tips-and-tools.htm
Советы и Инструменты Отладки. Продолжение
Начало 1-4
Продолжение 5-7
8. Наблюдение за приложением без отладчика
Вы можете наблюдать за http-вызовами, используя прокси-сервер, такой как Fiddler.
В браузерах на движке Chromium помимо вкладки Network (Сеть) в инструментах разработчика, есть:
-
about://net-internals/
- различные сетевые инструменты,-
about://net-export/
- экспорт данных по сетевым соединениям.Также следить за сетевым трафиком можно с помощью WireShark.
Вы можете следить за файловой системой, регистром и активностью процессов/потоков с помощью procmon (Монитор Процессов) или wtrace.
Можно использовать dotnet-counter для быстрого мониторинга таких вещей, как загрузка ЦП или частота исключений, возникающих в вашем приложении .NET Core.
dotnet-trace или PerfView используются для сбора ETW-событий вашего приложения .NET Core. Это позволяет получить много информации о приложении, такой как запросы к базе данных, HTTP-вызовы, DNS-запросы, конфликты блокировок, информация о пуле потоков, сборках мусора, JIT, загрузке сборок и т.д.
Анализатор производительности Windows создаёт графики и таблицы данных для событий трассировки событий Windows (ETW).
Дамп приложения можно сделать с помощью dotnet-dump и проанализировать его позже с помощью Visual Studio. Если вам нужно создать дамп при выполнении определенных условий, вы можете попробовать ProcDump.
Handle или ProcessExplorer помогут перечислить дескрипторы (открытые файлы, ключи реестра, порты, примитивы синхронизации).
Visual Studio предоставляет множество инструментов для отладки приложения. Кроме того, вы можете использовать WinDbg и dotnet-sos. Инструменты Sysinternals также очень полезны.
Окончание следует…
Источник: https://www.meziantou.net/debugging-tips-and-tools.htm
👍9
День 1395. #TipsAndTricks
Советы и Инструменты Отладки. Окончание
Начало 1-4
Продолжение 5-7
Продолжение 8
9. Просмотр значений в отладчике
Окна Watch – Debug > Windows > Watch > Watch 1 (Отладка > Окна > Просмотр > Просмотр 1) позволяют оценивать несколько выражений. Все выражения пересчитываются, когда выполнение приостанавливается. Обратите внимание, что вы можете использовать такие команды, как
Идентификатор объекта (Object ID) позволяет создать глобально доступный идентификатор для объекта. Таким образом, даже если значение недоступно в текущем контексте, вы всё равно можете сослаться на значение, используя
Подсказки к данным обеспечивают удобный способ просмотра информации о переменных в вашей программе во время отладки. Наведите курсор на переменную и нажмите на значок булавки, чтоб его закрепить. Если значение изменилось с момента последней оценки, оно отображается красным цветом, поэтому вы можете быстро увидеть, что изменилось.
Пользовательские визуализаторы позволяют отображать отформатированные значения вместо необработанных. Существуют визуализаторы для строк или коллекций IEnumerable.
Окно Immediate (Debug > Windows > Immediate при отладке) позволяет в точке останова выполнять произвольный код и видеть результат.
10. Отладка многопоточных приложений
- Получение списка запущенных потоков (Debug > Windows > Threads). Вы можете дважды щелкнуть по потоку, чтобы переключиться на него.
- Получение списка задач в .NET (Debug > Windows > Tasks).
- Окно Parallel watch (Debug > Windows > Parallel Watch > Parallel Watch 1) позволяет оценить значение объекта во всех потоках.
- Окно Parallel Stacks объединяет все похожие стеки/задачи вместе и показывает количество потоков/задач, выполняющих один и тот же код. Это даст вам хорошее представление о том, что происходит в вашем приложении.
Если вы отлаживаете несколько приложений, окно «Parallel Stacks» может объединять стеки вызовов из всех запущенных процессов.
11. Другие инструменты
- Проверка используемых портов в Windows
PowerShell
PowerShell
Источник: https://www.meziantou.net/debugging-tips-and-tools.htm
Советы и Инструменты Отладки. Окончание
Начало 1-4
Продолжение 5-7
Продолжение 8
9. Просмотр значений в отладчике
Окна Watch – Debug > Windows > Watch > Watch 1 (Отладка > Окна > Просмотр > Просмотр 1) позволяют оценивать несколько выражений. Все выражения пересчитываются, когда выполнение приостанавливается. Обратите внимание, что вы можете использовать такие команды, как
nq
(без кавычек) или nse
(без побочных эффектов), h
(шестнадцатеричный). См. подробнее про форматирование выражений в отладчике.Идентификатор объекта (Object ID) позволяет создать глобально доступный идентификатор для объекта. Таким образом, даже если значение недоступно в текущем контексте, вы всё равно можете сослаться на значение, используя
$N
, где N — идентификатор объекта. См. подробнее.Подсказки к данным обеспечивают удобный способ просмотра информации о переменных в вашей программе во время отладки. Наведите курсор на переменную и нажмите на значок булавки, чтоб его закрепить. Если значение изменилось с момента последней оценки, оно отображается красным цветом, поэтому вы можете быстро увидеть, что изменилось.
Пользовательские визуализаторы позволяют отображать отформатированные значения вместо необработанных. Существуют визуализаторы для строк или коллекций IEnumerable.
Окно Immediate (Debug > Windows > Immediate при отладке) позволяет в точке останова выполнять произвольный код и видеть результат.
10. Отладка многопоточных приложений
- Получение списка запущенных потоков (Debug > Windows > Threads). Вы можете дважды щелкнуть по потоку, чтобы переключиться на него.
- Получение списка задач в .NET (Debug > Windows > Tasks).
- Окно Parallel watch (Debug > Windows > Parallel Watch > Parallel Watch 1) позволяет оценить значение объекта во всех потоках.
- Окно Parallel Stacks объединяет все похожие стеки/задачи вместе и показывает количество потоков/задач, выполняющих один и тот же код. Это даст вам хорошее представление о том, что происходит в вашем приложении.
Если вы отлаживаете несколько приложений, окно «Parallel Stacks» может объединять стеки вызовов из всех запущенных процессов.
11. Другие инструменты
- Проверка используемых портов в Windows
PowerShell
$Port = 5000- Проверка конфигурации DNS
Get-Process -Id (Get-NetTCPConnection -LocalPort $port).OwningProcess
Get-Process -Id (Get-NetUDPEndpoint -LocalPort $port).OwningProcess
netstat -a -b
PowerShell
Resolve-DnsName -Name www.google.comТакже можно использовать онлайн сервис, вроде https://dnslookup.online/.
Resolve-DnsName -Type A -Name www.google.com
Источник: https://www.meziantou.net/debugging-tips-and-tools.htm
👍6
День 1396. #ЧтоНовенького
Dev Tunnels в Превью для Проектов ASP.NET Core
Туннели разработки (dev tunnels), переадресация портов, или туннелирование портов (Microsoft использует все три термина) обеспечивают специальные соединения между машинами, которые не могут напрямую подключаться друг к другу.
Проще говоря, это позволяет удаленным компьютерам в общедоступной среде подключаться к устройствам или службам в частных сетях или наоборот.
В Visual Studio 17.4 вы можете включить функцию туннелей разработки в Tools> Options > Environment > Preview Features (Инструменты > Параметры > Среда > Предварительный Просмотр Функций). Кроме того, потребуется войти в аккаунт в Visual Studio.
Также в файл
devTunnelEnabled — при значении true туннель будет использоваться при запуске проекта.
devTunnelAccess — для портов, используемых этим конкретным проектом:
- private (по умолчанию) — только для авторизованных пользователей,
- org — только для пользователей организации, в которой был создан туннель,
- public – разрешён анонимный доступ.
Если при старте проекта не запускается браузер, то URL можно будет посмотреть в окне Output. Кроме того, после старта проекта URL записывается в переменную окружения
Поддерживаемые типы учетных записей
Поддерживаются аккаунты Azure, Microsoft (MSA), а также GitHub. Поддержки организаций для учетных записей GitHub в настоящее время нет, но планируется.
В меню Tools > Options > Dev Tunnels (Инструменты > Параметры > Туннели разработки) можно настроить учётную запись, которая используется при создании и использовании туннелей разработки.
Дальнейшее развитие
Текущая модель поддержки туннелей разработки требует, чтобы пользователи записывали значения в файл
- Возможность настроить туннель под использование постоянного или временного URL.
- Создание туннелей на разных аккаунтах.
- Управление настройками туннеля.
- UI для получения токена доступа к туннелю.
и другие улучшения.
Источник: https://devblogs.microsoft.com/visualstudio/public-preview-of-dev-tunnels-in-visual-studio-for-asp-net-core-projects/
Dev Tunnels в Превью для Проектов ASP.NET Core
Туннели разработки (dev tunnels), переадресация портов, или туннелирование портов (Microsoft использует все три термина) обеспечивают специальные соединения между машинами, которые не могут напрямую подключаться друг к другу.
Проще говоря, это позволяет удаленным компьютерам в общедоступной среде подключаться к устройствам или службам в частных сетях или наоборот.
В Visual Studio 17.4 вы можете включить функцию туннелей разработки в Tools> Options > Environment > Preview Features (Инструменты > Параметры > Среда > Предварительный Просмотр Функций). Кроме того, потребуется войти в аккаунт в Visual Studio.
Также в файл
launchSettings.json
нужно добавить два свойства:devTunnelEnabled — при значении true туннель будет использоваться при запуске проекта.
devTunnelAccess — для портов, используемых этим конкретным проектом:
- private (по умолчанию) — только для авторизованных пользователей,
- org — только для пользователей организации, в которой был создан туннель,
- public – разрешён анонимный доступ.
{Теперь после локального запуска вашего проекта, с ним будет связан общедоступный URL-адрес. Его можно использовать для доступа к проекту из вне вашего локального компьютера, например, при разработке веб-перехватчиков, приложения Power Platform, которое вызывает веб-API ASP.NET Core, или совместного использования незавершённой работы. На данный момент при каждом запуске URL будет меняться, но это обещают исправить в будущем.
"iisSettings": {
…
},
"profiles": {
"TemplatesWeb": {
…,
"applicationUrl": "https://localhost:44318",
"devTunnelEnabled": true,
"devTunnelAccess": "public"
}
}
}
Если при старте проекта не запускается браузер, то URL можно будет посмотреть в окне Output. Кроме того, после старта проекта URL записывается в переменную окружения
VS_TUNNEL_URL
, откуда его можно извлечь при необходимости.Поддерживаемые типы учетных записей
Поддерживаются аккаунты Azure, Microsoft (MSA), а также GitHub. Поддержки организаций для учетных записей GitHub в настоящее время нет, но планируется.
В меню Tools > Options > Dev Tunnels (Инструменты > Параметры > Туннели разработки) можно настроить учётную запись, которая используется при создании и использовании туннелей разработки.
Дальнейшее развитие
Текущая модель поддержки туннелей разработки требует, чтобы пользователи записывали значения в файл
launchSettings.json
. В будущем появится пользовательский интерфейс в Visual Studio, который позволит более явно создавать туннели и управлять ими. Помимо этого обещают:- Возможность настроить туннель под использование постоянного или временного URL.
- Создание туннелей на разных аккаунтах.
- Управление настройками туннеля.
- UI для получения токена доступа к туннелю.
и другие улучшения.
Источник: https://devblogs.microsoft.com/visualstudio/public-preview-of-dev-tunnels-in-visual-studio-for-asp-net-core-projects/
👍15
День 1397. #БазыДанных
Сегодня порекомендую видео про нормализацию базы данных. 1я, 2я, 3я, 4я и даже 5я нормальные формы. Автор на простых примерах объясняет, что это такое, почему это важно и как позволяет избежать несогласованности данных.
Приятного изучения https://youtu.be/GFQaEYEc8_8
Извините, видео на английском, но можно включить автоматически переведённые субтитры.
Сегодня порекомендую видео про нормализацию базы данных. 1я, 2я, 3я, 4я и даже 5я нормальные формы. Автор на простых примерах объясняет, что это такое, почему это важно и как позволяет избежать несогласованности данных.
Приятного изучения https://youtu.be/GFQaEYEc8_8
Извините, видео на английском, но можно включить автоматически переведённые субтитры.
YouTube
Learn Database Normalization - 1NF, 2NF, 3NF, 4NF, 5NF
An easy-to-follow database normalization tutorial, with lots of examples and a focus on the design process. Explains the "why" and "how" of normalization, and takes you step-by-step through:
- First Normal Form (1NF)
- Second Normal Form (2NF)
- Third Normal…
- First Normal Form (1NF)
- Second Normal Form (2NF)
- Third Normal…
👍10
День 1398. #ЗаметкиНаПолях #AsyncTips
Блокирующие и Асинхронные Сигналы
1. Блокирующие Сигналы
Задача: требуется отправить уведомление от одного потока другому.
Решение
Самый распространённый и универсальный межпотоковый сигнал — событие с ручным сбросом ManualResetEventSlim. Оно может находиться в одном из двух состояний: установленном или сброшенном. Любой поток может перевести событие в установленное состояние или сбросить его. Поток также может ожидать перехода события в установленное состояние.
Следующие два метода вызываются разными потоками; один поток, использует Wait() и ожидает сигнала от другого, который вызывает Init():
2. Асинхронные Сигналы
Задача: требуется отправить уведомление от одного потока другому, при этом получатель оповещения должен ожидать его асинхронно.
Решение
Если уведомление должно быть отправлено только один раз, можно использовать TaskCompletionSource<T>. Код-отправитель вызывает TrySetResult, а код-получатель ожидает его свойство Task:
Итого
Сигналы представляют собой механизм уведомлений общего назначения, однако использовать их следует только тогда, когда это действительно уместно. Если «сигнал» представляет собой сообщение, отправляющее некоторые данные между потоками, рассмотрите возможность использования очереди «производитель/потребитель». А если сигналы используются только для координации доступа к общим данным, лучше использовать lock или асинхронную блокировку.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 12.
Блокирующие и Асинхронные Сигналы
1. Блокирующие Сигналы
Задача: требуется отправить уведомление от одного потока другому.
Решение
Самый распространённый и универсальный межпотоковый сигнал — событие с ручным сбросом ManualResetEventSlim. Оно может находиться в одном из двух состояний: установленном или сброшенном. Любой поток может перевести событие в установленное состояние или сбросить его. Поток также может ожидать перехода события в установленное состояние.
Следующие два метода вызываются разными потоками; один поток, использует Wait() и ожидает сигнала от другого, который вызывает Init():
class MyClassManualResetEventSlim является синхронным сигналом, поэтому WaitForInitialization блокирует вызывающий поток до отправки сигнала.
{
private readonly ManualResetEventSlim
_mres = new();
private int _val;
public int Wait()
{
_mres.Wait();
return _val;
}
public void Init()
{
_val = 42;
_mres.Set();
}
}
2. Асинхронные Сигналы
Задача: требуется отправить уведомление от одного потока другому, при этом получатель оповещения должен ожидать его асинхронно.
Решение
Если уведомление должно быть отправлено только один раз, можно использовать TaskCompletionSource<T>. Код-отправитель вызывает TrySetResult, а код-получатель ожидает его свойство Task:
class MyClassAsyncTaskCompletionSource<T> может использоваться для асинхронного ожидания любой ситуации — в данном случае уведомления от другой части кода. Этот способ хорошо работает, если сигнал отправляется только один раз, но не работает, если сигнал нужно не только включать, но и отключать.
{
private readonly TaskCompletionSource<object?>
_set = new();
private int _val1;
private int _val2;
public async Task<int> WaitAsync()
{
await _set.Task;
return _val1 + _val2;
}
public void Init()
{
_val1 = 42;
_val2 = 69;
_set.TrySetResult(null);
}
}
Итого
Сигналы представляют собой механизм уведомлений общего назначения, однако использовать их следует только тогда, когда это действительно уместно. Если «сигнал» представляет собой сообщение, отправляющее некоторые данные между потоками, рассмотрите возможность использования очереди «производитель/потребитель». А если сигналы используются только для координации доступа к общим данным, лучше использовать lock или асинхронную блокировку.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 12.
👍13
День 1399. #ЗаметкиНаПолях #TipsAndTricks
Современные Приёмы в C#. Часть 2
Часть 1
Записи-Значения
Записи-значения представляют собой форму более общего паттерна «объект-значение», специфичную для последних версий C#.
Антипаттерн, с которым мы здесь боремся, называется Одержимость Примитивами (Primitive Obsession). Разработчик чрезмерно использует примитивы (string, int, Guid, decimal и т.д.) для представления концепций бизнеса или предметной области. Одним из классических примеров являются идентификаторы сущностей. Здесь есть 2 проблемы:
1. Примитивы не являются типобезопасными. Легко случайно передать идентификатор клиента методу, ожидающему идентификатор ресурса. Или, если методу нужны и customerId, и resourceId, можно легко передать параметры в неправильном порядке.
2. Примитивы поддерживают операции, которые не имеют смысла. Например, если customerId имеет тип int, компилятор с радостью позволит вам разделить его на 2, но это не имеет смысла с точки зрения бизнеса.
Решение – объекты-значения. Идея состоит в том, что вы определяете (простой) объект предметной области, который заключает примитивный тип в типобезопасную оболочку. Иногда оболочка может иметь какую-то логику (например, валидацию), но чаще всего это анемичные модели, которые занимают промежуточное положение между примитивами и полноценными объектами предметной области.
Записи-значения
Современный способ создать объект-значение в C# - это записи-значения:
- Записи (record) обеспечивают семантику значений, дополненную равенством по значению и поддержкой ToString.
- Структуры-записи (record struct) предоставляют оболочку типа значения, избегая выделения в куче (размер оболочки равен размеру обёрнутого значения).
- Структуры-записи только для чтения обеспечивают неизменность.
Поскольку определение типа представляет собой всего одну строку, все типы записей-значений можно собрать в одном файле, вроде Primitives.cs.
Советы
1. Избегайте публичности
Записи-значения лучше всего работают как внутренние типы. Не в смысле internal, а в смысле в ядре вашего приложения. Не пытайтесь настроить сериализацию записей-значений. Путь ваш код просто оборачивает примитивные типы, полученные извне:
Не все примитивы должны быть записями-значениями. Рекомендуется использовать примитивы по умолчанию, а записи-значения в следующих ситуациях:
- Есть похожие типы значений (концептуально похожие или просто имеющие похожее имя), которые используются вместе. Например, идентификаторы сущностей, имеющие похожие имена, и когда есть несколько методов, которые одновременно работают с разными типами идентификаторов.
- Нужно гарантировать, что критически важный тип-значения должен использоваться только определённым образом. Например, компонент, отправляющий e-mail с персональной информацией должен использовать только подтверждённые адреса. Тогда он может принимать тип ValidatedEmail. А свойство этого типа задаётся у пользователя только при подтверждении e-mail.
Неправильное использование
В основном это чрезмерное использование записей-значений. Существует компромисс в поддержке: записи-значения увеличивают сложность кода. Каждый разработчик знает, что такое строка, но тип ValidatedEmail не понятен с первого взгляда. Примитивы — прекрасное решение для большинства данных.
Итого
Избирательная замена некоторых примитивных типов записями-значениями повысит безопасность типов, правильность и удобство сопровождения вашего кода.
Источник: https://blog.stephencleary.com/2022/10/modern-csharp-techniques-2-value-records.html
Современные Приёмы в C#. Часть 2
Часть 1
Записи-Значения
Записи-значения представляют собой форму более общего паттерна «объект-значение», специфичную для последних версий C#.
Антипаттерн, с которым мы здесь боремся, называется Одержимость Примитивами (Primitive Obsession). Разработчик чрезмерно использует примитивы (string, int, Guid, decimal и т.д.) для представления концепций бизнеса или предметной области. Одним из классических примеров являются идентификаторы сущностей. Здесь есть 2 проблемы:
1. Примитивы не являются типобезопасными. Легко случайно передать идентификатор клиента методу, ожидающему идентификатор ресурса. Или, если методу нужны и customerId, и resourceId, можно легко передать параметры в неправильном порядке.
2. Примитивы поддерживают операции, которые не имеют смысла. Например, если customerId имеет тип int, компилятор с радостью позволит вам разделить его на 2, но это не имеет смысла с точки зрения бизнеса.
Решение – объекты-значения. Идея состоит в том, что вы определяете (простой) объект предметной области, который заключает примитивный тип в типобезопасную оболочку. Иногда оболочка может иметь какую-то логику (например, валидацию), но чаще всего это анемичные модели, которые занимают промежуточное положение между примитивами и полноценными объектами предметной области.
Записи-значения
Современный способ создать объект-значение в C# - это записи-значения:
public readonly record struct CustomerId(string Value);По порядку:
- Записи (record) обеспечивают семантику значений, дополненную равенством по значению и поддержкой ToString.
- Структуры-записи (record struct) предоставляют оболочку типа значения, избегая выделения в куче (размер оболочки равен размеру обёрнутого значения).
- Структуры-записи только для чтения обеспечивают неизменность.
Поскольку определение типа представляет собой всего одну строку, все типы записей-значений можно собрать в одном файле, вроде Primitives.cs.
Советы
1. Избегайте публичности
Записи-значения лучше всего работают как внутренние типы. Не в смысле internal, а в смысле в ядре вашего приложения. Не пытайтесь настроить сериализацию записей-значений. Путь ваш код просто оборачивает примитивные типы, полученные извне:
CustomerId customerId = new(customerIdIntValue);и наоборот, разворачивает их обратно при отправке информации:
int customerIdIntValue = customerId.Value;2. Выбор примитивов для замены
Не все примитивы должны быть записями-значениями. Рекомендуется использовать примитивы по умолчанию, а записи-значения в следующих ситуациях:
- Есть похожие типы значений (концептуально похожие или просто имеющие похожее имя), которые используются вместе. Например, идентификаторы сущностей, имеющие похожие имена, и когда есть несколько методов, которые одновременно работают с разными типами идентификаторов.
- Нужно гарантировать, что критически важный тип-значения должен использоваться только определённым образом. Например, компонент, отправляющий e-mail с персональной информацией должен использовать только подтверждённые адреса. Тогда он может принимать тип ValidatedEmail. А свойство этого типа задаётся у пользователя только при подтверждении e-mail.
Неправильное использование
В основном это чрезмерное использование записей-значений. Существует компромисс в поддержке: записи-значения увеличивают сложность кода. Каждый разработчик знает, что такое строка, но тип ValidatedEmail не понятен с первого взгляда. Примитивы — прекрасное решение для большинства данных.
Итого
Избирательная замена некоторых примитивных типов записями-значениями повысит безопасность типов, правильность и удобство сопровождения вашего кода.
Источник: https://blog.stephencleary.com/2022/10/modern-csharp-techniques-2-value-records.html
👍18
День 1400.
35 Заблуждений о Дате и Времени. Начало
Дата и время – наверняка «любимый» тип данных очень многих программистов. В этой серии постов рассмотрим заблуждения о дате и времени. Просто факты, без объяснений, которые возможно и не пригодятся вам в повседневной работе, но всё-таки, думаю, что будет полезно иметь в виду.
1. Все используют один и тот же календарь
Не все страны перешли на григорианский календарь одновременно. Франция, Испания, Италия и некоторые другие страны перешли 15 октября 1582 года. Британская империя - в сентябре 1752. Россия не переходила до 1917, Греция - до 1923, а Садовская Аравия и вовсе перешла только в 2016 году.
В некоторых культурах используются разные календари, основанные на лунных, а не на солнечных циклах, или на комбинации лунных и солнечных.
2. 1 год = 12 месяцев
В некоторых календарях используются високосные месяцы, поэтому в году может быть 13 месяцев. В .NET BCL DateTimeFormat.GetMonthName принимает значение от 1 до 13 для поддержки календарных систем с високосными месяцами, например реализованных классами
3. В году 365 дней
В високосном году 366 дней.
4. В году 365 или 366 дней
В прошлом были различные примеры, когда некоторые страны меняли свой календарь. В этом случае год может быть короче. Например, Великобритания изменила свой календарь в 1752 году, и в сентябре не хватает нескольких дней:
5. Дни идут подряд
Если вы посмотрите на календарь из п.4 то увидите, что это не так. После 2го сентября идёт 14е.
6. За пятницей всегда следует суббота
Опять же, в календаре из п.4 дни идут не подряд. За пятницей 16 идёт воскресенье 17.
7. Високосные года идут каждые 4 года
Год должен делиться на 4 без остатка. Если год также можно без остатка разделить на 100, это не високосный год. Если только год не делится на 400 без остатка. Тогда это високосный год:
1900 год не високосный
2000 год високосный
2020 год високосный
8. Кто угодно может правильно реализовать логику високосных годов
Excel ошибочно предполагает, что 1900 год является високосным, и причина этой ошибки хорошо объяснена в документации.
9. GMT совпадает с UTC
Всемирное координированное время (UTC) — это международная шкала времени, рекомендованная Международным бюро мер и весов (BIPM) в качестве базисного времени.
Среднее время по Гринвичу (GMT) — это часовой пояс, который является средним солнечным временем в Королевской обсерватории в Гринвиче, Лондон. Это соответствует UT1, солнечному времени на 0° долготы.
Разница во времени между UTC и GMT составляет доли секунды. Поэтому для общих целей оба времени считаются одинаковыми. Но эта разница важна для научных целей.
Продолжение следует…
Источник: https://www.meziantou.net/misconceptions-about-date-and-time.htm
35 Заблуждений о Дате и Времени. Начало
Дата и время – наверняка «любимый» тип данных очень многих программистов. В этой серии постов рассмотрим заблуждения о дате и времени. Просто факты, без объяснений, которые возможно и не пригодятся вам в повседневной работе, но всё-таки, думаю, что будет полезно иметь в виду.
1. Все используют один и тот же календарь
Не все страны перешли на григорианский календарь одновременно. Франция, Испания, Италия и некоторые другие страны перешли 15 октября 1582 года. Британская империя - в сентябре 1752. Россия не переходила до 1917, Греция - до 1923, а Садовская Аравия и вовсе перешла только в 2016 году.
В некоторых культурах используются разные календари, основанные на лунных, а не на солнечных циклах, или на комбинации лунных и солнечных.
2. 1 год = 12 месяцев
В некоторых календарях используются високосные месяцы, поэтому в году может быть 13 месяцев. В .NET BCL DateTimeFormat.GetMonthName принимает значение от 1 до 13 для поддержки календарных систем с високосными месяцами, например реализованных классами
JewishCalendar
и EastAsianLunisolarCalendar
. В еврейском календаре Адар является 6-м месяцем обычного года, который становятся Адар 1 и Адар 2 (месяцы 6 и 7) в еврейском високосном году.3. В году 365 дней
В високосном году 366 дней.
4. В году 365 или 366 дней
В прошлом были различные примеры, когда некоторые страны меняли свой календарь. В этом случае год может быть короче. Например, Великобритания изменила свой календарь в 1752 году, и в сентябре не хватает нескольких дней:
Su Mo Tu We Th Fr SaДругим примером является Самоа, где изменили часовой пояс в 2011. В этом году пятницы, 30 декабря, не существует. Поскольку 2011 год не високосный, в том году было всего 364 дня.
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
TimeZoneInfoЕврейский календарь является лунным календарем. Лунные месяцы короче солнечных. Таким образом, каждые несколько лет они добавляют дополнительный месяц. Таким образом, вы получаете год короче 365 дней, а затем год с дополнительным месяцем в 380 с чем-то дней.
.FindSystemTimeZoneById("Pacific/Apia")
.IsInvalidTime(new DateTime(2011, 12, 30)));
// True
5. Дни идут подряд
Если вы посмотрите на календарь из п.4 то увидите, что это не так. После 2го сентября идёт 14е.
6. За пятницей всегда следует суббота
Опять же, в календаре из п.4 дни идут не подряд. За пятницей 16 идёт воскресенье 17.
7. Високосные года идут каждые 4 года
Год должен делиться на 4 без остатка. Если год также можно без остатка разделить на 100, это не високосный год. Если только год не делится на 400 без остатка. Тогда это високосный год:
1900 год не високосный
2000 год високосный
2020 год високосный
8. Кто угодно может правильно реализовать логику високосных годов
Excel ошибочно предполагает, что 1900 год является високосным, и причина этой ошибки хорошо объяснена в документации.
9. GMT совпадает с UTC
Всемирное координированное время (UTC) — это международная шкала времени, рекомендованная Международным бюро мер и весов (BIPM) в качестве базисного времени.
Среднее время по Гринвичу (GMT) — это часовой пояс, который является средним солнечным временем в Королевской обсерватории в Гринвиче, Лондон. Это соответствует UT1, солнечному времени на 0° долготы.
Разница во времени между UTC и GMT составляет доли секунды. Поэтому для общих целей оба времени считаются одинаковыми. Но эта разница важна для научных целей.
Продолжение следует…
Источник: https://www.meziantou.net/misconceptions-about-date-and-time.htm
👍19
День 1401.
35 Заблуждений о Дате и Времени. Продолжение
Начало
10. Смещение часового пояса всегда представляет собой целое число часов
Для многих часовых поясов нет. Летнее время Ньюфаундленда — UTC-2:30. Центрально-западное стандартное время Австралии — UTC+8:45.
11. Смещение часового пояса всегда представляет собой целое число минут
В прошлом было много контрпримеров, где смещение содержит секунды:
Зона Азия/Кабул 4:36:48 LMT 1890
Зона Азия/Баку 3:19:24 LMT 1924 2 мая
Зона Америка/Бойсе -7:44:49 LMT 1883 18 ноя 12:15:11
Зона Европа/Андорра 0:06:04 LMT 1901
12. Смещение часового пояса составляет от -12 часов до +12 часов
Время островов Лайн: UTC+14.
13. Дата и время в прошлом неизменны
Данные о часовом поясе могут быть неверными, и их исправляют. Таким образом, дата и время в прошлом могут измениться. Примером является зона Азия/Шанхай. А отсюда можно скачать данные часовых поясов и в файле NEWS найти раздел "Changes to past timestamps" с другими примерами.
14. Летнее время всегда -1 или +1 час
В зоне Австралия/Лорд Хоув стрелки переводят на 30 минут. А в зоне Антарктида/Тролл – на 2 часа.
15. Дни начинаются в полночь
Переход на полночь произошёл между 1920 и 1930 годами. Исторически день начинался в полдень. Это было полезно для астрономов, которые могли записывать свои наблюдения в один день. Например, если вы наблюдаете звезду в 23:59, вы можете записать ее в тот же день, что и наблюдение в 00:01.
День в раввинистическом еврейском календаре длится от заката (начало «вечера») до следующего заката.
16. Полночь - действительное время
В Чили летнее время начинается в полночь, поэтому полночь - недопустимое время в этот день.
2019-09-07 23:59:59
2019-09-08 01:00:00
17. Праздники охватывают целое число целых дней
В еврейском календаре день начинается, когда восходит луна. Это означает, что праздники, которые в большинстве календарей обозначаются как «среда», на самом деле начинаются во вторник вечером и продолжаются до вечера среды.
18. Дата и время + часовой пояс представляют один момент времени
Переход на летнее время может сдвинуть часы назад, поэтому это могут быть два разных момента времени.
19. В одном и том же месяце всегда одинаковое количество дней
В Швеции есть 30 февраля 1712 года. Эта дата действительна только в Швеции.
20. 1 минута = 60 секунд
Период вращения Земли меняется со временем. Как правило, изменение составляет около секунды каждые пару лет. Всякий раз, когда отклонение от атомного времени становится слишком большим, вводится поправка на одну секунду, называемая дополнительной секундой. Это сделано таким образом, чтобы разница никогда не превышала 0,9 секунды. Например, 30 июня 2012 г., 23:59:60 — допустимое время.
До Windows Server 2016 служба времени Microsoft не знала о секундах координации, но полагалась на внешнюю службу времени, чтобы позаботиться об этом. Сейчас Windows «знает» о дополнительных секундах.
21. Дни длятся 24 часа
Япония соблюдала летнее время в 1948-1951 гг. Однако они используют стратегию, которая сильно отличается от стратегии других стран. Они просто добавляют час в конце дня:
Суббота 23:58
Суббота 23:59
Суббота 24:00 (без изменения даты)
Суббота 24:01
...
Суббота 24:58
Суббота 24:59
Воскресенье 00:00
Воскресенье 00:01
22. Сломанные часы показывают правильное время дважды в день
Оно может быть правильным 3 раза в день или 1 раз из-за перехода на летнее время.
23. У небольшой страны 1 часовой пояс
Во Франции 12 часовых поясов (13, включая ее притязания на Антарктиду) от UTC-11 до UTC+12. Список часовых поясов по странам можно найти в Википедии.
Окончание следует…
Источник: https://www.meziantou.net/misconceptions-about-date-and-time.htm
35 Заблуждений о Дате и Времени. Продолжение
Начало
10. Смещение часового пояса всегда представляет собой целое число часов
Для многих часовых поясов нет. Летнее время Ньюфаундленда — UTC-2:30. Центрально-западное стандартное время Австралии — UTC+8:45.
11. Смещение часового пояса всегда представляет собой целое число минут
В прошлом было много контрпримеров, где смещение содержит секунды:
Зона Азия/Кабул 4:36:48 LMT 1890
Зона Азия/Баку 3:19:24 LMT 1924 2 мая
Зона Америка/Бойсе -7:44:49 LMT 1883 18 ноя 12:15:11
Зона Европа/Андорра 0:06:04 LMT 1901
12. Смещение часового пояса составляет от -12 часов до +12 часов
Время островов Лайн: UTC+14.
13. Дата и время в прошлом неизменны
Данные о часовом поясе могут быть неверными, и их исправляют. Таким образом, дата и время в прошлом могут измениться. Примером является зона Азия/Шанхай. А отсюда можно скачать данные часовых поясов и в файле NEWS найти раздел "Changes to past timestamps" с другими примерами.
14. Летнее время всегда -1 или +1 час
В зоне Австралия/Лорд Хоув стрелки переводят на 30 минут. А в зоне Антарктида/Тролл – на 2 часа.
15. Дни начинаются в полночь
Переход на полночь произошёл между 1920 и 1930 годами. Исторически день начинался в полдень. Это было полезно для астрономов, которые могли записывать свои наблюдения в один день. Например, если вы наблюдаете звезду в 23:59, вы можете записать ее в тот же день, что и наблюдение в 00:01.
День в раввинистическом еврейском календаре длится от заката (начало «вечера») до следующего заката.
16. Полночь - действительное время
В Чили летнее время начинается в полночь, поэтому полночь - недопустимое время в этот день.
2019-09-07 23:59:59
2019-09-08 01:00:00
17. Праздники охватывают целое число целых дней
В еврейском календаре день начинается, когда восходит луна. Это означает, что праздники, которые в большинстве календарей обозначаются как «среда», на самом деле начинаются во вторник вечером и продолжаются до вечера среды.
18. Дата и время + часовой пояс представляют один момент времени
Переход на летнее время может сдвинуть часы назад, поэтому это могут быть два разных момента времени.
19. В одном и том же месяце всегда одинаковое количество дней
В Швеции есть 30 февраля 1712 года. Эта дата действительна только в Швеции.
20. 1 минута = 60 секунд
Период вращения Земли меняется со временем. Как правило, изменение составляет около секунды каждые пару лет. Всякий раз, когда отклонение от атомного времени становится слишком большим, вводится поправка на одну секунду, называемая дополнительной секундой. Это сделано таким образом, чтобы разница никогда не превышала 0,9 секунды. Например, 30 июня 2012 г., 23:59:60 — допустимое время.
До Windows Server 2016 служба времени Microsoft не знала о секундах координации, но полагалась на внешнюю службу времени, чтобы позаботиться об этом. Сейчас Windows «знает» о дополнительных секундах.
21. Дни длятся 24 часа
Япония соблюдала летнее время в 1948-1951 гг. Однако они используют стратегию, которая сильно отличается от стратегии других стран. Они просто добавляют час в конце дня:
Суббота 23:58
Суббота 23:59
Суббота 24:00 (без изменения даты)
Суббота 24:01
...
Суббота 24:58
Суббота 24:59
Воскресенье 00:00
Воскресенье 00:01
22. Сломанные часы показывают правильное время дважды в день
Оно может быть правильным 3 раза в день или 1 раз из-за перехода на летнее время.
23. У небольшой страны 1 часовой пояс
Во Франции 12 часовых поясов (13, включая ее притязания на Антарктиду) от UTC-11 до UTC+12. Список часовых поясов по странам можно найти в Википедии.
Окончание следует…
Источник: https://www.meziantou.net/misconceptions-about-date-and-time.htm
👍20
День 1402.
35 Заблуждений о Дате и Времени. Окончание
Начало
Продолжение
24. Переход на летнее время происходит одновременно во всех часовых поясах
Летнее время в Чили начинается в полночь, а в зоне Америка/Торонто - в 2 часа ночи. В Европе переход на летнее время происходит в последнее воскресенье марта, а в Северной Америке - во второе воскресенье марта.
25. Недели начинаются в понедельник
В Израиле неделя начинается в воскресенье.
26. Выходные - это суббота и воскресенье
Многие страны используют разные правила для рабочей недели и выходных. Вот несколько примеров:
Канада: суббота-воскресенье
Израиль: пятница-суббота
Иран: пятница
Непал: суббота
27. Месяц длится 28-31 день
Википедия говорит, что месяцы могут длиться 32 дня в юлианском календаре. Также см. пример из п.4.
28. Год начинается 1 января
В Великобритании до 1752 года юридический год начинается 25 марта. Так, например, за 24 марта 1707 г. сразу же следовало 25 марта 1708 г., а днем, следующим за 31 декабря 1708 г., был 1 января 1708 г.
В эфиопском календаре год начинается 11 или 12 сентября.
Другие примеры есть в статье Википедии про Новый год.
29. Существует нулевой год
В системе Anno Domini (AD), которая используется для нумерации лет по григорианскому календарю, за годом 1 до н.э. непосредственно следует год 1 н.э. Таким образом, года 0 не существует.
30. Об изменениях в часовых поясах предупреждают заранее
Аргентина уведомила за 11 дней о своем решении отменить переход на летнее время в 2009/2010 гг.
В 2011 Турция уведомила за две недели о том, что откладывает переход на летнее время.
В 2013 Марокко менее чем за день уведомили об изменении своих правил часового пояса.
В 2022 Чили уведомили за 1 месяц, что меняют правила часового пояса. Для компаний, использующих ежемесячные обновления, это может быть сложно реализовать. В Майкрософт даже выпустили особое руководство на этот случай.
Ну и не стоит забывать про метания России то в летнее, то в зимнее время.
31. DateTime.UtcNow равномерно увеличивается
Вы можете изменить системное время вручную или оно может обновиться с NTP-сервера. Поэтому
32. При отображении даты вы получите тот же год, что и в объекте DateTime
Некоторые библиотеки позволяют различать год (например, yyyy) и год как неделю ISO (например, YYYY). В этом случае отображаемый год может отличаться от года, хранящегося в объекте DateTime. Например, 2022-01-02 — это 52-я неделя 2021 года.
33. 32 бита достаточно, чтобы хранить любую дату
При использовании 32-битных целых чисел со знаком временная метка Unix позволяет представлять дату до 2038 года. Чтобы избежать такого ограничения, используйте 64-битные целые числа.
В некоторых тестах у .NET возникали проблемы с производительностью примерно каждые 29 дней https://github.com/dotnet/runtime/issues/51935. Основная причина заключалась в том, что реализация пула потоков использовала Environment.TickCount, 32-разрядное значение со знаком.
34. Хранение даты в формате UTC предотвращает все проблемы
Если вы хотите запланировать встречу в следующем апреле в 10 утра в Нью-Йорке, не стоит вычислять дату в формате UTC и хранить её. Ведь нельзя быть уверенным, что правила перехода на летнее время не изменятся до встречи.
35. Синхронизация времени между машинами проста
Протокол Network Time Protocol (NTP) позволяет синхронизировать время между машинами. Есть несколько факторов, которые могут затруднить получение точного времени:
- Сервер NTP может быть неточным
- Сеть может быть перегружена
- Сеть может быть несимметричной (не то же количество переходов для запроса к серверу, что и количество переходов для ответа).
- Машинные часы могут быть неточными, поэтому машинные часы дрейфуют довольно часто.
Подробнее об этом было в недавнем посте.
Источник: https://www.meziantou.net/misconceptions-about-date-and-time.htm
35 Заблуждений о Дате и Времени. Окончание
Начало
Продолжение
24. Переход на летнее время происходит одновременно во всех часовых поясах
Летнее время в Чили начинается в полночь, а в зоне Америка/Торонто - в 2 часа ночи. В Европе переход на летнее время происходит в последнее воскресенье марта, а в Северной Америке - во второе воскресенье марта.
25. Недели начинаются в понедельник
В Израиле неделя начинается в воскресенье.
26. Выходные - это суббота и воскресенье
Многие страны используют разные правила для рабочей недели и выходных. Вот несколько примеров:
Канада: суббота-воскресенье
Израиль: пятница-суббота
Иран: пятница
Непал: суббота
27. Месяц длится 28-31 день
Википедия говорит, что месяцы могут длиться 32 дня в юлианском календаре. Также см. пример из п.4.
28. Год начинается 1 января
В Великобритании до 1752 года юридический год начинается 25 марта. Так, например, за 24 марта 1707 г. сразу же следовало 25 марта 1708 г., а днем, следующим за 31 декабря 1708 г., был 1 января 1708 г.
В эфиопском календаре год начинается 11 или 12 сентября.
Другие примеры есть в статье Википедии про Новый год.
29. Существует нулевой год
В системе Anno Domini (AD), которая используется для нумерации лет по григорианскому календарю, за годом 1 до н.э. непосредственно следует год 1 н.э. Таким образом, года 0 не существует.
30. Об изменениях в часовых поясах предупреждают заранее
Аргентина уведомила за 11 дней о своем решении отменить переход на летнее время в 2009/2010 гг.
В 2011 Турция уведомила за две недели о том, что откладывает переход на летнее время.
В 2013 Марокко менее чем за день уведомили об изменении своих правил часового пояса.
В 2022 Чили уведомили за 1 месяц, что меняют правила часового пояса. Для компаний, использующих ежемесячные обновления, это может быть сложно реализовать. В Майкрософт даже выпустили особое руководство на этот случай.
Ну и не стоит забывать про метания России то в летнее, то в зимнее время.
31. DateTime.UtcNow равномерно увеличивается
Вы можете изменить системное время вручную или оно может обновиться с NTP-сервера. Поэтому
DateTime.UtcNow <= DateTime.UtcNow
может выдать false
, если между вызовами произошла корректировка назад.32. При отображении даты вы получите тот же год, что и в объекте DateTime
Некоторые библиотеки позволяют различать год (например, yyyy) и год как неделю ISO (например, YYYY). В этом случае отображаемый год может отличаться от года, хранящегося в объекте DateTime. Например, 2022-01-02 — это 52-я неделя 2021 года.
33. 32 бита достаточно, чтобы хранить любую дату
При использовании 32-битных целых чисел со знаком временная метка Unix позволяет представлять дату до 2038 года. Чтобы избежать такого ограничения, используйте 64-битные целые числа.
В некоторых тестах у .NET возникали проблемы с производительностью примерно каждые 29 дней https://github.com/dotnet/runtime/issues/51935. Основная причина заключалась в том, что реализация пула потоков использовала Environment.TickCount, 32-разрядное значение со знаком.
34. Хранение даты в формате UTC предотвращает все проблемы
Если вы хотите запланировать встречу в следующем апреле в 10 утра в Нью-Йорке, не стоит вычислять дату в формате UTC и хранить её. Ведь нельзя быть уверенным, что правила перехода на летнее время не изменятся до встречи.
35. Синхронизация времени между машинами проста
Протокол Network Time Protocol (NTP) позволяет синхронизировать время между машинами. Есть несколько факторов, которые могут затруднить получение точного времени:
- Сервер NTP может быть неточным
- Сеть может быть перегружена
- Сеть может быть несимметричной (не то же количество переходов для запроса к серверу, что и количество переходов для ответа).
- Машинные часы могут быть неточными, поэтому машинные часы дрейфуют довольно часто.
Подробнее об этом было в недавнем посте.
Источник: https://www.meziantou.net/misconceptions-about-date-and-time.htm
День 1404. #ЧтоНовенького #DotNet8
Новые Анализаторы в .NET 8
.NET 7 вышел, пора посмотреть, что нас ждёт в новой версии))) Сегодня рассмотрим новые анализаторы кода. Анализаторы в версии превью можно использовать, если добавить в файл .csproj следующее:
Строки
1. Рекомендация заменить string.ToLower() на регистронезависимый Equals
По сути, правило предложит заменить
2. Рекомендация использовать StartsWith вместо IndexOf == 0
Проблема с IndexOf в том, что в худшем случае он пройдёт по всей строке. StartsWith, напротив, сразу прервётся при первом несоответствии, поэтому он быстрее.
Коллекции
3. Рекомендация использовать Length/Count/IsEmpty вместо Any()
Если в коллекции есть свойство вроде Length (T[]), Count (List<T>) или IsEmpty (потокобезопасные коллекции), то лучше использовать его, а не Any().
4. Предотвращение неправильного использования Split
Допустим, вы хотите разделить строку по пробелам и табам:
6. Атрибут NonCopyable
Обычно, если вы передаёте типы-значения в функцию, они копируются. В чём проблема? Предположим, вы пишете какой-то высокопроизводительный API (например, ValueStringBuilder), используя (ref) struct, чтобы избежать аллокаций на куче. Потребители этих типов должны быть осторожны, чтобы объект не копировался повсюду. И вот тут-то поможет новый атрибут.
Новые Анализаторы в .NET 8
.NET 7 вышел, пора посмотреть, что нас ждёт в новой версии))) Сегодня рассмотрим новые анализаторы кода. Анализаторы в версии превью можно использовать, если добавить в файл .csproj следующее:
<PropertyGroup Label="Analyzer settings">Уже сейчас есть более 100 правил, которые предупредят вас в определённых ситуациях. Полный список можно найти на странице GitHub. Сегодня рассмотрим, что нового собираются добавить.
<AnalysisLevel>preview</AnalysisLevel>
</PropertyGroup>
Строки
1. Рекомендация заменить string.ToLower() на регистронезависимый Equals
По сути, правило предложит заменить
str.ToLowerCase() == "string";на
string.Equals(str, "string", StringComparison.OrdinalIgnoreCase);Это быстрее и не аллоцирует строки.
2. Рекомендация использовать StartsWith вместо IndexOf == 0
Проблема с IndexOf в том, что в худшем случае он пройдёт по всей строке. StartsWith, напротив, сразу прервётся при первом несоответствии, поэтому он быстрее.
Коллекции
3. Рекомендация использовать Length/Count/IsEmpty вместо Any()
Если в коллекции есть свойство вроде Length (T[]), Count (List<T>) или IsEmpty (потокобезопасные коллекции), то лучше использовать его, а не Any().
// Это будет помечено новым анализаторомЭто может быть быстрее, т.к. Any выполняет проверку типов, а Length или Count — это простые свойства, которые, помимо прочего, могут быть встроены. Хотя, субъективно, в коде Any может выглядеть более читабельно.
var isEmpty = names.Any();
// Это не будет помечено как "предпочтительное"
var isEmpty = names.Count != 0;
4. Предотвращение неправильного использования Split
Допустим, вы хотите разделить строку по пробелам и табам:
var split = s.Split(' ', '\t');Это прекрасно работает. Но если вы захотите удалить пустые элементы:
var split = s.Split(' ', '\t',Но это не будет работать правильно, потому что второй char преобразуется в int и будет использован в качестве аргумента count (максимального количества элементов в результирующем массиве) для перегруженной версии Split. Правильное использование будет таким:
StringSplitOptions.RemoveEmptyEntries);
var split = s.Split(new[] { ' ', '\t' },Структуры
StringSplitOptions.RemoveEmptyEntries);
6. Атрибут NonCopyable
Обычно, если вы передаёте типы-значения в функцию, они копируются. В чём проблема? Предположим, вы пишете какой-то высокопроизводительный API (например, ValueStringBuilder), используя (ref) struct, чтобы избежать аллокаций на куче. Потребители этих типов должны быть осторожны, чтобы объект не копировался повсюду. И вот тут-то поможет новый атрибут.
[NonCopyable]Здесь будет ошибка, т.к. ValueStringBuilder должен передаваться по ссылке. Правильное использование:
public ref struct ValueStringBuilder
{ … }
// Использование
public void MyMethod(ValueStringBuilder vsb)
{ … }
var vsb = new ValueStringBuilder();
MyMethod(vsb);
public void MyMethod(ref ValueStringBuilder vsb)Источник: https://steven-giesel.com/blogPost/1a03a998-e428-4a42-9c30-1cfbf6ac5f4c
{ … }
var vsb = new ValueStringBuilder();
MyMethod(ref vsb);
👍20
День 1405. #МоиИнструменты
Интеграционные Тесты ASP.NET Core с Помощью Alba
Alba — это небольшая библиотека, которая обеспечивает простое интеграционное тестирование маршрутов ASP.NET Core, полностью совместимая с NUnit/xUnit.Net/MSTest. Недавно вышедшая версия 7.1 поддерживает .NET 7, улучшила обработку JSON для конечных точек Minimal API и поддерживает составной тип содержимого форм.
Допустим, есть проект Minimal API:
Помимо GetAsText() есть другие полезные методы, вроде PostJson(), который использует конфигурацию сериализации JSON вашего приложения, если вдруг она у вас кастомизирована. Аналогично Receive<T>() использует сериализацию JSON вашего приложения.
Когда тест выполняется, он проходит через весь конвейер ASP.NET Core вашего приложения, включая любое зарегистрированное промежуточное ПО.
Альтернативным вариантом для теста может быть использование класса Scenario и его удобочитаемых методов:
Имейте в виду, чтобы ваши тесты «видели» ваш класс Program, вам нужно разрешить вашему тестовому проекту «видеть» ваш основной проект. Для этого в файл .csproj основного проекта добавьте:
Интеграционные Тесты ASP.NET Core с Помощью Alba
Alba — это небольшая библиотека, которая обеспечивает простое интеграционное тестирование маршрутов ASP.NET Core, полностью совместимая с NUnit/xUnit.Net/MSTest. Недавно вышедшая версия 7.1 поддерживает .NET 7, улучшила обработку JSON для конечных точек Minimal API и поддерживает составной тип содержимого форм.
Допустим, есть проект Minimal API:
var builder = WebApplication.CreateBuilder(args);Мы можем написать тест маршрута, вроде такого:
var app = builder.Build();
app.MapGet("/", () => "Hello, World!");
app.Run();
[Fact]Тест загружает ваше фактическое приложение, используя его конфигурацию, но используя TestServer вместо Kestrel в качестве веб-сервера.
public async Task sample_test()
{
// Здесь для примера хост создаётся внутри теста.
// В реальности это дорого,
// его лучше создать один на все тесты.
await using var host =
await AlbaHost.For<global::Program>();
var result = await host.GetAsText("/");
Assert.Equal(result, "Hello, World!");
}
Помимо GetAsText() есть другие полезные методы, вроде PostJson(), который использует конфигурацию сериализации JSON вашего приложения, если вдруг она у вас кастомизирована. Аналогично Receive<T>() использует сериализацию JSON вашего приложения.
Когда тест выполняется, он проходит через весь конвейер ASP.NET Core вашего приложения, включая любое зарегистрированное промежуточное ПО.
Альтернативным вариантом для теста может быть использование класса Scenario и его удобочитаемых методов:
await host.Scenario(_ =>Библиотека предлагает множество настроек и вариантов использования, которые можно посмотреть в документации.
{
_.Get.Url("/");
_.ContentShouldBe("Hello, World!");
_.StatusCodeShouldBeOk();
});
Имейте в виду, чтобы ваши тесты «видели» ваш класс Program, вам нужно разрешить вашему тестовому проекту «видеть» ваш основной проект. Для этого в файл .csproj основного проекта добавьте:
<ItemGroup>Источник: https://jeremydmiller.com/2022/11/28/alba-for-effective-asp-net-core-integration-testing/
<InternalsVisibleTo Include="[ИмяПроектаТестов]" />
</ItemGroup>
👍21
День 1406. #ЗаметкиНаПолях #AsyncTips
Планирование работы в пуле потоков
Любой блок кода должен выполняться в каком-то потоке. Планировщик (scheduler) решает, где должен выполняться тот или иной код. Обычно планировщик по умолчанию работает как надо. Например, оператор await в асинхронном коде автоматически возобновит выполнение метода в том же контексте, если только вы не переопределите это поведение.
Задача: имеется фрагмент кода, который должен выполняться в потоке из пула потоков.
Решение
В большинстве случаев следует использовать Task.Run. Следующий пример блокирует поток из пула на 2 секунды:
Task.Run идеально подходит для UI-приложений с продолжительной работой, которая не должна выполняться в UI-потоке. Тем не менее не используйте Task.Run в ASP.NET, если только вы не уверены в том, что делаете. В ASP.NET код обработки запросов уже выполняется в потоке из пула, так что перенесение его в другой поток из пула обычно нерационально.
Task.Run является фактической заменой для BackgroundWorker, Delegate.BeginInvoke и ThreadPool.QueueUserWorkItem. Ни один из этих старых API не следует использовать в новом коде; код с Task.Run намного проще писать и сопровождать.
Task.Run справляется с большинством задач, для которых используется Thread, так что в большинстве случаев Thread может заменяться на Task.Run (за редким исключением).
Параллельный код и код потоков данных выполняется в пуле потоков по умолчанию, поэтому обычно Task.Run не нужно использовать с кодом, выполняемым через Parallel, библиотекой TPL Dataflow или Parallel LINQ.
Если вы применяете динамический параллелизм, используйте Task.Factory.StartNew вместо Task.Run. Это необходимо из-за того, что у объекта Task, возвращаемого Task.Run, параметры по умолчанию настроены для асинхронного использования (т.е. для потребления в асинхронном или реактивном коде). Кроме того, он не поддерживает такие расширенные возможности, как задачи «родитель/потомок», типичные для динамического параллельного кода.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
Планирование работы в пуле потоков
Любой блок кода должен выполняться в каком-то потоке. Планировщик (scheduler) решает, где должен выполняться тот или иной код. Обычно планировщик по умолчанию работает как надо. Например, оператор await в асинхронном коде автоматически возобновит выполнение метода в том же контексте, если только вы не переопределите это поведение.
Задача: имеется фрагмент кода, который должен выполняться в потоке из пула потоков.
Решение
В большинстве случаев следует использовать Task.Run. Следующий пример блокирует поток из пула на 2 секунды:
Task task = Task.Run(() =>Task.Run также поддерживает возвращаемые значения и асинхронные лямбда-выражения. Задача, возвращаемая Task.Run в следующем коде, завершится через 2 секунды с результатом 13:
{
Thread.Sleep(2000);
});
Task<int> task = Task.Run(async () =>Task.Run возвращает объект Task (или Task<T>), который может естественным образом потребляться асинхронным или реактивным кодом.
{
await Task.Delay(2000);
return 13;
});
Task.Run идеально подходит для UI-приложений с продолжительной работой, которая не должна выполняться в UI-потоке. Тем не менее не используйте Task.Run в ASP.NET, если только вы не уверены в том, что делаете. В ASP.NET код обработки запросов уже выполняется в потоке из пула, так что перенесение его в другой поток из пула обычно нерационально.
Task.Run является фактической заменой для BackgroundWorker, Delegate.BeginInvoke и ThreadPool.QueueUserWorkItem. Ни один из этих старых API не следует использовать в новом коде; код с Task.Run намного проще писать и сопровождать.
Task.Run справляется с большинством задач, для которых используется Thread, так что в большинстве случаев Thread может заменяться на Task.Run (за редким исключением).
Параллельный код и код потоков данных выполняется в пуле потоков по умолчанию, поэтому обычно Task.Run не нужно использовать с кодом, выполняемым через Parallel, библиотекой TPL Dataflow или Parallel LINQ.
Если вы применяете динамический параллелизм, используйте Task.Factory.StartNew вместо Task.Run. Это необходимо из-за того, что у объекта Task, возвращаемого Task.Run, параметры по умолчанию настроены для асинхронного использования (т.е. для потребления в асинхронном или реактивном коде). Кроме того, он не поддерживает такие расширенные возможности, как задачи «родитель/потомок», типичные для динамического параллельного кода.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
👍19
День 1407. #ЗаметкиНаПолях #TipsAndTricks
Современные Приёмы в C#. Часть 3. Начало
Часть 1
Часть 2
Компиляция Обобщённого Кода
Шаблоны C++
Шаблоны C++ (и дженерики) являются формами полиморфных функций. Вы определяете один тип (или метод), который принимает параметр типа и может изменить своё поведение на основе переданного типа. Это просто конструкция времени компиляции. Они указывают компилятору, как генерировать код для типа/метода шаблона. А компилятор выполняет преобразование, называемое мономорфизацией: для каждого аргумента шаблона, фактически переданного типу/методу, компилятор создаёт новую копию типа/метода.
Обобщения в С#
В С# это конструкция времени выполнения: компилятор фактически выводит сам обобщённый тип/метод в IL (промежуточный язык). Во время выполнения реализация обобщённого типа/метода распределяется между обобщёнными аргументами.
Другими словами, обобщения в C# не подвергаются мономорфизации для ссылочных типов. Существует только одна копия реализации типа/метода, которая является общей для всех. Однако для типов-значений обобщения подвергаются мономорфизации!
Это имеет смысл: если метод Something<T> определяет значение локальной переменной T, компилятору необходимо знать, насколько велико это значение. Размер ссылки одинаков независимо от типа, на который она ссылается, но размер типа-значения может различаться.
Обобщения и ограниченные типы значений
Мономорфизация наиболее полезна, если ограничиваете обобщения определённым интерфейсом. Простой и бесполезный пример:
Тогда каждая копия метода имеет высокую вероятность оптимизации. Компилятор знает тип T для каждой копии. Когда он оптимизирует Function<Sample7>, он знает, что default(T).Setting вызывает метод ISample.get_Setting для типа Sample7. Реализация ISample.Setting в Sample7 тривиальна и, скорее всего, будет встроена, что означает, что ветвь if может быть предварительно вычислена. Очень вероятно, что обе копии Function<T> в итоге будут иметь только один вызов Console.WriteLine без оператора if.
Статические абстрактные методы интерфейса позволяют нам ещё немного почистить код. Вместо того, чтобы определять Setting как метод экземпляра, он может быть статическим абстрактным методом:
Окончание следует…
Источник: https://blog.stephencleary.com/2022/10/modern-csharp-techniques-3-generic-code-generation.html
Современные Приёмы в C#. Часть 3. Начало
Часть 1
Часть 2
Компиляция Обобщённого Кода
Шаблоны C++
Шаблоны C++ (и дженерики) являются формами полиморфных функций. Вы определяете один тип (или метод), который принимает параметр типа и может изменить своё поведение на основе переданного типа. Это просто конструкция времени компиляции. Они указывают компилятору, как генерировать код для типа/метода шаблона. А компилятор выполняет преобразование, называемое мономорфизацией: для каждого аргумента шаблона, фактически переданного типу/методу, компилятор создаёт новую копию типа/метода.
Обобщения в С#
В С# это конструкция времени выполнения: компилятор фактически выводит сам обобщённый тип/метод в IL (промежуточный язык). Во время выполнения реализация обобщённого типа/метода распределяется между обобщёнными аргументами.
Другими словами, обобщения в C# не подвергаются мономорфизации для ссылочных типов. Существует только одна копия реализации типа/метода, которая является общей для всех. Однако для типов-значений обобщения подвергаются мономорфизации!
Это имеет смысл: если метод Something<T> определяет значение локальной переменной T, компилятору необходимо знать, насколько велико это значение. Размер ссылки одинаков независимо от типа, на который она ссылается, но размер типа-значения может различаться.
Обобщения и ограниченные типы значений
Мономорфизация наиболее полезна, если ограничиваете обобщения определённым интерфейсом. Простой и бесполезный пример:
interface ISampleКомпилятор C# обрабатывает Function как обычную обобщённую функцию. JIT создаст две отдельные копии Function, поскольку Sample7 и Sample13 являются типами-значений. В обеих копиях код default(T).Setting передаётся как ограниченный виртуальный вызов.
{
int Setting { get; }
}
void Function<T>()
where T : struct, ISample
{
if (default(T).Setting == 13)
Console.WriteLine("Тринадцать!");
else
Console.WriteLine(default(T).Setting);
}
readonly struct Sample7 : ISample
{
public int Setting => 7;
}
readonly struct Sample13 : ISample
{
public int Setting => 13;
}
Function<Sample7>(); // 7
Function<Sample13>();// Тринадцать!
Тогда каждая копия метода имеет высокую вероятность оптимизации. Компилятор знает тип T для каждой копии. Когда он оптимизирует Function<Sample7>, он знает, что default(T).Setting вызывает метод ISample.get_Setting для типа Sample7. Реализация ISample.Setting в Sample7 тривиальна и, скорее всего, будет встроена, что означает, что ветвь if может быть предварительно вычислена. Очень вероятно, что обе копии Function<T> в итоге будут иметь только один вызов Console.WriteLine без оператора if.
Статические абстрактные методы интерфейса позволяют нам ещё немного почистить код. Вместо того, чтобы определять Setting как метод экземпляра, он может быть статическим абстрактным методом:
interface ISampleТеперь свойства Sample7 и Sample13 будут статическими, зато нет необходимости в default(T).
{
static abstract int Setting { get; }
}
void Function<T>()
where T : struct, ISample
{
if (T.Setting == 13)
Console.WriteLine("Тринадцать!");
else
Console.WriteLine(T.Setting);
}
Окончание следует…
Источник: https://blog.stephencleary.com/2022/10/modern-csharp-techniques-3-generic-code-generation.html
👍12
День 1408. #ЗаметкиНаПолях #TipsAndTricks
Современные Приёмы в C#. Часть 3. Окончание
Часть 1
Часть 2
Часть 3. Начало
Пример применения
В кодовой базе бывают области, где определённые аргументы метода всегда являются константами. Обычно это указывает на то, что метод следует разделить на два, но это не всегда возможно. Возьмём популярный костыль с булевым аргументом, отвечающим за синхронное или асинхронное исполнение метода:
Воспользуемся обобщённой генерацией кода для создания двух разных методов:
Извлечём различия в коде (Thread.Sleep и Task.Delay). Для них потребуется определение в интерфейсе, и они будут реализованы для каждого типа-значения. Поскольку код может быть синхронным или асинхронным, мы будем использовать ValueTask в качестве типа результата. GetCoreAsync может просто вызывать эти методы интерфейса:
Стивен Тауб рассказывает в недавнем посте, как BCL использует эту технику.
Примечания и Ограничения
Используя дженерики C# с типами-значений, мы можем обеспечить мономорфизацию, однако остальная часть поведения не гарантируется. JIT не гарантирует, что какие-либо конкретные методы будут встроены или что произойдет какая-либо оптимизация. Разумно предположить, что она будет иметь место, а при современной многоуровневой оптимизации можно также ожидать, что метод станет более оптимизированным, если вызывается много раз.
Вы ограничены только тем, что может быть выражено в виде членов интерфейса. Вы можете определять методы и свойства, но не константные значения или вложенные типы.
Источник: https://blog.stephencleary.com/2022/10/modern-csharp-techniques-3-generic-code-generation.html
Современные Приёмы в C#. Часть 3. Окончание
Часть 1
Часть 2
Часть 3. Начало
Пример применения
В кодовой базе бывают области, где определённые аргументы метода всегда являются константами. Обычно это указывает на то, что метод следует разделить на два, но это не всегда возможно. Возьмём популярный костыль с булевым аргументом, отвечающим за синхронное или асинхронное исполнение метода:
private async Task<string> GetCoreAsync(bool sync)Допустим, что GetCoreAsync намного длиннее и сложнее, и создание двух разных методов действительно усложнит обслуживание.
{
if (sync)
Thread.Sleep(TimeSpan.FromSeconds(1));
else
await Task.Delay(TimeSpan.FromSeconds(1));
return "Hi!";
}
public string Get() =>
GetCoreAsync(true).GetAwaiter().GetResult();
public Task<string> GetAsync() =>
GetCoreAsync(false);
Воспользуемся обобщённой генерацией кода для создания двух разных методов:
Извлечём различия в коде (Thread.Sleep и Task.Delay). Для них потребуется определение в интерфейсе, и они будут реализованы для каждого типа-значения. Поскольку код может быть синхронным или асинхронным, мы будем использовать ValueTask в качестве типа результата. GetCoreAsync может просто вызывать эти методы интерфейса:
private interface IDelayОсновная реализация (GetCoreAsync) упрощена и более очевидна. Публичный интерфейс (Get и GetAsync) вообще не изменился. И во время выполнения, если используется только один путь, то только один путь будет скомпилирован JIT-компилятором. Если используются оба пути, JIT создаст две копии GetCoreAsync, каждая из которых оптимизирована для своей ситуации (асинхронной или синхронной). Это особенно полезный метод для библиотек, которым может потребоваться предоставлять обе формы методов, но существует высокая вероятность использования только одного из них.
{
static abstract ValueTask
DelayAsync(TimeSpan delay);
}
private readonly struct SyncDelay : IDelay
{
public static ValueTask
DelayAsync(TimeSpan delay)
{
Thread.Sleep(delay);
return new();
}
}
private readonly struct AsyncDelay : IDelay
{
public static async ValueTask
DelayAsync(TimeSpan delay) =>
await Task.Delay(delay);
}
private async Task<string> GetCoreAsync<TDelay>()
where TDelay: struct, IDelay
{
await TDelay.DelayAsync(TimeSpan.FromSeconds(1));
return "Hi!";
}
public string Get() =>
GetCoreAsync<SyncDelay>()
.GetAwaiter().GetResult();
public Task<string> GetAsync() =>
GetCoreAsync<AsyncDelay>();
Стивен Тауб рассказывает в недавнем посте, как BCL использует эту технику.
Примечания и Ограничения
Используя дженерики C# с типами-значений, мы можем обеспечить мономорфизацию, однако остальная часть поведения не гарантируется. JIT не гарантирует, что какие-либо конкретные методы будут встроены или что произойдет какая-либо оптимизация. Разумно предположить, что она будет иметь место, а при современной многоуровневой оптимизации можно также ожидать, что метод станет более оптимизированным, если вызывается много раз.
Вы ограничены только тем, что может быть выражено в виде членов интерфейса. Вы можете определять методы и свойства, но не константные значения или вложенные типы.
Источник: https://blog.stephencleary.com/2022/10/modern-csharp-techniques-3-generic-code-generation.html
👍3
День 1409. #Тестирование
Динамическое Тестирование Безопасности Приложений
Dynamic application security testing (DAST) может помочь обнаружить недостатки безопасности в вашем коде. И может делать это автоматически в процессе сборки.
В прошлом автоматизированное тестирование безопасности было довольно медленным, ресурсоемким, и часто результаты были полны ложных срабатываний. Сегодня существует DAST — это ПО, которое взаимодействует с запущенным приложением, автоматически отправляя запросы и ответы, пытаясь найти уязвимости в веб-приложениях и API. Если приложение или API находится в Интернете, это означает, что любой может сканировать его с помощью динамического сканера. Хотя существуют системы, которые могут блокировать некоторые из атак, большинство сайтов не используют их (из-за стоимости, задержки и требуемых ресурсов), т.е. большинство веб-приложений и API хотя бы один раз были просканированы на уязвимости.
DAST будет взаимодействовать с приложением сначала пассивно (чтение запросов и ответов), а затем активно (создание или обработка запросов, а затем корректировка на основе ответов в попытках найти уязвимости).
Большинство разработчиков могут настроить DAST в течение часа, а ещё через час найти хотя бы одну уязвимость. Вы не найдете 100% уязвимостей, как при профессиональном пен-тесте, но наверняка найдете много, включая все очевидные. Это самые важные уязвимости, которые нужно исправить в первую очередь, поскольку их может обнаружить и неопытный злоумышленник.
Чтобы DAST мог протестировать ваше приложение, оно должно быть запущено; доступ к коду не требуется. Приложение может работать на виртуальной машине, в контейнере или как служба. Некоторые динамические сканеры позволяют выполнять ручное тестирование, т.е. вы можете вручную создавать запросы, в то время как другие выполняют фаззинг, который проверяет валидацию ввода приложения.
DAST может работать по принципу «plug-n-play», подразумевая, что настройка практически не требуется. Но тогда придётся подождать несколько часов для получения результатов и запускать каждое сканирование вручную.
Один из вариантов ускорить тесты - создать архивные файлы HTML (HAR) и передать их в DAST. Если у вас есть команда обеспечения качества (QA), которая создаёт файлы HAR для автоматизации тестирования UI, эта стратегия может работать очень хорошо. DAST проверяет только те функции и код, которые являются частью HAR. Тесты пройдут значительно быстрее, и будет тестироваться только небольшая часть приложения, которая содержится в HAR. Однако, если вы не создаёте и не обновляете файлы HAR, добавится много работы.
Некоторые организации отдают приоритет только определённым ошибкам и тестируют только их. Можно настроить DAST так, чтобы он выполнял только конкретные тесты автоматически, и запускать ручной тест по необходимости.
Ещё вариант — удалить любые тесты, которые просто не применимы к тому, что вы создали. Если вы создали приложение .Net Core, размещённое в Azure с базой данных MS SQL, вам не нужно запускать тесты для MongoDB, AWS, WordPress и множество других, включённых в большинство DAST.
Однако для достижения наилучших результатов следует регулярно сканировать всё приложение. Можно запускать тесты на ночь или в нерабочее время. Если вы хотите запустить динамическое сканирование в рабочей среде, следует убедиться, что вы отключили функцию фаззинга, так как вы можете случайно испортить живые данные. Выполните фаззинг в зеркале продакшена и убедитесь, что вы сначала сделали резервную копию данных — на всякий случай.
Итого
Динамическое сканирование не заменит тест на проникновение, выполненный экспертом по безопасности с точки зрения глубины и достоверности, но может помочь вам создать более безопасное приложение с гораздо меньшими затратами времени и средств.
Источник: https://stackoverflow.blog/2022/11/30/continuous-delivery-meet-continuous-security/
Динамическое Тестирование Безопасности Приложений
Dynamic application security testing (DAST) может помочь обнаружить недостатки безопасности в вашем коде. И может делать это автоматически в процессе сборки.
В прошлом автоматизированное тестирование безопасности было довольно медленным, ресурсоемким, и часто результаты были полны ложных срабатываний. Сегодня существует DAST — это ПО, которое взаимодействует с запущенным приложением, автоматически отправляя запросы и ответы, пытаясь найти уязвимости в веб-приложениях и API. Если приложение или API находится в Интернете, это означает, что любой может сканировать его с помощью динамического сканера. Хотя существуют системы, которые могут блокировать некоторые из атак, большинство сайтов не используют их (из-за стоимости, задержки и требуемых ресурсов), т.е. большинство веб-приложений и API хотя бы один раз были просканированы на уязвимости.
DAST будет взаимодействовать с приложением сначала пассивно (чтение запросов и ответов), а затем активно (создание или обработка запросов, а затем корректировка на основе ответов в попытках найти уязвимости).
Большинство разработчиков могут настроить DAST в течение часа, а ещё через час найти хотя бы одну уязвимость. Вы не найдете 100% уязвимостей, как при профессиональном пен-тесте, но наверняка найдете много, включая все очевидные. Это самые важные уязвимости, которые нужно исправить в первую очередь, поскольку их может обнаружить и неопытный злоумышленник.
Чтобы DAST мог протестировать ваше приложение, оно должно быть запущено; доступ к коду не требуется. Приложение может работать на виртуальной машине, в контейнере или как служба. Некоторые динамические сканеры позволяют выполнять ручное тестирование, т.е. вы можете вручную создавать запросы, в то время как другие выполняют фаззинг, который проверяет валидацию ввода приложения.
DAST может работать по принципу «plug-n-play», подразумевая, что настройка практически не требуется. Но тогда придётся подождать несколько часов для получения результатов и запускать каждое сканирование вручную.
Один из вариантов ускорить тесты - создать архивные файлы HTML (HAR) и передать их в DAST. Если у вас есть команда обеспечения качества (QA), которая создаёт файлы HAR для автоматизации тестирования UI, эта стратегия может работать очень хорошо. DAST проверяет только те функции и код, которые являются частью HAR. Тесты пройдут значительно быстрее, и будет тестироваться только небольшая часть приложения, которая содержится в HAR. Однако, если вы не создаёте и не обновляете файлы HAR, добавится много работы.
Некоторые организации отдают приоритет только определённым ошибкам и тестируют только их. Можно настроить DAST так, чтобы он выполнял только конкретные тесты автоматически, и запускать ручной тест по необходимости.
Ещё вариант — удалить любые тесты, которые просто не применимы к тому, что вы создали. Если вы создали приложение .Net Core, размещённое в Azure с базой данных MS SQL, вам не нужно запускать тесты для MongoDB, AWS, WordPress и множество других, включённых в большинство DAST.
Однако для достижения наилучших результатов следует регулярно сканировать всё приложение. Можно запускать тесты на ночь или в нерабочее время. Если вы хотите запустить динамическое сканирование в рабочей среде, следует убедиться, что вы отключили функцию фаззинга, так как вы можете случайно испортить живые данные. Выполните фаззинг в зеркале продакшена и убедитесь, что вы сначала сделали резервную копию данных — на всякий случай.
Итого
Динамическое сканирование не заменит тест на проникновение, выполненный экспертом по безопасности с точки зрения глубины и достоверности, но может помочь вам создать более безопасное приложение с гораздо меньшими затратами времени и средств.
Источник: https://stackoverflow.blog/2022/11/30/continuous-delivery-meet-continuous-security/
👍6
День 1410. #ЧтоНовенького #DotNet8
Замороженные Коллекции
Продолжаем рассматривать, чего новенького могут предложить нам в 8й версии .NET. Сегодня рассмотрим, что такое замороженные коллекции и как они работают.
Вот код, который работает в альфа-версии .NET8:
Замораживаемую коллекцию можно изменять до тех пор, пока она не будет заморожена. После этого она больше не отражает изменений. Поэтому Count = 3.
Неизменяемый список также содержит только 3 элемента, но при этом неизменяемые коллекции имеют эффективные способы изменения списка путем создания нового:
На данный момент в .NET8 есть два типа замороженных коллекций: FrozenSet и FrozenDictionary. Возникает вопрос: зачем они?
Замороженные объекты вследствие своих ограничений могут дать преимущества в производительности. Часто коллекции инициализируются в какой-то момент, но никогда не меняют своего состояния. Например, посмотрим для списка из чисел от 1 до 1000 тест скорости метода Contains:
1. Они действительно доступны только для чтения и неизменны. Они создаются таким образом, чтобы данные были плотными и оптимально организованными для быстрого чтения.
2. При создании находится оптимальный размер таблицы сегментов, чтобы уменьшить или устранить коллизии хэшей, что сокращает среднее время поиска.
3. Особое внимание уделяется коллекциям со строковыми ключами. Различные компараторы выбираются динамически для повышения скорости хеширования во время поиска. Код просматривает все ключи и пытается найти кратчайшую подстроку, которая создаёт уникальный набор ключей. Обычно это значительно сокращает количество символов, используемых при вычислении хэш-кодов во время поиска.
4. Эти коллекции могут содержать не более 64КБ состояния, что покрывает 99% случаев.
Очевидно, что создание замороженной коллекции обходится дороже, чем создание простого списка. Это отражает типичный вариант их использования. Создать один раз в начале, а затем часто обращаться к ним (например, через поиск элемента).
Вот репозиторий с примерами кода и тестами.
Источник: https://steven-giesel.com/blogPost/34e0fd95-0b3f-40f2-ba2a-36d1d4eb5601
Замороженные Коллекции
Продолжаем рассматривать, чего новенького могут предложить нам в 8й версии .NET. Сегодня рассмотрим, что такое замороженные коллекции и как они работают.
Вот код, который работает в альфа-версии .NET8:
List<int> list = new() { 1, 2, 3 };ReadOnlyList — это просто «представление» объекта, из которого он был создан. Поэтому, если вы обновите список, из которого он был первоначально создан, ReadOnlyList также отразит это изменение. Пользователь ReadOnlyList просто не имеет возможности изменить внутреннее состояние самого списка.
ReadOnlyCollection<int> readonlyList =
list.AsReadOnly();
ImmutableList<int> immutableList =
list.ToImmutableList();
FrozenSet<int> frozenSet =
list.ToFrozenSet();
list.Add(4);
list.Count; // 4
readonlyList.Count; // 4
immutableList.Count; // 3
frozenSet.Count; // 3
Замораживаемую коллекцию можно изменять до тех пор, пока она не будет заморожена. После этого она больше не отражает изменений. Поэтому Count = 3.
Неизменяемый список также содержит только 3 элемента, но при этом неизменяемые коллекции имеют эффективные способы изменения списка путем создания нового:
// это создаст новый неизменяемый списокДля замороженных коллекций такого поведения не предусмотрено.
var newList = immutableList.Add(2);
На данный момент в .NET8 есть два типа замороженных коллекций: FrozenSet и FrozenDictionary. Возникает вопрос: зачем они?
Замороженные объекты вследствие своих ограничений могут дать преимущества в производительности. Часто коллекции инициализируются в какой-то момент, но никогда не меняют своего состояния. Например, посмотрим для списка из чисел от 1 до 1000 тест скорости метода Contains:
public void Lookup()Особенности замороженных коллекций:
{
for (var i = 0; i < 1000; i++)
_ = list.Contains(i);
}
List: 57.561 us
FrozenSet: 1.963 us
HashSet: 2.997 us
ImmutableHashSet: 15.422 us
1. Они действительно доступны только для чтения и неизменны. Они создаются таким образом, чтобы данные были плотными и оптимально организованными для быстрого чтения.
2. При создании находится оптимальный размер таблицы сегментов, чтобы уменьшить или устранить коллизии хэшей, что сокращает среднее время поиска.
3. Особое внимание уделяется коллекциям со строковыми ключами. Различные компараторы выбираются динамически для повышения скорости хеширования во время поиска. Код просматривает все ключи и пытается найти кратчайшую подстроку, которая создаёт уникальный набор ключей. Обычно это значительно сокращает количество символов, используемых при вычислении хэш-кодов во время поиска.
4. Эти коллекции могут содержать не более 64КБ состояния, что покрывает 99% случаев.
Очевидно, что создание замороженной коллекции обходится дороже, чем создание простого списка. Это отражает типичный вариант их использования. Создать один раз в начале, а затем часто обращаться к ним (например, через поиск элемента).
Вот репозиторий с примерами кода и тестами.
Источник: https://steven-giesel.com/blogPost/34e0fd95-0b3f-40f2-ba2a-36d1d4eb5601
👍17
Developing-on-AWS-with-CSharp.pdf
12.7 MB
День 1411. #Книги
Сегодня порекомендую вам (и сохраню для себя, т.к. ещё не читал) книгу «Developing on AWS with C#», O’Reilly Media, 2023.
Авторы Ноа Гифт, основатель Pragmatic AI Labs, и Джеймс Чарльзуорт, технический руководитель Pendo, расскажут вам о широте инструментов .NET на AWS. Вы изучите методы использования контейнеров Linux и Windows и бессерверной архитектуры для создания, обслуживания и масштабирования современных приложений .NET на AWS. Из этой книги вы узнаете, как сделать ваши приложения более современными, отказоустойчивыми и экономичными. Кроме того, в книге описано:
- Как начать создавать решения с помощью C# на AWS.
- Какие существуют лучшие практики DevOps для AWS, инструменты и сервисы разработки, которые предоставляет AWS.
- Как успешно перенести устаревшее приложение .NET на AWS.
- Как разрабатывать бессерверные микросервисы .NET на AWS.
- Как использовать контейнеризацию, переместить приложение в облако, отслеживать и тестировать.
Сегодня порекомендую вам (и сохраню для себя, т.к. ещё не читал) книгу «Developing on AWS with C#», O’Reilly Media, 2023.
Авторы Ноа Гифт, основатель Pragmatic AI Labs, и Джеймс Чарльзуорт, технический руководитель Pendo, расскажут вам о широте инструментов .NET на AWS. Вы изучите методы использования контейнеров Linux и Windows и бессерверной архитектуры для создания, обслуживания и масштабирования современных приложений .NET на AWS. Из этой книги вы узнаете, как сделать ваши приложения более современными, отказоустойчивыми и экономичными. Кроме того, в книге описано:
- Как начать создавать решения с помощью C# на AWS.
- Какие существуют лучшие практики DevOps для AWS, инструменты и сервисы разработки, которые предоставляет AWS.
- Как успешно перенести устаревшее приложение .NET на AWS.
- Как разрабатывать бессерверные микросервисы .NET на AWS.
- Как использовать контейнеризацию, переместить приложение в облако, отслеживать и тестировать.
👍15
День 1412. #ЗаметкиНаПолях #AsyncTips
Выполнение Кода с Помощью Планировщика. Начало: Создание Планировщика
Задача
Есть несколько частей кода, которые требуется выполнить определённым способом. Например, все они должны выполняться в UI-потоке или же в любой момент времени должно выполняться только определённое количество частей.
Решение
Здесь рассмотрим тип TaskScheduler, хотя, в .NET есть и другие способы решения этой задачи.
Простейшая разновидность — TaskScheduler.Default — ставит работу в очередь пула потоков. Его редко придётся использовать явно, потому что он используется по умолчанию во многих сценариях планирования: в Task.Run, в параллельном коде и в коде потоков данных.
Вы можете сохранить конкретный контекст и позднее спланировать работу в этом контексте:
ConcurrentExclusiveSchedulerPair — тип, представляющий в действительности два планировщика, связанных друг с другом:
- ConcurrentScheduler позволяет нескольким задачам выполняться одновременно, при условии, что ни одна задача не выполняется в ExclusiveScheduler;
- ExclusiveScheduler выполняет только по одной задаче за раз и только если в настоящее время никакие задачи не выполняются в ConcurrentScheduler.
Также тип может применяться в качестве регулирующего планировщика, который будет ограничивать собственный уровень параллелизма. При этом ExclusiveScheduler обычно не используется:
Заметьте, что конструктору ConcurrentExclusiveSchedulerPair передаётся объект TaskScheduler.Default. Это объясняется тем, что ConcurrentExclusiveSchedulerPair применяет свою логику конкурентности/эксклюзивности выполнения к существующему TaskScheduler.
Никогда не используйте платформенно-зависимые типы для выполнения кода в UI-потоке. WPF, Silverlight, iOS и Android предоставляют тип Dispatcher, Universal Windows использует тип CoreDispatcher, а в Windows Forms существует интерфейс ISynchronizeInvoke (т. е. Control.Invoke). Не используйте эти типы в новом коде; просто считайте, что их вообще нет. Эти типы только без всякой необходимости привязывают код к конкретной платформе. SynchronizationContext — абстракция общего назначения на базе этих типов.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
Выполнение Кода с Помощью Планировщика. Начало: Создание Планировщика
Задача
Есть несколько частей кода, которые требуется выполнить определённым способом. Например, все они должны выполняться в UI-потоке или же в любой момент времени должно выполняться только определённое количество частей.
Решение
Здесь рассмотрим тип TaskScheduler, хотя, в .NET есть и другие способы решения этой задачи.
Простейшая разновидность — TaskScheduler.Default — ставит работу в очередь пула потоков. Его редко придётся использовать явно, потому что он используется по умолчанию во многих сценариях планирования: в Task.Run, в параллельном коде и в коде потоков данных.
Вы можете сохранить конкретный контекст и позднее спланировать работу в этом контексте:
var scheduler =Этот код создаёт объект TaskScheduler, чтобы сохранить текущий контекст и спланировать выполнение кода в нём. Тип SynchronizationContext представляет контекст планирования общего назначения. В .NET предусмотрено несколько разных контекстов: многие UI-фреймворки предоставляют контекст SynchronizationContext, представляющий UI-поток, а в ASP.NET до Core предоставлялся контекст SynchronizationContext, представляющий контекст запроса HTTP. Также возможно напрямую использовать SynchronizationContext для выполнения кода в этом контексте; но это не рекомендуется. Там, где это возможно, используйте await для возобновления в неявно сохранённом контексте либо TaskScheduler.
TaskScheduler
.FromCurrentSynchronizationContext();
ConcurrentExclusiveSchedulerPair — тип, представляющий в действительности два планировщика, связанных друг с другом:
- ConcurrentScheduler позволяет нескольким задачам выполняться одновременно, при условии, что ни одна задача не выполняется в ExclusiveScheduler;
- ExclusiveScheduler выполняет только по одной задаче за раз и только если в настоящее время никакие задачи не выполняются в ConcurrentScheduler.
var schPair = new ConcurrentExclusiveSchedulerPair();Одно из частых применений этого типа — просто использование ExclusiveScheduler, гарантирующее, что в любой момент времени будет выполняться только одна задача. Код, выполняемый в ExclusiveScheduler, будет выполняться в пуле потоков эксклюзивно - без всего остального кода, использующего тот же экземпляр ExclusiveScheduler.
var concurrent = schPair.ConcurrentScheduler;
var exclusive = schPair.ExclusiveScheduler;
Также тип может применяться в качестве регулирующего планировщика, который будет ограничивать собственный уровень параллелизма. При этом ExclusiveScheduler обычно не используется:
var schPair = new ConcurrentExclusiveSchedulerPair(Заметьте, что такая регулировка влияет на код только во время его выполнения. В частности, асинхронный код не считается выполняемым во время ожидания операции. ConcurrentScheduler регулирует выполняющийся код; тогда как другие виды регулировки (такие, как SemaphoreSlim) осуществляют регулировку на более высоком уровне (т.е. всего async-метода.)
TaskScheduler.Default,
maxConcurrencyLevel: 8);
var scheduler = schPair.ConcurrentScheduler;
Заметьте, что конструктору ConcurrentExclusiveSchedulerPair передаётся объект TaskScheduler.Default. Это объясняется тем, что ConcurrentExclusiveSchedulerPair применяет свою логику конкурентности/эксклюзивности выполнения к существующему TaskScheduler.
Никогда не используйте платформенно-зависимые типы для выполнения кода в UI-потоке. WPF, Silverlight, iOS и Android предоставляют тип Dispatcher, Universal Windows использует тип CoreDispatcher, а в Windows Forms существует интерфейс ISynchronizeInvoke (т. е. Control.Invoke). Не используйте эти типы в новом коде; просто считайте, что их вообще нет. Эти типы только без всякой необходимости привязывают код к конкретной платформе. SynchronizationContext — абстракция общего назначения на базе этих типов.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
👍11
День 1413. #ЗаметкиНаПолях #AsyncTips
Выполнение Кода с Помощью Планировщика. Окончание: Использование Планировщика
Задача: требуется управлять выполнением отдельных фрагментов в параллельном коде.
Решение
После того как вы создадите экземпляр TaskScheduler (см. Создание Планировщика), можете включить его в набор параметров, передаваемых методу Parallel. Следующий код получает набор коллекций матриц, запускает несколько параллельных циклов и ограничивает общий параллелизм всех циклов одновременно независимо от количества матриц в каждом наборе:
Передать TaskScheduler коду Parallel LINQ (PLINQ) невозможно.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
Выполнение Кода с Помощью Планировщика. Окончание: Использование Планировщика
Задача: требуется управлять выполнением отдельных фрагментов в параллельном коде.
Решение
После того как вы создадите экземпляр TaskScheduler (см. Создание Планировщика), можете включить его в набор параметров, передаваемых методу Parallel. Следующий код получает набор коллекций матриц, запускает несколько параллельных циклов и ограничивает общий параллелизм всех циклов одновременно независимо от количества матриц в каждом наборе:
void RotateMatrices(Parallel.Invoke также получает экземпляр ParallelOptions, поэтому вы можете передать TaskScheduler при вызове Parallel.Invoke так же, как и для Parallel.ForEach. При выполнении динамического параллельного кода можно передать TaskScheduler непосредственно TaskFactory.StartNew или Task.ContinueWith.
IEnumerable<IEnumerable<Matrix>> collections,
float degrees)
{
var schPair = new ConcurrentExclusiveSchedulerPair(
TaskScheduler.Default,
maxConcurrencyLevel: 8);
var scheduler = schPair.ConcurrentScheduler;
var opts = new ParallelOptions {
TaskScheduler = scheduler };
Parallel.ForEach(
collections,
opts,
matrices => Parallel.ForEach(
matrices,
opts,
m => m.Rotate(degrees)));
}
Передать TaskScheduler коду Parallel LINQ (PLINQ) невозможно.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 13.
👍3