Присоединяйтесь к нашему каналу и погрузитесь в мир для C#-разработчикаСотрудничество, реклама: @devmangxМенеджер: @Spiral_YuriРКН: https://clck.ru/3FocB6
Воскресенский совет для .NET-разработчиков: перестаньте использовать DateTime.UtcNow напрямую в коде.Используйте TimeProvider.Тогда приложение будет работать с реальными часами, а в тестах вы сможете подставлять фейковые часы и вручную двигать время вперёд.Небольшая абстракция — зато тесты становятся гораздо удобнее.На примере:public sealed class TrialService(TimeProvider clock){ public bool IsExpired(DateTimeOffset startedAt) => clock.GetUtcNow() >= startedAt.AddDays(14);}В тесте:var clock = new FakeTimeProvider(startedAt);var service = new TrialService(clock);clock.Advance(TimeSpan.FromDays(15));Assert.True(service.IsExpired(startedAt));Никаких задержек, никаких сюрпризов от DateTime.UtcNow. Просто перематываете время вперёд в тестах и проверяете нужный сценарий.https://learn.microsoft.com/ru-ru/dotnet/standard/datetime/timeprovider-overview👉 @KodBlog
Большинство разработчиков изучают PostgreSQL в неправильном порядке.Вот мой личный план изучения, разделённый на 7 уровней.Не начинайте сразу с:* тюнинга производительности* индексов* репликации* партиционированияЧтобы действительно хорошо разбираться в PostgreSQL, нужно понимать, за что отвечает каждый уровень.1️⃣ Основы SQLПрежде чем думать об индексах, производительности или масштабировании, нужно уверенно владеть базовым языком работы с базами данных:* SELECT* WHERE* ORDER BY* GROUP BY* агрегатные функции* базовые JOIN'ы* LIMIT и OFFSET* DISTINCTСлабое знание SQL обычно приводит к неаккуратным запросам, лишней сложности и проблемам с производительностью в будущем.2️⃣ Моделирование данныхКогда вы уже умеете писать запросы, следующий шаг — научиться правильно структурировать данные.Сюда входят:* таблицы* схемы* первичные ключи* внешние ключи* ограничения* связи* нормализация* типы данных* соглашения по именованиюПрежде чем оптимизировать базу данных, нужно её правильно спроектировать.3️⃣ Продвинутые запросыНа этом этапе вы переходите от простых запросов к более выразительным SQL-конструкциям:* JOIN'ы* подзапросы* CTE* оконные функции* условные выражения* продвинутые агрегации* запросы к JSON и JSONB* операции с массивамиЭтот уровень позволяет решать сложные бизнес-задачи непосредственно внутри базы данных.4️⃣ ИндексыСначала вас волнует, работает ли запрос вообще.Потом начинает волновать, работает ли он быстро.Здесь в игру вступают индексы.Нужно понимать:* индексы B-tree* индексы GIN* индексы GiST* частичные индексы* составные индексы* уникальные индексы* обслуживание индексов* когда индексы помогают* когда индексы вредятЦель не в том, чтобы добавлять индексы везде подряд.Цель — понимать, как ваше приложение читает данные.5️⃣ Тюнинг производительностиНа этом уровне вы учитесь анализировать, что база данных делает на самом деле.Важные темы:* EXPLAIN* EXPLAIN ANALYZE* планирование запросов* анализ использования индексов* VACUUM* ANALYZE* раздувание таблиц (table bloa
Сравнение, которое снова разожгло споры о лаконичности языков программирования.На скриншоте один и тот же HTTP-сервер с маршрутом "/" и ответом "Hello world" реализован на Clojure и C#.Вариант на C# использует минимальный API из ASP.NET Core и укладывается в несколько строк.Реализация на Clojure выглядит заметно объёмнее: отдельное описание обработчика, заголовков ответа и запуск Jetty-сервера.👉 @KodBlog
Как работает git?На изображении схематично изображён процесс работы с Git — системой контроля версий.Workspace: Рабочее пространство, где находятся файлы проекта (например, .git, src, index.html). 🟢Команда git add перемещает изменения в Stage (область индексации). 🟢Команда git reset отменяет индексацию изменений.Stage: Область индексации, где изменения подготавливаются для фиксации. 🟢 Команда git commit сохраняет изменения в локальном репозитории.Local Repository: Локальный репозиторий, где хранятся зафиксированные изменения. 🟢 Команда git push отправляет изменения в удалённый репозиторий.Remote Repository: Удалённый репозиторий, например, на платформах GitLab, GitHub или Bitbucket. 🟢 Команда git fetch извлекает изменения с удалённого репозитория. 🟢 Команда git pull объединяет изменения удалённого и локального репозиториев (эквивалентно git fetch + git merge).В нижней части схемы представлена последовательность действий при выполнении команды git pull. 😮Эта схема полезна для понимания основных этапов работы с Git.👉 @KodBlog
Перестаньте использовать исключения для управления логикой приложенияВот почему 👇Большинство разработчиков используют исключения каждый день.Но есть проблема: исключения подходят далеко не для всех сценариев.Работая архитектором ПО и .NET-разработчиком, я пришёл к простому выводу:Использование исключений для управления потоком выполнения делает код сложнее для чтения, тестирования и сопровождения.👉 Исключения нужны для обработки действительно нештатных ситуаций.Но ими не стоит заменять обычную бизнес-логику и ожидаемые условия.Основные недостатки такого подхода:• Непредсказуемость — по сигнатуре метода не всегда понятно, какие исключения он может выбросить• Снижение читаемости — try/catch ломает линейный поток чтения кода• Вложенность — отладка и навигация по коду становятся менее удобными• Потери производительности — обработка исключений обходится дороже обычных проверок (даже с улучшениями в .NET 9)Что использовать вместо этого?✅ Result PatternВместо выбрасывания исключений метод возвращает объект Result.Так успех и ошибка становятся явной частью контракта метода.📌 Обычно объект Result содержит:1️⃣ IsSuccess / IsError — успешно ли выполнена операция2️⃣ Value — результат выполнения при успехе3️⃣ Error — информация об ошибке при неудачеПлюсы такого подхода:↳ Более предсказуемые API↳ Более чистый поток выполнения↳ Проще писать unit-тесты↳ Лучше производительностьДля этого паттерна уже есть готовые библиотеки:• FluentResults• CSharpFunctionalExtensions• Ardalis.Result• ErrorOrНо на практике сторонние пакеты вовсе не обязательны.👉 @KodBlog
Практическое применение архитектуры Vertical Slice в ASP.NET Corehttps://www.telerik.com/blogs/practicing-vertical-slice-architecture-aspnet-coreАвтор: Assis Zang#aspnetcore👉 @KodBlog
Вопрос с C#-собеседования уровня Middle/Senior.Многие разработчики на нём ошибаются Посмотрите на код на первом слайде.Что там видно:ProductStock не равен null;мы проходимся по элементам в цикле;внутри цикла используется yield return.Так почему всё равно появляется warning или error?Подумайте немного... А потом откройте второй слайд и проверьте свой ответ.Ответ 👇yield return не останавливает выполнение цикла.В отличие от обычного return, который сразу завершает метод, yield return лишь приостанавливает выполнение и возвращает очередной элемент последовательности.После этого выполнение продолжается с того места, где оно было остановлено.Поэтому, если нужно пропустить текущую итерацию, следует использовать continue.Иначе код после yield return всё равно выполнится, что может привести к неожиданному поведению или исключениям.Это одна из тех особенностей C#, о которых часто забывают даже опытные разработчики.👉 @KodBlog
Создаём Базовый Компонент для Всех Компонентов в BlazorПри разработке Blazor-приложения может понадобиться пользовательский базовый компонент для всех остальных компонентов. Это полезно для совместного использования общих функций, таких как токены отмены, логирование или управление состоянием, во всех компонентах. Вместо добавления @inherits YourBaseComponent в каждый файл Razor, вы можете использовать файл _Imports.razor для глобальной установки базового компонента.Создадим файл _Imports.razor в папке, к компонентам которой нужно применить базовый компонент. Все файлы Razor в этой папке и её подпапках будут наследовать указанный базовый компонент.@inherits YourNamespace.CustomComponentBaseФайл _Imports.razor обрабатывается перед любым компонентом Razor в том же каталоге или его подкаталогах. Все компоненты затем автоматически наследуют от CustomComponentBase без необходимости объявлять @inherits в каждом файле.Пример: CustomComponentBase с CancellationTokenВот пример базового компонента, который предоставляет CancellationToken всем производным компонентам. Это полезно для отмены асинхронных операций при удалении компонента:@* CustomComponentBase.razor (Razor) *@@implements IDisposable@code { private readonly CancellationTokenSource _cts = new CancellationTokenSource(); public CancellationToken CancellationToken => _cts.Token; public void Dispose() { _cts.Cancel(); _cts.Dispose(); }}Теперь все наши компоненты могут получить доступ к свойству CancellationToken без какой-либо дополнительной настройки:@* MyComponent.razor (Razor) *@@* Не нужно использовать @inherits, т.к. _Imports.razor импортируется автоматически *@<h3>Мой компонент</h3>@code { protected override async Task OnInitializedAsync() { // Используем CancellationToken из базового компонента await LoadDataAsync(CancellationToken); } private async Task LoadDataAsync(CancellationToken ct) { // … асинхронный код … await Task.Delay(1000, ct); }}Переопределение базового компонент
Вышел roadmap для SqlClient https://github.com/dotnet/SqlClient/blob/main/roadmap.mdСтоит посмотреть, если тебе интересны:- особенности работы пула соединений (connection pooling)- сценарии аутентификации (Entra ID, MSI и другие)- производительность и надёжность под высокой нагрузкойНа этом этапе обратная связь от сообщества ещё может повлиять на архитектурные и технические решения.#dotnet #sqlserver👉 @KodBlog
Перед тем как читать новый проект, я первым делом иду не в код, а в Git.Пара команд за 2 минуты часто рассказывает о кодовой базе больше, чем час листания файлов.Что меняют чаще всегоgit log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20Показывает 20 файлов, которые меняли чаще всего за последний год. Верхние строчки списка обычно быстро находят тот самый файл, к которому все боятся прикасаться.Кто реально поддерживает проектgit shortlog -sn --no-mergesСразу видно распределение коммитов между разработчиками и потенциальный автобусный фактор.Где чаще всего чинят багиgit log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20Если файл часто появляется и здесь, и в списке самых изменяемых файлов — это один из главных источников технического долга.Проект набирает темп или затухаетgit log --format='%ad' --date=format:'%Y-%m' | sort | uniq -cКоличество коммитов по месяцам помогает быстро понять динамику разработки.Как часто приходится тушить пожарыgit log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'Откаты, хотфиксы и экстренные исправления многое говорят о качестве релизного процесса.Эти команды не заменят аудит, но помогают понять, какие части системы стоит изучать в первую очередь, а какие проблемы уже давно кричат о себе через историю Git.👉 @KodBlog
Завязал с Clean Architecture.Перешёл на Vertical Slices. И назад пока не тянет ❌ Чтобы добавить один endpoint, приходится лазить по 4 проектам и 5 слоям❌ Controllers, Services, Repositories, DTOs — папок больше, чем логики❌ Любая мелкая правка расползается по всему решению❌ AI-агенты сжигают токены, пока добираются до нужной фичи✅ Vertical Slice Architecture.Весь код фичи лежит в одной папке.Что это даёт:1. Фича = папкаДля Create Shipment:→ CreateShipment.Endpoint.cs→ CreateShipment.Handler.cs→ CreateShipment.Mapping.cs→ CreateShipment.Validators.csНужно изменить фичу — открываешь одну папку и работаешь.2. В 2026 маленькие классы реально решаютПро это почему-то редко говорят в архитектурных статьях.↳ Небольшие классы экономят токены при работе с AI↳ AI-агенты гораздо быстрее понимают код, когда всё рядом↳ Новые разработчики вникают за несколько дней, а не за пару недель3. Clean Architecture никто не отменялЯ не говорю, что она плохая.Просто подключать её стоит тогда, когда проект реально дорос до этого: сложная доменная модель, несколько инфраструктурных адаптеров или жёсткие требования к изоляции слоёв.4. Между слайсами всё равно нужны границыVSA — не про «пусть все вызывают всех».В модульном монолите я обычно выделяю отдельный PublicApi-проект для каждого модуля:→ Снаружи видны только интерфейсы и DTO→ Реализация остаётся скрытой через internal sealed→ Если модуль однажды переедет в отдельный сервис, контракт менять не придётся5. Побочные эффекты через событияЕсли Create Shipment должен обновить остатки и уведомить перевозчика, handler не дёргает их напрямую.👉 @KodBlog
Большинство разработчиков неправильно понимают DRY.Они думают, что DRY про устранение дублирующегося кода.На самом деле DRY про дублирование знаний.Повторяющийся код часто оказывается лишь симптомом. Настоящая проблема начинается тогда, когда одно и то же бизнес-правило разбросано по всей системе.Если одно правило живёт в пяти местах, любое изменение становится рискованным. Исправили одно место, забыли про остальные четыре.Где команды обычно ошибаются:❌Слишком рано выносят код в общие хелперы❌Создают универсальные утилиты, которые потом сложно поддерживать❌Связывают между собой несвязанные части системы ради борьбы с дублированиемВ итоге код становится сложнее.Иногда дублирование вполне нормально.Если два участка кода меняются по разным причинам, лучше оставить их отдельно. Часто это даже лучше соответствует принципу единственной ответственности.DRY нарушается тогда, когда одна и та же причина для изменения присутствует в нескольких местах.Примеры реальных нарушений DRY:→ Правила валидации, скопированные между контроллерами→ Логика ценообразования, продублированная в сервисах и фоновых задачах→ Проверки авторизации, разбросанные по разным слоям приложенияА вот такие случаи обычно не проблема:→ Похожие циклы, выполняющие разные задачи→ Повторяющиеся маппинги рядом с местом использования→ Небольшие дублирующиеся SQL или EF Core запросыНеправильное понимание DRY часто приводит к «энтерпрайзному» коду:* слишком много абстракций* сложно понять, где находится реальная логика* простые изменения требуют пройти через несколько слоёв обёртокЕсли один и тот же EF Core запрос из пары строк встречается в двух классах, это ещё не повод тащить в проект Repository.Лично я придерживаюсь простого правила:Если мне приходится копировать одну и ту же логику в третий раз, тогда стоит задуматься о выносе в отдельное место. До этого момента никакого преждевременного рефакторинга.Когда в следующий раз увидите дублирование, задайте себе вопрос:«Это действительно одно и то же знание или прост
Фича дня в EF Core: алгоритм Hi/Lo.С его помощью можно генерировать идентификаторы на стороне приложения и реже обращаться к базе данных.Как это работает:Вместо запроса нового ID для каждой записи приложение получает сразу диапазон идентификаторов.База хранит значение hi_value, которое указывает на верхнюю границу текущего диапазона.При запросе нового диапазона используется следующий блок ID, начиная с hi_value + 1.Такой подход уменьшает количество запросов к базе и снижает конкуренцию за блокировки.Есть и компромисс. Если приложение завершится до того, как использует весь диапазон, в последовательности ID появятся пропуски. На практике это редко становится проблемой.В EF Core достаточно вызвать метод UseHiLo(). После этого EF Core создаст в базе последовательность и будет получать диапазоны идентификаторов автоматически. Дальше первичные ключи могут назначаться прямо на стороне клиента.Особенно удобно при сохранении связанных сущностей, например родительских и дочерних записей.👉 @KodBlog
Если вы программируете на Windows, стоит обратить внимание на Windows Developer Config от Microsoft.Инструмент позволяет подготовить машину для разработки одной командой.Что умеет:✓ Настраивает WSL и Ubuntu✓ Устанавливает Windows Terminal✓ Ставит Node.js, Python, Rust, Go, Java, .NET, PHP и другие инструменты✓ Автоматизирует настройку рабочего окруженияПроект полностью открытый и доступен на GitHub.Удобный способ поднять новое окружение без ручной установки десятков компонентов.👉 @KodBlog
Ваш lock в C# работает идеально.Пока вы не запускаете вторую копию приложения.На одном сервере всё красиво: один поток зашёл в критическую секцию, второй ждёт.Но как только приложение работает в нескольких инстансах, у каждого инстанса своя память.И свои локи.Они не видят друг друга.В итоге два воркера могут одновременно:→ запустить одну и ту же scheduled task→ обновить один и тот же cache entry→ обработать один shared resource→ сгенерировать один и тот же отчёт→ одновременно записать данныеА дальше начинается веселье: дубли, лишняя нагрузка, гонки и иногда сломанные данные.Для этого и нужны distributed locks.Они дают нескольким инстансам одно общее правило:только один из вас делает эту работу прямо сейчас.Не надо тащить это везде. Но если job должна выполниться один раз, или shared work не должен пересекаться, distributed lock может сэкономить много боли.Если у вас уже есть PostgreSQL, можно начать с advisory locks.А если нужен более чистый вариант поверх Postgres, Redis или SQL Server, можно взять готовую distributed locking library.На одном сервере приложение может выглядеть нормально.Настоящая проверка начинается со второго инстанса.👉 @KodBlog
Ты не делаешь integration tests, если тестируешь через in-memory database.В лучшем случае это раздутый unit test.Я видел кучу примеров с EF Core in-memory provider.Но это не integration test, потому что настоящей базы данных там нет.Хуже того, такие тесты не поймают баги в LINQ или SQL.Лучший подход:- использовать настоящую базу данных или Docker-контейнер- подключаться к этой базе из тестов- писать нормальные integration tests, от которых есть пользаЕсли хочешь использовать Docker, посмотри в сторону Testcontainers.Он позволяет поднимать одноразовые контейнеры прямо из тестов.А какие инструменты или подходы вы используете для integration testing?👉 @KodBlog
Вот 5 недооценённых методов LINQ, о которых стоит знать:SequenceEqualAggregateGroupJoinToLookupIntersectВ LINQ есть немало полезных методов, которые помогают писать более чистый и эффективный код.Какой метод LINQ, по твоему мнению, получает незаслуженно мало внимания?👉 @KodBlog
Новичкам кажется, что хороший программист пишет быстро.Быстро печатает. Быстро решает задачи. Быстро отвечает на вопросы.Потом приходит опыт, и оказывается, что скорость переоценена.Можно решить 500 задач на LeetCode и всё ещё теряться при первом реальном продакшен-инциденте. Можно знать пять языков и не понимать, почему система разваливается под нагрузкой. Можно наизусть помнить сложности алгоритмов и при этом проектировать хрупкую архитектуру.Потому что программирование почти никогда не упирается в синтаксис.Настоящие вопросы выглядят иначе:• Почему это сломалось?• Почему это решение не масштабируется?• Почему этот крайний случай никто не учёл?• Почему архитектура ощущается неправильной?Хорошие разработчики отличаются не скоростью набора текста.Они умеют сидеть с проблемой дольше остальных.Они могут часами разбирать логическую ошибку. Переписывать решение несколько раз подряд. Удалять сотни строк кода и радоваться этому. Отказываться от «рабочего» решения в пользу правильного.Со стороны это выглядит скучно.На GitHub никто не выкладывает скриншот с подписью:«Сегодня четыре часа искал одну гонку данных».Никто не пишет:«Шесть раз переделал архитектуру, прежде чем она начала нормально масштабироваться».Хотя именно там и происходит рост.Языки меняются.Фреймворки меняются.Тренды меняются.Способность ясно мыслить остаётся.Поэтому вопрос не в том, хорошо ли вы знаете программирование.Вопрос в другом:Можете ли вы разобраться в хаосе?Можете ли вы построить систему из ничего?Можете ли вы продолжать искать решение, когда ничего не работает?Потому что программирование давно перестало быть соревнованием по скорости.Это дисциплина мышления.👉 @KodBlog