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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2615. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

28. Методы оценки
«Можете ли вы описать методы оценки, которые вы использовали в своих проектах, и объяснить, как они помогли вам обеспечить выполнение проектов в установленные сроки?»

Хороший ответ
В моём опыте управления проектами .NET я использовал комбинацию методов оценки для обеспечения точного прогнозирования и эффективного управления проектом. Среди них экспертная оценка (Expert Judgment), покер планирования (Planning Poker) и анализ точек использования (Case Point Analysis).

1. Экспертная оценка предполагает консультации с опытными членами команды для получения оценок на основе их прошлого опыта. Например, при работе над веб-сервисом, включающим ASP.NET Core и Entity Framework Core, я обсуждал задачи со старшими разработчиками, имеющими аналогичный опыт работы над проектами, чтобы получить реалистичное представление о трудозатратах и сроках.

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

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

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

Часто встречающийся плохой ответ
«Я примерно по опыту оцениваю время, которое потребуется на написание кода и добавляю к нему некоторый процент на непредвиденные проблемы. Обычно этого достаточно.»

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

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

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👍3
День 2616. #SQLServer
SQL Server Незаметно Переименовывает Пользователя
Автор оригинала: Bart Wullems

Учитывая кучу существующих ИИ-помощников, можно было бы ожидать, что мы больше не будем тратить время на глупые проблемы. К сожалению, до этого ещё далеко. Сегодня я потерял немало времени из-за поведения SQL Server, которое кажется очевидным, если вы об этом знаете, а если не знаете —ужасно раздражает.

У меня был скрипт, который должен был быть идемпотентным: создать пользователя БД, если его не существует, или обновить его, если существует. Стандартная процедура. Вот упрощённая версия:
USE [ONT_SampleDB];
GO
IF EXISTS (SELECT * FROM sys.database_principals WHERE name = N'usr_DB_reader')
BEGIN
ALTER USER [usr_DB_reader] WITH LOGIN = [lg_DB_dev_reader];
END
ELSE
BEGIN
CREATE USER [usr_DB_reader] FOR LOGIN [lg_DB_dev_reader];
END
GO
ALTER ROLE [db_datareader] ADD MEMBER [usr_DB_reader];
GO

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

Что происходит на самом деле?
Когда вы запускаете ALTER USER […] WITH LOGIN = […], SQL Server переименовывает пользователя в соответствии с логином — по умолчанию, без предупреждений.

Таким образом, после первого запуска пользователя usr_DB_reader больше не существует. Он переименован в lg_DB_dev_reader, чтобы соответствовать логину. При втором запуске проверка IF EXISTS ищет usr_DB_reader, ничего не находит и переходит в ELSE — где CREATE USER завершается с ошибкой, потому что такой логин уже сопоставлен с пользователем c другим именем.

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

Решение
Явно укажите SQL Server сохранить имя пользователя как есть, добавив NAME = к оператору ALTER USER:
ALTER USER [usr_DB_reader]
WITH NAME = [usr_DB_reader],
LOGIN = [VLM\lg_DB_dev_reader];

Добавление WITH NAME = [usr_DB_reader] говорит SQL Server: да, измени логин, но не трогай имя пользователя. Теперь скрипт действительно идемпотентный.

Почему SQL Server так делает?
SQL Server различает логины (субъекты уровня сервера) и пользователей (субъекты уровня БД). Когда вы связываете пользователя с логином с помощью команды ALTER USER … WITH LOGIN, не указывая имя, SQL Server предполагает, что вы хотите синхронизировать их — поэтому он переименовывает пользователя в соответствии с логином. Это следует соглашениям, но это не то, чего вы ожидаете, если привыкли рассматривать имена пользователей как стабильные идентификаторы.

Источник: https://bartwullems.blogspot.com/2026/03/sql-server-silently-renames-your-user.html
👍4
День 2617. #Шпаргалка
Виды Классов в C#

Сегодня рассмотрим различные виды классов в C# и как они работают.

Абстрактный
Базовый класс, экземпляр которого нельзя создать. Он может содержать абстрактные и неабстрактные члены и предназначен для наследования от него.
public abstract class Vehicle
{
public abstract int Wheels { get; }
public abstract void TurnOn();
public bool Started { get; protected set; }
}

Создать экземпляр класса Vehicle нельзя, поэтому унаследуем от него. При этом класс-наследник обязан переопределить абстрактные члены:
public class Car : Vehicle
{
public override int Wheels => 4;
public override void TurnOn()
=> Started = true;
}


Запечатанный (sealed)
Специальный тип класса, который ограничивает иерархию наследования. Это предотвращает создание производных типов, что повышает безопасность кода и позволяет компилятору применять оптимизации производительности.
public sealed class Vehicle
{
}


Статический
Экземпляр статического класса нельзя создать, и от статического класса нельзя унаследовать. Все члены должны быть помечены статическими.
public static class SpeedConverter
{
public static decimal ToMph(decimal kph)
=> return kph / 1.6093m;
}


Частичный
В одном и том же пространстве имён нельзя создавать несколько классов с одним именем. Частичный класс позволяет разделить объявление класса на несколько файлов. При компиляции объявления объединяются в один класс. Нельзя дублировать члены с одной сигнатурой в разных объявлениях частичного класса:
public partial class Team
{
public Team() { }
public string Name { get; set; }
}

public partial class Team
{
public int Players { get; set; }
}


Небезопасный
Небезопасный класс позволяет использовать код с указателями:
public unsafe class MemoryReader
{
public void Read(int* value)
{
Console.WriteLine(*value);
}
}

Если вам необходимо работать с указателями, нужно включить небезопасный код в файле .csproj, установив параметр AllowUnsafeBlocks в значение true.

Запись
Запись — ссылочный тип, предназначенный для данных, а не для поведения, и по умолчанию является неизменяемой:
public record class Team(string Name, int Players);

См. подробнее про записи.

Модификаторы доступа
- public – доступен отовсюду;
- internal – доступен внутри сборки;
- private – доступен только внутри включающего его типа;
- protected – доступен внутри включающего его типа и всех его наследников;
- file – доступен только внутри содержащего его файла исходного кода.

Источник: https://www.roundthecode.com/dotnet-tutorials/c-sharp-class-types-explained-examples
👍15👎5
День 2618. #МоиИнструменты #PG
Инструменты Оптимизации Запросов в PostgreSQL. Часть 5


5. EXPLAIN ANALYZE (для всех SQL баз данных)
Что даёт: точное понимание, как БД выполняет запрос.
Тип: встроенная команда (для всех основных БД)
Зачем: показывает план выполнения запроса — как БД фактически обрабатывает SQL-запрос. Без этого оптимизация — это гадание.

Использование в разных базах данных:
-- PostgreSQL
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT … FROM orders WHERE …;

-- MySQL
EXPLAIN ANALYZE
SELECT … FROM orders WHERE …;

-- SQL Server
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT … FROM orders WHERE …;


Вывод (пример Postgres):
EXPLAIN (ANALYZE, BUFFERS) 
SELECT * FROM orders
WHERE customer_id = 12345
AND order_date >= '2024-01-01';


Seq Scan on orders  (cost=0.00..185234.25 rows=1 width=120) 
(actual time=0.045..2341.234 rows=247 loops=1)
Filter: ((customer_id = 12345) AND (order_date >= '2024-01-01'::date))
Rows Removed by Filter: 9847234
Buffers: shared hit=47234 read=138000
Planning Time: 0.234 ms
Execution Time: 2341.567 ms

Что это значит:
- "Seq Scan" – полное сканирование таблицы (ПЛОХО – не используется индекс)
- "Rows Removed by Filter: 9847234" - Просканировано 9.8млн строк, возвращено 247
- "Execution Time: 2341ms" - 2.3 секунды

Решение – добавить индекс на поля customer_id и order_date:
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);

После:
Index Scan using idx_orders_customer_date on orders  
(cost=0.43..8.45 rows=1 width=120)
(actual time=0.023..0.087 rows=247 loops=1)
Index Cond: ((customer_id = 12345) AND (order_date >= '2024-01-01'::date))
Buffers: shared hit=5
Planning Time: 0.123 ms
Execution Time: 0.112 ms

Что это значит:
- "Index Scan" – Используется индекс (ХОРОШО)
- "Buffers: shared hit=5" – только 5 блоков прочитано (против 185234 в предыдущем случае)
- "Execution Time: 0.112ms" – в 20000 раз быстрее

Основные шаги оптимизации запросов
1. Выполнить EXPLAIN ANALYZE
2. Обнаружить узкое место (seq scan, дорогой join или сортировку и т.п.)
3. Исправить (добавить индекс, переписать запрос, изменить порядок объединения)
4. Ещё раз выполнить EXPLAIN ANALYZE для проверки

Когда использовать
- Анализ любого медленного запроса;
- Перед написанием сложных запросов (прогноз производительности);
- После изменений схемы (проверка влияния);
- При добавлении индексов (обоснование использования).

Когда отказаться
Особых причин не использовать нет.

Скрытая функция
Сравнение планов запросов
1. Сохранить в файл
psql -c "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT …" > plan_before.json

2. Сделать изменения
3. Сохранить новый план
psql -c "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT …" > plan_after.json

4. Использовать утилиту визуализации плана:
- https://explain.dalibo.com/
- https://explain.depesz.com/
5. Использовать утилиту визуального сравнения (например, https://winmerge.org/)

С осторожностью
EXPLAIN с параметром ANALYZE на самом деле выполняет запрос.
-- Удалит данные!!!
EXPLAIN ANALYZE DELETE FROM orders WHERE …;

-- Безопасное тестирование
BEGIN;
EXPLAIN ANALYZE DELETE FROM orders WHERE …;
ROLLBACK;
END;

-- Либо без ANALYZE (только оценка)
EXPLAIN DELETE FROM orders WHERE …;


Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
👍17
День 2619. #ЗаметкиНаПолях
Паттерн Адаптер: Упрощаем Интеграцию со Сторонними Сервисами. Начало

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

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

Сценарий
Есть приложение, использующее следующий интерфейс:
public interface IPaymentProcessor
{
void ProcessPayment(decimal amount);
}

Всё приложение использует эту абстракцию. Однако, есть существующий сервис, производящий оплату:
public class LegacyPaymentService
{
public void MakePayment(string amount)
{
// обработка оплаты
}
}

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

Решение
1. Создаём адаптер
public class PaymentAdapter(
LegacyPaymentService legacyService) :
IPaymentProcessor
{
public void ProcessPayment(decimal amount)
{
var amntStr = amount.ToString("F2");
legacyService.MakePayment(amntStr);
}
}

Адаптер:
- реализует интерфейс приложения (IPaymentProcessor);
- преобразует десятичное число в строку;
- делегирует вызов устаревшему сервису.
Приложение остаётся чистым и не знает об устаревшей реализации.

2. Используем адаптер
LegacyPaymentService legacySvc = new();
IPaymentProcessor processor =
new PaymentAdapter(legacySvc);
processor.ProcessPayment(123.4567868m);


Результат:
- Приложение использует только IPaymentProcessor;
- Адаптер обрабатывает все преобразования;
- Устаревшая система полностью изолирована.

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

Источник:
https://thecodeman.net/posts/simplifying-integration-with-adapter-pattern
👍18
День 2620. #ЗаметкиНаПолях
Паттерн Адаптер: Упрощаем Интеграцию со Сторонними Сервисами. Окончание

Начало

Пример из реальной жизни: интеграция с облачным хранилищем
Представьте себе систему, поддерживающую несколько облачных провайдеров:
- Amazon S3
- Azure Blob Storage
- Google Cloud Storage
У каждого провайдера свои SDK и API. Без адаптера код становится тесно связанным с конкретной реализацией.

1. Определение общего интерфейса
public interface ICloudStorage
{
Task UploadAsync(string container,
string file, Stream stream);
Task<Stream> DownloadAsync(
string container, string file);
Task DeleteAsync(string container,
string file);
}

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

2. Реализуем адаптер (пример для Google Cloud)
public class GoogleCloudStorageAdapter 
: ICloudStorage
{
private readonly StorageClient _client;

public GoogleCloudStorageAdapter(
StorageClient client)
{
_client = client;
}

public async Task UploadAsync(
string container, string file, Stream stream)
{
await _client.UploadObjectAsync(
container, file, null, stream);
}

public async Task<Stream> DownloadAsync(
string container, string file)
{
MemoryStream stream = new();

await _client.DownloadObjectAsync(
container, file, stream);
stream.Position = 0;

return stream;
}

public async Task DeleteAsync(
string container, string file)
{
await _client.DeleteObjectAsync(container, file);
}
}

Адаптер:
- оборачивает SDK от Google,
- приводит вызовы к вашему интерфейсу,
- скрывает детали реализации.

3. Настройка внедрения зависимостей
builder.Services
.AddTransient<Func<string, ICloudStorage>>(
sp => provider =>
{
return provider switch
{
"Azure" => sp.GetRequiredService<AzureBlobStorageAdapter>(),
"Google" => sp.GetRequiredService<GoogleCloudStorageAdapter>(),
"AWS" => sp.GetRequiredService<S3StorageAdapter>(),
_ => throw new ArgumentException("Неподдерживаемый провайдер")
};
});

Пояснение
- адаптер выбирается динамически,
- обеспечивается гибкость системы,
- есть возможность переключаться во время выполнения.

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

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

Источник: https://thecodeman.net/posts/simplifying-integration-with-adapter-pattern
👍11
День 2621. #ЧтоНовенького #МоиИнструменты
Bookmark Studio Закладки на Стеройдах в Visual Studio

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

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

Bookmark Studio - новое экспериментальное расширение Visual Studio от Мэдса Кристенсена, которое развивает существующий опыт работы с закладками. Вот его основные функции.

1. Навигация по слотам. Закладки можно назначать слотам с 1 по 9 и переходить к ним напрямую с помощью простых сочетаний клавиш, таких как Alt+Shift+1Alt+Shift+9. Это делает закладки более продуманными и удобными для быстрого доступа к нескольким важным разделам. Новые закладки автоматически назначаются следующему доступному слоту, если это возможно, поэтому быстрая навигация часто работает без дополнительной настройки. Bookmark Studio также интегрируется с существующими командами закладок Visual Studio, т.е. текущие сочетания клавиш продолжат работать.

2. Менеджер закладок (см. картинку). Отображает все закладки в одном месте и упрощает просмотр, поиск и навигацию между ними. Вы можете фильтровать по имени, файлу, местоположению, цвету или слоту и переходить непосредственно к закладке двойным щелчком или навигацией с помощью клавиатуры. Он разработан для того, чтобы упростить повторное обращение к закладкам, особенно при переключении контекста или возвращении к коду позже.

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

4. Экспорт. Закладки часто наиболее ценны, когда они отражают намерение, а не просто местоположение. Bookmark Studio позволяет легко экспортировать закладки в виде обычного текста, Markdown или CSV. Т.е. вы можете включать закладки в пул-реквесты, делиться с коллегами или перемещать полезные наборы закладок между репозиториями.

5. Отслеживание. Bookmark Studio отслеживает закладки по мере перемещения текста во время редактирования, поэтому они остаются прикрепленными к соответствующему коду, а не смещаются на неправильную строку.

Итого
Если вы уже используете закладки в Visual Studio, Bookmark Studio покажется вам знакомым за считанные минуты. А если вы когда-либо хотели, чтобы закладки могли делать немного больше, это расширение стоит посмотреть.

Источник: https://devblogs.microsoft.com/visualstudio/bookmark-studio-evolving-bookmarks-in-visual-studio/
👍8
День 2622. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

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

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

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

- Проверки кода: внедрение надёжного процесса проверки кода имеет жизненно важное значение. Мы используем пул-реквесты, которые не только облегчают коллегиальную проверку, но и интегрируют проверки в конвейер CI/CD, где код автоматически собирается и запускаются тесты. Этот процесс гарантирует, что код соответствует стандартам качества до слияния, и способствует обмену знаниями и наставничеству внутри команды.

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

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

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

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

Часто встречающийся плохой ответ
«Пока каждый выполняет свою работу и соблюдает сроки, нет особой необходимости в дополнительном сотрудничестве или частых встречах. Мы просто отправляем дневные отчёты через e-mail или общаемся в чатах, чтобы держать всех в курсе».

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

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

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
День 2623. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Начало

Путь от провала на собеседовании до проектирования масштабируемых производственных систем.
Автор оригинала: Soma Sharma

Собеседование, которое изменило всё
Google. Второй раунд. Собеседование по системному проектированию: «Спроектируйте ленту Instagram для 2 миллиардов пользователей». У меня в голове всё помутнело. Я начал мямлить что-то о REST API. Упомянул MySQL. Поговорил о кэшировании… А потом тишина. Я понятия не имел, как это делать.
Шесть месяцев спустя я успешно прошёл собеседования по проектированию систем в Meta, Amazon и Netflix. Вот путь от полной растерянности до уверенного в себе системного проектировщика.

1. Я признал, что ничего не знал (и это нормально)
Годами я избегал проектирования систем. Я видел видео под названием «Разрабатываем Твиттер» и думал: «Это для архитекторов, а не для разработчиков, как я». Нет. Проектирование систем — это для всех, кто хочет понять, как ПО работает в масштабе. Я перестал спрашивать: «Достаточно ли я умён для этого?» и начал спрашивать: «Что нужно изучить в первую очередь?»

Проектирование систем — это набор концепций:
1) Инфраструктура:
- Как запросы проходят через интернет;
- Что происходит, когда вы вводите URL-адрес;
- DNS, балансировщики нагрузки, CDN.

2) Данные:
- Где хранить различные типы данных;
- Как сделать БД быстрыми;
- Когда использовать SQL, а когда NoSQL.

3) Масштабирование:
- Обработка 1 тыс пользователей против 1 млн;
- Стратегии кэширования;
- Горизонтальное и вертикальное масштабирование.

4) Надёжность:
- Что происходит при сбоях серверов;
- Как избежать единой точки отказа;
- Стратегии резервного копирования и восстановления.

2. Я разбил «проектирование систем» на части
1) Основы (недели 1–2)
Что происходит, когда вы набираете google.com?
- Поиск DNS (домен → IP-адрес);
- Установление TCP-соединения;
- Отправка HTTP-запроса;
- Обработка запроса сервером;
- Отправка ответа;
- Отображение страницы браузером.

Изученные концепции:
- DNS (телефонная книга интернета);
- Балансировка нагрузки (распределение трафика);
- CDN (сети доставки контента);
- Протоколы HTTP/HTTPS.

2) Данные и хранилища (недели 3–4)
SQL или NoSQL — когда что использовать?
Изученные концепции:
- SQL: структурированные данные, связи, ACID-транзакции;
- NoSQL: гибкая схема, горизонтальное масштабирование, итоговая согласованность;
- Индексирование: ускорение запросов в 100 раз;
- Репликация: копирование данных для повышения надёжности;
- Шардинг: разделение данных между несколькими БД.
Вывод: нет «лучшей» БД, есть подходящая для конкретного случая.

3) Методы масштабирования (недели 5–6)
Как системы обрабатывают миллионы пользователей?
- Вертикальное масштабирование: большие серверы (ограниченно, дорого);
- Горизонтальное масштабирование: больше серверов (неограниченно, сложно).

Кэширование:
- Хранение часто используемых данных в памяти (Redis, Memcached);
- 90% запросов попадают в кэш, а не в БД;
- Ускоряет работу систем в 100 раз.

Балансировка нагрузки:
- Распределение запросов между несколькими серверами: по очереди, по наименьшему количеству соединений, по хэшу IP;
- Предотвращает перегрузку какого-либо отдельного сервера.

Очереди сообщений:
- Разделение сервисов;
- Обработка пиковых нагрузок;
- Асинхронная обработка задач.

4) Архитектурные шаблоны (недели 7–8)
Монолитная архитектура:
Простота разработки и развёртывания;
Лёгкость понимания;
Сложность масштабирования отдельных частей независимо друг от друга;
Сбой в одном месте ведёт к сбою всей системы.

Микросервисы:
Независимое масштабирование компонентов;
Разные команды отвечают за разные сервисы;
Сложное развёртывание;
Проблемы распределённых систем.

Архитектура, управляемая событиями:
- Сервисы взаимодействуют посредством событий;
- Слабая связанность;
- Kafka, RabbitMQ для передачи сообщений.

Архитектура — это не вопрос «что лучше», а вопрос «что подходит для вашего масштаба и команды».

Продолжение следует…

Источник:
https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
👍16
День 2624. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Продолжение

Начало

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

Обучающие видео учат вас, что делать. Пробные собеседования учат, как думать. Кандидаты на пробных собеседованиях:
- Задавали уточняющие вопросы;
- Совершали ошибки и исправляли их;
- Обсуждали компромиссы;
- Меняли подходы в процессе проектирования.
Я научился процессу, а не только ответам.

Что я узнал, наблюдая?
1) Всегда задавайте уточняющие вопросы
- Каков масштаб? (1000 пользователей? 1 миллиард?)
- Каковы основные функции?
- Что важнее: скорость или стабильность?
- Для чего мы оптимизируем (время или размер)?

2) Чётко определите требования
Функциональные:
- Публикация фото;
- Подписка на других;
- Просмотр ленты.

Нефункциональные:
- 100 миллионов активных пользователей в день;
- Загрузка ленты менее чем за 200 мс;
- 99,9% времени безотказной работы;
- 10 миллионов фотографий загружается ежедневно.

3) Всегда обсуждайте компромиссы
Никогда не говорите: «Мы будем использовать Redis для кэширования».
Говорите: «Мы будем использовать Redis для кэширования, потому что он работает в оперативной памяти (быстро), но нам понадобится стратегия резервного копирования, поскольку данные изменчивы».

4. Я начал рисовать (хотя не умею)
Неожиданное открытие: эскизы помогли во всём разобраться.
Я не художник. Мои диаграммы — это просто прямоугольники и стрелки. Но рисунок «клиент → балансировщик нагрузки → серверы приложений → база данных» позволяет наглядно представить абстрактные понятия.

1) Поток запроса стал реальным
Когда я нарисовал путь запроса пользователя, стало:
- Видно, где возникают узкие места;
- Понятно, почему помогает кэширование;
- Очевидны точки отказа.

2) Компоненты обрели смысл
Рисование поможет понять:
- Зачем размещать кэш между приложением и БД;
- Где разместить очередь сообщений;
- Когда следует разделять на микросервисы.

3) Компромиссы стали очевидны
Рисование двух подходов рядом покажет:
- Какой дизайн проще;
- Какой масштабируется лучше;
- Какой стоит дороже.

Процесс рисования
1) Нарисуйте счастливый сценарий (всё работает);
2) Добавьте сценарии сбоев (что ломается?);
3) Добавьте решения (кэширование, репликация, балансировка нагрузки);
4) Оцените показатели (запросы в секунду, хранилище, пропускная способность).

Даже сегодня, когда я застреваю, я беру бумагу и ручку. 5-минутный набросок часто даст больше ясности, чем 2 часа чтения.

Продолжение следует…

Источник:
https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
👍9
День 2625. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Продолжение

Начало
Продолжение

5. Я проектировал реальные системы (на бумаге, а затем в коде)
Теория без практики бесполезна. Освоив основы, я перестал просто наблюдать. Я начал проектировать системы. Каждое воскресенье я выбирал реальную систему: WhatsApp, YouTube, Uber, Instagram, TinyURL, Netflix, Twitter, Dropbox.

Шаблон разработки системы
1) Функциональные требования (Что нужно делать?)
Пример (Twitter):

- Публиковать твиты (280 символов);
- Подписываться на пользователей;
- Просматривать ленту (твиты от людей, на которых вы подписаны);
- Лайки/ретвиты.

2) Нефункциональные требования (Масштабируемость, производительность, доступность)
- 300 млн активных пользователей;
- 500 млн твитов в день;
- Загрузка ленты <300 мс;
- 99,95% времени безотказной работы.

3) Оценка (приблизительные расчёты)
Хранилище:
- 500 млн твитов в день × 280 символов × 365 дней = 51 ТБ/год (только текст);
- Фотографии: 200 млн/день × 200 КБ = 40 ТБ/день;
- Видео: отдельный расчёт.

Трафик:
500 млн твитов/день ÷ 86400 секунд = ~5800 твитов/секунду.
Пик: 5x среднее значение = 29000 твитов/секунду

4) Высокоуровневый дизайн (нарисуйте архитектуру)
Клиент (веб/мобильное приложение) -> Балансировщик нагрузки -> API-серверы (сервис твитов, сервис ленты, сервис пользователей) -> Кэш (Redis) + БД (PostgreSQL, Cassandra) -> Объектное хранилище (S3 для медиафайлов).

5) Детальный дизайн (подробный анализ компонентов)
Как сгенерировать ленту?
- Подход 1: Пул-модель (интенсивное чтение)
Когда пользователь открывает приложение, запросить все подписки, получить последние твиты, объединить и отсортировать по времени.
Проблема: медленно для пользователей, подписанных на тысячи человек.

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

- Подход 3: Гибридный
Пул для твитов знаменитостей. Пуш для обычных пользователей.

6) Масштабирование и оптимизация
- Добавить кэширование (Redis);
- Шардирование базы данных (по user_id);
- Асинхронная обработка (очереди сообщений);
- CDN для медиафайлов.

7) Обработка сбоев
- Что если БД выйдет из строя? (Репликация)
- Что если сервер приложения выйдет из строя? (Проверки работоспособности балансировщика нагрузки)
- Что если кэш выйдет из строя? (стратегия Cache-aside).

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

Продолжение следует…

Источник:
https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
👍21
День 2626. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Продолжение

Часть 1
Часть 2
Часть 3

6. Применение в работе (реальные системы и ограничения)
Теория бесполезна, пока вы не создадите что-то реальное. На работе я был в команде, разрабатывавшей систему обработки платежей:
- Большой объём транзакций;
- Требуется строгая согласованность данных;
- Нулевая терпимость к потере данных;
- Сложная бизнес-логика.

Что я применил
1) Архитектурное решение - разделить монолит на сервисы:
- платежи;
- уведомления;
- бухгалтерия;
- обнаружение мошенничества.
Почему: Каждый сервис имеет разные потребности в масштабировании.

2) Стратегия коммуникаций:
- Синхронная: REST API для операций, ориентированных на пользователя.
- Асинхронная: Kafka для внутренних событий:
Платеж обработан
→ Уведомить пользователя;
→ Обновить учётную запись;
→ Провести проверку на мошенничество.

3) Согласованность данных:
Проблема: деньги не могут быть дублированы или потеряны.
Решение:
- Ключи идемпотентности (предотвращение дублирования транзакций);
- Паттерн Saga для распределённых транзакций;
- Паттерн Источники Событий для аудита.

4) Надёжность:
- Прерыватели цепи (предотвращение каскадных сбоев)
- Повторные попытки с экспоненциальной задержкой;
- Очереди недоставленных сообщений.

5) Мониторинг:
- Отслеживание задержек (P50, P95, P99);
- Оповещения об уровне ошибок;
- Уровень успешности транзакций;
- Панель мониторинга в реальном времени.

Создание реальных систем научило меня:
1) Идеальных проектов не существует
- Каждый выбор — это компромисс;
- Бизнес-ограничения имеют значение;
- Технический долг неизбежен.

2) Эксплуатация важнее проектирования
- Мониторинг и оповещения критически важны;
- Отладка распределённых систем сложна;
- Необходимо планирование отката.

3) Важна команда
- Самый лучший проект ничего не значит, если команда не может его поддерживать;
- Документация имеет решающее значение;
- Привлечение новых инженеров занимает время.

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

7. Я обучал других (лучший способ учиться)
Обучение выявляет пробелы в понимании.

Что я начал делать
1) Наставлял младших инженеров:
- Объяснял кэширование, базы данных, масштабирование;
- Разбирал реальные системные проекты;
- Отвечал на их вопросы «почему».

2) Проводил внутренние технические презентации:
- «Как работает наша платежная система»;
- «Введение в очереди сообщений»;
- «Базы данных: когда что использовать».

3) Писал посты в блоге с диаграммами:
- Помогает систематизировать мысли;
- Приходится ясно объяснять;
- Появляется обратная связь от читателей.

4) Проводил пробные собеседования с коллегами:
- Практика в формулировании мыслей;
- Умение справляться с трудностями;
- Разные подходы к решению.

Почему обучение сработало?
Если вы не можете объяснить что-то просто, значит, вы недостаточно хорошо это понимаете.

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

Преподавание превращает понимание в мастерство.

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

Источник:
https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
👍8
День 2627. #Карьера #SystemDesign
Как я Освоил Системное Проектирование. Окончание

Часть 1
Часть 2
Часть 3
Часть 4

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

Книги:
1) «System Design. Подготовка к сложному интервью» Сюй А.
- Красивые диаграммы;
- Более 15 вариантов системного проектирования.

2) «Высоконагруженные приложения. Программирование, масштабирование, поддержка» Клеппман М.
- Глубокий технический анализ;
- Читать после изучения основ;
- Отличный справочник.

Сдвиг мышления, который изменил всё
Перед изучением системного проектирования.
Синдром самозванца:
- «Я недостаточно опытен»
- «Это слишком сложно»
- «Я никогда не пойму распределённые системы»

Тревога перед собеседованием:
- Надеюсь, я сталкивался с этой проблемой;
- Паника, когда застреваю;
- Заучивание решений.

Карьерный застой:
- Ограничен задачами по программированию;
- Неучастие в принятии архитектурных решений;
- Мимо руководящих должностей.

После освоения системного проектирования
Уверенность:
- Возможность обсуждать архитектуру с кем угодно;
- Понимание компромиссов;
- Мышление в масштабе.

Успех на собеседованиях:
- Возможность ответить на любой вопрос по проектированию;
- Сосредоточенность на размышлении, а не на запоминании.

На собеседованиях по системному проектированию (и в реальной работе):
Плохой ответ: «Мы будем использовать микросервисы и Kafka».
Хороший ответ: «Мы могли бы использовать микросервисы для независимого масштабирования, но, учитывая размер нашей команды (5 инженеров) и текущий масштаб (10 000 пользователей), хорошо структурированный монолит с чёткими границами модулей будет проще в обслуживании. Мы можем выделить сервисы позже, когда достигнем 100 тыс пользователей или нам потребуется независимое развёртывание».

Что изменилось:
- Продемонстрировал понимание компромиссов;
- Учел команду и масштаб;
- Предоставил обоснование;
- Обсудил эволюцию.

Интервьюерам не нужны идеальные проекты. Им нужно увидеть, как вы мыслите.

Итого
Проектирование систем — это навык, а не талант.
Вам не нужны:
- 10 лет опыта;
- Степень в области компьютерных наук;
- Фотографическая память;
- Природный талант.

Нужно только:
- Любопытство изучать, как всё работает;
- Готовность к систематическому обучению;
- Практика проектирования реальных систем;
- Терпение.

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

Источник: https://medium.com/javarevisited/how-i-learned-system-design-861efe86f173
👍15
День 2628. #SystemDesign101
Что Происходит, Когда вы Вводите
google.com в Браузере?

1. Вы вводите адрес веб-сайта в адресную строку браузера.

2. Браузер сначала проверяет свой кэш. Если адрес не найден в кэше, он должен найти IP-адрес.

3. Начинается поиск DNS (представьте, что вы ищете номер телефона). Запрос проходит через различные DNS-серверы (корневой, TLD - сервер имен доменов верхнего уровня - и авторитативный). Наконец, извлекается IP-адрес.

4. Браузер инициирует TCP-соединение, начиная с рукопожатия. Например, в случае HTTP 1.1 клиент и сервер выполняют трёхстороннее TCP-рукопожатие с сообщениями SYN, SYN-ACK и ACK.

5. Браузер отправляет HTTP-запрос на сервер, и сервер отвечает файлами HTML, CSS и JS.

6. Браузер обрабатывает ответ. Он анализирует HTML-документ и создаёт деревья DOM и CSSOM.

7. Браузер выполняет код JavaScript и отображает страницу, проходя через различные этапы (токенизатор, парсер, дерево рендеринга, компоновка и отрисовка).

8. Веб-страница появляется на экране.

Источник: https://bytebytego.com/guides/what-happens-when-you-type-google/
👍27
День 2629. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании.

30. Лидерство и наставничество
«Можете ли вы рассказать о своем подходе к лидерству и наставничеству в команде разработчиков? Как вы обеспечиваете положительное влияние вашего лидерства на результаты проекта и на развитие членов команды?»

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

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

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

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

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

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

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

- Отсутствие делегирования: Не вовлекая команду в процесс принятия решений, лидер упускает ценные идеи и лишает членов команды возможности расти, преодолевая трудности и принимая решения.

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

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

Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
👎4👍2
День 2630. #ЗаметкиНаПолях
Проверяем Конфигурацию при Запуске. Начало

.NET-приложения со строго типизированной конфигурацией IOptions<T> и его вариантами предоставляют удобный способ привязки разделов appsettings.json к классам C#. Но привязка — это не то же самое, что проверка: отсутствие обязательного значения или число, выходящее за пределы допустимого диапазона, без проблем привяжутся и незаметно сломают приложение во время выполнения. IValidateOptions<T> — механизм, предоставляемый .NET для решения этой проблемы.

Рассмотрим типичный класс параметров:
public class SmtpOptions
{
public string Host { get; set; } = "";
public int Port { get; set; }
public string FromAddress { get; set; } = "";
}

Если в файле appsettings.json отсутствует Host, приложение запустится нормально. Ошибка проявится только при отправке email. Аннотации данных ([Required], [Range] и т.п.) в сочетании с вызовом ValidateDataAnnotations() помогают, но с ними есть 2 проблемы.

1. Они используют рефлексию, что приводит к:
- Затратам на производительность, что важно в сценариях с высокой нагрузкой и требованиям к быстрому запуску.
- Несовместимости с AOT и агрессивным триммингом (<PublishTrimmed>true</PublishTrimmed>). Могут быть исключены метаданные, от которых зависит рефлексия, что приводит к предупреждениям IL2026/IL3050 или к сбоям во время выполнения.

Это можно исправить, используя генератор кода в .NET8+. Для активации генератора для конкретного класса валидатора параметров потребуется:
- Класс параметров, помеченный атрибутами Data Annotation.
- Частичный класс, реализующий IValidateOptions<T> и помеченный [OptionsValidator], с пустым телом:
using Microsoft.Extensions.Options;

[OptionsValidator]
public partial class ValidateSmtpOptions
: IValidateOptions<SmtpOptions>
{
}

Генератор заполнит тело класса при сборке.

2. Но иногда аннотация данных вовсе недостаточно, когда необходимы:
- Проверка нескольких свойств объекта конфигурации;
- Асинхронные или основанные на данных из БД проверки;
- Условная логика в зависимости от среды;
- Многократно используемые валидаторы, общие для нескольких типов конфигурации.

Здесь пригодится реализовать IValidateOptions<T>. Это интерфейс в Microsoft.Extensions.Options с единственным методом:
public interface IValidateOptions<TOptions>
where TOptions : class
{
ValidateOptionsResult Validate(
string? name, TOptions options);
}


Вы реализуете этот интерфейс, регистрируете его в DI-контейнере, и инфраструктура Options вызывает его автоматически:
- лениво (при первом обращении),
- немедленно при запуске, если используется ValidateOnStart().

Вот простой валидатор для класса, описанного выше:
public class SmtpOptionsValidator
: IValidateOptions<SmtpOptions>
{
public ValidateOptionsResult Validate(
string? name, SmtpOptions opts)
{
var errors = new List<string>();

if (string.IsNullOrWhiteSpace(opts.Host))
errors.Add("Требуется Host");

if (opts.Port is < 1 or > 65535)
errors.Add($"Port должен быть от 1 до 65535");

if (string.IsNullOrWhiteSpace(opts.FromAddress))
errors.Add("Требуется FromAddress");

return errors.Count > 0
? ValidateOptionsResult.Fail(errors)
: ValidateOptionsResult.Success;
}
}

Регистрируем в Program.cs:
builder.Services
.Configure<SmtpOptions>(
builder.Configuration.GetSection("Smtp"))
.AddSingleton<IValidateOptions<SmtpOptions>, SmtpOptionsValidator>()
.AddOptions<SmtpOptions>()
.ValidateOnStart();


Если проверка не проходит, во время выполнения app.Run() генерируется исключение OptionsValidationException, поэтому сервис никогда не запустится с некорректной конфигурацией. Этот вариант «быстрого сбоя» обычно предпочтительнее, чем исключение NullReferenceException во время выполнения, возникающее где-то в глубинах бизнес-логики.

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

Источник:
https://bartwullems.blogspot.com/2026/03/validating-configuration-at-startup.html
👍24
День 2631. #ЗаметкиНаПолях
Проверяем Конфигурацию при Запуске. Окончание

Начало

Доступ к другим сервисам внутри валидатора
Поскольку валидатор является обычным классом, зарегистрированным в DI, вы можете внедрять зависимости. Это одно из ключевых преимуществ по сравнению с аннотациями данных. Кроме того, аннотации данных не могут выражать зависимости между свойствами. В валидаторе вы можете добавить любую логику.
public class DatabaseOptionsValidator 
: IValidateOptions<DatabaseOptions>
{
private readonly IHostEnvironment _env;

public DatabaseOptionsValidator(IHostEnvironment env)
=> _env = env;

public ValidateOptionsResult Validate(
string? name, DatabaseOptions opts)
{
var errors = new List<string>();

// Более строгие правила вне среды разработки
if (!_env.IsDevelopment())
{

}

}
}


Именованные конфигурации
IValidateOptions<T> поддерживает именованные конфигурации через параметр name. Это важно при регистрации нескольких экземпляров одного типа конфигурации — например, двух разных клиентов API:
public class ApiClientOptionsValidator
: IValidateOptions<ApiClientOptions>
{
public ValidateOptionsResult Validate(
string? name, ApiClientOptions opts)
{


// … Валидация общих свойств …

// Более строгие правила для API Payments
if (name == "Payments"
&& opts.Timeout > 30)
errors.Add($"Таймаут клиента [{name}] не должен превышать 30 сек.");


}
}

Регистрация именованных настроек:
builder.Services
.AddSingleton<IValidateOptions<ApiClientOptions>, ApiClientOptionsValidator>();

builder.Services
.AddOptions<ApiClientOptions>("Payments")
.BindConfiguration("ApiClients:Payments")
.ValidateOnStart();

builder.Services
.AddOptions<ApiClientOptions>("Shipping")
.BindConfiguration("ApiClients:Shipping")
.ValidateOnStart();


Использование в сочетании с OptionsBuilder<T>
API OptionsBuilder<T> (возвращаемый функцией AddOptions<T>()) имеет перегрузку метода .Validate(), которая принимает делегат. Это подходит для простых случаев. Для чего-либо более сложного предпочтительнее использовать реализацию IValidateOptions<T> — она позволяет тестировать логику проверки и не размещать её в Program.cs:
// Делегат — для простых проверок
builder.Services
.AddOptions<FeatureFlagOptions>()
.BindConfiguration("FeatureFlags")
.Validate(opts => opts.Percentage is >= 0 and <= 100,
"Percentage должен быть от 0 до 100")
.ValidateOnStart();

// Класс – для сложных проверок
builder.Services
.AddSingleton<IValidateOptions<SmtpOptions>, SmtpOptionsValidator>()
.AddOptions<SmtpOptions>()
.BindConfiguration("Smtp")
.ValidateOnStart();

Вы можете объединить оба варианта в одном типе параметров — фреймворк запускает все зарегистрированные валидаторы и агрегирует ошибки.

Источник: https://bartwullems.blogspot.com/2026/03/validating-configuration-at-startup.html
👍9
День 2632. #МоиИнструменты #PG
Инструменты Оптимизации Запросов в PostgreSQL. Часть 6


6. SQLFluff (SQL-линтер для производительности)
Что даёт: выявляет антипаттерны производительности до того, как запросы попадут в прод.
Зачем нужен: Большинство проблем с производительностью SQL возникают из-за распространённых ошибок: SELECT *, ненужные DISTINCT, отсутствие WHERE, неэффективные соединения. SQLFluff выявляет их на этапе разработки, до того, как они вызовут проблемы в продакшене.
Установка:
pip install sqlfluff

# Создаём конфиг
cat > .sqlfluff <<EOF
[sqlfluff]
dialect = postgres
templater = dbt
[sqlfluff:rules:L042]
# Запрет SELECT * в проде
select_
_targets = qualified
[sqlfluff:rules:L045]
# Обязательные алиасы полей
aliasing = explicit
[sqlfluff:rules:L052]
# Требование явных объединений
join_types = explicit
EOF


Использование
1. Линтинг SQL-файлов:
sqlfluff lint models/*.sql

Пример вывода:
== [models/orders.sql] FAIL
L: 5 | P: 8 | L042 | SELECT * is not allowed in production.
L: 12 | P: 15 | L031 | Avoid DISTINCT where possible (use GROUP BY).
L: 18 | P: 1 | L044 | Query is missing WHERE clause (full table scan).
L: 23 | P: 22 | L052 | JOIN should specify type (INNER, LEFT, etc.).


2. Автоматические исправления:
sqlfluff fix models/*.sql

До SQLFluff:
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE order_date > '2024-01-01';

Проблемы:
1. SELECT * (возвращает ненужные поля);
2. Неявное объединение (неясное намерение);
3. Сравнение даты без явного приведения типа.

После SQLFluff:
SELECT 
o.order_id,
o.order_date,
o.total,
c.customer_name
FROM orders AS o
INNER JOIN customers AS c
ON o.customer_id = c.id
WHERE o.order_date > CAST('2024-01-01' AS DATE);


Когда использовать
- dbt-проекты (отлично интегрируется);
- Несколько инженеров, пишущих SQL-запросы (обеспечение согласованности);
- Предотвращение распространённых ошибок;
- Конвейер CI/CD для SQL.

Когда отказаться
- Разработчик-одиночка (меньшая ценность);
- ad-hoc запросы (избыточно);
- Нестандартный диалект SQL (ограниченная поддержка).

Скрытая функция
Пользовательские правила для антипаттернов в вашей команде:
# custom_rules/no_cross_joins.py
from sqlfluff.core.rules import BaseRule, LintResult
class Rule_Custom_NoCrossJoin(BaseRule):
"""Запрет CROSS JOIN (почти всегда необязательны)."""

def _eval(self, segment, **kwargs):
if segment.is_type("join_clause"):
if "CROSS" in segment.raw_upper:
return LintResult(
anchor=segment,
description="Найден CROSS JOIN. Почти всегда необязателен.",
fixes=[...] # Опционально: предложение исправления
)

Добавить в .sqlfluff.

С осторожностью
Слишком агрессивный линтинг убивает продуктивность:
# Слишком строго (инженеры возненавидят)
sqlfluff lint --rules all
# Лучше начать с критических правил
sqlfluff lint --rules L042,L044,L052

Постепенно добавляйте правила со временем. Соблюдайте баланс между отловом проблем и сопротивлением разработчиков.

Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97
👍3
День 2633. #TipsAndTricks
Вызываем Файлы из Репозитория Действий в GitHub Actions
GitHub Actions позволяет создавать многократно используемые действия. Один из самых простых способов сделать это — использовать составные действия (Composite Actions), которые позволяют объединять несколько шагов в одно действие. Это уменьшает дублирование кода в ваших рабочих процессах.

При создании составного действия вам может потребоваться сослаться на файлы, хранящиеся в репозитории действий, например, на скрипт PowerShell или Bash.

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

Для доступа к файлам из репозитория действий используйте контекстную переменную github.action_path, которая содержит путь к каталогу, где находится ваше действие.

Вот пример того, как использовать её в файле action.yml:
name: 'My Composite Action'
description: 'An example'
runs:
using: "composite"
steps:
- name: Run script
run: ${{ github.action_path }}/script.sh
shell: bash

В этом примере файл script.sh находится в корневом каталоге репозитория действий. Использование ${{ github.action_path }}/script.sh гарантирует, что исполнитель выполнит скрипт из правильного места.

Дополнительные ресурсы:
- Контексты GitHub - github.action_path
- Создание составного действия

Источник: https://www.meziantou.net/accessing-files-from-the-action-repository-in-a-github-composite-action.htm
👍1
День 2634. #TipsAndTricks #Git
Команды Git, Которые Я Запускаю Перед Чтением Кода
Автор оригинала: Ally Piechowski

Я занимаюсь аудитом кодовых баз моих клиентов. Первое, что я обычно делаю, когда берусь за новую кодовую базу, открываю терминал и запускаю несколько команд Git. Я хочу понять, кто основные авторы, где сосредоточены проблемы, уверенно ли команда выпускает продукт или ходит по минному полю.

1. Что меняется чаще всего
git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20

20 самых часто изменяемых файлов за последний год. Файл, находящийся вверху списка, почти всегда тот, о котором говорят: «К нему все боятся прикасаться».

Высокая активность в файле - иногда это просто активная разработка. Но если авторов много — это явный признак проблемы. Это файл, где каждое изменение — это исправление исправления. Я беру 5 файлов из этого списка и сопоставляю их с командой поиска ошибок (см. п. 3). Файл с высокой активностью и большим количеством ошибок — это основной источник риска.

2. Кто авторы
git shortlog -sn --no-merges

Авторы по количеству коммитов. Если на одного человека приходится 60%+, это ваш «автобусный фактор». Если он ушёл полгода назад, это кризис. Если ведущий автор из списка не появляется в течение 6 месяцев (git shortlog -sn --no-merges --since="6 months ago"), я немедленно сообщаю об этом клиенту.

Я также смотрю на хвост. 30 авторов, но только 3 были активны в течение последнего года. Т.е. люди, которые создали эту систему, не те, кто её поддерживает.

Замечание: рабочие процессы объединения коммитов сжимают информацию об авторстве. Если команда объединяет каждый пул-реквест в один коммит, этот результат отражает, кто объединил, а не кто написал. Стоит уточнить стратегию объединения, прежде чем делать выводы.

3. Места ошибок
git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20

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

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

4. Проект ускоряется или умирает?
git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c

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

5. Как часто «тушат пожары»?
git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'

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

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

Источник: https://piechowski.io/post/git-commands-before-reading-code/
👍30
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4