.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
День 1694.
DotNext 2023
Посетил конференцию DotNext 2023. Это мой первый опыт офлайн конференции. Вот некоторые фотки оттуда. Доклады выбирал по степени полезности для текущей работы, поэтому некоторые хотел, но не посетил, обязательно посмотрю в записи.

Были и другие активности. Великолепно неформально с Антоном Оникийчуком и Юлией Цисык про найм в .NET. Чего ждут работодатели, случаи были на собеседовании, красные флаги для компаний и для кандидатов и о многом другом. Множество шуток, баек и полезной информации. К концу 3го часа Антон с Юлей взмолились отпустить их домой :)

PVS-Studio предлагали задачки на поиск ошибок (я молодец, заслужил пряник – реальный тульский пряник). Positive Technologies - найти уязвимости в примерах кода. А также можно было поиграть на старых консольках, пособирать деревянные паззлы или потыкать механические клавиатуры.

В общем, если у вас будет возможность, советую посещать конференции офлайн. А ещё лучше там выступить, в DotNext уже принимают заявки.
👍46👎1
День 1695.
Значения Задержек, Которые Должен Знать Каждый Программист
Сегодня темя для любителей оптимизации и быстродействия. Подсмотрел её на одном из докладов DotNext и решил поделиться.

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

Итак,
Обращение к кэшу L1 – 1ns
Ошибка при предсказании условного перехода – 3ns
Обращение к кэшу L2 – 4ns
Открытие/закрытие мьютекса – 17ns
Обращение к главной памяти – 100ns
Сжатие 1Кб быстрым алгоритмом Snappy – 2μs
Чтение 1Мб последовательно из памяти – 2μs
Произвольный доступ к SSD – 16μs
Пересылка 2Кб по сети со скоростью 1Гб/с – 20μs
Чтение 1Мб последовательно с SSD – 49μs
Передача сообщения туда/обратно в одном дата-центре – 500μs
Чтение 1Мб последовательно с HDD – 825μs
Произвольный доступ к HDD – 2ms
Передача пакета между континентами и обратно – 150ms

Здесь:
1ns (наносекунда) = 10^-9 секунды
1μs (микросекунда) = 10^-6 секунды = 1000ns
1ms (миллисекунда) = 10^-3 секунды = 1000μs = 1000000ns

Таким образом, можно прочитать:
- последовательно с жесткого диска со скоростью ~200Мб/с.
- последовательно с SSD со скоростью ~1Гб/с (3-7Гб/с для NVME).
- последовательно из основной памяти со скоростью ~100Гб/с (пакетная скорость)
- последовательно из Ethernet 10Гбит/с со скоростью ~1000Мб/с.
Между Европой и Америкой возможно не более 6-7 рейсов туда/обратно в секунду, но в центре обработки данных можно достичь примерно 2000 в секунду.

Пример
Какая будет общая задержка получения 30 изображений по 256Кб с одного сервера?

Упрощённое решение: вся работа делается одним сервером, где чтение с диска превалирует над остальными операциями.

Время на чтение одного изображения с SSD:
(256Кб / 1Мб) * 49μs + 16μs доступ = 28.25μs
Среднее время генерации результата:
30 чтений * 28.25μs = 847.5μs

Таким образом, один сервер с SSD может выдать 1000000μs / 847.5μs ~ 1179 страниц результатов в секунду.
👍28
День 1696. #Карьера
Что Мы Имеем в Виду, Когда Говорим о Синдроме Самозванца
Синдром самозванца — сомнение в своих способностях до такой степени, что вы чувствуете себя мошенником — является вечной темой разговоров среди разработчиков.

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

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

Особенно когда IT-инфлюенсеры делятся своими пет-проектами, может показаться, что все работают над чем-то более сложным, творческим или инновационным, чем вы. А поскольку многие работают удалённо, иногда трудно оценить, как работают ваши коллеги или как они на самом деле проводят своё время.

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

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

«Это как программировать в темноте»
Доктор Кэт Хикс, директор Developer Success Lab в Pluralsight, провела исследование, в котором дала 25 авторам кода исследовать и отладить чужую кодовую базу, а потом провела с ними подробное интервью. Выяснилось, что испытуемые не ценили усилия коллег по написанию кода и в реальности считали проверку чужого кода непродуктивной работой. Это усугублялось тем, что боязнь «не выглядеть как инженер» провоцировала их меньше делиться с коллегами результатами своего труда. В итоге авторы кода часто выражали острое одиночество, даже в многочисленных командах. Доктор Хикс назвала это «программированием в темноте». Это одиночество и изоляция могут усугубить чувство самозванства на работе. А страх не выглядеть инженером легко интерпретируется как синдром самозванца.

«Даже если вы параноик…»
Синдром самозванца вовсе не синдром, если это действительное восприятие того, что люди относятся к вам хуже, чем к другим того же уровня. Это может выражаться в дискриминации либо просто в том, что организация не ценит вашего вклада в её работу. В этом случае синдром самозванца – это лишь удобное объяснение проблем, которые на самом деле больше, чем просто недостаток уверенности у отдельного человека.

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

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

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

Источник: https://stackoverflow.blog/2023/09/11/what-we-talk-about-when-we-talk-about-imposter-syndrome/
👍15
День 1697. #DesignPatterns
Принцип Применения Принципов: Когда НЕ Использовать SOLID
Мы знаем много примеров того, когда и как использовать принципы SOLID. Есть ли какие-нибудь хорошие примеры того, когда соблюдение этих принципов является плохой идеей?

Принцип Применения Принципов (Principle of Applying Principles - POAP):
Принципы, модели и лучшие практики не являются конечными целями. Хорошее и правильное применение каждого из них вдохновляется и ограничивается высшей, более конечной целью. Нужно понимать, почему вы делаете то, что делаете.

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

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

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

1. Принцип единственной обязанности (SRP)
Цель: сделать изменения изолированными, их легко тестировать и анализировать.
Проблемы: гипердекомпозиция. Вряд ли вам потребуется отдельный класс для установки заголовка страницы.
Примечания по конкретному домену: это ваше домашнее задание.

2. Принцип «открыт/закрыт» (OCP)
Цель: предотвратить выход из строя старых функций при реализации новых.
Проблемы: «закрытие» модуля, которым вы владеете, поддерживаете и можете безопасно расширять, может привести к ненужной сложности, цепочкам наследования и увеличению размера кода.
Примечания:

3. Принцип подстановки Лисков (LSP)
Цель: предотвратить появление неправильного кода, который выглядит правильно.
Проблемы: чрезмерная уверенность в LSP как в сигнале правильности.
Примечания:

4. Принцип разделения интерфейса (ISP)
Цель: уменьшить связанность между классами и их клиентами.
Проблемы: требование минимально возможного интерфейса может противоречить намерениям LSP.
Примечания:

5. Принцип инверсии зависимостей (DIP)
Цель: обеспечить возможность повторного использования, замены и тестирования большей части системы.
Проблемы: чрезмерная инверсия. Создание и «инвертирование» абстракций без всякой выгоды, усложнение чтения и понимания кода.
Примечания:

Бонус. Не повторяйтесь (DRY).
Цель: предотвратить несогласованное применение бизнес-правил в системе.
Проблемы: чрезмерное применение. Код, который выглядит одинаково, может служить разным целям, а поведение, которое должно отличаться, становится трудно изменить.
Примечания:

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

Помните, что даже POAP подлежит POAP! И цель POAP — удержать вас от совершения глупых, расточительных или вредных поступков ради принципов. Спор из-за принципов обычно является нарушением как намерения POAP, так и, возможно, смысла принципа, о котором вы спорите.

Источник: https://softwareengineering.stackexchange.com/questions/447532/when-to-not-use-solid-principles
👍19👎1
День 1698. #МоиИнструменты
Как Освоить Клавиатуру и Стать Эффективным Разработчиком ПО
Мышь непродуктивна. Нужно переместить руку в другое место, точно навести курсор на что-то и нажать кнопку. Вы можете работать гораздо более продуктивно, используя только клавиатуру при правильном рабочем процессе, ПО, оборудовании и знаниях. Кроме того, использование мыши увеличивает нагрузку на мышцы, что может стать проблемой, если вы используете компьютер по много часов в день. В любом случае, если вы попробуете следующие советы, многие вещи вы сможете делать гораздо быстрее.

1. Слепая печать
Возможность печатать, не глядя на клавиатуру - самый важный навык, позволяющий максимально эффективно использовать клавиатуру. Каждый палец отвечает за определённый набор кнопок, т.е. вы сможете лучше развивать мышечную память, печатать быстрее и точнее. Печатание вслепую способствует правильному расположению рук и распределяет нагрузку между всеми пальцами. Приложения для обучения слепой печати: TypingClub, Typing.io или «Соло на клавиатуре». Будьте терпеливы. Чтобы освоиться, нужно много времени.

2. Найдите горячие клавиши в часто используемых приложениях
Вы будете удивлены, сколько их существует, например, в Visual Studio. Погуглите горячие клавиши для каждого инструмента, которым вы часто пользуетесь. Они точно вам пригодятся.

3. Горячие клавиши ОС
Начните с основных для редактирования текста, таких как Ctrl+[стрелка влево/вправо] для перемещения между словами, Ctrl+[del/backspace] для удаления слов и Ctrl+Shift+[стрелки влево/вправо] для выделения. Некоторые другие полезные — WinKey+V для стека буфера обмена и WinKey+Shift+S для создания снимка экрана.

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

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

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

7. Попробуйте механическую клавиатуру
Щелкать по механической клавиатуре просто веселее. Многие из них более эргономичны: изогнутые, разделённые, с педалями и т.п. Многие являются программируемыми. Вы можете расположить клавиши, как угодно, а также иметь несколько «слоёв» клавиатуры и даже управлять мышью.

8. Используйте ПО, подходящее для клавиатуры
Например, в Markdown (с Typora) не нужно делать что-то неприятное, например, тянуться за мышкой, чтобы отформатировать текст, как в Word. Другой пример – диаграммы. С помощью Mermaid вы можете описывать диаграммы в виде текста. Клоны Norton Commander, имеют кучу горячих клавиш и обеспечивают лучший опыт просмотра, чем проводник.

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

Источник: https://michaelscodingspot.com/keyboard-master/
👍18👎1
День 1699. #ЗаметкиНаПолях
Как Создать Таблицу с Ключом Enum в EF?
Допустим, у нас есть сущность с ограниченным количеством типов сущности, которые редко, но могут добавляться. Для лучшей читаемости кода мы хотели бы использовать enum для типов сущности, при этом имея полноценную таблицу в БД, где каждому значению enum соответствовала бы строка с дополнительной информацией. Также желательно, чтобы при добавлении значения в enum, у нас в БД появлялась новая строка.

Для этого мы можем использовать комбинацию двух функций EF Core:
- Преобразование значений — для преобразования перечисления в int при чтении/записи в БД.
- Присвоение начальных значений данных — для добавления значений перечисления в базу данных при миграции.

Допустим, мы создаём систему для винного магазина. У нас есть вина и их виды:
public enum WineType
{
Red = 1,
White = 2
}

public class Wine
{
public int Id { get; set; }
public string Name { get; set; }

public WineType Type { get; set; }
public WineTypeDetails TypeDetails { get; set; }
}

public class WineTypeDetails
{
public WineType Type { get; set; }
public string Name { get; set; }

public List<Wine>? Wines { get; set; }
}

Тогда в файле контекста WineContext в методе OnModelCreating нам нужно настроить преобразование значений (enum в int):
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Wine>()
.Property(w => w.Type)
.HasConversion<int>();

mb.Entity<WineTypeDetails>()
.Property(t => t.Type)
.HasConversion<int>();

mb.Entity<WineTypeDetails>()
.HasKey(t => t.Type);


И присвоение начальных значений (таблица типов заполняется из enum):
  mb.Entity<WineTypeDetails>().HasData(
Enum.GetValues(typeof(WineType))
.Cast<WineType>()
.Select(t => new WineTypeDetails() {
Type = t,
Name = t.ToString()
})
);
}

Теперь используем это в программе:
using Microsoft.EntityFrameworkCore;

using (var db = new WineContext())
{
await db.Database.MigrateAsync();
if (!db.Wines.Any())
{
db.Wines.Add(new Wine
{
Name = "Saperavi",
Type = WineType.Red,
});
db.Wines.Add(new Wine
{
Name = "Cabernet",
Type = WineType.Red,
});
db.Wines.Add(new Wine
{
Name = "Chardonnay",
Type = WineType.White,
});
}

await db.SaveChangesAsync();
}

using (var db = new WineContext())
{
var types = await db.WineTypes
.ToListAsync();

foreach (var type in types)
{
Console.WriteLine($"{type.Name} wines:");

var wines = await db.Wines
.Include(w => w.TypeDetails)
.Where(w => w.Type == type.Type)
.ToListAsync();

foreach (var wine in wines)
Console.WriteLine($"{wine.Name} is
a {wine.TypeDetails.Name} wine");
}
}

Вывод:
Red wines:
Saperavi is a Red wine
Cabernet is a Red wine
White wines:
Chardonnay is a White wine

Теперь мы можем добавить значение в enum, например, Rose, и добавить новую миграцию. Соответствующая строка будет добавлена в таблицу WineTypeDetails автоматически. При этом существующие строки при миграциях изменяться не будут. Кроме того, в таблицу типов можно добавить и другие столбцы, например, Description.
Полный код примера тут.

Источник: https://stackoverflow.com/questions/50375357/how-to-create-a-table-corresponding-to-enum-in-ef-core-code-first
👍26
День 1700. #ЗаметкиНаПолях #Microservices
Оркестрация Или Хореография. Начало
Работа с распределёнными системами одновременно интересна и сложна. Одной из задач является разработка эффективной коммуникации между службами. Больше централизации или меньше централизации? Больше связи или меньше связи? Больше контроля или меньше контроля?

Внутри монолитной системы общение происходит посредством прямых вызовов методов. Это простой подход, который хорошо работает, когда все компоненты находятся в одном процессе. Однако это не работает с микросервисами.

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

Примером оркестрации может быть паттерн Saga, реализованный с помощью RabbitMQ.

Преимущества:
- Простота
- Централизованность
- Простота мониторинга и устранения неполадок

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

Недостатки:
- Тесная связанность
- Единая точка отказа
- Сложности добавления, удаления или замены микросервисов.

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

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

Источник:
https://www.milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking
👍17
День 1701. #ЗаметкиНаПолях #Microservices
Оркестрация Или Хореография. Окончание
Начало

Хореография — коммуникация через события
Это децентрализованный подход к коммуникации. Хореография использует взаимодействие, управляемое событиями. Событие – это то, что произошло в прошлом и является фактом. Отправитель не знает, кто будет обрабатывать событие и что произойдёт после его обработки.

Преимущества:
- Слабая связанность
- Простота обслуживания
- Децентрализованное управление
- Асинхронная связь

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

Недостатки:
- Сложность
- Усложнён мониторинг и устранение неполадок
- Сложнее реализовать и поддерживать, чем оркестрацию.

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

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

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

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

Источник: https://www.milanjovanovic.tech/blog/solving-race-conditions-with-ef-core-optimistic-locking
👍9
👍16👎1
День 1702. #ВопросыНаСобеседовании #Многопоточность
Самые часто задаваемые вопросы на собеседовании по C#

23. Как обеспечить взаимоисключающий доступ к общим ресурсам в многопоточной системе в C# без использования
lock или Monitor?

Вы можете использовать другие примитивы синхронизации. Некоторые распространенные альтернативы:
1) Мьютекс: гарантирует, что только один поток может одновременно получить доступ к общему ресурсу. В отличие от lock, мьютекс – примитив ОС и может обеспечивать межпроцессную синхронизацию. Пример:
var mutex = new Mutex();
//...
mutex.WaitOne();
try
{
// Доступ к общему ресурсу
}
finally
{
mutex.ReleaseMutex();
}

2) Семафор: ограничивает количество одновременных потоков, которые могут получить доступ к общему ресурсу. Пример:
var sem = new Semaphore(1, 1); 
// Начальное и максимальное количество
// потоков установлено в 1
//...
sem.WaitOne();
try
{
// Доступ к общему ресурсу
}
finally
{
sem.Release();
}

3) ReaderWriterLockSlim: обеспечивает эффективный доступ для чтения и записи к общим ресурсам. Позволяет выполнять несколько одновременных операций чтения, когда ни один писатель не удерживает блокировку на запись. Пример:
var rwLock = new ReaderWriterLockSlim();
//...
// Чтение
rwLock.EnterReadLock();
try
{
// Доступ к общему ресурсу
}
finally
{
rwLock.ExitReadLock();
}
//...
// Запись
rwLock.EnterWriteLock();
try
{
// Доступ к общему ресурсу
}
finally
{
rwLock.ExitWriteLock();
}

4) SpinLock: пытается получить блокировку до тех пор, пока не будет достигнут успех. SpinLock следует использовать в сценариях с низким уровнем конфликтов, когда ожидается, что блокировка будет удерживаться в течение очень короткого времени. Пример:
var spinLock = new SpinLock();
bool lockTaken = false;
//...
spinLock.Enter(ref lockTaken);
try
{
// Доступ к общему ресурсу
}
finally
{
if (lockTaken)
{
spinLock.Exit();
}
}

Помните, что эти примитивы синхронизации имеют более высокие накладные расходы чем обычный lock.

UPD: спасибо подписчикам, напомнили про прекрасный доклад о примитивах синхронизации от Станислава Сидристого.

Источник: https://dev.to/bytehide/c-multithreading-interview-questions-and-answers-4opj
👍17
День 1703. #МоиИнструменты
Тестируем Конечные Точки HTTP в Visual Studio 2022
В Visual Studio 17.6 представлен новый инструмент, Обозреватель Конечных Точек (Endpoints Explorer), который упрощает процесс обнаружения и тестирования конечных точек API.

Чтобы использовать его, перейдите в View > Other Windows > Endpoint Explorer (Вид > Другие окна > Обозреватель конечных точек).

Он автоматически определит конечные точки в проекте, предоставляя обзор проектов и связанных с ними конечных точек (см. в правой панели на рисунке). Если вызвать команду Generate Request (Создать запрос), будет создан HTTP-файл с запросом для конечной точки (слева на рисунке).

Ссылки над запросами позволяют отправить запрос к запущенному приложению или запустить отладку приложения (Debug). Результат показан в панели по центру на рисунке.

Этот инструмент значительно упрощает процесс тестирования конечных точек и проверки ответа, позволяя не переходить лишний раз в Postman.

Источник
👍27👎1
День 1704. #ЗаметкиНаПолях
Использование SQL-Функций в Entity Framework
SQL Server (или другая база данных) имеет множество встроенных функций, которые можно использовать в запросах. Но в Entity Framework их нельзя использовать напрямую.

Некоторые функции имеют аналоги в .NET, например, для добавления дней к дате вы используете DATEADD в TSQL, а в C# это становится DateTime.AddDays. Но, например, SOUNDEX в TSQL (для поиска похожих строк вместо полного совпадения) не имеет C#-версии.

Вы можете написать свою реализацию на C# или просто зарегистрировать эти SQL-функции в DBContext. После регистрации функции вы можете использовать её в EF-запросах независимо от поставщика БД.

Зарегистрируем функцию SOUNDEX. Для этого создадим новый метод в DbContext и аннотируем его атрибутом DbFunction.
public class MyDbContext : DbContext
{

[DbFunction(Name = "SoundEx",
IsBuiltIn = true)]
public static string SoundEx(string input)
{
throw new NotImplementedException();
}
}
Замечание: реализовывать метод не надо, только предоставить правильную сигнатуру.

Теперь можно использовать метод SoundEx в запросах:
app.MapGet("/customers", 
([FromQuery] name, MyDbContext ctx) => {
return ctx.Customers
.Where(c => MyDbContext.SoundEx(c.Name) ==
MyDbContext.SoundEx(customerName))
.ToListAsync();
});

Когда запрос выполняется, генерируется следующий код SQL:
SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE SoundEx([c].[Name]) = SoundEx(N'Timothy')
OR ((SoundEx([c].[Name]) IS NULL)
AND (SoundEx(N'Timothy') IS NULL))

Функция по запросу "Timothy", вернёт также слова, вроде "Timmothy", "Timoteo" или "Timotheo".

Чтобы убрать проверку на NULL, можно установить свойство IsNullable атрибута DbFunction в false:
[DbFunction(Name = "SoundEx", 
IsBuiltIn = true,
IsNullable = false)]
public static string SoundEx(string input)
{
throw new NotImplementedException();
}

Помимо встроенных функций, вы также можете создавать собственные и аналогичным образом добавлять их в DbContext. Для пользовательских функций установите для свойства IsBuiltIn значение false, а также определите схему функции:
[DbFunction(Name = "MyCustomFunction",
Schema = "dbo",
IsBuiltIn = false)]
public static int MyCustomFunction(int input)
{
throw new NotImplementedException();
}

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

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

Источник: https://timdeschryver.dev/blog/consuming-sql-functions-with-entity-framework
👍17
День 1705. #юмор
👍12
День 1706. #ЗаметкиНаПолях
Мультитаргетинг NuGet-Пакетов
При создании NuGet-пакета мы хотели бы использовать минимально возможную версию платформы (чтобы обеспечить максимальный охват), но также пользоваться преимуществами новейших функций dotnet. Используя мультитаргетинг, мы можем одновременно поддерживать разные версии платформы.

1. Проект
Для начала в файл проекта добавим нужные нам версии:
<Project>
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>

</PropertyGroup>
</Project>

2. Код, зависимый от платформы
В .NET 8 добавлен новый тип коллекции — FrozenSet. Попробуем его использовать.
Этого можно достичь с помощью условных директив компилятора:
private static readonly IReadOnlySet<Hamburger> AllItems;

static Hamburger()
{
#if NET8_0_OR_GREATER
AllItems = new HashSet<Hamburger>()
{

}.ToFrozenSet();
#else
AllItems = new HashSet<Hamburger>()
{

};
#endif
}

3. Внешние зависимости
Нам бы хотелось использовать разные версии внешних зависимостей в зависимости от используемой версии платформы. Например, в .NET 7 - версию 7 EF Core, а в .NET 8 – превью версии 8.
* Здесь мы используем централизованное управление пакетами.
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>
true
</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- общие зависимости -->
<PackageVersion Include="BenchmarkDotNet" Version="0.13.7"/>

</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="7.0.10"/>

</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.0-preview.7.23375.4"/>

</ItemGroup>

</Project>

При сборке проекта для net7.0 используются зависимости, специфичные для .NET 7, и аналогично для .NET 8.

4. Сборка и публикация пакетов
В GitHub Actions единственное, что будет отличаться от обычного скрипта, это то, что вам необходимо убедиться, что вы указали все платформы, которые вы поддерживаете, на этапе настройки dotnet.
steps:

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
7.0.400
8.0.x
dotnet-quality: 'preview'


Больше ничего менять не надо, просто выполняйте dotnet build, dotnet pack и dotnet publish, как обычно.

Источник: https://josef.codes/multi-targeting-your-nuget-packages/
👍10
День 1707. #ЗаметкиНаПолях
Дискриминируемые Объединения в C#
Дискриминируемые объединения (discriminated unions) – один из давних запросов на включение в язык C#. В F# они используются давно, а C#-разработчикам придется подождать их ещё немного.

Дискриминируемые объединения позволяют сообщить компилятору (и другим инструментам, таким как IDE), что данные могут быть одним из ряда предопределённых типов. Например, у вас может быть метод RegisterUser(), который возвращает класс User, UserAlreadyExists или InvalidUsername. Эти классы не обязаны наследовать друг от друга. Вы просто хотите поддерживать 3 потенциальных типа возврата, сообщить об этом языку и получать ошибки компилятора, если возвращаете другой тип.

В минимальных API ASP.NET Core типы Results<> и TypedResults позволяют определить, какие типы объектов могут быть возвращены. Например, здесь может быть возвращён тип результат ApiItem или Unauthorized:
app.MapGet("/item/{id}", 
async Task<Results<Ok<ApiItem>, Unauthorized>>(
[FromRoute]int id,
GroceryListDb db) => {
// …
return TypedResults.Ok(item);
});

Тип Results<> по сути представляет собой дискриминируемое объединение: возвращаемое значение будет одним из (в данном случае) двух типов, и механизм минимальных API может использовать эту информацию для возврата правильного типа.

Упрощённо класс Results с поддержкой 2 типов выглядит так:
public class Results<TResult1, TResult2>
{
private Results(object result)
{
Result = result;
}

public object Result { get; }

public static implicit operator
Results<TResult1, TResult2>(TResult1 result)
=> new(result);

public static implicit operator
Results<TResult1, TResult2>(TResult2 result)
=> new(result);
}

Очевидно, что аналогично выглядят классы для 3, 4 и т.д. типов результата. Используя неявные операторы, класс Results может быть создан из любого типа, поддерживающего преобразования.

Вы можете использовать аналогичный класс в вашем коде, например, для создания метода, который может возвращать int, bool или строку, но ничего больше:
Results<int, bool, string> GetData() 
=> "Hello, world!";

Если тип результата будет другим, IDE (и компилятор) сообщат вам об ошибке. Даже сопоставление по шаблону доступно (для свойства Result):
var data = GetData();
var type = data.Result switch
{
int => "int",
bool => "bool",
string => "string",
_ => throw new NotImplementedException()
};

Console.WriteLine(type);

Недостатком является то, что если вы измените метод GetData(), чтобы он возвращал один из 4 типов (вместо 3), вы не получите ошибку компиляции в приведенном выше выражении switch. Это должно быть одним из преимуществ дискриминируемых объединений (если их когда-либо реализуют в языке): возможность получить инструментальную поддержку для этих случаев, информирующую вас о том, что у вас нет исчерпывающего соответствия для всех типов. Для минимальных API класс Results<> работает отлично. Использование результата является частью механики фреймворка, и в идеале вам никогда не придётся проводить исчерпывающее сравнение самостоятельно.

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

Источник: https://blog.maartenballiauw.be/post/2023/09/18/discriminated-unions-in-csharp.html
👍13
День 1708. #ЧтоНовенького
Использование Сервисов по Ключу в .NET 8
В DI-контейнер .NET 8 наконец-то добавили то, что уже много лет включено в другие контейнеры — использование сервиса по ключу, позволяющее нам внедрять несколько сервисов одного типа, но с разной реализацией. Ключ позволяет определить, какую реализацию использовать.

Допустим, у нас есть интерфейс
public interface IVehicle
{
int Wheels { get; }
}

И две реализации:
public class Car : IVehicle
{
public int NoOfWheels => 4;
}
public class Bike : IVehicle
{
public int NoOfWheels => 2;
}

До .NET 8 можно было добавлять несколько реализаций:
builder.Services.AddScoped<IVehicle, Car>();
builder.Services.AddScoped<IVehicle, Bike>();

Однако, при внедрении использовалась последняя зарегистрированная реализация. Если бы мы хотели получить другие реализации, приходилось внедрять IEnumerable<IVehicle> и выбирать правильную реализацию уже в клиенте. Подробнее тут.

В .NET 8 можно разделить несколько сервисов с помощью ключа. Для IServiceCollection были добавлены методы расширения AddKeyedSingleton, AddKeyedScoped и AddKeyedTransient. Например:
builder.Services.AddKeyedScoped<IVehicle, Car>("car");
builder.Services.AddKeyedScoped<IVehicle, Bike>("bike");

Теперь в минимальных API можно использовать атрибут FromKeyedServices:
app.MapGet("/api/carweels", (
[FromKeyedServices("car")] IVehicle v) =>
{
return Ok(new {
CarWheels = v.NoOfWheels
});
});

Ключ определяет, какую реализацию необходимо использовать.

Аналогично для контроллеров:
[ApiController]
public class VehicleApiController : Controller
{
[HttpGet("carweels")]
public IActionResult CarWheels(
[FromKeyedServices("car")] IVehicle v)
{
return Ok(new {
CarWheels = v.NoOfWheels,
});
}
}

Интерфейс IServiceProvider также получил методы GetRequiredKeyedService (и GetKeyedService, чтобы избежать исключения при null).
var car = _serviceProvider
.GetRequiredKeyedService<IVehicle>("car");

Источник: https://www.roundthecode.com/dotnet-tutorials/keyed-services-dotnet-8-dependency-injection-update
👍40👎2
День 1709. #ЗаметкиНаПолях
HashIDs. Что Это и Зачем Их Использовать?
В REST API для CRUD операций часто используются первичные ключи для доступа к ресурсу. Они часто реализуются как суррогатные ключи с использованием последовательных целочисленных значений. Это самый простой и эффективный способ указать данные/запись для извлечения.

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

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

Одним из решений является использование хэш-значений. Это эффективный подход для генерации неугадываемых непоследовательных идентификаторов, полученных из последовательных идентификаторов (например, целых чисел).
[HttpGet]
public IActionResult Get([FromRoute] string id)
{
int[] intId = _hashids.Decode(id);

if (intId.Length == 0) return NotFound();

var user = _userService.GetUser(intId[0]);

if (user is null) return NotFound();

return Ok(user);
}

Приведенный выше метод API принимает идентификатор аргумента строкового типа. Библиотека HashIds (недавно переименованная в Sqids) предоставляет готовую к использованию реализацию с функцией Decode, которая преобразует строковый идентификатор (предварительно полученный с помощью функции Encode) обратно в исходный числовой идентификатор.

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

Как это работает?
HashIDs использует принцип преобразования десятичных чисел в шестнадцатеричные, однако base62 в качестве алфавита. Кроме того, алфавит перемешивается на основе уникального значения соли, которое может быть установлено пользователем. Это делает каждый алгоритм реализации уникальным и снижает риски безопасности. Идентификаторы нигде не сохраняются, а должны вычисляться каждый раз. Это позволяет нам менять соль без изменения данных. Таким образом для каждого запроса потребуется как минимум два преобразования: декодирование полученной из запроса строки в id и обратное кодирование id в строку при возврате результата клиенту. Скорость кодирования/декодирования измеряется в наносекундах, поэтому за производительность в подавляющем большинстве случаев переживать не придётся.

Итого
Хэши — это эффективный подход для приложений малого и среднего масштаба, которые не требуют более сложных стратегий, вроде идентификаторов Snowflake (объединение различных метаданных, таких как метки времени, идентификаторы сегментов и т. д.). И многие отраслевые решения, такие как GraphQL, используют простые хэши получая преимущества, изложенные выше.

Источник: https://stefandjokic.tech/blog
👍25
День 1710. #ЗаметкиНаПолях #Debugging
Правила Отладки: Заставь Его Упасть. Начало
В своей книге «Совершенный Код» Стив МакКоннелл предлагает научный метод отладки. Он состоит из следующих шагов:
- Стабилизировать ошибку
- Найти источник ошибки
- Исправить дефект
- Проверить исправление
- Найти похожие ошибки

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

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

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

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

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

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

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

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

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

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

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

Источник:
https://dev.to/rajasegar/debugging-rules-make-it-fail-33c0
👍8