codaza
5.34K subscribers
41 photos
27 links
Канал о разработке на платформе .NET с использованием языка программирования C#. Рассматриваются актуальные подходы и современные методологии разработки.

YouTube:
https://www.youtube.com/c/codaza-channel

Контакты:
codaza.channel@gmail.com
Download Telegram
Дааа... Кажется, из последнего примера ребята действительно выжали максимум. Это был мастер-класс по code review на высоком уровне 😎 Если вы не успели пробежаться по комментариям в предыдущем посте, то сделайте это.

Итак, главную проблему, первым заметил подписчик Алексей. Он увидел, что ожидание выполнения всех задач (Task.WhenAll()) - расточительно. По логике метода необходимо:

1. Обратиться ко всем имеющимся адресам, которые возвращает метод GetReplicationUrls(). Нам предоставляют множество резервных адресов, так как подразумевается, что некоторые могут быть недоступны по самым разным причинам (неисправен, находится на техническом обслуживании и т. д.). Разумеется, какие адреса и в какой момент они будут нам доступны, нам никто не сообщит.
2. Получить цены.
3. Вернуть одну цену.

Исходя из этой логики, ожидание всех цен абсолютно не требуется. Достаточно дождаться первой полученной цены и вернуть её. В этом нам поможет статический метод WhenAny(), который реализован в классе Task. Как только одна из задач будет завершена (цена получена), мы сразу же вернём её результат. В противном случае, мы будем вынуждены ждать столько времени, сколько занимает самая длительная задача, что увеличит время выполнения метода LoadItemPriceAsync(). Это будет особенно критично в тех случаях, когда будут сетевые задержки, таймауты и прочие сценарии, которые нельзя назвать успешными.

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

На этом можно было закончить, но подписчик Alexander Radchenko заметил, что производительность можно поднять еще выше. Александр очень точно написал эту мысль в комментарии, поэтому я приведу цитату:

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

Безусловно, в Enterprise разработке высоконагруженных систем, применить такой подход можно и нужно.

Завершил картину Руслан Петряев, предложив лаконичный вариант рефакторинга, который мне очень понравился:

private async Task<int> LoadItemPriceAsync(int itemId) =>
await Task.WhenAny(GetReplicationServersUrls()
.Select(async url => await LoadCurrentPriceAsync(itemId, url)))
.Result;

Здесь не подразумевается обработка исключений. Это вам на самостоятельную работу 😉 Главная суть полностью раскрыта!

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

Спасибо! 👍
#капитану_на_заметку

Всем привет!

Enum — это тип значения, определенный набором именованных констант и т.д. и т.п. далее по тексту из MSDN.

Это понятно. Есть вещи важнее понятных истин. Я говорю про дизайн. Вы знаете про хороший дизайн для Enum в C#?
Если нет, то скорее ловите, чтобы уже с понедельника ваш код стал выглядеть более профессионально.

Когда мы создаём новый Enum, мы всегда должны предоставлять значение по умолчанию (default value). В противном случае, первое значение в созданном Enum может быть рассмотрено как значение по умолчанию. Такое поведение нельзя считать верным и может вести к потенциально опасным и трудно отлаживаемым ошибкам.

Добавьте значение по умолчанию, которое будет адекватно вашему бизнес-домену. Это может быть "None", "Unknown" и прочее. Тем самым вы явно укажете то, как следует обращаться с вашим типом.

+1 к чистому коду

Всем хороших выходных!
Всем привет! 👻

Продолжаем разбираться с шаблонами проектирования "банды четырёх (GoF)". Сегодня на повестке: Builder (Строитель). Строитель отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирования могут получаться разные представления.

Внимание❗️ Ролик может вызывать сильное чувство аппетита. Перекусите что-нибудь перед просмотром 😉

Приятного просмотра! 💙

https://www.youtube.com/watch?v=2ReKJaM2glI
#капитану_на_заметку

Всем привет!

В прошлой рубрике #капитану_на_заметку мы говорили про хороший дизайн для Enum. Сегодня мы затронем вопрос производительности использования Enum.

Получение строкового представления для перечисляемого типа — весьма частая операция. Это может потребоваться при формировании JSON, выводе данных в интерфейс Web или Desktop приложений, логировании в системы сбора логов и т. д. Для решения такой задачи, естественным действием разработчика на C# будет вызов метода ToString().

Если же значение перечисляемого типа известно заранее (на этапе компиляции), хорошей практикой является использование выражения nameof. Значение выражения nameof вычисляется во время компиляции, и это не влияет на время выполнения. В ряде случаев, это может существенно повысить общую производительность вашего приложения. Поэтому, в следующий раз, перед вызовом ToString(), задумайтесь... может лучше nameof? 😉

+1 к производительному коду

Всем хороших выходных!
#codaza_спрашивает

Всем привет! 👋

Сегодня у нас еще один занятный кусочек кода, которому стоит провести code review. Как обычно, всё компилируется и все тесты проходят на ура. Сомнений нет — код рабочий. Но всё же есть ощущение что в production-среду этому методу рановато. В production предстоит работа с большим массивом данных и, возможно, этот метод еще не готов к таким нагрузкам. А может и готов...

Есть идеи? 🤔
#codaza_отвечает

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

Где же был зарыт "главный слон" 🐘? Проблема, с которой данный код никак нельзя было допускать в production-среду. Проблема была на строках 12-13:

int keyWordIndex = dataSlice.Value.TextFraction.Substring(actualPosition).IndexOf(keyWord);

Вызов метода Substring() - вот основная проблема. Дело в том, что при каждом вызове этого метода, в памяти будет создаваться полная копия строки, содержащаяся в свойстве TextFraction. Это не проблема при нечастных вызовах на небольших строках и без использования циклов. Для нашей ситуации это совершенно не годится, так как методу предстоит работать с большим массивом данных. Только представьте что может произойти с оперативной памятью при частых вызовах метода ExtractStochasticDataAsync(). И первым проблему заметил Александр Максименко, указав на то что вместо вызова Substring() следует делать вызов метода расширения для класса String - AsSpan(). Такой подход позволит не создавать копию строки TextFraction в оперативной памяти, что существенно снизит нагрузку на неё.

Далее, Александр увидел возможность для параллельной обработки данных в словаре dataSlices. При параллельной обработке нужно учитывать проблемы совместного доступа, поэтому, для словаря indexes, вместо Dictionary<int, int>, Александр взял потокобезопасную коллекцию ConcurrentDictionary<int, int>.

Метод ExtractStochasticDataAsync() является асинхронным и хорошей практикой является предоставления возможности для остановки метода (тем более метод работает в цикле с большим объемом данных). На эту проблему обратил внимание Aleksandr Radchenko, предложив использование CancellationToken.

Итоговый вариант после рефакторинга стал выглядеть так:

public async Task<IDictionary<int, int>> ExtractStochasticDataAsync(string keyWord, CancellationToken ct = default)
{
IDictionary<int, AnalyticalDataSlice> dataSlices = await LoadAnalyticalDataSlicesAsync(ct);

ConcurrentDictionary<int, int> indexes = new();

var tasks = dataSlices.Select(async ds =>
{
ct.ThrowIfCancellationRequested();

int actualPosition = await LoadActualPositionAsync(ct);

int keyWordIndex = ds.Value.TextFraction
.AsSpan(actualPosition).IndexOf(keyWord);

indexes.TryAdd(ds.Key, keyWordIndex + actualPosition);
});

await Task.WhenAll(tasks);

return indexes;
}

Кроме того, Aleksandr Radchenko предложил еще ряд важных улучшений с примерами листингов (за что ему отдельное спасибо). Например, использовать в качестве возвращаемого значения метода ExtractStochasticDataAsync() асинхронную реализацию интерфейса IAsyncEnumerable и оператор yield, что позволит организовать асинхронную обработку данных из вызываемого метода. Это потребует изменение выходного API у метода ExtractStochasticDataAsync(), но если это возможно по имеющимся бизнес-требованиям, сделать это точно стоит. Этот вариант будет выглядеть так:

public async IAsyncEnumerable<(int Key, int Position)> ExtractStochasticDataAsync01(string keyWord, [EnumeratorCancellation] CancellationToken ct = default)
{
IDictionary<int, AnaliticalDataSlice> dataSlices = await LoadAnalyticalDataSlicesAsync(ct);

foreach(var dataSlice in dataSlices)
{
int actualPosition = await LoadActualPositionAsync(ct);

int keyWordIndex = dataSlice.Value.TextFraction
.AsSpan(actualPosition).IndexOf(keyWord);

ct.ThrowIfCancellationRequested();

yield return (dataSlice.Key, keyWordIndex + actualPosition);
};
}

Из таких code review можно получить гораздо больше ценных и практических знаний нежели из очередной статьи в интернете.

Всем активным участникам огромное спасибо за вклад! 💙
#капитану_на_заметку

Всем привет!

В прошлый раз мы поговорили об использовании метода расширения AsSpan(), который можно вызывать на объектах класса String и предотвратить ненужные аллокации в оперативной памяти. В продолжении темы, мне хотелось бы рассказать про похожую возможность, но уже для коллекции List.

Возникают ситуации, когда нам нужно получить некоторый диапазон значений из List. Как поступал разработчик на C# до появления .NET5? Очень просто, он вызывал метод GetRange() и получал желаемый диапазон значений. Такое действие неминуемо сопровождается выделениями в оперативной памяти. Сегодня мы можем этого избежать и значительно снизить нагрузку на оперативную память воспользовавшись статическим методом AsSpan(), который располагается в классе CollectionsMarshal. Этот метод вернёт нам Span<T> представление данных в списке, после чего можно вызвать метод Slice() c такими же параметрами, как у GetRange().

+1 к производительному коду

Всем хороших выходных!
#капитану_на_заметку

Я никогда не делаю исключений. Исключения опровергают правило.

Артур Конан Дойл

Всем привет!

Тема правильной работы с исключениями (exceptions) весьма холиварна и едва ли подлежит окончательному разрешению 🙂 Но сегодня я хочу обратить вниманием на другое — на цену. Цену использования исключений. Работаете вы с ними правильно или неправильно, держите в голове что за это придётся заплатить цену. При работе с большими массивами данных это может привести к значительной деградации производительности вашего приложения. Я не говорю о том, что мы не должны генерировать исключения, но разработчик обязан понимать, что выбрасывать исключения без аргументированной необходимости — плохая идея.

Руководствуйтесь простым правилом: Исключения для исключительных ситуаций.

Вы знаете как обработать исключение? Просто сделайте это без лишних throw 😉

+1 к исключительному коду

Всем хороших выходных!
#капитану_на_заметку

Всем привет!

Спешу поделиться с вами интересным практическим случаем работы со строками. Недавно мне довелось работать с одним из австрийских сервисов обработки сообщений из различных мессенджеров (WhatsApp, Viber и пр). Ребята собирают аналитическую статистику по отправляемым сообщениям и столкнулись с тем, что данные в этой статистике, время от времени, искажались непонятным образом.

Я проанализировал исходный код сервиса и обнаружил, что проблема в подсчёте длины строки сообщений. Разработчик сервиса воспользовался свойством Length у экземпляра класса String. Это решение было логично и казалось единственно верным. Проблема в том, что люди часто используют Emoji в своих сообщениях и 1 символ Emoji не равен длине одного символа, так как для их кодировки используется Unicode.

Для правильного сбора статистки требовалась именно посимвольная длина строки. Свойство LengthInTextElements (класс StringInfo) решает именно эту задачу.

+1 к опыту работу со строками

Всем хороших выходных!
Всем привет!

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

Уверен, что в самых разных источниках, вы читали, что в качестве параметра метода следует использовать IEnumerable<T>, если не требуется функциональность IList<T> или List<T>. Кроме того, хорошей практикой всегда считается следование принципу: Принимай наиболее общее (generic), а возвращай наиболее частное (specific). Это важные принципы, которых стоит придерживаться в подавляющем большинстве случаев. Всё же, бывают ситуации, в которых от этих принципов приходится отступать.

Как вы думаете, есть ли разница в определении параметра метода как IEnumerable<T> или List<T>?

Короткий ответ: да, разница есть.

Дело в том, что, когда параметр метода определяется через интерфейс IEnumerable<T>, в списке выполняются операции boxing и unboxing, что неизбежно ведет к лишним аллокациям в оперативной памяти. Как следствие, мы получаем потери в производительности. Иногда не критичные, а иногда вполне ощутимые. Таким образом, поменяв только тип параметра, мы можем существенно поднять производительность без внесения значительных правок. В моём случае, поставленные цели были достигнуты, и задача решена.

+1 к производительному коду

Всем хороших выходных!
#капитану_на_заметку

Всем привет!

Как здорово генерировать исключения в секции try и ловко ловить их в секции catch 🌈 Одно удовольствие работать с синхронным кодом. Мрачнее дела обстоят при работе с асинхронным кодом. Это как классическая механика и квантовая физика: в асинхронном коде постоянно возникает "квантовая запутанность" и работает там всё по другим законам 🤯

Взгляните на два примера на картинке. В первом варианте мы не ожидаем (await) результата выполнения Task.FromException(), а сразу возвращаем Task, что приводит к несрабатыванию блока catch. Во втором примере мы решаем эту проблему ожидая результат выполнения Task.FromException().

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

Поиграться с примером можно по ссылке: https://clck.ru/hcRSc

+1 к тонкостям работы с асинхронным кодом

Всем хороших выходных!
#капитану_на_заметку

Всем привет!

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

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

Посмотрите на пример выше. Вам хочется установить ширину прямоугольника? Сделайте это через свойство Width. Вам хочется установить длину прямоугольника? Да просто сделайте это уже в конце концов! Не пытайтесь препятствовать собственным желаниям 😉

Рефакторингов много, но начните применять этот принцип сегодня, и вы увидите, как радуга падает на ваш исходник 🌈

+1 к простым мудростям

Всем хороших выходных!
Всем привет! 👻

Недавно на канале вышло короткое видео, где я рассказал о 10 лучших практиках, которые стоит применять при разработке приложений на ASP.NET. Одна из этих практик - Глобальная Обработка Исключений (Global Exceptions Handling). В сегодняшнем ролике я раскрываю эту тему более подробно. Наливайте чайку и присаживайтесь поудобнее. Воскресенье — отличный день, чтобы обрабатывать исключения глобально.

Приятного просмотра! 💙

https://youtu.be/RAcpAc0EYV0
#капитану_на_заметку

Всем привет!

Недавно мне довелось провести в аэропорту 9 часов из-за непредвиденной задержки рейса. Было весело 😐 Но примечательно другое - когда мне пришла SMS с напоминаем о вылете, я обратил вниманием что время было указано неправильно, оно выглядело так, как будто смещение GMT было сделано дважды. Не уверен что так должно быть 🤔, но я подумал, что это отличная идея чтобы рассказать подписчикам codaza о необходимости использования перечисляемого типа DateTimeKind.

При определении DateTime, по умолчанию используется DateTimeKind.Unspecified, из-за чего приложение не может понять, какой часовой пояс вы используете. Поэтому очень важно указать формат, с которым вы работаете, иначе это может привести к SMS, которую я получил.

В своих проектах я предпочитаю формат UTC. Таким образом, вся работа с датами идёт в базовом формате, и конвертация в локальный формат производится только при отдаче данных на уровень представления (сайт, SMS и пр.).

+1 к тонкостям работы со временем
#капитану_на_заметку

Всем привет!

На технических собеседованиях часто можно услышать вопрос:
"Как улучшить производительность при конкатенации строк?"
И получить такой ответ:
"Использовать класс StringBuilder! Везде! Всегда!"

Ох... нет... Так это не работает. Но звучит обнадеживающе 🙂

Именно из-за этого ответа, на code review, мне нередко приходится видеть неразумное использование класса StringBuilder. Ненужно использовать StringBuilder на небольших наборах данных. Если у вас пapa-тpoйкa строк, воспользуйтесь обычном оператором "+". Мало того что это гораздо читабельнее, так еще и производительнее. Это ли не то, чего мы так хотим при создании качественного программного обеспечения? 😉

StringBuilder нужен только когда у вас ну ооочень много строк (не три) — вот тогда есть смысл его использования. Неразумное использование StringBuilder может даже приводить к деградации производительности. Именно поэтому нужно всегда отслеживать производительность вашего ПО делая benchmarking.

+1 к работе со строками
#капитану_на_заметку

Всем привет!

А вы знали, что Enum можно прокачать? Для этого нужно добавить атрибут [Flags] там, где мы делаем объявление перечисляемого типа. Это сделает возможным хранение не одного, а множества значений в одной переменной 🪄

Зачем это нужно? Очень нужно 🔥 Вот несколько примеров:

- Файл или запись в базе данных может быть с правами доступа (только чтение, чтение + запись)
- Персонаж в игре может двигаться определённым образом (только вправо, вправо + вверх)
- Продукт может сочетать несколько характеристик (солёный, сладкий + острый)

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

+1 к тонкостям работы с перечисляемыми типами
#капитану_на_заметку

Всем привет!

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

Один из методов, который позволяет упростить сложные синтаксические конструкции в C# называется Pattern matching. Pattern matching предоставляет более краткий синтаксис для тестирования выражений и выполнения действий при их совпадении. Таким образом, вы сможете визуально проще проверять типы и свойства ваших переменных.

Pattern matching — это синтаксический сахар, который помогает сделать ваш код слаще 🧁

+1 к сладости вашего кода
#капитану_на_заметку

Всем привет!

Вам когда-нибудь приходилось делать поиск по строковому ключу в Dictionary? Главная проблема со строковыми ключами — непредсказуемость регистра (Premium, PREMIUM, PrEmIuM).

Можно придумать разные "велосипеды", которые помогут обойти эту проблему (например, везде делать String.ToLower()). Но зачем что-то делать, если можно этого не делать?

Существует классный способ решения вопроса: передать параметр StringComparer.CurrentCultureIgnoreCase в конструкторе Dictionary.

Вот и всё. Вы великолепны! 🍸

+1 к простым решениям
Всем привет! 👻

Сегодня я хочу рассказать вам о том, как управлять конфигурационными данными приложения ASP.NET в development среде. Хранение секретных данных (логины, пароли, токены и пр.) никогда не должны попасть в общедоступный репозиторий. В ASP.NET существует удобный механизм, который решает эту задачу просто и понятно — User Secrets. Поэтому наливайте чайку и давайте посекретничаем 🫖🍪

Приятного просмотра! 💙

https://youtu.be/6OjVlXSKYcg