DotNet developer blog C# | .Net
802 subscribers
85 photos
36 links
Blog about C# .NET development. Almost all posts are published in english on my linkedin page https://www.linkedin.com/in/yegor-sychev/
Download Telegram
Кастомизация Visual Studio с помощью расширений

В своих проектах я стараюсь использовать sealed классы. Во-первых, я стремлюсь не применять наследование классов и sealed классы дополнительно указывают на это. Во-вторых, sealed классы могут немного повысить производительность.

Для новых классов я создал шаблон (class template) для Visual Studio. Возможно, об этом я напишу в одном из следующих постов. Для уже существующего кода мне требовалось решение для подсветки и автоматической замены на sealed. Я не нашёл способа настроить .editorconfig для этой задачи, поэтому решил создать собственный плагин для Visual Studio. К тому же у меня не было подобного опыта.

На создание плагина у меня ушло несколько часов — я ориентировался на статью How to write a Roslyn analyzer, а также использовал GitHub Copilot. Затем около часа потребовалось на регистрацию и публикацию расширения в маркетплейсе. О том, как это сделать, можно прочесть в статье Walkthrough: Publishing a Visual Studio Extension.

В целом, создавать собственные расширения для Visual Studio оказалось не так сложно. Исходный код моего расширения можно найти в репозитории GitHub. У меня есть ещё много идей для будущих расширений. Для более глубокого погружения рекомендую книгу Visual Studio Extensibility Development автора Rishabh Verma.
Моя одна из любимых тем — это создание надёжных и отказоустойчивых приложений, поэтому я не мог не поделиться этим видео Антология технологий Яндекс Такси. Надёжность сервиса.

Основные моменты для разработчиков:

Изоляция точек отказа (1:53) — по этой теме я в ближайшее время напишу отдельный пост. Думаю, я ещё неоднократно буду освещать этот вопрос.
Фолбэк (13:15) и Деградация (14:14) — позволяет отключать дополнительные сервисы, чтобы снизить нагрузку на систему.
Дополнительная нагрузка "виртуальными заказами" прямо на проде (7:25) — казалось бы, мы тестируем систему в реальном окружении и можем её «положить», но, в отличие от реальных заказов, виртуальные заказы можно быстро отключить для восстановления нормальной работы.

Очень крутая практика — симуляция инцидентов (9:44) и тренировка навыков их решения. Она также позволяет понять, насколько хорошо у нас налажены мониторинг и логирование. В идеале логи и графики должны быть такими, чтобы дежурный специалист из отдела мониторинга, который никогда не видел наш код, смог понять логи и при необходимости отключить сломанный сервис, а также предпринять другие меры.
👍2
Вот и завершился мой отпуск... Это была моя вторая поездка в Майами (впервые я побывал здесь в 2022 году) и пока для меня это самое комфортное место для отдыха. В этот раз я впервые посетил Ки-Уэст — самую южную точку США. Остался под большим впечатлением и обязательно сюда вернусь.
🔥4👍1
Как и обещал в предыдущем посте, продолжаем обсуждать отказоустойчивость и надежность приложения.

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

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

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

Допустим, в нашем бэкенде есть endpoint возвращающий список рекомендуемых товаров фронтенду по ID пользователя. Это не ключевая функциональность. Для упрощения примера, в бизнес-логике мы просто вызываем внешний REST-сервис, который и становится точкой отказа.

Если не предпринять никаких мер, то при сбое внешнего сервиса или при отправке им некорректных данных могут возникнуть неприятные сценарии для пользователя, например «белый экран» из-за сломанного JS и невозможность оформить заказ.

Чтобы избежать таких ситуаций, делаем следующее:

1. Оборачиваем вызов сервиса в try-catch.

2. Устанавливаем минимальный таймаут. Из-за деградации второстепенного сервиса пользователь не должен долго ждать загрузки ключевого функционала.

3. Делаем возможность отключения вызова сервиса «на горячую» через appsettings.json с помощью IOptionsMonitor.
Например, если мы понимаем, что сервис стал долго отвечать или отправлять некорректные данные, мы можем вручную отключить его вызов, не дожидаясь срабатывания таймаута. Здесь можно применить шаблон «размыкания цепи» (circuit breaker), например, с помощью библиотеки Polly, но возможность ручного отключения должна быть сохранена.

Таким образом, при выбросе исключения, срабатывании таймаута или ручном отключении вызова мы возвращаем пустой список рекомендаций вместо ошибки.
На фронте мы ожидаем такой сценарий и просто скрываем блок «Рекомендации».
👍5🔥4
Упрощаем написание юнит-тестов с Visual Studio и GitHub Copilot

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

Давайте попробуем использовать команды Visual Studio для генерации проектов xUnit с нужной нам структурой директорий и файлов.

Команда Create Unit Tests по умолчанию работает с MSTest, но так как я использую xUnit, мне необходимо скачать расширение для Visual Studio — xUnit.net.TestGenerator2022.

Чтобы сгенерировать заглушку для юнит-теста, нам нужно:

1. Выделить метод (в моём случае GetProductById класса ProductService).

2. Кликнуть правой кнопкой мыши и выбрать команду Create Unit Tests.

3. В появившемся диалоговом окне в выпадающем списке Test Framework выбрать xUnit (он появится только в том случае, если установлено соответствующее расширение).

Если у нас ещё нет проекта с тестами, оставляем New Test Project или же выбираем существующий.
При необходимости задаём шаблон наименования пространства имён (namespace), класса и метода, после чего нажимаем OK.

4. После нескольких секунд Visual Studio подтянет необходимые пакеты и мы получим сгенерированный xUnit-проект с нужными пакетами, структурой, ссылкой на тестируемый проект, а также (в моём случае) с классом ProductServiceTests и методом-заглушкой.

Теперь нам нужно сгенерировать сами тесты:

Снова выделяем тестируемый метод.

Кликаем правой кнопкой мыши — Ask Copilot.

Вводим простой prompt, например: generate unit tests using xunit, nsubstitute and insert the result into ProductServiceTests
(Здесь надо через # выбрать наш класс с тестами. Для быстрого поиска желательно, чтобы ProductServiceTests был открыт в отдельной вкладке.)

Выполняем prompt, нажимаем Accept, и Copilot генерирует код тестов. После этого остаётся установить необходимые пакеты — в моём случае это NSubstitute.

Когда пакеты будут установлены, можно запускать тесты. У меня они выполнились с первого раза: Copilot отлично знает, как работать с NSubstitute, а все зависимости у меня были оформлены через интерфейсы.

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

Пост на английском в Linkedin
👍10🔥2💯1
Как мы можем помочь GitHub Copilot генерировать качественные юнит тесты

В предыдущем посте я писал о том, как мы можем генерировать структуру тестовых проектов с помощью IDE, а также сами тесты — с помощью Copilot.

Качество генерации юнит-тестов напрямую зависит от того, насколько наш код и проект готовы к тестированию и насколько хорошо их понимает GitHub Copilot.
К счастью, рекомендации по написанию кода для Copilot не противоречат принципам «чистого кода».

Контекст

Основное преимущество Copilot перед ChatGPT — это доступ к большему контексту. Copilot ориентируется на открытые вкладки с файлами в IDE.
Таким образом, первая рекомендация — открывать вкладки, соответствующие запросу (промпту), а также ссылаться на файлы в самом промпте через символ #.
На самом деле, новые версии GitHub Copilot уже могут «подтягивать» весь проект в свой контекст. Однако это зависит от размера проекта. Поэтому вторая рекомендация — использовать хорошо структурированные, небольшие проекты. Copilot будет работать эффективнее с отдельным микросервисом, чем с огромным монолитом.

Мы разобрались с объёмом контекста, теперь перейдём к вопросу качества.

Сложность зависимостей

• Используйте зависимости через интерфейсы. Так Copilot сможет без проблем их «замокать» при помощи выбранной библиотеки, что упростит написание юнит-тестов.
• Старайтесь избегать избыточного использования паттернов и наследования — это может запутать не только Copilot, но и других разработчиков, которые впервые сталкиваются с проектом. (Об этом ещё напишу отдельный пост)

Ясность кода

• При необходимости составьте ADR (Architecture Decision Records) и включайте их в контекст, чтобы Copilot «понимал» ключевые архитектурные решения.
• Используйте описательные, недвусмысленные имена для переменных, методов и классов.
• Дополняйте код комментариями там, где это нужно.
• В основном Copilot верно определяет тип переменных, однако если возникают ошибки, лучше вместо var явно указать тип.
• Удалите код который сейчас не используется, если считаете что он может понадобиться в будущем, вы сможете восстановить его используя GIT

Следуя этим рекомендациям, вы сможете помочь GitHub Copilot лучше понимать ваш код и генерировать более качественные юнит-тесты.

Этот пост на Medium на английском языке
👍4🔥2
Как замокать HttpClient

При написании юнит-тестов на сервисы, где есть вызов внешнего API через HttpClient, в нашем упрощенном примере это ArticleService, нам необходимо подменить реальные вызовы API на моки.

При этом просто замокать интерфейс IHttpClientFactory, используя, например, NSubstitute, будет недостаточно. Нам необходимо создать класс MockHttpMessageHandler, который будет наследоваться от HttpMessageHandler.

MockHttpMessageHandler в конструкторе принимает HttpResponseMessage (сообщение, которое мы ожидаем получить от нашего мока) и просто возвращает его в переопределенном методе SendAsync.

Далее, при создании HttpClient, передаем в конструктор экземпляр класса MockHttpMessageHandler и делаем этот HttpClient возвращаемым значением метода CreateClient экземпляра mockHttpClientFactory.

В результате мы получили достаточно простой способ мокать HttpClient без внешних зависимостей с возможностью гибко настраивать ожидаемые http ответы.

Пост на Medium
👍11