.NET Разработчик
6.73K subscribers
480 photos
4 videos
14 files
2.41K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2717. #Оффтоп #AI
Прекратите Промптить. Спроектируйте Цикл. Начало

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

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

Борис Черни, возглавляющий Claude Code в Anthropic больше не пишет промпты для Клода. У него работают циклы, которые запрашивают Клод и решают, что делать дальше, сканируя трекер задач, командный чат и временную шкалу в поисках того, что нужно создать.

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

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

Удивительно, что это уже почти не проблема инструментов. Год назад цикл означал кучу кода на Bash, который вы писали и поддерживали бесконечно. Теперь же компоненты поставляются внутри продуктов, и аналогичные структуры встречаются в Claude Code и Codex. Цикл работает по таймеру, запускает вспомогательные средства и питает себя сам.

Пять составляющих и шестая, которая их объединяет
И Claude Code, и Codex теперь имеют все пять. Названия немного различаются, но возможности одинаковы.

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

2. Рабочие деревья (Worktrees). Как только вы запускаете более одного агента, они начинают конфликтовать за файлы. Рабочее дерево Git — это отдельный рабочий каталог в своей собственной ветке, поэтому изменения одного агента не могут коснуться изменений другого.

3. Навыки. Агент начинает каждую сессию с нуля и заполняет любые пробелы в вашем намерении уверенным предположением. Навык — это соглашения, этапы сборки, фраза «мы так не делаем из-за одного инцидента», записанные один раз, и агент читает их при каждом запуске. Без навыков цикл каждый раз заново анализирует весь ваш проект с нуля.

4. Коннекторы. Созданные на основе MCP, они позволяют агенту читать трекер задач, запрашивать БД, обращаться к API или отправлять сообщения в чат. В этом разница между агентом, который говорит «вот исправление», и циклом, который открывает пул-реквест, связывает тикет и отправляет уведомление в канал, как только CI завершится успешно.

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

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

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

Источник:
https://www.pulumi.com/blog/stop-prompting-design-the-loop/
👎12👍3
День 2718. #Оффтоп #AI
Прекратите Промптить. Спроектируйте Цикл. Окончание

Начало

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

Самая чистая версия этого уже включена в инструменты. Функция /goal в Claude Code продолжает работать на протяжении нескольких итераций, пока не выполнится условие, которое вы фактически написали, например, «все тесты в auth/ проходят и сборка не имеет предупреждений», и после каждой итерации отдельная, более быстрая модель считывает протокол и решает, достигли ли вы цели. Агент, написавший код, не тот, кто его оценивает. Это разделение «создатель и проверяющий», применяется к самому условию остановки. Функция /goal в Claude Code достигает той же финишной линии другим способом: агент проверяет свою собственную работу на соответствие доказательствам, прежде чем объявить цель выполненной.

Чего цикл по-прежнему не сделает для вас
Цикл меняет форму работы, а не делает её за вас. Отдельный рецензент — это то, что делает фразу «готово» осмысленной, но «готово» — это утверждение, а не доказательство. Ваша задача по-прежнему — выпускать код, работоспособность которого вы подтвердили, в чём сложнее быть уверенным, если изменения сделаны, пока вы были на обеде.

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

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

С чего начать
1. Начните с того места, где понятие «готово» однозначно. Отладка конвейера CI, проблемы с зависимостями, поиск нестабильных тестов, падающее задание, которое вы постоянно перезапускаете вручную. Циклам нужен «оракул», поэтому начните с того места, где «оракул» уже существует.

2. Напишите файл памяти перед циклом. Один файл Markdown. Что сделано, что дальше, что было опробовано и не удалось. Это основа, и всё остальное от неё зависит.

3. Отделите проверку от создания. Используйте /goal с проверяемым условием или второй агент с отдельными инструкциями. Никогда не позволяйте агенту, который выполнил работу, решать, что работа завершена.

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

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

Источник:
https://www.pulumi.com/blog/stop-prompting-design-the-loop/
👎5👍2
День 2719. #TipsAndTricks
Паттерн «Строитель» с Неявным Оператором
Обычно паттерн «Строитель» позволяет вам выполнить сложную логику создания объекта, а потом вызвать метод Build(), который вернёт нужный вам объект. Например:
internal record PersonBuilder
{
internal PersonBuilder()
{
FirstName = "Jane";
LastName = "Doe";
Age = 42;
Height = 169;
}

internal string FirstName { get; init; }
internal string FamilyName { get; init; }
internal int Age { get; init; }
internal int Height { get; init; }

internal Person Build()
=> new Person(FirstName, LastName, Age, Height);
}

Мы можем создать объект Person так:
var person = new PersonBuilder
{
FirstName = "John"
}.Build();

Было бы неплохо, если бы можно было опустить вызов .Build() в конце. А это возможно. Можно создать неявный оператор для нашего строителя:
public static implicit operator 
Person(PersonBuilder b) => b.Build();


Теперь можно использовать строитель так:
Person person = new PersonBuilder
{
FirstName = "John"
};

Неявный оператор вызывается, т.к. мы изменили var на Person при объявлении переменной.

Источник:
https://josef.codes/bonus-builder-pattern-with-the-implicit-operator-using-c-sharp/
👎16👍2
День 2720. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

39. Преимущества микросервисов
«Расскажите о преимуществах использования микросервисной архитектуры в приложениях .NET?»

Хороший ответ
Микросервисная архитектура предлагает ряд преимуществ для разработки сложных приложений, особенно в средах, требующих гибкости, масштабируемости и высокой доступности.

- Масштабируемость: микросервисы могут масштабироваться независимо, что позволяет более эффективно использовать ресурсы. Вы можете масштабировать части системы, требующие больше ресурсов, без масштабирования всего приложения.

- Гибкость: микросервисы позволяют небольшим автономным командам разрабатывать, развёртывать и масштабировать свои сервисы независимо друг от друга. Это приводит к более быстрым циклам разработки и упрощает экспериментирование и инновации.

- Устойчивость: независимость сервисов повышает общую устойчивость системы. При сбое в одном сервисе остальные могут продолжать работать.

- Гибкость развёртывания: микросервисы могут развёртываться независимо. Это особенно полезно в средах непрерывного развёртывания, где необходимо часто выпускать обновления.

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

Часто встречающийся неверный ответ
«Можно разбить приложение на более мелкие проекты для каждой сущности или группы сущностей. Это делает приложение микросервисной архитектурой.»

Почему это неверно
- Непонимание концепции микросервисов: ответ демонстрирует фундаментальное непонимание того, что такое микросервисы. Микросервисы — это не просто разбиение приложения на части, а определение сервисов на основе бизнес-возможностей и границ предметной области.

- Чрезмерное упрощение проектирования архитектуры: сведение микросервисов к операциям CRUD для каждой сущности может привести к сильно фрагментированной архитектуре с избыточными накладными расходами, сложным управлением данными и увеличенной задержкой из-за межсервисного взаимодействия.

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

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

Источник:
https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
1👎9👍2
День 2721. #ЗаметкиНаПолях #AI
Превращаем Claude из Автодополнителя в Коллегу. Начало
Claude не «плохо разбирается в .NET». Он слеп. Вы просите его «добавить конечную точку», а он уверенно:
- изобретает структуру папок, которая не соответствует вашему репозиторию;
- добавляет шаблоны, которые вы не используете;
- пропускает ваш рабочий процесс сборки/тестирования;
- нарушает соглашения и заставляет вас убирать за ним.
Это не проблема ИИ. Это проблема адаптации.


Решение — один файл, который становится системой оперирования вашим проектом для Claude: CLAUDE.md.

В Claude Code файлы памяти, такие как ./CLAUDE.md (и модульные правила в ./.claude/rules/*.md), автоматически загружаются в качестве контекста при запуске инструмента, поэтому Claude начинает каждую сессию с картой вашего проекта и правилами, вместо того чтобы гадать.

Что на самом деле делает CLAUDE.md
Представьте CLAUDE.md как вводные инструкции для нового старшего разработчика. Однако этот «разработчик» будет:
- последовательно следовать вашим инструкциям,
- запоминать их между сессиями,
- перестанет каждый раз заново изобретать вашу архитектуру.

В документации Claude Code файл CLAUDE.md описывается как файл конфигурации проекта, который находится в вашем репозитории и автоматически загружается в контекст.

И что важно: вам не нужно начинать с нуля. Claude Code поддерживает команду /init для генерации стартового файла CLAUDE.md путём сканирования вашего репозитория — затем вы его дорабатываете.

Правило: ваш CLAUDE.md должен предотвращать дорогостоящие ошибки
Если CLAUDE.md не предотвращает одну из следующих ошибок, он не выполняет свою работу:
- неправильные шаблоны (репозитории, AutoMapper, случайные абстракции, которые вы не используете);
- неправильный рабочий процесс (изменения без плана, отсутствие тестов, отсутствие валидации);
- неправильные значения по умолчанию (тайм-ауты, отмена, повторные попытки, фоновая работа, время жизни DI).

Поэтому не пишите его как документацию. Пишите его как: ограничители + карта + команды.

Структура памяти Claude Code
Claude Code поддерживает небольшую иерархию файлов памяти, включая:
- ./CLAUDE.md для общей памяти проекта,
- ./.claude/rules/*.md для модульных правил,
- ~/.claude/CLAUDE.md для ваших личных глобальных настроек,
- ./CLAUDE.local.md для ваших личных заметок по проекту (и он НЕ предназначен для того, чтобы его коммитили).
Это означает, что вы можете:
- держать краткий корневой файл CLAUDE.md,
- поместить подробные правила в .claude/rules/,
- хранить ваши личные данные в CLAUDE.local.md, не засоряя репозиторий.

Минимально необходимый CLAUDE.md для бэкенда на .NET 10
Создайте CLAUDE.md в корневом каталоге вашего репозитория:
## What this repo is
- .NET 10 backend API focused on scalability, p95/p99 latency, and production reliability.
- Prefer simple, copy/pasteable patterns over "clever architecture".

## Tech stack --> Зависит от ваших условий
- .NET 10 / C# (modern style)
- ASP.NET Core (Minimal APIs)
- Dapper (NOT EF Core)
- Redis (IDistributedCache / StackExchange.Redis depending on project)
- Azure hosting (App Service / Containers)
- Observability: Application Insights (logs + metrics)

## Repo map --> Измените, чтоб соответствовало вашим папкам
- src/Api/ → endpoints, DI, middleware, startup
- src/App/ → use-cases/services (business orchestration)
- src/Infra/ → DB, Redis, HTTP clients, external integrations
- tests/ → unit + integration tests

## Hard rules (do not violate)
- NEVER add new framework layers or "Clean Architecture cosplay".
- NEVER introduce patterns we don't use (AutoMapper, repositories, magic abstractions).
- Always pass CancellationToken through all async calls.
- No sync-over-async (no .Result/.Wait).
- No Task.Run inside request handlers.
- Outbound HTTP MUST have timeouts + cancellation.
- Caching MUST have: time budget, stampede protection strategy, and key versioning.

## Default workflow
1) Ask for missing requirements before changing code.
2) Propose a plan + list files to touch.
3) Implement smallest change that works.
4) Add/update tests when relevant.
5) Provide commands to verify (build/test/run).

## Commands --> Измените на нужные вам
- Build: `dotnet build`
- Test: `dotnet test`
- Run API: `dotnet run --project src/Api`
- Format: `dotnet format`

## Output format
- Prefer short sections, small code blocks, and explain trade-offs.
- When making changes: show diff-level guidance + why.


Этого достаточно, чтобы избежать 80% гаданий Claude.

Далее сделаем его жутко эффективным.

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

Источник:
https://medium.com/codetodeploy/claude-md-for-net-10-turn-claude-from-autocomplete-into-a-teammate-5ae9d5ad0b92
👎7👍2
День 2722. #ЗаметкиНаПолях #AI
Превращаем Claude из Автодополнителя в Коллегу. Окончание

Начало

Модульные правила: прекратите повторяться и остановите импровизации Claude
Claude Code поддерживает модульные правила в папке .claude/rules/*.md.

Создадим:
1. .claude/rules/api-conventions.md:
# API conventions (Minimal APIs) ---> Зависит от ваших настроек
- Prefer Minimal APIs (`MapGet/MapPost`) over controllers.
- Endpoints must accept CancellationToken.
- Validate inputs early. Return ProblemDetails for errors.
- For outbound calls: use IHttpClientFactory + timeouts.
- For "slow work": queue it (don't run it in the request).


2. .claude/rules/perf-reliability.md:
# Perf + reliability rules
- All outbound HTTP:
- Timeout per request
- CancellationToken wired through
- Retry ONLY for safe idempotent calls (and never infinite)
- Redis:
- Cache calls get a small time budget (fail fast)
- Stampede protection for hot keys (singleflight or equivalent)
- TTL jitter for large fleets
- Logging:
- Avoid high-cardinality fields
- Avoid expensive structured logs in hot paths


3. .claude/rules/security.md:
# Security rules
- No secrets in code or CLAUDE.md.
- Avoid putting sensitive IDs/tokens in query strings.
- Auth must be explicit on endpoints (no accidental public routes).
- CORS must be restrictive (no AllowAnyOrigin in prod configs).

Теперь корневой CLAUDE.md остаётся коротким и читаемым, а правила — таргетированными.

Как быстро сделать Claude полезным?
Собственные рекомендации Claude сводятся к следующему: относитесь к CLAUDE.md как к живому конфигурационному файлу, который вы совершенствуете со временем.

Пошагово:
- Запустите /init один раз, чтобы получить начальный файл.
- Удалите всё «сферическое в вакууме». Сохраняйте только специфичные для репозитория правила.
- Каждый раз, когда Claude делает что-то не то, добавляйте правило, которое это предотвращает.
- Переместите большие фрагменты правил в .claude/rules/, чтобы корневой файл оставался чистым.
- При переключении задач очищайте контекст с помощью /clear, чтобы Claude не переносил старые неактуальные детали в следующую имплементацию.
В блоге Claude также упоминается использование клавиши # для добавления инструкций, которые вы постоянно повторяете (так что они накапливаются в CLAUDE.md со временем).

Что добавлять (а что нет)
1. Добавить (полезно)
- точные версии технологий + фреймворки, которые вы действительно используете;
- карту папок + «где что находится»;
- ваши строгие правила (тайм-ауты, отмена, отсутствие Task.Run, отсутствие синхронного кода поверх асинхронного);
- команды сборки/тестирования/запуска;
- шаблоны, которые вы не хотите, чтобы предлагались.

2. Не добавлять (мало полезно или опасно)
- секреты, токены, строки подключения (никогда!);
- правила стиля, которые уже применяются вашим форматтером;
- «войну-и-мир» документации, которую Claude всё равно сможет просмотреть.

Цель не в том, чтобы научить Клода .NETу. Цель — научить Клода вашему репозиторию.

Простой «тестовый промпт» для проверки вашего CLAUDE.md:
«Добавь новую конечную точку, которая вызывает нижестоящий HTTP-сервис и безопасно кэширует ответ. Сначала покажи план, затем реализуй».


Если Claude (например):
- использует минимальный API;
- использует CancellationToken по всей цепочке;
- устанавливает тайм-аут запроса;
- избегает Task.Run;
- добавляет безопасные шаблоны кэширования
…ваш CLAUDE.md работает.

Если он импровизирует, в вашем CLAUDE.md отсутствуют правила.

Итого
Если вы серьёзно относитесь к разработке с использованием ИИ в .NET, перестаньте настойчиво промптить и начните лучше проводить адаптацию. CLAUDE.md поможет превратить Claude из «умного автозаполнения» в «надёжного члена команды».

Источник:
https://medium.com/codetodeploy/claude-md-for-net-10-turn-claude-from-autocomplete-into-a-teammate-5ae9d5ad0b92
👎7👍3
День 2723. #TipsAndTricks
Пагинация в EF Core
Если вы повторяете одну и ту же логику пагинации — Count, Skip и Take — во всём приложении, вы добавляете ненужный код. Рассмотрим более чистый способ.

Проблема
Рассмотрим класс:
public class PagedProductsDto
{
public IEnumerable<ProductDto> Records { get; set; } = [];
public int TotalCount { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public int PageCount { get; set; }
}

В самом классе нет ничего плохого. Он содержит все необходимые свойства для пагинации: записи, общее количество, страница, размер страницы и количество страниц. Проблема в том, что он тесно связан с товарами. Если бы мы хотели добавить аналогичный класс, например, для пользователей, нам пришлось бы создать новый класс.

Создадим универсальный класс PagedResults
Он будет принимать в качестве обобщённого параметра тип возвращаемой записи:
public class PagedResults<T>
{
public IEnumerable<T> Records { get; }
public int TotalCount { get; }
public int Page { get; }
public int PageSize { get; }
public int PageCount { get; }
}

В Records мы используем IEnumerable, чтобы обозначить его как доступный только для чтения.

Свойства имеют только аксессор, т.е. доступны только для чтения. Поэтому единственный способ установить их — через конструктор, где мы вычислим все необходимые значения:
public class PagedResults<T>
{
// …
public PagedResults(
IEnumerable<T> records,
int totalCount,
int page,
int pageSize
)
{
Records = records;
TotalCount = totalCount;
Page = page;
PageSize = pageSize;
PageCount = totalCount > 0 && pageSize > 0 ?
(int)Math.Ceiling((double)totalCount / pageSize) : 1;
}
}


Используем в EF
Создадим метод расширения для IQueryable, который позволит применять пагинацию к любым нашим запросам:
public static async Task<PagedResults<T>>
ToPagedResultsAsync<T>(
this IQueryable<T> query,
int page,
int pageSize,
CancellationToken ct = default)
{
var total = await query.CountAsync(ct);

var records = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(ct);

return new PagedResults<T>(records, total, page, pageSize);
}

Теперь методы получения результатов короткие, и это применимо к любым сущностям:
public Task<PagedResults<ProductDto>> 
GetPagedProductsAsync(
int page,
int pageSize,
CancellationToken ct = default)
{
return _dbContext.Products
.Include(x => x.Category)
.AsNoTracking()
.Select(x => new ProductDto
{
Id = x.Id,
Name = x.Name,
// …
})
.ToPagedResultsAsync(page, pageSize, ct);
}


Источник:
https://www.roundthecode.com/dotnet-tutorials/pagedresults-ef-core-one-class-endless-reuse
👍3
День 2724. #Оффтоп
Можно Установить Пакет, но Владеете ли вы им? Начало

Создавайте форки зависимостей, ограничивайте их только вашими вариантами использования, никогда не обновляйте без крайней необходимости. Обновление гораздо рискованнее, чем обнаружение скрытых ошибок (которые можно отслеживать, а CVE – мониторить). Если вы обновляете зависимость, вы должны проанализировать каждый коммит во всё транзитивном наборе зависимостей. Если вы не видите ничего убедительного, не обновляйте!
Я помню, как в HashiCorp инженеры иногда пытались обновить зависимость или заменить самодельную библиотеку внешней, и я всегда спрашивал: «Покажите мне нужный коммит». Не обновляйте просто так.
Я очень доволен таким подходом, учитывая все эти атаки на цепочки поставок.

Это от Митчелла Хашимото

Вроде звучит здраво. Но есть проблема. Можно создать форк небольшой библиотеки и сократить её до того, что вам нужно. Но можно ли создать форк React и поддерживать его? Думаю, большинство команд не смогут. Поэтому совет «создавайте форки зависимостей» прекрасен до тех пор, пока зависимость не становится слишком большой.

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

Устанавливать или нет?
Установить Automapper или написать маппинг вручную? Конечно, гораздо проще установить пакет и передать его обслуживание на аутсорс. Особенно когда вы спешите, вы ставите, что есть, и это может быть неплохим промежуточным решением, но затем стоит задуматься о следующем шаге.

Посмотрите, какие пакеты на самом деле попадают под все эти атаки на цепочки поставок. Почти всегда это мелочи. Помните инцидент с Left-pad? Обычно это какой-нибудь крошечный или низкоуровневый вспомогательный код, о котором никто не задумывается. Они повсюду, они тривиальны, и именно поэтому никто за ними не следит. Поэтому их так легко написать самому. Так что написание небольшого вспомогательного кода от руки — это не паранойя. Это более спокойный вариант, и это один из немногих случаев, когда точно знаешь, что находится в твоей системе.

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

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

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

Инвентаризация зависимостей
В большинстве сред существуют инструменты для генерации SBOM (Software Bill of Materials) — инвентаризации дерева зависимостей. Проблема в том, что большинство организаций никогда в жизни не создавали ни SBOM, ни инвентаризации, ничего нигде не записано. Поэтому в день следующего Log4Shell, а он обязательно будет, они не смогут ответить на самый первый вопрос, который им зададут: используем ли мы это, и если да, то где?

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

Источник:
https://www.architecture-weekly.com/p/you-can-fork-a-package-but-can-you
👍4
День 2725. #Оффтоп
Можно Установить Пакет, но Владеете ли вы им? Окончание

Начало

Фактор автобуса и неожиданные изменения
Есть ещё один фактор, когда популярный пакет меняет условия. Fluent, Assertions, MassTransit и AutoMapper стали коммерческими. Moq выпустил обновление, незаметно хешировавшее ваш email в Git и отправлявшее его на сервер. И почти каждый раз реакция в .NET-компаниях – это смесь:
- срочно удаляем и пишем своё,
- ищем бесплатную альтернативу,
- обращаемся к Microsoft с просьбой купить библиотеку или предоставить замену.

Это решение не той проблемы. Проблема в том, что мы взяли на себя критически важную зависимость, так и не признав её критически важной, и ни разу не подумали о том, что будем делать, если условия изменятся. Замена пакета ничего не исправит, лишь повторит ту же ошибку.

Эпизод с IdentityServer — более наглядный пример. Люди были недовольны тем, что им внезапно пришлось платить. Затем они назвали это критически важным компонентом безопасности. А потом спросили, какие есть бесплатные альтернативы. Критически важный компонент безопасности, который вы хотите получить бесплатно, либо готовы тут же заменить на аналог... Что может пойти не так?

Несложная математика. Возьмём стоимость лицензии в год. И сколько бы стоило нанять инженера для разработки и поддержки собственной версии. Сравните варианты и решите.

LLM как форк
Изначальная мысль Митчелла интересна, учитывая нынешнюю ситуацию. Кажется, LLM меняют дело, так что написание собственного кода внезапно становится тривиальным, и вопрос зависимостей смягчается. Но всё не так просто. Написание чего-то небольшого никогда не было сложной задачей. Сложнее владеть этим, понимать, поддерживать, устранять инциденты в 2 часа ночи, и никакая модель не снимет с вас эту ответственность.

LLM (возможно) cмогут изменить стоимость производства кода. Но это не решает проблему «установить, не принимая решения». Раньше было «установить и двигаться дальше». Теперь — «навайбить» и двигаться дальше. То же отсутствие ответственности и владения.

Тенденция не нова. Это классическое «теневое ИТ» (инструменты и системы, которые люди создают или внедряют внутри компании, не получая одобрения от тех, кто официально должен их утверждать). Таблица Excel, незаметно управляющая целым отделом; небольшой скрипт, от которого теперь зависит половина команды; интеграция, о которой никто в команде никогда не слышал. Это всегда существовало, потому что люди обходят медленные механизмы управления, чтобы выполнить свою работу, и в большинстве случаев никто не замечает этого, пока система не сломается. В случае с LLM это стало более заманчивым, чем когда-либо.

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

1. Инвентаризация: перечислите используемые вами зависимости (используйте инструменты SBOM).
2. Критичность: определите, какие из этих пакетов абсолютно критически важны для вашей системы.
3. Жизненный цикл: разработайте чёткую стратегию обновления и версионирования. Вы обновляете их просто так, или ищете конкретные полезные коммиты, как предлагает Митчелл?
4. Фактор автобуса: что произойдет, если автора критически важного пакета «собьёт автобус», он выгорит или попросит денег?
5. Смягчение последствий: разработайте конкретный план резервного копирования на этот сценарий. Сделать форк? Оплатить лицензию?
6. Время отклика: как быстро вы сможете обновить и развернуть приложение, если произойдет серьезное нарушение безопасности в какой-либо из зависимостей. Если стратегия заключается в её замене, насколько быстро вы сможете заменить?

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

Источник: https://www.architecture-weekly.com/p/you-can-fork-a-package-but-can-you
👍5
Недавно обновлял резюме и наткнулся на старую версию из 2018 года. Ох, были же времена, когда гордо писали: «уверенный пользователь ПК, MS Office» 😄 А ведь, сейчас этим уже никого не удивишь

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

Один из способов прокачать этот навык — магистратура «Управление внедрением ИИ в бизнес» от МИФИ и «Школы 21».

Вы научитесь:
— работать с данными, Python, ML и нейросетями
— запускать и развивать ИИ-продукты от идеи до MVP
— управлять командами и цифровыми продуктами

Что дает обучение:
— онлайн-формат с регулярными очными интенсивами;
— студенческий билет, льготы и доступ к инфраструктуре «Школы 21»;
— отсрочка от срочной службы;

Подать заявку и узнать все подробности можно здесь.

#реклама Рекламодатель АНО «Школа 21». ИНН 7736316133
👎22👍1
День 2726. #TipsAndTricks
Исправляем Ошибку «Слишком длинное имя файла» в Windows для Git

Бывает, что при клонировании или извлечении репозитория в Windows Git выдаёт следующую ошибку:
error: unable to create file some/very/deeply/nested/path/to/a/file.ts: Filename too long (невозможно создать файл … Имя файла слишком длинное)

С вашим кодом всё в порядке, с репозиторием тоже. Проблема в Windows.

Что происходит?
В Windows существует ограничение на длину пути по умолчанию в 260 символов (печально известный MAX_PATH). Операции Git, создающие файлы с полным путём длиннее этого, завершаются с этой ошибкой. Репозитории с глубоко вложенной структурой папок (например, node_modules или сгенерированный код) постоянно сталкиваются с этой проблемой.

Решение: разрешить длинные пути в Git
В Git есть параметр конфигурации именно для этого: core.longpaths. У вас есть два способа установить его, в зависимости от ваших прав на машине.

Системно (требует прав администратора):
git config --system core.longpaths true


На уровне пользователя (не требуется прав администратора):
git config --global core.longpaths true


Если у вас нет прав администратора, версия --global применяется только к вашей учётной записи пользователя, но обычно этого достаточно, чтобы разблокировать доступ.

Примечание: это только указывает Git на необходимость поддержки длинных путей, но не решает всей проблемы. Для полноценной работы некоторых инструментов и API в Windows также требуется включение поддержки длинных путей на уровне ОС.

Включение длинных путей в Windows
Если вы хотите, чтобы длинные пути поддерживались на уровне всей системы (другие инструменты, Проводник и т. д.), вам также необходимо включить их в самой Windows. Откройте Group Policy Editor (Редактор Групповой Политики - gpedit.msc) — только для версий Enterprise/Pro. Перейдите в Computer Configuration > Administrative Templates > System > Filesystem (Конфигурация компьютера > Административные шаблоны > Система > Файловая система. Включите параметр Enable Win32 long paths (Включить длинные пути Win32).

Или через реестр, если Редактор Групповой Политики недоступен (например, Windows Home):
reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1

Чтобы изменение вступило в силу, потребуется перезагрузка.

Источник:
https://bartwullems.blogspot.com/2026/07/fixing-filename-too-long-errors-on.html
👍1
День 2727. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.


40. Проблемы микросервисной архитектуры
«Какие проблемы возникают при внедрении микросервисной архитектуры в приложениях .NET, и как бы вы их решили?»

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

1. Сложность координации сервисов: Микросервисы требуют сложной координации, поскольку каждый сервис разрабатывается, развёртывается и масштабируется независимо. Это может привести к проблемам с согласованностью данных и межсервисным взаимодействием. Для решения этой проблемы можно использовать инструменты оркестровки, такие как .NET Aspire во время локальной разработки и Kubernetes в производственной среде для управления развёртыванием и масштабированием сервисов. Поможет реализация Event-driven архитектуры или использование системы обмена сообщениями, такой как RabbitMQ, для обеспечения надежной связи между сервисами.

2. Управление данными: Каждый сервис управляет своей собственной базой данных, что может привести к проблемам с управлением транзакциями и согласованностью данных между сервисами. Для решения этой проблемы можно реализовать компенсирующие транзакции или паттерн Saga для управления согласованностью распределённых данных. В качестве альтернативы можно выбрать технологии баз данных, поддерживающие конечную согласованность, и использовать общие базы данных только тогда, когда необходима строгая согласованность.

3. Отладка и мониторинг: Поскольку каждый сервис работает независимо, выявление и диагностика сбоев может быть сложной. Для решения этой проблемы реализуют централизованное логирование и мониторинг с использованием таких инструментов, как OpenTelemetry, Prometheus и Grafana. Также используют идентификаторы корреляции для отслеживания запросов между сервисами на разных уровнях и между самими сервисами.

4. Проблемы безопасности: Микросервисы увеличивают площадь поверхности для потенциальных угроз безопасности, поскольку данные перемещаются через границы сети. Для решения этой проблемы используют защищённые соединения HTTPS и решения для управления идентификацией и доступом (Identity and Access Management - IAM), такие как IdentityServer, для аутентификации и авторизации между сервисами.

5. Сложность развёртывания: Независимое развёртывание сервисов может привести к проблемам с версионированием и совместимостью. Для решения этой проблемы можно использовать контейнеры Docker для согласованной упаковки и развёртывания сервисов. Требуется тщательно управлять версиями сервисов и использовать API-шлюзы для обработки запросов к соответствующим версиям сервисов.

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍7👎3
День 2728. #ЗаметкиНаПолях
Сравнение Чисел в .NET
Когда люди сравнивают числа, они обычно ожидают, что равенство означает одинаковые значения. Для чисел с плавающей точкой (Half, float и double) в .NET существуют два понятия равенства:
1. Семантика сравнения IEEE, используемая оператором ==,
2. Семантика эквивалентности для объектов .NET, используемая методом Equals.

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

Краткое сравнение
Для чисел Half, double и float важны следующие крайние случаи:
1. NaN и NaN
- == = false
- Equals = true
- CompareTo = 0

2. +0.0 и -0.0
- == = true
- Equals = true
- CompareTo = 0

3. +Infinity и +Infinity
- == = true
- Equals = true
- CompareTo = 0

4. -Infinity и +Infinity
- == = false
- Equals = false
- CompareTo < 0

5. NaN и конечное число
- == = false, a != = true
- Equals = false
- NaN.CompareTo(x) < 0

Оператор == соответствует правилам сравнения чисел с плавающей запятой из IEEE 754 / IEC 60559. Equals разработан для поддержки контрактов равенства .NET, используемых коллекциями и словарями.

Почему NaN == NaN ложно, а NaN.Equals(NaN) истинно?
NaN означает «Не число». Это специальное значение IEEE 754, используемое, когда операция не определена, например, деление на 0 (0.0 / 0.0).

IEEE 754 рассматривает NaN как неупорядоченное значение при сравнениях, т.е.:
- x == y ложно, если хотя бы одна сторона NaN;
- x < y, x > y, x <= y, x >= y ложно, если хотя бы одна сторона NaN;
- x != y истинно, если хотя бы одна сторона NaN;
Таким образом:
double x = double.NaN;

Console.WriteLine(x == x); // False
Console.WriteLine(x != x); // True
Console.WriteLine(x < 0); // False
Console.WriteLine(x >= 0); // False


Чтобы проверить, что значение является NaN, используйте API типа:
Half h = Half.NaN;
float f = float.NaN;
double d = double.NaN;

Console.WriteLine(Half.IsNaN(h)); // True
Console.WriteLine(float.IsNaN(f)); // True
Console.WriteLine(double.IsNaN(d)); // True

Но .NET также нуждается в понятии равенства, которое работает с контейнерами на основе хэшей. Если бы оператор Equals соответствовал стилю IEEE для NaN, это нарушило бы рефлексивность (x.Equals(x) должно быть true), и ключи, содержащие NaN, вели бы себя некорректно в словарях/хэшсетах. Поэтому Half.Equals, Double.Equals и Single.Equals обрабатывают NaN особым образом и возвращают true, когда оба значения являются NaN.

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

Источник:
https://www.meziantou.net/number-comparison-in-dotnet-equals-and-ieee-754-edge-cases.htm
👍6
День 2729. #ЗаметкиНаПолях
Сравнение Чисел в .NET. Окончание

Начало

+0.0 и -0.0: равны, но не идентичны
В стандарте IEEE 754 используется знаковый ноль. +0.0 и -0.0 сравниваются как равные, поэтому == и Equals возвращают true:
double pz = +0.0;
double nz = -0.0;

Console.WriteLine(pz == nz); // True
Console.WriteLine(pz.Equals(nz)); // True

Однако знак всё же имеет значение для некоторых операций:
Console.WriteLine(1.0 / +0.0); // +Infinity
Console.WriteLine(1.0 / -0.0); // -Infinity

Таким образом, «равенство» не всегда означает «взаимозаменяемость в каждом выражении».

Примечание: значения NaN также имеют знак. Такие методы, как float.IsNegative, возвращают true для -NaN.

CompareTo обеспечивает упорядочивание, включая обработку NaN
Операторы сравнения с NaN намеренно неудобны, поскольку NaN не упорядочен. Для сортировки .NET предоставляет возможность упорядочивания через CompareTo.

Для Half, double и float:
- NaN равно NaN;
- NaN меньше, чем не-NaN.
Это поведение явно закодировано в реализациях CompareTo в исходном коде среды выполнения.

Хэши нормализованы для NaN и знакового нуля
Еще один тонкий, но важный момент: GetHashCode() намеренно канонизирует значения. В методах Half.GetHashCode, Double.GetHashCode и Single.GetHashCode .NET гарантирует, что:
- все шаблоны битов NaN дают одинаковый хэш;
- +0.0 и -0.0 дают одинаковый хэш.
Это необходимо для согласованности с Equals, когда значения используются в качестве ключей.

Особый случай точности: значения, которые кажутся равными, могут быть не равными
Отдельным источником путаницы является представление. Многие десятичные значения не могут быть точно выражены в двоичном представлении с плавающей запятой, поэтому прямое сравнение на равенство часто не работает:
Console.WriteLine(0.1 + 0.2 == 0.3); // False

Тип decimal решает эту проблему, поскольку хранит целое число в десятичной системе счисления, а не дробь в двоичной системе. Такие значения, как 0.1m, 0.2m и 0.3m, точно воспроизводимы, поэтому:
Console.WriteLine(0.1m + 0.2m == 0.3m); // True

Точность decimal составляет от 28 до 29 значащих цифр. Внутри используется 96-битное целое число плюс коэффициент масштабирования (от 0 до 28 десятичных знаков) и знак.

Важные ограничения:
- decimal не является числом с произвольной точностью. Оно по-прежнему имеет конечный диапазон и может переполняться;
- Операции, которые дают более 28–29 значащих цифр, округляются;
- decimal относится к точному десятичному представлению, а не к поведению чисел с плавающей запятой по стандарту IEEE. В нём нет значений NaN или Infinity.

Для числовых алгоритмов сравнивайте с допуском (абсолютным + относительным), а не с Double.Epsilon:
static bool NearlyEqual(
double a,
double b,
double relTol = 1e-12,
double absTol = 1e-15)
{
if (a == b)
return true; // нули

if (double.IsNaN(a) || double.IsNaN(b))
return false;

if (double.IsInfinity(a) || double.IsInfinity(b))
return false;

double diff = Math.Abs(a - b);
double scale = Math.Max(Math.Abs(a), Math.Abs(b));
return diff <= Math.Max(absTol, relTol * scale);
}


Практические рекомендации
Используйте:
- ==, когда явно нужна семантика сравнения IEEE;
- Equals, когда нужна семантика равенства .NET (особенно в коллекциях);
- Half.IsNaN, double.IsNaN или float.IsNaN для проверки на NaN;
- сравнение с погрешностью для вычисленных результатов с плавающей запятой;
- decimal, когда нужны точные десятичные дроби (например, деньги) и когда достаточно 28-29 значащих цифр.

Итого
И ==, и Equals являются корректными. Они отвечают на разные вопросы:
- ==: «Равны ли эти значения согласно правилам сравнения чисел с плавающей запятой IEEE?»
- Equals: «Следует ли считать эти два значения .NET равными для контрактов равенства объектов?»
Как только вы разделите эти два намерения, крайние случаи, связанные с NaN, знаковым нулем и бесконечностью, станут предсказуемыми.

Источник:
https://www.meziantou.net/number-comparison-in-dotnet-equals-and-ieee-754-edge-cases.htm
👍4
День 2730. #Курсы
Курс «Модернизация .NET для Начинающих»

В Microsoft разработали курс, который поможет вам разобраться в процессе модернизации приложения, созданного на устаревшей платформе .NET. Инструментарий модернизации в GitHub Copilot поможет модернизировать ваш код .NET. А курс научит использовать эти инструменты.

https://github.com/microsoft/dotnet-modernization-for-beginners

Этот бесплатный, открытый, практический курс шаг за шагом проведёт через процесс модернизации реального устаревшего приложения ASP.NET до .NET 10 с использованием агента модернизации GitHub Copilot. В процессе обучения вы узнаете, как инструмент создаёт оценки и планы до внесения изменений в код, и как вы можете модифицировать их, чтобы точно указать агенту кодирования, что нужно сделать.

В этом курсе вы не просто будете читать теорию о модернизации с GitHub Copilot, но и получите практические упражнения (потребуется подписка на GitHub Copilot).

Модернизация в GitHub Copilot работает иначе, чем ИИ-агенты, с которыми вы, возможно, работали раньше. Она не просто переписывает ваш код за кулисами и возвращает вам то, что нужно будет править. Она создаёт прозрачные, редактируемые артефакты, которые вы можете читать, задавать по ним вопросы и корректировать: assessment.md, plan.md и tasks.md. Эти файлы становятся вашим руководством и источником истины. Вы можете их просмотреть, отредактировать, и только потом начинается работа. Агент помогает, но вы принимаете решения.

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

Глава 1 – Оценка
Вы начинаете с того, что указываете агенту модернизации на устаревшее решение и позволяете ему проанализировать существующий код. Вы изучаете сгенерированный файл assessment.md, чтобы понять риски, зависимости и уровень необходимых усилий.

Глава 2 – Планирование
Далее вы превращаете эти данные в план. Агент создает файл plan.md с рекомендуемыми целевыми платформами, последовательностью шагов обновления и оценкой трудозатрат. Вы научитесь проверять его и настраивать под свой контекст.

Глава 3 – Обновление и выполнение
Здесь происходит агентская разработка. Агент выполняет план, а вы отслеживаете прогресс в файле tasks.md. Вы увидите, как система выполняет основную работу, пока вы принимаете важные решения.

Глава 4 – Azure
Модернизация не завершена, пока ваше приложение не будет работать там, где ему положено. В заключительной главе вы публикуете модернизированное приложение в Azure App Service и рассматриваете следующие шаги по его эксплуатации и развитию, чтобы завершить процесс созданием работающего приложения, а не просто компилируемого.

Вам потребуется Visual Studio 2022 (17.10 или более поздняя версия) или Visual Studio 2026 с подпиской GitHub Copilot.
Клонируйте репозиторий и начните с введения в главе 0.

Источник: https://devblogs.microsoft.com/dotnet/announcing-dotnet-modernization-for-beginners/
👍3👎1
День 2731. #TipsAndTricks
Добавление Статического Свойства Благодаря Расширениям
Вот простой сценарий: у вас есть StringComparer, и вы хотите добавить свой собственный компаратор — например, порядок «естественной сортировки», чтобы «file2» сортировался перед «file10», а не после.

Начиная с C#14 мы можем расширить семейство компараторов и сделать это естественным!

Пре-C#14
Итак, мы хотим добавить и использовать что-то вроде:
public class NaturalSortComparer : StringComparer
{
public override int Compare(string? x, string? y)
=> …
public override bool Equals(string? x, string? y)
=> …
public override int GetHashCode(string obj)
=> …
}


До C#14 у нас были только методы расширения, поэтому мы могли сделать только что-то такое:
public static class StringComparerExtensions
{
public static StringComparer NaturalSort(
this StringComparer comparer)
=> new NaturalSortComparer();
}

Что было бы странно использовать: StringComparer.NaturalSort();. Если вы посмотрите на аналогичные члены, вроде StringComparer.Ordinal или StringComparer.OrdinalIgnoreCase, то это не совсем то, что мы бы хотели.

В качестве альтернативы мы могли создать отдельный класс:
public static class NaturalSort
{
public static StringComparer Instance { get; }
= new NaturalSortComparer();
}

// Использование:
var files = Directory.GetFiles(path)
.OrderBy(f => f, NaturalSort.Instance);

Тоже выглядит не совсем естественно.

C#14
«Расширенные» (простите за каламбур) расширения в C#14 могут также добавлять статические члены, не только методы, но и свойства. Кроме того, в перечислении CompareOptions был добавлен элемент NumericOrdering для натуральной сортировки. Поэтому мы можем создать такое статическое свойство-расширение:
public static class StringComparerExtensions
{
extension(StringComparer)
{
public static StringComparer NaturalSort =>
StringComparer.Create(
CultureInfo.CurrentCulture,
CompareOptions.NumericOrdering
);
}
}


Теперь мы можем использовать его так, как и хотелось:
var files = Directory.GetFiles(path)
.OrderBy(f => f, StringComparer.NaturalSort);


Источник: https://steven-giesel.com/blogPost/605274d2-719b-4b1f-b0d5-3ad9001d9b56/adding-static-getter-thanks-to-extensions
👍11
День 2732. #ЗаметкиНаПолях
Решаем Проблему Паники в Кэше
В приложении ASP.NET Core API кэшировался довольно ресурсоёмкий отчёт на 60 секунд. При обычной нагрузке всё было нормально: один запрос перестраивал кэш, все остальные читали из него. При пиковой нагрузке десятки запросов поступали одновременно, все видели промах кэша и параллельно выполняли один и тот же ресурсоёмкий запрос. Слой кэширования не помогал. Это называется «паникой в кэше» (cache stampede).

Почему это происходит
IMemoryCache.GetOrCreate (и его асинхронный аналог) сами по себе не добавляют блокировок:
public async Task<Report> GetReportAsync(string key)
{
if (_cache.TryGetValue(key, out Report cached))
return cached;

var report =
await _service.BuildReportAsync(key);

_cache.Set(key, report, TimeSpan.FromSeconds(60));

return report;
}

Если 10 запросов придут одновременно после истечения срока действия кэша, для всех TryGetValue вернёт false, и все они вызовут BuildReportAsync. Это не специфично для IMemoryCache. Распределённые кэши, такие как Redis, имеют ту же проблему. Сам кэш не знает и не заботится о том, что параллельные вызывающие процессы собираются запросить тот же ключ.

Решение 1: блокировка для каждого ключа с помощью SemaphoreSlim
Самое простое решение — заставить одновременные вызывающие процессы для одного и того же ключа ждать завершения первого:
private static readonly 
ConcurrentDictionary<string, SemaphoreSlim> _locks = new();

public async Task<Report> GetReportAsync(string key)
{
if (_cache.TryGetValue(key, out Report cached))
return cached;

var keyLock = _locks.GetOrAdd(key,
_ => new SemaphoreSlim(1, 1));

await keyLock.WaitAsync();
try
{
// Перепроверка: другой запрос мог уже
// задать значение, пока мы ждали семафора
if (_cache.TryGetValue(key, out cached))
return cached;

var report = await _service.BuildReportAsync(key);
_cache.Set(key, report, TimeSpan.FromSeconds(60));
return report;
}
finally
{
keyLock.Release();
}
}

Примечание: ConcurrentDictionary<string, SemaphoreSlim> будет бесконечно расти, если его не чистить. Для небольшого набора ключей это нормально. Иначе - удаляйте неиспользуемые семафоры.

Решение 2: HybridCache сделает это за вас
Microsoft.Extensions.Caching.Hybrid.HybridCache координирует одновременные вызовы для одного и того же ключа, так что одновременно выполняется только один вызов:
// регистрация
services.AddHybridCache(options =>
{
opts.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromSeconds(60),
LocalCacheExpiration = TimeSpan.FromSeconds(60)
};
});

// использование
public async Task<Report> GetReportAsync(string key)
{
return await _cache.GetOrCreateAsync(
key,
async ct => await _service.BuildReportAsync(key, ct),
cancellationToken: default);
}

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

Решение 3: не допускать одновременного истечения срока действия всего кэша
Даже при блокировке по ключу, проблема может возникать, если срок действия множества разных ключей истекает одновременно. Решение - добавить разброс (jitter):
var jitter = TimeSpan.FromSeconds(Random.Shared.Next(0, 10));

_cache.Set(key, report, TimeSpan.FromSeconds(60) + jitter);


Источник:
https://steven-giesel.com/blogPost/605274d2-719b-4b1f-b0d5-3ad9001d9b56/adding-static-getter-thanks-to-extensions
👍10
День 2733. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

41. Контейнеры Docker и .NET
«Расскажите, как Docker можно использовать для улучшения разработки, тестирования и развёртывания приложений .NET. Как вы бы настроили и развернули приложение .NET с использованием Docker».

Хороший ответ
Использование Docker значительно улучшает разработку, тестирование и развёртывание, обеспечивая согласованную среду от разработки до производства. Это устраняет проблему «работает на моей машине», когда ошибки не возникают в среде разработки, но появляются в производственной среде из-за разных настроек среды или разного установленного программного обеспечения. Контейнеризация также способствует более надёжному развёртыванию. Вот как Docker можно интегрировать с приложением .NET.

1. Начнём с создания файла Dockerfile в корне проекта .NET. Этот файл описывает процесс сборки образа Docker и указывает среду, в которой работает приложение:
# Используем официальный образ SDK .NET 10 от Microsoft
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build-env
WORKDIR /app

# Копируем csproj и восстанавливаем зависимости
COPY *.csproj ./
RUN dotnet restore

# Копируем файлы проекта и публикуем
COPY . ./
RUN dotnet publish -c Release -o out

# Создаём образ среды выполнения
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "MyApp.dll"]


2. Создадим Docker-образ, используя Dockerfile:
docker build -t myapp:latest .


3. Запустим приложение в контейнере Docker:
docker run -d -p 8080:80 --name myrunningapp myapp:latest


4. Используем Docker Compose для управления многоконтейнерными приложениями в Docker. Здесь мы определяем сервисы, параметры сети и дисковые тома в файле docker-compose.yml:
version: '3.4'

services:
webapp:
image: myapp:latest
build:
context: .
dockerfile: Dockerfile
ports:
- "5000:80"
environment:
ASPNETCORE_ENVIRONMENT: Development
volumes:
- .:/app
- ~/.aspnet/https:/https:ro

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

Преимущества
- Согласованность: Docker гарантирует, что приложение работает одинаково во всех средах.
- Изоляция: Каждая часть приложения может содержаться в отдельных контейнерах, позволяя явно управлять зависимостями.
- Масштабируемость: можно легко масштабировать приложение, изменяя количество контейнеров, в которых оно работает, с помощью инструментов оркестрации, таких как Kubernetes.

Использование Docker упрощает процесс разработки приложений .NET, повышает совместимость с производственной средой и улучшает общую масштабируемость и управляемость приложения.

Источник:
https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍5👎3