.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1718. #юмор
👍32
День 1719. #ВопросыНаСобеседовании #Многопоточность
Самые часто задаваемые вопросы на собеседовании по C#

25. Как реализовать синхронизацию потоков с помощью ReaderWriterLockSlim? В чём его преимущества перед традиционным ReaderWriterLock?

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

Чтобы добиться синхронизации потоков с помощью ReaderWriterLockSlim, нужно:
1) Создать экземпляр ReaderWriterLockSlim.
2) Использовать EnterReadLock() перед доступом к общему ресурсу для чтения и ExitReadLock() после завершения операции чтения.
3) Использовать EnterWriteLock() перед доступом к общему ресурсу для записи и ExitWriteLock() после завершения операции записи.

var rwl = new ReaderWriterLockSlim();

// чтение
rwl.EnterReadLock();
try
{
// Доступ к общему ресурсу для чтения
}
finally
{
rwl.ExitReadLock();
}

// Запись
rwl.EnterWriteLock();
try
{
// Доступ к общему ресурсу для записи
}
finally
{
rwl.ExitWriteLock();
}

Преимущества ReaderWriterLockSlim над ReaderWriterLock:
1) Производительность: ReaderWriterLockSlim использует относительно дешёвую спин-блокировку и другие оптимизации для сценариев, в которых ожидается, что блокировка будет беспрепятственной или будет удерживаться в течение короткого времени.
2) Рекурсия: ReaderWriterLockSlim обеспечивает гибкую поддержку рекурсии блокировки, позволяя вам несколько раз входить и выходить из блокировки в одном потоке, тогда как ReaderWriterLock имеет ограничения на рекурсию.
3) Предотвращение нехватки писателей (writer starvation): ReaderWriterLockSlim имеет возможности уменьшить нехватку писателей, отдавая предпочтение запросам на блокировку записи вместо запросов на чтение. ReaderWriterLock может страдать от нехватки писателей при наличии непрерывного потока читателей.

Однако обратите внимание, что ReaderWriterLockSlim не поддерживает межпроцессную синхронизацию, и его не следует использовать, если объект блокировки необходимо использовать в нескольких процессах. В таких случаях используйте примитив синхронизации Mutex.

Источник: https://dev.to/bytehide/c-multithreading-interview-questions-and-answers-4opj
👍10
День 1720. #ЧтоНовенького
Доступна Бета-Версия Чата Github Copilot
Чат GitHub Copilot — это интерфейс чата, который позволяет разработчикам задавать вопросы о написании кода и получать ответы непосредственно в IDE. В настоящее время он находится в открытой бета-версии и доступен для всех индивидуальных пользователей GitHub Copilot в Visual Studio и VS Code.

Чат GitHub Copilot был представлен в виде общедоступной бета-версии как расширение для разработки ПО на базе искусственного интеллекта для всех пользователей GitHub Copilot для бизнеса. Теперь он доступен бесплатно для всех индивидуальных пользователей Copilot и поддерживается как в редакторах Visual Studio, так и в VS Code. Он использует расширенную обработку естественного языка (natural language processing, NLP), чтобы предлагать помощь и ответы на естественном языке непосредственно в поддерживаемых редакторах. Это избавляет разработчиков от необходимости обращаться к документации или искать на интернет-форумах. Уменьшая необходимость переключения контекста, он оптимизирует процесс разработки, что помогает разработчикам сохранять концентрацию и динамику.

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

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

Чтобы использовать чат Copilot, пользователи должны иметь активную подписку на GitHub Copilot, а пользователи VS Code должны иметь последнее обновление и войти в VS Code с тем же идентификатором GitHub, который имеет доступ к GitHub Copilot.

Источник: https://www.infoq.com/news/2023/10/github-copilot-chat-open-beta/
👍4👎2
День 1721. #Книги
«Код, который умещается в голове» (Симан М. — СПб.: Питер, 2023).

От создателя «Внедрения зависимостей на платформе .NET». Для тех, кто не осилил «Совершенный код»)))

На самом деле, очень интересная книга. Разработчикам с большим опытом она вряд ли откроет что-то новое, но поможет напомнить некоторые лучшие практики. Марк Симан проделал огромную работу, перелопатив более 100 источников по разработке, написанию кода, работе в команде и т.п., среди которых «Совершенный код», «Программист-прагматик», «Экстремальное программирование», «Чистый код», «Шаблоны корпоративных приложений», «Разработка через тестирование», «Непрерывное развёртывание ПО»… В общем, всё, что должен прочитать каждый уважающий себя разработчик, собрано в одном месте. Как организовать проект, как писать читабельный код без ошибок, как работать в команде, как искать и исправлять ошибки, как организовать рабочий процесс и многое другое. Везде по тексту есть ссылки на источники, так что в любую тему можно углубиться, прочитав источник.

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

Короче, отнесу книгу к категории «мастрид». Особенно для начинающих свою карьеру в разработке (в .NET то точно, потому что в книге неплохо разобран пример создания .NET веб-API).
👍35
День 1722. #ЗаметкиНаПолях #Debugging
Правила Отладки: Ведите Журнал Аудита
Отладка - сложная задача, часто занимающая много времени. Поэтому наша цель - минимизация времени отладки. Каждая ошибка даёт возможность освоить методы предотвращения аналогичных проблем в будущем или быстрого их выявления, если они возникнут снова.

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

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

Ведение журнала аудита во время отладки даёт несколько преимуществ:
1) Комплексное отслеживание.
Журнал фиксирует действия по расследованию, позволяя вам перепроверять ваши действия и решения.

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

3) Точное воспроизведение.
Следуя описанным шагам, вы увеличиваете вероятность точного воспроизведения проблемы, что приводит к более эффективному устранению неполадок.

4) Диагностика коренных причин.
Анализ журнала помогает определить момент или последовательность событий, вызвавших ошибку, что важно для выявления первопричин проблемы и формулирования точных решений.

5) Целостное понимание.
Маршрут воспроизведения ошибки фиксирует не только действия, но и контекст и состояние системы. Это полезно для понимания более широкой картины проблемы.

6) Обучение и совершенствование.
Анализ прошлых записей журнала позволяет учиться на предыдущем опыте и может помочь избежать подобных ошибок в будущем.

7) Адаптируемое решение проблем.
Благодаря журналу аудита вы можете переключаться между различными подходами, не теряя при этом прогресса. Если один из способов окажется неудачным, вы можете повторить свои шаги и поискать альтернативные решения.

8) Эффективная документация.
Журнал служит документацией усилий по отладке. Его можно использовать в дальнейшем для обучения новых членов команды и создания базы знаний.

9) Оптимизация времени.
Вместо бесцельного повторения своих шагов раз за разом вы можете обратиться к журналу, чтобы быстро возобновить расследование.

10) Аналитика на основе данных.
Записывая наблюдения, гипотезы и результаты, вы собираете данные, которые могут дать представление о повторяющихся проблемах, закономерностях и потенциальных улучшениях процесса разработки.

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

Источник: https://dev.to/rajasegar/debugging-rules-keep-an-audit-trail-3he9
👍9
День 1723. #ЗаметкиНаПолях
Заменяем Разрывы Строк в C#
Замена разрывов строк в тексте - довольно распространённая задача. Иногда нужно сделать текст в одну строку, либо заменить разрыв строки HTML-элементом <br /> для отображения на веб-сайте. Посмотрим, как это можно сделать.

В C# используются два основных типа разрывов строк:
- \n: символ новой строки, используемый в системах Unix и Linux.
- \r\n: символ возврата каретки в сочетании с символом новой строки, используется в системах Windows.
Рекомендуемый способ записи разрыва строки — использовать Environment.Newline, который преобразуется в \n или \r\n в зависимости от системы.

1. Замена старым способом
Используем метод Replace() класса String:
const string text = 
"This is a line.\rThis is another line.";

var newText = text
.Replace("\r\n", "\n")
.Replace("\r", "\n");

Результат:
This is a line.\nThis is another line.

2. Новый способ
Метод ReplaceLineEndings(), представленный в .NET 6, является более специализированным инструментом для замены разрывов строк и это рекомендуемый подход. Требуется один аргумент: любая строка для строки замены (по умолчанию - Environment.NewLine)
var newText = text.ReplaceLineEndings("\n");

Результат будет таким же, как и в предыдущем способе.

3. Регулярные выражения
Аналогично методу Replace() класса String, можно использовать метод Regex.Replace():
var newText = Regex
.Replace(text, @"(\r\n|\r)", "\n");

И снова, результат будет тем же.

Производительность
Я протестировал производительность всех трёх способов в трёх версиях: .NET6, .NET7 и .NET8.
- string.Replace() примерно постоянен (~30-40нс).
- ReplaceLineEndings() примерно в 3 раза медленнее в .NET6 и .NET7 (~100-130нс), но в .NET8 он стал наоборот быстрее - ~25нс.
- Regex.Replace() примерно в 7 раз медленнее string.Replace() (~240-270нс) в .NET6 и .NET7, в .NET8 стал немного быстрее - ~200нс.

Однако, в .NET7+ для регулярных выражений мы можем использовать кодогенерацию (это даже предлагает сделать IDE):
var newText = MyRegex().Replace(text, "\n");

[GeneratedRegex(@"(\r\n|\r)")]
private static partial Regex MyRegex();
Тогда метод Regex станет быстрее: ~180нс в .NET7 и ~130нс в .NET8.

Памяти все методы используют одинаково – 96 байт.

Источник: https://code-maze.com/csharp-replace-line-breaks-in-a-string/
👍17
День 1724. #ЗаметкиНаПолях
Структурированный Параллелизм в C#
Структурированный параллелизм — это концепция, которая помогает писать более надёжный и удобный в обслуживании асинхронный код. Она не нова, но не так широко известна. Идея состоит в том, чтобы иметь возможность группировать асинхронные операции и гарантировать, что все операции завершатся до завершения группы. Это делается с помощью специальной конструкции, называемой областью задачи (Task Scope). Область задачи — это конструкция, похожая на блок try-catch-finally. Она используется для группировки асинхронных операций и обеспечения завершения всех операций до завершения группы.
Рассмотрим пример:
public class TaskScope
{
private CancellationTokenSource _cts = new();
private ConcurrentBag<Task> _tasks = new();

private TaskScope() { }

public static async Task Create(
Func<TaskScope, Task> act)
{
await using var ts = new TaskScope();
await act(ts);
await ts.WaitForAll();
}

public static async Task Create(
Action<TaskScope> act)
{
await using var ts = new TaskScope();
act(ts);
await ts.WaitForAll();
}

public async ValueTask DisposeAsync()
{
_cts.Cancel();
await WaitForAll();
}

public Task Run(
Func<CancellationToken, Task> act)
{
var task = Task.Run(async () =>
{
try
{
await act(_cts.Token);
}
catch (Exception ex) when (
ex is not OperationCanceledException)
{
_cts.Cancel();
throw;
}
});

_tasks.Add(task);
return task;
}

private async Task WaitForAll()
{
try
{
await Task.WhenAll(_tasks.ToArray());
}
catch (Exception ex) when (
ex is not OperationCanceledException)
{
throw;
}
}
}
Извините за много кода, но это только упрощённая реализация.
Идея в том, что мы определяем области в методе Create. Всё внутри Create будет выполняться в определённой области. Таким образом, задачи логически связаны друг с другом: если в одной выбрасывается исключение, мы отменяем все задачи вместе.
Использование:
var tasks = TaskScope.Create(group =>
{
group.Run(async token =>
{
await Task.Delay(100, token);
throw new Exception("Boom!!!");
});
group.Run(async token =>
await Task.Delay(1000, token));
});

// Выполняется 100мс
// после ошибки в 1й задаче отменяются все.
// Исключение поднимается наверх
try
{
await tasks;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

// True
Console.WriteLine(tasks.IsFaulted);
Как видите, мы можем сгруппировать задачи и убедиться, что все они выполнены до того, как будет завершена область действия. Если одна из задач завершается сбоем, все остальные задачи отменяются и появляется исключение. Это очень мощная концепция, которая помогает писать более надёжный и удобный в обслуживании асинхронный код. Более сложную реализацию можно найти здесь.

Источник: https://steven-giesel.com/blogPost/59e57336-7c73-472f-a781-b0b79f0d47ad
👍25
День 1725. #Оффтоп #Обучение
Запертые Двери, Головные Боли и Интеллектуальные Потребности
Есть ли такие вещи, узнав о которых, вы начинали их видеть повсюду? Это «проблема очерёдности проблемы и решения». Сегодня поговорим о ней.

Игры
Новичок начинает игру с тщательно продуманного учебного уровня. Уровень приводит его к ключу, который он берёт, а затем к запертой двери, которую он успешно открывает. Далее в игре он натыкается на вторую запертую дверь… и оказывается в тупике. Он уже однажды решал эту проблему – почему ему так трудно решить её снова?

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

Математика
Помните, как на уроке математики вам давали концепцию, потом ещё более сложную концепцию, а вам всё это казалась совершенно бессмысленным? Проблема в том, что, прежде чем предлагать вам новую, более сложную концепцию, вам не дали возможности испытать ограничения старой. Другими словами: если вам представят решение (новую концепцию) до того, как представят тип задач, которые оно должно решить, решение, скорее всего, покажется бессмысленным и произвольным.

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

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

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

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

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

Источник: https://mkremins.github.io/blog/doors-headaches-intellectual-need/
👍28
День 1726. #ЗаметкиНаПолях
Изменяем Имена Встроенных Ресурсов в .NET
Когда вы используете встроенные ресурсы (embedded resources) в проекте .NET, имя ресурса вычисляется на основе пути к файлу. По умолчанию используется формат, вроде <Имя сборки>.<Путь к файлу>. Но путь к файлу не содержит разделителя пути (/ или \). Вместо этого разделитель пути заменяется точкой. Например, для файла resources/index.html:
<Project>
<ItemGroup>
<EmbeddedResource Include="resources\index.html" />
</ItemGroup>
</project>

имя ресурса будет
MyProject.resources.index.html

Вы можете вывести список встроенных ресурсов, используя метод GetManifestResourceNames класса Assembly.
foreach(var name in
Assembly.GetExecutingAssembly()
.GetManifestResourceNames())
{
Console.WriteLine(name);
}

Пример вывода:
MyProject.resources.favicon.ico
MyProject.resources.img.background.png
MyProject.resources.index.html

Однако вы не можете быть на 100% уверены, как читать встроенные ресурсы, поскольку такое именование может вызывать конфликты.

Можно изменить имена встроенных ресурсов, используя LogicalName:
<Project>
<ItemGroup>
<EmbeddedResource Include="Resources/**/*">
<LogicalName>
$([System.String]::new('%(RelativeDir)').Replace('\','/'))%(FileName)%(Extension)
</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Project>

Если добавить это в проект и снова выполнить код выше, получится что-то вроде этого:
Resources/favicon.ico
Resources/img/background.png
Resources/index.html

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

Источник: https://www.meziantou.net/customizing-the-embedded-resource-name-in-dotnet.htm
👍13
День 1727. #Testing
Тестирование на Основе Свойств. Теория
В этой серии рассмотрим, что такое тестирование на основе свойств, почему оно полезно и как оно может помочь писать лучший код.

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

Тестирование на основе свойств было введено в 2000 году Коэном Классеном и Джоном Хьюзом через библиотеку Haskell QuickCheck. Марк Симанн использует следующее определение в своем курсе Pluralsight по тестированию на основе свойств:
Тестирование на основе свойств — это метод автоматического тестирования, при котором вы постепенно концентрируетесь на правильном поведении системы, описывая её свойства или качества в общих чертах, а затем используете случайно сгенерированные тестовые данные для выполнения детерминированных тестов.

Оно довольно запутанное. Давайте разбираться.

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

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

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

Например:
int Add(int x, int y) => x+y;

Чтобы протестировать метод Add, мы можем попытаться придумать репрезентативные примеры, но существует бесконечный список возможных комбинаций. Вместо этого попробуем описать метод Add как связь между входными и выходными данными:
1) Не имеет значения, в каком порядке мы вводим входные данные x и y, выходные данные одинаковы. 1+7 = 7+1
2) Ноль вместо x или y равносилен пустой операции (вывод не меняется). 7+0 = 0+7 = 7.
3) Если вызывать Add несколько раз, порядок вызовов не имеет значения. 7+(4+5) = (7+4)+5

Это прекрасно согласуется с математическим описанием операции сложения.

Пока это всё теория. Завтра рассмотрим тесты на основе свойств в C#.

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

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-1.html
👍14
День 1728. #Testing
Тестирование на Основе Свойств. Простой пример
Теория

Итак, напишем тесты согласно свойствам, которые мы определили.
int Add(int x, int y) => x+y;

У нас должно получиться 3 теста:
public class AddTwoNumbersTests
{
int Add(int x, int y) => x + y;

[Theory]
public void NoOrderOfParameters(int x, int y)
{
var res1 = Add(x, y);
var res2 = Add(y, x);

Assert.Equal(res1, res2);
}

[Theory]
public void ZeroDoesNothing(int x)
{
var res1 = Add(x, 0);
var res2 = x;

Assert.Equal(res1, res2);
}

[Theory]
public void OrderDoesntMatter(int x, int y, int z)
{
var res1 = Add(Add(x, y), z);
var res2 = Add(x, Add(y, z));

Assert.Equal(res1, res2);
}
}

Пока это не сильно отличается от обычных тестов на основе примеров. Теперь задача состоит в том, чтобы сгенерировать правильные псевдослучайные значения, которые можно использовать в качестве входных данных. В этом нам поможет FsCheck.

FsCheck — это платформа тестирования на основе свойств, созданная для F#, но её можно использовать и в C#. Программист предоставляет спецификацию программы в виде свойств, которым должны удовлетворять функции, методы или объекты, а FsCheck проверяет, сохраняются ли эти свойства на большом количестве случайно сгенерированных случаев. Вы фактически пишете тестируемую спецификацию вашей программы. Спецификации выражаются на F#, C# или VB с использованием комбинаторов, определённых в библиотеке FsCheck. FsCheck предоставляет комбинаторы для определения свойств, наблюдения за распределением тестовых данных и определения генераторов тестовых данных. При сбое свойства FsCheck автоматически отображает минимальный контрпример.

Есть плагины FsCheck для всех популярных тестовых сред. Всё, что нам нужно, - это установить NuGet пакет и заменить атрибуты [Theory] на [Property].
Тесты по-прежнему будут проходить. По умолчанию FsCheck останавливается после 100 попыток. Если вы хотите знать протестированные значения, задайте свойству Verbose атрибута Property значение true:
[Property(Verbose = true)]

Вот пример вывода:
NoOrderOfParameters
Standard Output:
0:
(0, 0)
1:
(-1, 1)
2:
(1, 1)


Конечно, это очень простой пример. Далее рассмотрим что-то более реалистичное.

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

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-2.html
👍13
День 1729. #Testing
Тестирование на Основе Свойств. Реальный пример
Теория
Простой пример

Сегодня рассмотрим более реалистичный пример и функции, которые предоставляет FsCkeck. Допустим, нам нужно проверить номер кредитной карты.

Если у нас есть метод CardNumber.IsValid(), проверяющий валидность номера кредитной карты, мы можем добавить тесты, вроде следующих:
[TestCase("12345678910")]
[TestCase("12345621748")]
[TestCase("12345621777")]
[TestCase("Test")]
[TestCase("00000000000")]
[TestCase("99999999999")]
[TestCase("!@#!@%^@^@$^&@$^sdfasdf")]
[TestCase("$^@#^@##$44")]
[TestCase("15435#$%4354dfsg")]
[TestCase("90022742192")]
public void ValidationShouldFail(string num)
{
var result = CardNumber.IsValid(num);
result.Should().BeFalse();
}

Как видите, это вполне типичные примеры тестов. Некоторые магические значения использовались для проверки теста, но уверены ли мы, что охватили все крайние случаи?

Используем FsCheck:
[Property]
public bool ValidationShouldFail(string num)
{
return !CardNumber.IsValid(num);
}

Также можно использовать альтернативную форму записи теста:
[Property]
public void ValidationShouldFail()
{
Prop.ForAll<string>(
x => !CardNumber.IsValid(x)
)
.VerboseCheck();
}

Тест проходит, но если посмотреть расшифровку ([Property(Verbose = true)]), мы можем заметить, что проверяются случайные строки, и вряд ли хотя бы одна из них будет валидным номером карты. Мы можем заменить строку на long, но это тоже вряд ли сильно ограничит варианты.

Произвольные значения (Arbitraties)
FsCheck использует комбинацию генераторов (generator) и сокращателей (shrinker) для создания тестовых данных. Генераторы производят случайный набор значений из интервала с равномерным распределением. Сокращатели ограничивают (фильтруют) этот набор по заданному условию. В FsCheck определены произвольные значения по умолчанию для некоторых часто используемых типов:
- NegativeInt
- NonNegativeInt
- NonEmptyString
- IntWithMinMax
- NonEmptyArray
и т.п.

Используем альтернативную форму записи тестового метода с использованием предопределённых произвольных значений:
[Property(Verbose = true)]
public Property ValidationShouldFail()
{
var arb = Arb
.Default
.Int64()
.Filter(x => x > 100);

return Prop.ForAll<Int64>(
arb,
x => !CardNumber.IsValid(x.ToString()));
}

Здесь мы использовали произвольные значения для long и функцию-сокращатель (x > 100). Заметьте, что FsCheck сначала генерирует случайные значения, а затем проверяет их на соответствие условию сокращателя. В примере выше при попытке задать большое значение в функции Filter, тесты могут выполняться очень долго.

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

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-3.html
👍4
День 1730. #Testing
Тестирование на Основе Свойств. Собственный генератор
Теория
Простой пример
Реальный пример

Сегодня рассмотрим, как писать собственные генераторы.

В предыдущем примере мы использовали Filter(), чтобы возвращать только нужные числа.

Для создания генератора нужен публичный статический класс с публичным статическим методом, возвращающим Arbitrary<T>.
public static class CCNumberGenerator
{
public static Arbitrary<Int64> Generate()
{
return Arb.Default
.Int64()
.Filter(x => x > 100);
}
}

Теперь его можно использовать в качестве типа генератора в атрибуте Property:
[Property(Arbitrary = new[] { 
typeof(CCNumberGenerator) },
Verbose = true)]
public bool ValidateShouldFail(long num)
{
return !CardNumber.IsValid(num.ToString());
}

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

Например, так можно создать генератор месяца в определённом году:
public static class MonthOfYearGenerator
{
public static Gen<MonthOfYear> Generator =
from month in Gen.Choose(1, 12)
from year in Gen.Choose(1982, 2023)
select new MonthOfYear()
{
Month = month,
Year = year
};

public static Arbitrary<MonthOfYear> Generate() =>
Arb.From(Generator);
}

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

В этом же классе можно создать свой сокращатель:
public static IEnumerable<MonthOfYear> 
Shrinker(MonthOfYear moy)
{
yield return new MonthOfYear() {
Month = moy.Month,
Year = moy.Year - 1
};
yield return new MonthOfYear() {
Month = moy.Month,
Year = moy.Year + 1
};
}

А затем использовать перегрузку метода Generate, принимающую и генератор, и сокращатель:
public static Arbitrary<MonthOfYear> Generate() =>
Arb.From(Generator, Shrinker);

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

Источник:
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-4.html
👍5
День 1731. #Testing
Тестирование на Основе Свойств. Воспроизведение
Теория
Простой пример
Реальный пример
Собственный генератор

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

Если посмотреть на выходные данные неудавшегося теста, можно заметить строку вроде
Falsifiable, after 3 tests (4241 shrinks) (StdGen(318823861,297138967)).

StdGen – это начальные значения, которые FsCheck использовал для генерации входных данных. Если вы хотите использовать в своём тесте точно такие же входные данные, используйте это значение в свойстве Replay атрибута Property:
[Property(Replay="318823861,297138967")]
public bool ValidationShouldFail(long num)
{

}

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

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

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

Источники:
-
https://bartwullems.blogspot.com/2023/01/property-based-testing-in-cpart-5.html
-
https://rasmus-feldthaus.medium.com/supercharge-your-testing-with-property-based-tests-bc3a7b75ca9f
👍4
День 1732. #ЗаметкиНаПолях
Оптимизация памяти с помощью ArrayPool в C#
В C# пул массивов представлен классом ArrayPool<T>. Это потокобезопасный класс, предоставляющий структурированный механизм для эффективного управления и повторного использования массивов типа T.

var arrPool = ArrayPool<int>.Shared;

Здесь мы создаём пул целочисленных массивов, который используется во всём приложении. Он имеет максимальную длину массива по умолчанию, равную 2^20 (1024*1024 = 1.048.576) байт.

Также можно использовать метод расширения Create():
var arrPool = ArrayPool<int>.Create(100, 10);

Здесь первый аргумент метода Create() устанавливает максимальную длину массива в ArrayPool. Второй аргумент определяет количество массивов в сегменте. Пул группирует массивы одинаковой длины в эти сегменты, чтобы мы могли быстрее получить к ним доступ. Однако ограничивать пулы массивов этими параметрами нужно с осторожностью. Если мы превысим лимиты, произойдёт выделение памяти для нового массива.

Использование:
var pool = ArrayPool<int>.Shared;
var size = 10;
var arr = pool.Rent(size);
for (var i = 0; i < size; i++)
arr[i] = i * 2;
Console.WriteLine("Элементы:");
for (var i = 0; i < size; i++)
Console.Write(arr[i] + " ");
pool.Return(arr);

Сначала мы инициализируем пул, затем используем метод Rent() для получения массива длиной size из пула.
// Вывод
Элементы:
0 2 4 6 8 10 12 14 16 18

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

Здесь следует отметить, что фактический размер массива, который мы получаем из ArrayPool, как минимум равен размеру, который мы запрашиваем. Т.е. полученный массив может иметь большую длину. ArrayPool состоит из массивов с размерами, равными степени 2, начиная с длины 16. Таким образом, когда мы запрашиваем массив определённого размера, мы получаем ближайший доступный массив как минимум этого размера. В примере выше размер массива будет 16, а не 10.

Использование ArrayPool не только значительно быстрее, но и эффективно использует память. ArrayPool не выделяет память (кроме изначальной инициализации). Поэтому при интенсивном переиспользовании массивов разница между pool.Rent() и new становится огромной.

Итого
ArrayPool — отличный выбор, когда нужно оптимизировать память и уменьшить её фрагментацию, особенно в сценариях, где производительность критична. Он позволяет повторно использовать существующие блоки памяти посредством переиспользования массива. Однако придётся явно возвращать взятые массивы в пул с помощью метода Return(). Кроме того, арендованный массив может иметь размер, превышающий тот, который нам действительно нужен, что потребует специальных манипуляций для соответствия нашему конкретному сценарию использования. В приложениях, где производительность критична, и где часто используются и инициализируются массивы, предпочтительным вариантом является ArrayPool, тогда как для более простых случаев использования с менее частым созданием массивов предпочтительнее будет new.

Источник: https://code-maze.com/csharp-arraypool-memory-optimization/
👍20
День 1733. #BestPractices #ProjectManagement
Принципы Бережливой Разработки ПО
Бережливая Разработка ПО (Lean Software Development) — это гибкая система управления проектами и разработки продуктов, основанная на принципах бережливого производства. Основное внимание уделяется обеспечению ценности для клиента путём оптимизации ресурсов, рабочих потоков и процессов. Ниже рассмотрим основные принципы бережливой разработки.

1. Устранение потерь
Эта концепция заимствована из мира бережливого производства, где она известна как «муда» (от японского бесполезность, расточительность). В разработке ПО потерями может быть что угодно: от написания ненужного кода до чрезмерных совещаний, которые не приносят никакой пользы. Цель состоит в том, чтобы оптимизировать рабочий процесс, выявляя и удаляя всё, что не помогает конечному продукту или не удовлетворяет потребностям клиента.

2. Усиленное обучение
Обучение является неотъемлемой частью разработки. Принцип усиленного обучения подчёркивает, что команда всегда должна находиться в состоянии непрерывного обучения. Будь то проверка кода, обратная связь или изучение новых материалов, этот принцип предполагает, что более информированная команда производит продукт более высокого качества. Такие практики, как предметно-ориентированное проектирование (DDD), помогают команде сосредоточиться на изучении и правильном моделировании предметной области.

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

4. Максимально быстрая поставка
Скорость и эффективность являются ключевыми факторами в бережливой разработке программного обеспечения. Этот принцип направлен на максимально быструю поставку функционального продукта покупателю. Речь идет не о спешке, а о поиске оптимального потока, который позволит выполнить быструю поставку без ущерба для качества. Важными показателями, которые команды могут отслеживать, чтобы определить скорость поставки готовых продуктов, являются
- время цикла (cycle time) - время, которое нужно команде, чтобы создать продукт,
- время поставки (lead time) – время между выставлением заказа клиентом и исполнением заказа.

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

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

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

Помните: «система, порождающая дефекты, является дефектной системой» - Джеффри Палермо. Обеспечьте качество всего процесса и выявляйте проблемы до того, как они покинут процесс.

Источник: https://ardalis.com/principles-lean-software-development/
👍11
День 1734. #ЗаметкиНаПолях
Фоновые Задачи и Как Их Использовать. Начало
В .NET фоновые задачи — это асинхронные операции, которые выполняются независимо от основного потока приложения. Они используются для выполнения задач, которые не должны блокировать основной поток, например длительных вычислений, операций ввода-вывода или задач, которые могут выполняться одновременно.

Одной из распространённых реализаций является использование async/await:

Console.WriteLine("Основной поток: старт");
var bgTask = Task.Run(() => DoBgWork());

// продолжаем работу в основном потоке
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Основной поток: {i}...");
await Task.Delay(1000);
}

// ждём завершения фоновой задачи
await bgTask;

Console.WriteLine("Основной поток: завершено.");

static void DoBgWork()
{
Console.WriteLine("Фоновый поток: старт");
// какая-то длительная работа
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Фоновый поток: {i}...");
Task.Delay(1000).Wait();
}

Console.WriteLine("Фоновый поток: завершено.");
}

Варианты использования в реальных приложениях
1. Уведомления по email:
Отправка уведомлений по email может занимать много времени и вызывать задержки в сети. Вы не хотите, чтобы основной поток приложения ждал завершения доставки email.

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

3. Очистка и обслуживание базы данных:
В приложениях с БД рутинные задачи обслуживания, такие как архивирование, очистка старых данных или переиндексация, могут быть критически важными, но отнимать много времени. Такие задачи можно реализовать в фоновом процессе и запланировать на время минимальной нагрузки основного приложения.

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

Завтра рассмотрим пример, как реализовать фоновую задачу очистки кэша.

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

Источник:
https://stefandjokic.tech/posts/background-tasks-how-to-use-them
👍23