Как эффективно внедрить практику чистого кода в команде?
Несмотря на усилия по созданию codestyle конвенций часто возникают трудности с их практическим применением в команде.
Чтобы договоренности работали на практике, необходимо создать единое техническое пространство:
— Единые настройки профиля Code Cleanup: В Visual Studio или Rider важно использовать единые настройки профиля Code Cleanup, чтобы все участники команды могли автоматически применять одни и те же стандарты по форматированию и рефакторингу кода.
— Auto Cleanup в IDE: Убедитесь, что в вашей IDE настроен автоматический запуск Cleanup при сохранении файлов. Это поможет соблюдать единый стиль кодирования в реальном времени.
— Использование унифицированных шаблонов классов и интерфейсов: Используйте стандартизированные шаблоны для классов и интерфейсов. В предыдущем посте я рассказывал, как создать шаблон класса для Visual Studio.
— EditorConfig: Подготовьте и настройте файл
— Отсутствие Warnings: Договоритесь, что в проекте не допускаются warnings, что позволит поддерживать высокое качество кода с самого начала.
— Статические анализаторы: После реализации базовых настроек интегрируйте инструменты статического анализа кода, такие как
— Настройка подавлений (Suppression): Определите, какие правила анализаторов можно подавлять для упрощения работы, и добавьте соответствующие исключения в
Важно понимать, что эти техники не заменяют соглашения о коде, принятые в вашей команде. В самом начале этого соглашения четко определите его цели - улучшить качество кода, снизить риск ошибок и упростить процесс код-ревью.
Внедряя эти практики, вы не только повысите качество своего продукта, но и разовьете культуру ответственности за код, что положительно скажется на профессиональном росте всей команды.
Пост в LinkedIn, Medium
Несмотря на усилия по созданию codestyle конвенций часто возникают трудности с их практическим применением в команде.
Чтобы договоренности работали на практике, необходимо создать единое техническое пространство:
— Единые настройки профиля Code Cleanup: В Visual Studio или Rider важно использовать единые настройки профиля Code Cleanup, чтобы все участники команды могли автоматически применять одни и те же стандарты по форматированию и рефакторингу кода.
— Auto Cleanup в IDE: Убедитесь, что в вашей IDE настроен автоматический запуск Cleanup при сохранении файлов. Это поможет соблюдать единый стиль кодирования в реальном времени.
— Использование унифицированных шаблонов классов и интерфейсов: Используйте стандартизированные шаблоны для классов и интерфейсов. В предыдущем посте я рассказывал, как создать шаблон класса для Visual Studio.
— EditorConfig: Подготовьте и настройте файл
.editorconfig, который будет задавать базовые правила форматирования кода для всей команды.— Отсутствие Warnings: Договоритесь, что в проекте не допускаются warnings, что позволит поддерживать высокое качество кода с самого начала.
— Статические анализаторы: После реализации базовых настроек интегрируйте инструменты статического анализа кода, такие как
Roslynator, Meziantou, SonarQube. Они помогут обнаруживать проблемы до этапа ревью.— Настройка подавлений (Suppression): Определите, какие правила анализаторов можно подавлять для упрощения работы, и добавьте соответствующие исключения в
.editorconfig.Важно понимать, что эти техники не заменяют соглашения о коде, принятые в вашей команде. В самом начале этого соглашения четко определите его цели - улучшить качество кода, снизить риск ошибок и упростить процесс код-ревью.
Внедряя эти практики, вы не только повысите качество своего продукта, но и разовьете культуру ответственности за код, что положительно скажется на профессиональном росте всей команды.
Пост в LinkedIn, Medium
👍11
Удобный способ сгенерировать файл editorconfig по вашим требованиям
В предыдущем посте я рассказывал, как создать единое техническое пространство для соблюдения codestyle-конвенций внутри команды.
Это пространство включает настройки IDE (например, настройки правил code cleanup, автоматическую очистку кода при сохранении файлов), набор статических анализаторов и файл
Сегодня я хочу поделиться удобным способом генерации файла
Для начала откройте диалоговое окно Options через меню
В открывшемся окне вы увидите список правил стиля кода с указанием их предпочтений (Preference) и уровня важности (Severity), а также визуальное отображение примеров настроек для каждого правила. Именно благодаря такому наглядному представлению я предпочитаю этот метод генерации
После выбора необходимых предпочтений для правил, установите для каждого из них соответствующий уровень важности (Severity).
Например, в слайде, где показаны настройки для
После настройки правил достаточно нажать Generate
Пост на LinkedIn
В предыдущем посте я рассказывал, как создать единое техническое пространство для соблюдения codestyle-конвенций внутри команды.
Это пространство включает настройки IDE (например, настройки правил code cleanup, автоматическую очистку кода при сохранении файлов), набор статических анализаторов и файл
editorconfig, по которому работает функция очистки IDE и в котором задаются исключения для правил статических анализаторов.Сегодня я хочу поделиться удобным способом генерации файла
editorconfig для C# в Visual Studio 2022.Для начала откройте диалоговое окно Options через меню
Tools, затем выберите Text-Editor–C#–Code Style–General. В открывшемся окне вы увидите список правил стиля кода с указанием их предпочтений (Preference) и уровня важности (Severity), а также визуальное отображение примеров настроек для каждого правила. Именно благодаря такому наглядному представлению я предпочитаю этот метод генерации
editorconfig.После выбора необходимых предпочтений для правил, установите для каждого из них соответствующий уровень важности (Severity).
Например, в слайде, где показаны настройки для
Code block preferences, для параметра Preference braces выбрано значение Yes с уровнем важности Warning. Это означает, что если в проекте будет использована конструкция if без фигурных скобок, IDE выдаст предупреждение.После настройки правил достаточно нажать Generate
.editorconfig file from settings и сохранить файл в корне проекта.Пост на LinkedIn
👍8
Спустя два месяца мой пул-реквест наконец принят и замержен в документацию .NET. Надеюсь, что уже в ближайшее время на learn.microsoft.com появится мой небольшой гайд по генерации юнит-тестов с использованием Copilot и Visual Studio 2022. А сейчас в разработке находится ещё один гайд
🔥35
Как навести порядок в зависимостях пакетов в проектах?
Часто в legacy-проектах возникает хаос с зависимостями: неиспользуемые библиотеки, пакеты, подключенные не к тем проектам, или использование транзитивных зависимостей вместо прямых.
Visual Studio позволяет быстро удалить неиспользуемые пакеты.
Для этого:
— Кликните правой кнопкой мыши по проекту.
— Выберите пункт
Однако после этой операции могут возникнуть ошибки. Почему?
Первая причина
Пакет подключен к одному проекту, но используется в другом (зависимом).
Например:
— Проекты
— Пакет prometheus-net.AspNetCore ошибочно подключен к
— После команды Remove Unused References пакет prometheus-net.AspNetCore удаляется из
— Решение: доустановите нужный пакет непосредственно в
Вторая причина
Используются косвенные зависимости.
Например:
— В проекте зарегистрирован пакет prometheus-net.AspNetCore, хотя прямое использование Prometheus отсутствует.
— Однако класс
— После удаления неиспользуемых ссылок появятся ошибки, так как
— Решение — подключение подходящего целевого пакета, например, Microsoft.AspNetCore.Http.Abstractions.
Таким образом, правильный подход к управлению зависимостями не только очищает проект от лишних библиотек, но и делает кодовую базу более прозрачной и управляемой.
Пост на LinkedIn, Medium
Часто в legacy-проектах возникает хаос с зависимостями: неиспользуемые библиотеки, пакеты, подключенные не к тем проектам, или использование транзитивных зависимостей вместо прямых.
Visual Studio позволяет быстро удалить неиспользуемые пакеты.
Для этого:
— Кликните правой кнопкой мыши по проекту.
— Выберите пункт
Remove Unused References...Однако после этой операции могут возникнуть ошибки. Почему?
Первая причина
Пакет подключен к одному проекту, но используется в другом (зависимом).
Например:
— Проекты
API и Core, где API зависит от Core.— Пакет prometheus-net.AspNetCore ошибочно подключен к
Core, хотя используется только в API.— После команды Remove Unused References пакет prometheus-net.AspNetCore удаляется из
Core, что вызовет ошибки в проекте API.— Решение: доустановите нужный пакет непосредственно в
API (например, через комбинацию Alt+Enter → Install package).Вторая причина
Используются косвенные зависимости.
Например:
— В проекте зарегистрирован пакет prometheus-net.AspNetCore, хотя прямое использование Prometheus отсутствует.
— Однако класс
HttpRequest использует зависимости этого пакета.— После удаления неиспользуемых ссылок появятся ошибки, так как
HttpRequest потеряет необходимые зависимости.— Решение — подключение подходящего целевого пакета, например, Microsoft.AspNetCore.Http.Abstractions.
Таким образом, правильный подход к управлению зависимостями не только очищает проект от лишних библиотек, но и делает кодовую базу более прозрачной и управляемой.
Пост на LinkedIn, Medium
👍8🔥1
Насыщенная была неделя. Мои гайды наконец появились на официальном сайте Learn Microsoft. Опубликовался гайд по мутационному тестированию (https://learn.microsoft.com/en-us/dotnet/core/testing/mutation-testing) и руководство по генерации тестов с помощью GitHub Copilot в Visual Studio 2022 (https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-copilot).
Встретился с Данилом @danny_v3 автор канала "Логгирую разработку". На его канале можно найти записи реальных собеседований. Это его первый визит в Алматы, и я не мог упустить шанс показать горы.
Ребята с подкаста RadioDotNet порадовали меня классным мерчем (см. фото) – такие подарки отправляются тем, кто поддерживает подкаст на Boosty. Очень рекомендую послушать их передачи, особенно плейлист RadioDotNet, обсуждение книг "System Design Interview" и "Architecture for Containerized .NET Applications".
Встретился с Данилом @danny_v3 автор канала "Логгирую разработку". На его канале можно найти записи реальных собеседований. Это его первый визит в Алматы, и я не мог упустить шанс показать горы.
Ребята с подкаста RadioDotNet порадовали меня классным мерчем (см. фото) – такие подарки отправляются тем, кто поддерживает подкаст на Boosty. Очень рекомендую послушать их передачи, особенно плейлист RadioDotNet, обсуждение книг "System Design Interview" и "Architecture for Containerized .NET Applications".
🔥12👍5
Использование различных политик CORS для конечных точек в ASP.NET
Иногда возникает ситуация, когда отдельным конечным точкам (endpoint) требуется применять отличную от основной политику CORS.
Представим, что большинство конечных точек вашего API взаимодействует только с фронтендом, размещенным на домене sample com, и вы настроили общую политику CORS с origin sample com (например, назвав ее
Однако одному или нескольким методам нужно разрешить запросы с другого домена, скажем, admin-sample com.
В таком случае удобно создать отдельную политику, например
Затем вы можете точечно применять созданную политику к методам:
— Для контроллеров используйте атрибут
— Для Minimal API используйте метод
Если же для конкретного метода требуется вовсе отключить CORS, используйте атрибут
Это позволяет эффективно и гибко управлять CORS в вашем приложении.
Пост на LinkedIn, Medium
Иногда возникает ситуация, когда отдельным конечным точкам (endpoint) требуется применять отличную от основной политику CORS.
Представим, что большинство конечных точек вашего API взаимодействует только с фронтендом, размещенным на домене sample com, и вы настроили общую политику CORS с origin sample com (например, назвав ее
AllowSampleCom).Однако одному или нескольким методам нужно разрешить запросы с другого домена, скажем, admin-sample com.
В таком случае удобно создать отдельную политику, например
AllowAdminSampleCom, специально для этого домена.Затем вы можете точечно применять созданную политику к методам:
— Для контроллеров используйте атрибут
[EnableCors("AllowAdminSampleCom")]— Для Minimal API используйте метод
app.MapGet("/endpoint", handler).RequireCors("AllowAdminSampleCom")Если же для конкретного метода требуется вовсе отключить CORS, используйте атрибут
[DisableCors]Это позволяет эффективно и гибко управлять CORS в вашем приложении.
Пост на LinkedIn, Medium
🔥12👍5
Используйте панель Toolbox в Visual Studio 2022
Панель Toolbox хорошо известна разработчикам, которые работали с WinForms - в ней располагались готовые компоненты.
В разработке современных приложений мы также можем использовать эту панель для сохранения собственных сниппетов кода, которые часто применяются в различных проектах.
Чтобы открыть панель, перейдите в
Чтобы добавить сниппет, достаточно выделить нужные строки кода и перенести их в панель с помощью drag-n-drop. Затем, для удобства, можно переименовать сниппет.
После этого вы можете перетаскивать сниппеты (также с помощью drag-n-drop) в нужные участки кода в разных проектах и решениях.
Панель Toolbox хорошо известна разработчикам, которые работали с WinForms - в ней располагались готовые компоненты.
В разработке современных приложений мы также можем использовать эту панель для сохранения собственных сниппетов кода, которые часто применяются в различных проектах.
Чтобы открыть панель, перейдите в
View → Toolbox или используйте сочетание клавиш Ctrl+Alt+X.Чтобы добавить сниппет, достаточно выделить нужные строки кода и перенести их в панель с помощью drag-n-drop. Затем, для удобства, можно переименовать сниппет.
После этого вы можете перетаскивать сниппеты (также с помощью drag-n-drop) в нужные участки кода в разных проектах и решениях.
👍12✍1
Архитектура которую я использую в своих проектах
В этом посте я хочу поделиться архитектурой приложений, которую я использую в реальных проектах и которая хорошо себя зарекомендовала.
При проектировании архитектуры приложения я поставил для себя следующие требования:
— Приложение должно легко покрываться тестами
— Приложение должно легко обновляться, переходить на новые версии .Net
— Код должен быть простым, так его становится легче поддерживать, новые разработчики быстрее втягиваются в процесс
— Легко можем заменить поставщика данных, например с БД на внешний сервис, такое часто происходит в крупных компаниях с множеством команд разработки
— Не использовать библиотеки которые влияют на архитектуру Revo, Marten, MediatR и т. д.
На практике я встречал много проектов которые не соответствовали этим требованиям. Поддержка, написание новых фич было достаточно затруднительно, никаких тестов соответственно тоже не было.
Данная архитектура является чистой, используется всего три слоя:
Пример структуры приложения:
Использование только трех слоев позволяет избежать путаницы и возможной протечки абстракции в перспективе. Вся бизнес-логика расположена в слое
Как мы помним, основное требование было возможность замены поставщика данных без внесения изменений в бизнес-слой. Это действительно важное требование в наших приложениях, так как источники данных часто меняются. Например, ранее отправляли данные по REST сейчас другая команда просит отправлять эти данные в Kafka или раннее данные получали из БД затем часть этого функционала забрала себе другая команда и выставила REST.
Чтобы выполнить это требование бизнес-логика в слое Core должна работать с данными только через интерфейсы, в моем случае они имеют суффикс Repository тем самым абстрагируясь от типа источника данных.
Далее для выполнения этого требования модель данных, которую мы получили от источники должна мапится в другую модель - возвращаемый результат
Такая абстракция также позволяет нам легко писать юнит-тесты, а с началом использования Copilot в нашей команде с этим отлично справляется Github Copilot.
Я использую эту архитектуру на протяжении 3-х лет и она отлично себя зарекомендовала, мы начали применять ее для других проектов в команде.
За это время действительно несколько раз менялись источники данных и это происходило без серьезных изменений в коде проекта. Благодаря простоте архитектуры Copilot отлично генерирует юнит-тесты, которые, в большинстве случаев запускаются с первого раза.
Пост на LinkedIn, Medium
В этом посте я хочу поделиться архитектурой приложений, которую я использую в реальных проектах и которая хорошо себя зарекомендовала.
При проектировании архитектуры приложения я поставил для себя следующие требования:
— Приложение должно легко покрываться тестами
— Приложение должно легко обновляться, переходить на новые версии .Net
— Код должен быть простым, так его становится легче поддерживать, новые разработчики быстрее втягиваются в процесс
— Легко можем заменить поставщика данных, например с БД на внешний сервис, такое часто происходит в крупных компаниях с множеством команд разработки
— Не использовать библиотеки которые влияют на архитектуру Revo, Marten, MediatR и т. д.
На практике я встречал много проектов которые не соответствовали этим требованиям. Поддержка, написание новых фич было достаточно затруднительно, никаких тестов соответственно тоже не было.
Данная архитектура является чистой, используется всего три слоя:
Api, Core, Infrastructure, однако не стоит путать с трехзвенной архитектурой, в нашем случае используется принцип инверсии зависимостей. Так например, бизнес-слой (Core) не использует напрямую слой Infrastructure и не ссылается на него как проект. Пример структуры приложения:
Api.csproj
├── Controllers
│ └── ProductController.cs
Core.csproj
├── RepositoriesContracts
│ └── IProductRepository.cs
├── UseCases
│ └── GetProduct
│ ├── GetProductUseCase.cs
│ └── IGetProductUseCase.cs
Infrastructure.csproj
├── Repositories
│ └── ProductRepository.cs
Использование только трех слоев позволяет избежать путаницы и возможной протечки абстракции в перспективе. Вся бизнес-логика расположена в слое
Core. Как мы помним, основное требование было возможность замены поставщика данных без внесения изменений в бизнес-слой. Это действительно важное требование в наших приложениях, так как источники данных часто меняются. Например, ранее отправляли данные по REST сейчас другая команда просит отправлять эти данные в Kafka или раннее данные получали из БД затем часть этого функционала забрала себе другая команда и выставила REST.
Чтобы выполнить это требование бизнес-логика в слое Core должна работать с данными только через интерфейсы, в моем случае они имеют суффикс Repository тем самым абстрагируясь от типа источника данных.
Далее для выполнения этого требования модель данных, которую мы получили от источники должна мапится в другую модель - возвращаемый результат
IProductRepository. Маппинг должен происходить на уровне Infrastructure, помним что Core не должен иметь зависимость от других проектов, но Infrastructure имеет зависимость от Core т.к. реализует интерфейс IProductRepository.Такая абстракция также позволяет нам легко писать юнит-тесты, а с началом использования Copilot в нашей команде с этим отлично справляется Github Copilot.
Я использую эту архитектуру на протяжении 3-х лет и она отлично себя зарекомендовала, мы начали применять ее для других проектов в команде.
За это время действительно несколько раз менялись источники данных и это происходило без серьезных изменений в коде проекта. Благодаря простоте архитектуры Copilot отлично генерирует юнит-тесты, которые, в большинстве случаев запускаются с первого раза.
Пост на LinkedIn, Medium
👍18🔥8
Архитектура, которую я использую в проектах. Продолжение
Предыдущий пост об архитектуре на разных площадках собрал отличные вопросы, и я решил разобрать их подробнее в отдельной публикации.
Давайте вспомним основные требования
- Приложение должно легко покрываться тестами.
- Приложение должно без труда обновляться и переходить на новые версии .NET.
- Код должен быть простым.
- Поставщика данных должно быть легко заменить, например, с базы данных на внешний сервис.
- Не использовать библиотеки, которые влияют на архитектуру.
Эти требования ориентированы на практическое использование. Нет цели строго соответствовать DDD, CQRS, Event Sourcing и т.д.
Почему я против внешних библиотек, которые глубоко проникают в архитектуру
В целом, использование внешних библиотек — это всегда риск. Платные библиотеки могут существенно повысить стоимость поддержки проекта. Автор open source-библиотеки может потерять мотивацию, добавить вредоносный код или сделать проект платным. Конечно, можно сделать форк, но тогда поддержка и обновление лягут на вас. Если библиотека используется только в ограниченной части приложения, это приемлемо, но если она плотно интегрируется в архитектуру — это становится проблемой.
Такие библиотеки обычно используются для сокращения объёма кода, например, за счет рефлексии. Однако если вернуться к изначальным требованиям, такой цели не было. Не всегда "меньше кода" — это лучше, тем более сейчас IDE отлично помогают с автодополнением. Отказавшись от подобных библиотек, мы получаем преимущества: независимость проекта, удобство дебага и простоту навигации по коду.
Почему я использую юзкейсы, а не большие сервисы
В комментариях часто спрашивают, зачем создавать отдельный класс use case для каждого действия, если можно сделать один сервис с несколькими методами. Такой подход действительно не противоречит архитектурным требованиям — и раньше я тоже использовал сервисы. Но со временем сервисы разрастаются и превращаются в классы на 1000+ строк с десятками методов. Внутри появляются приватные методы, которые используются разными публичными, что вроде бы способствует переиспользованию, но на деле сильно увеличивает связность кода. Кроме того, такие сервисы часто имеют десятки зависимостей, что сильно усложняет тестирование.
Можно ли использовать базовые классы для use case или репозиториев?
Я предпочитаю так не делать. В требованиях я это пока не указывал, но всегда держу в уме, что должна быть возможность легко выделить часть функционала в отдельный сервис или микросервис. Наследование базовых классов усложняет этот процесс, навязывает поведение и увеличивает связность кода.
EF и Repository
Существует распространённое мнение, что если мы используем EF, то не нужно создавать дополнительную абстракцию — ведь EF уже абстрагирует работу с БД. Но в этом случае мы получаем сильную зависимость от ORM, хотя можем захотеть заменить часть операций, например, отправлять данные в Kafka вместо сохранения в БД. В моей архитектуре работа с EF происходит только на уровне Infrastructure, внутри реализации репозитория (важно, у меня нет базового репозитория, каждый — независим).
Пост на LinkedIn
Предыдущий пост об архитектуре на разных площадках собрал отличные вопросы, и я решил разобрать их подробнее в отдельной публикации.
Давайте вспомним основные требования
- Приложение должно легко покрываться тестами.
- Приложение должно без труда обновляться и переходить на новые версии .NET.
- Код должен быть простым.
- Поставщика данных должно быть легко заменить, например, с базы данных на внешний сервис.
- Не использовать библиотеки, которые влияют на архитектуру.
Эти требования ориентированы на практическое использование. Нет цели строго соответствовать DDD, CQRS, Event Sourcing и т.д.
Почему я против внешних библиотек, которые глубоко проникают в архитектуру
В целом, использование внешних библиотек — это всегда риск. Платные библиотеки могут существенно повысить стоимость поддержки проекта. Автор open source-библиотеки может потерять мотивацию, добавить вредоносный код или сделать проект платным. Конечно, можно сделать форк, но тогда поддержка и обновление лягут на вас. Если библиотека используется только в ограниченной части приложения, это приемлемо, но если она плотно интегрируется в архитектуру — это становится проблемой.
Такие библиотеки обычно используются для сокращения объёма кода, например, за счет рефлексии. Однако если вернуться к изначальным требованиям, такой цели не было. Не всегда "меньше кода" — это лучше, тем более сейчас IDE отлично помогают с автодополнением. Отказавшись от подобных библиотек, мы получаем преимущества: независимость проекта, удобство дебага и простоту навигации по коду.
Почему я использую юзкейсы, а не большие сервисы
В комментариях часто спрашивают, зачем создавать отдельный класс use case для каждого действия, если можно сделать один сервис с несколькими методами. Такой подход действительно не противоречит архитектурным требованиям — и раньше я тоже использовал сервисы. Но со временем сервисы разрастаются и превращаются в классы на 1000+ строк с десятками методов. Внутри появляются приватные методы, которые используются разными публичными, что вроде бы способствует переиспользованию, но на деле сильно увеличивает связность кода. Кроме того, такие сервисы часто имеют десятки зависимостей, что сильно усложняет тестирование.
Можно ли использовать базовые классы для use case или репозиториев?
Я предпочитаю так не делать. В требованиях я это пока не указывал, но всегда держу в уме, что должна быть возможность легко выделить часть функционала в отдельный сервис или микросервис. Наследование базовых классов усложняет этот процесс, навязывает поведение и увеличивает связность кода.
EF и Repository
Существует распространённое мнение, что если мы используем EF, то не нужно создавать дополнительную абстракцию — ведь EF уже абстрагирует работу с БД. Но в этом случае мы получаем сильную зависимость от ORM, хотя можем захотеть заменить часть операций, например, отправлять данные в Kafka вместо сохранения в БД. В моей архитектуре работа с EF происходит только на уровне Infrastructure, внутри реализации репозитория (важно, у меня нет базового репозитория, каждый — независим).
Пост на LinkedIn
🔥17👍3
Как NASA измеряет риски в коде. Уроки цикломатической сложности
После программных сбоев во время испытательного полета Boeing CST-100, Центр инженерной безопасности NASA провел масштабное исследование цикломатической сложности — и его выводы стоит знать каждому разработчику.
Что такое цикломатическая сложность?
Это метрика, которая показывает количество точек принятия решений в коде на основе его графа управления (Control Flow Graph). Проще говоря - сколько разных путей может выбрать программа. Каждый оператор ветвления или проверки добавляет один путь:
Главные выводы исследования NASA:
— Для критически важных систем NASA рекомендует держать цикломатическую сложность функции ≤ 15. В индустрии стандарт обычно колеблется от 10 до 20.
— Функцию со сложностью 28 пришлось тестировать 31 час, тогда как сложность 5 заняла меньше часа. Зависимость не линейная, а экспоненциальная!
— Низкая сложность упрощает не только тестирование — она напрямую связана с меньшим числом дефектов.
— Не путать с объемом кода. Количество строк не отражает сложность, можно написать 100 строк "прямолинейного" кода со сложностью 1 и всего 10 строк с несколькими вложенными условиями и куда большей сложностью.
Как посчитать в Visual Studio 2022 (C#)
Правый клик на
В отчёте для каждого метода будет показана
Полный отчет можно найти по названию — "Cyclomatic Complexity and Basis Path Testing Study, 2020"
После программных сбоев во время испытательного полета Boeing CST-100, Центр инженерной безопасности NASA провел масштабное исследование цикломатической сложности — и его выводы стоит знать каждому разработчику.
Что такое цикломатическая сложность?
Это метрика, которая показывает количество точек принятия решений в коде на основе его графа управления (Control Flow Graph). Проще говоря - сколько разных путей может выбрать программа. Каждый оператор ветвления или проверки добавляет один путь:
if , switch, циклы for, while, do…while (когда условие прерывает поток) и т. д.Главные выводы исследования NASA:
— Для критически важных систем NASA рекомендует держать цикломатическую сложность функции ≤ 15. В индустрии стандарт обычно колеблется от 10 до 20.
— Функцию со сложностью 28 пришлось тестировать 31 час, тогда как сложность 5 заняла меньше часа. Зависимость не линейная, а экспоненциальная!
— Низкая сложность упрощает не только тестирование — она напрямую связана с меньшим числом дефектов.
— Не путать с объемом кода. Количество строк не отражает сложность, можно написать 100 строк "прямолинейного" кода со сложностью 1 и всего 10 строк с несколькими вложенными условиями и куда большей сложностью.
Как посчитать в Visual Studio 2022 (C#)
Правый клик на
Solution → Analyze and Code Cleanup → Calculate Code Metrics.В отчёте для каждого метода будет показана
Cyclomatic Complexity вместе с другими метриками.Полный отчет можно найти по названию — "Cyclomatic Complexity and Basis Path Testing Study, 2020"
🔥14👍3
Какой длины должен быть метод?
Многие разработчики знают главное правило - метод должен выполнять одно действие и легко читаться, не создавая когнитивной нагрузки.
Однако в некоторых случаях нам нужны конкретные числовые рекомендации. Например, для начинающих разработчиков числовое ограничение более практично. Также числовые ограничения удобны при настройке статических анализаторов и настройке флагов при создании пул-реквестов.
Ниже прямые цитаты из ключевых источников, которые чаще всего всплывают в дискуссиях о размере методов.
Роберт Мартин, «Чистый код»
«Первое правило функций — они должны быть маленькими. Второе правило функций — они должны быть ещё меньше… Желательно, чтобы длина функции не превышала 20 строк»
Стив Макконнел, «Совершенный код»
«…Время от времени реализация сложного алгоритма будет требовать создания более длинного метода, и тогда методу можно будет позволить вырасти до 100–200 строк… Десятилетия исследований говорят о том, что методы такой длины не более подвержены ошибкам, чем методы меньших размеров.»
Android Java Style Guide
«Методы должны быть небольшими и решать конкретную задачу. Если метод превышает 40 строк, подумайте, можно ли разбить его на части, не нарушив структуру программы.»
Я же придерживаюсь рекомендаций статического анализатора Meziontou - не более 60 строк кода. Для меня и моей команды это стало хорошей практикой.
Многие разработчики знают главное правило - метод должен выполнять одно действие и легко читаться, не создавая когнитивной нагрузки.
Однако в некоторых случаях нам нужны конкретные числовые рекомендации. Например, для начинающих разработчиков числовое ограничение более практично. Также числовые ограничения удобны при настройке статических анализаторов и настройке флагов при создании пул-реквестов.
Ниже прямые цитаты из ключевых источников, которые чаще всего всплывают в дискуссиях о размере методов.
Роберт Мартин, «Чистый код»
«Первое правило функций — они должны быть маленькими. Второе правило функций — они должны быть ещё меньше… Желательно, чтобы длина функции не превышала 20 строк»
Стив Макконнел, «Совершенный код»
«…Время от времени реализация сложного алгоритма будет требовать создания более длинного метода, и тогда методу можно будет позволить вырасти до 100–200 строк… Десятилетия исследований говорят о том, что методы такой длины не более подвержены ошибкам, чем методы меньших размеров.»
Android Java Style Guide
«Методы должны быть небольшими и решать конкретную задачу. Если метод превышает 40 строк, подумайте, можно ли разбить его на части, не нарушив структуру программы.»
Я же придерживаюсь рекомендаций статического анализатора Meziontou - не более 60 строк кода. Для меня и моей команды это стало хорошей практикой.
👍6🔥3
Оптимизируем nullable-аннотации с помощью [NotNullWhen(true)]
Если вы используете nullable-аннотации в C#, скорее всего сталкивались с предупреждениями CS8602/CS8603.
Часть этих сообщений действительно помогает ловить потенциальные null-референции, однако в более сложных методах компилятор не всегда способен учесть пользовательские проверки и трактует значения как "возможно null", даже когда они проверены.
Эту проблему могут решить атрибуты из
Ниже показан пример, как
1. Без использования атрибута, получаем предупреждение
2. С использованием атрибута
Зачем это нужно
Меньше проверок на null — сокращается объtм кода.
Явные контракты в сигнатуре — упрощается чтение и ревью.
Точнее статический анализ — компилятор концентрируется на реальных ошибках.
Другие атрибуты, которые стоит знать
Посты на LinkedIn, Medium
Если вы используете nullable-аннотации в C#, скорее всего сталкивались с предупреждениями CS8602/CS8603.
Часть этих сообщений действительно помогает ловить потенциальные null-референции, однако в более сложных методах компилятор не всегда способен учесть пользовательские проверки и трактует значения как "возможно null", даже когда они проверены.
Эту проблему могут решить атрибуты из
System.Diagnostics.CodeAnalysis, позволяющие формально описать контракты методов.Ниже показан пример, как
[NotNullWhen(true)] устраняет лишние предупреждения в классическом Try*-паттерне.1. Без использования атрибута, получаем предупреждение
bool TryGetDomain(string? url, out string? domain)
{
if (url is null || !url.Contains("://"))
{
domain = null;
return false;
}
domain = url.Split("://")[1];
return true; // здесь domain гарантированно не null
}
if (TryGetDomain(link, out var d))
{
Console.WriteLine(d.Length); // Предупреждение CS8602 «возможен null»
}
2. С использованием атрибута
[NotNullWhen(true)] анализатор понимает что при true, out-параметр не будет null.bool TryGetDomain(
string? url,
[NotNullWhen(true)] out string? domain)
{
if (url is null || !url.Contains("://"))
{
domain = null;
return false; // при false null допустим
}
domain = url.Split("://")[1];
return true; // при true null невозможен
}
if (TryGetDomain(link, out var d))
{
Console.WriteLine(d.Length); // без варнингов и лишних проверок
}
Зачем это нужно
Меньше проверок на null — сокращается объtм кода.
Явные контракты в сигнатуре — упрощается чтение и ревью.
Точнее статический анализ — компилятор концентрируется на реальных ошибках.
Другие атрибуты, которые стоит знать
[MaybeNullWhen(true)] — метод возвращает true, однако значение может быть null.
[NotNullIfNotNull("param")] — если входной параметр не null, результат тоже не будет null.
[MemberNotNull("Field")] — метод гарантирует инициализацию поля или свойства до выхода.
[DoesNotReturnIf(true)] — метод не возвращает управление при выполнении условия (обычно выбрасывает исключение).Посты на LinkedIn, Medium
👍15🔥3
Как добавить юнит-тесты в легаси‑проект
Может возникнуть вопрос - зачем писать тесты для старого кода?
Давайте рассмотрим несколько причин:
— Защита ключевой логики, ошибка в которой может повлечь финансовые потери. Представьте, что в методе спрятан сложный расчёт цены или процентной ставки. Тест гарантирует, что будущие изменения не сломают его.
— Быстрая отладка. Вместо настройки БД под определенный кейс, SMTP‑сервера и других сервисов, вы можете протестировать нужный кусок кода отдельно.
— Пошаговый рефакторинг. Полная переписка проекта — это риск. Вынося небольшие части в тестируемые единицы, вы получаете мгновенные плюсы и уверенность для более масштабных изменений в будущем.
Подход "Простой объект" (Humble Object)
Чтобы начать писать тесты, не нужна идеальная архитектура. С шаблоном Humble Object:
1. Выносите бизнес‑логику в отдельный класс без внешних зависимостей.
2. Оставляете доступ к базе, отправку email, логирование и прочую инфраструктуру в исходном методе.
3. Пишете тесты только для этого нового простого класса. Остальной код не меняется.
Пример
Вот пример контроллера, который смешивает вычисления с вызовами email, базы данных и логирования. Тестирование такого кода напрямую потребует замокать EF Core, DateTime.Now, SMTP и логгер одновременно
Выносим расчет цены в отдельный класс
Пишем тесты
Сейчас контроллер просто связывает компоненты
Теперь вы можете начать тестировать легаси‑код сразу, без масштабного рефакторинга.
Достаточно просто вынести основную логику в небольшие классы, которые легко тестировать. Даже несколько тестов критических методов может начать приносить пользу.
Посты на LinkedIn, Medium
Может возникнуть вопрос - зачем писать тесты для старого кода?
Давайте рассмотрим несколько причин:
— Защита ключевой логики, ошибка в которой может повлечь финансовые потери. Представьте, что в методе спрятан сложный расчёт цены или процентной ставки. Тест гарантирует, что будущие изменения не сломают его.
— Быстрая отладка. Вместо настройки БД под определенный кейс, SMTP‑сервера и других сервисов, вы можете протестировать нужный кусок кода отдельно.
— Пошаговый рефакторинг. Полная переписка проекта — это риск. Вынося небольшие части в тестируемые единицы, вы получаете мгновенные плюсы и уверенность для более масштабных изменений в будущем.
Подход "Простой объект" (Humble Object)
Чтобы начать писать тесты, не нужна идеальная архитектура. С шаблоном Humble Object:
1. Выносите бизнес‑логику в отдельный класс без внешних зависимостей.
2. Оставляете доступ к базе, отправку email, логирование и прочую инфраструктуру в исходном методе.
3. Пишете тесты только для этого нового простого класса. Остальной код не меняется.
Пример
Вот пример контроллера, который смешивает вычисления с вызовами email, базы данных и логирования. Тестирование такого кода напрямую потребует замокать EF Core, DateTime.Now, SMTP и логгер одновременно
[HttpPost("create")]
public IActionResult Create(OrderDto dto)
{
var customer = _db.Customers.Find(dto.CustomerId);
var now = DateTime.Now;
decimal subtotal = dto.Lines.Sum(l => l.Price * l.Qty);
// Скидка на черную пятницу
if (now.Month == 11 && now.Day >= 25 && now.Day <= 30)
subtotal *= 0.8m;
decimal vat = subtotal * 0.12m;
decimal total = subtotal + vat;
customer.Balance -= total;
_db.SaveChanges();
_smtp.Send("sales@corp", customer.Email,
"Thanks for your order", $"Total = {total:C}");
_log.Write($"{now:u} Order {dto.Id} for {customer.Id} = {total}");
return Ok(new { id = dto.Id, total });
}Выносим расчет цены в отдельный класс
public class OrderPricer
{
private readonly IClock _clock;
private const decimal VatRate = 0.12m;
// в .NET 8 и выше можно использовать встроенный TimeProvider вместо IClock
public OrderPricer(IClock clock) => _clock = clock;
public decimal CalculateTotal(IEnumerable<OrderLineDto> lines)
{
decimal subtotal = lines.Sum(l => l.Price * l.Qty);
var now = _clock.UtcNow;
// Скидка на черную пятницу
if (now.Month == 11 && now.Day >= 25 && now.Day <= 30)
subtotal *= 0.8m;
decimal vat = subtotal * VatRate;
return subtotal + vat;
}
}
Пишем тесты
public class FixedClock : IClock
{
public DateTime UtcNow { get; init; }
}
public class OrderPricerTests
{
[Fact]
public void CalculatesTotalWithVat_NoDiscount()
{
var clock = new FixedClock { UtcNow = new DateTime(2025, 7, 19) };
var pricer = new OrderPricer(clock);
var total = pricer.CalculateTotal(new[]
{
new OrderLineDto { Price = 100, Qty = 1 }
});
Assert.Equal(112m, total);
}
// остальные тесты
}
Сейчас контроллер просто связывает компоненты
[HttpPost("create")]
public IActionResult Create(OrderDto dto)
{
var customer = _db.Customers.Find(dto.CustomerId);
var pricer = new OrderPricer(_clock);
decimal total = pricer.CalculateTotal(dto.Lines);
customer.Balance -= total;
_db.SaveChanges();
_smtp.Send("sales@corp", customer.Email,
"Thanks for your order", $"Total = {total:C}");
_log.Write($"{_clock.UtcNow:u} Order {dto.Id} for {customer.Id} = {total}");
return Ok(new { id = dto.Id, total });
}Теперь вы можете начать тестировать легаси‑код сразу, без масштабного рефакторинга.
Достаточно просто вынести основную логику в небольшие классы, которые легко тестировать. Даже несколько тестов критических методов может начать приносить пользу.
Посты на LinkedIn, Medium
🔥7✍6
Как подключить локальный MCP сервер к GitHub Copilot
MCP (Model Context Protocol) описывает, как ИИ-агент взаимодействует с внешними инструментами. Это простой способ встроить ваши процессы в Copilot Chat.
Для этого поста я написал MCP-сервер, который запускается локально (пример на TypeScript. Поддержка .NET уже в превью).
Этот MCP сравнивает c помощью Git две ветки и помогает провести раннее код-ревью прямо в Copilot Chat.
Что сделаем
— Поднимем сервер с тремя функциями:
— Подключим его к Copilot через mcp.json.
— Запустим промпт и получим обзор пулл-реквеста.
Зачем это разработчику
— Подключайте готовые серверы, оборачивайте свои приложения, скрипты в MCP серверы для расширения возможностей Copilot или других ИИ агентов.
— Прокачиваете навыки работы с ИИ сегодня это требование к AI-ready developer.
— Все операции выполняются в привычном интерфейсе Copilot Chat.
Шаги
— Клонируйте репозиторий https://github.com/sigmade/Git-MCP-Server
— Выполните команды ниже
— В папке build появится
— В
— Откройте любой проект в VS Code с двумя ветками в моем случае это
— Убедитесь что MCP стал доступен в VS Code, для этого в Copilot Chat в режиме Agent нажмите на кнопку Configure Tools... в выпадающем списке появится список доступных инструментов, найдите секцию MCP Server в ней должен отобразиться наш MCP - simple-merge-review а так же его функции
— В чат введите промпт например -
Copilot сам поймет какой MCP сервер нужен и какие функции использовать, после чего соберет диффы и покажет изменения, улучшения и риски.
*Если вы используете Cursor возможно ему придется явно прописать в промпт какой MCP использовать.
Итог
MCP дает разработчику возможность подключать любые скрипты, API или внутренние сервисы к ИИ-агенту и использовать их в Copilot Chat для автоматизации рутины и расширения возможностей для работы с ИИ агентами
MCP (Model Context Protocol) описывает, как ИИ-агент взаимодействует с внешними инструментами. Это простой способ встроить ваши процессы в Copilot Chat.
Для этого поста я написал MCP-сервер, который запускается локально (пример на TypeScript. Поддержка .NET уже в превью).
Этот MCP сравнивает c помощью Git две ветки и помогает провести раннее код-ревью прямо в Copilot Chat.
Что сделаем
— Поднимем сервер с тремя функциями:
quick_merge_summary, show_merge_diff, show_file_diff.— Подключим его к Copilot через mcp.json.
— Запустим промпт и получим обзор пулл-реквеста.
Зачем это разработчику
— Подключайте готовые серверы, оборачивайте свои приложения, скрипты в MCP серверы для расширения возможностей Copilot или других ИИ агентов.
— Прокачиваете навыки работы с ИИ сегодня это требование к AI-ready developer.
— Все операции выполняются в привычном интерфейсе Copilot Chat.
Шаги
— Клонируйте репозиторий https://github.com/sigmade/Git-MCP-Server
— Выполните команды ниже
npm install
npm run build
npm run start
— В папке build появится
index.js.— В
C:\Users\YourUserName\AppData\Roaming\Code\User создайте mcp.json и укажите путь к сбилженному файлу index.js.— Откройте любой проект в VS Code с двумя ветками в моем случае это
master и new-feature .— Убедитесь что MCP стал доступен в VS Code, для этого в Copilot Chat в режиме Agent нажмите на кнопку Configure Tools... в выпадающем списке появится список доступных инструментов, найдите секцию MCP Server в ней должен отобразиться наш MCP - simple-merge-review а так же его функции
quick_merge_summary, show_merge_diff, show_file_diff.— В чат введите промпт например -
Из ветки new-feature в master будет создан пулл-реквест. Сделай предварительное код-ревью
Copilot сам поймет какой MCP сервер нужен и какие функции использовать, после чего соберет диффы и покажет изменения, улучшения и риски.
*Если вы используете Cursor возможно ему придется явно прописать в промпт какой MCP использовать.
Итог
MCP дает разработчику возможность подключать любые скрипты, API или внутренние сервисы к ИИ-агенту и использовать их в Copilot Chat для автоматизации рутины и расширения возможностей для работы с ИИ агентами
🔥5👍4