ch
Feedback
.NET Разработчик

.NET Разработчик

前往频道在 Telegram

Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#. Для связи: @SBenzenko Поддержать канал: - https://boosty.to/netdeveloperdiary - https://patreon.com/user?u=52551826 - https://pay.cloudtips.ru/p/70df3b3b

显示更多
6 702
订阅者
-124 小时
-37
+1930
帖子存档
День 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/

День 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

День 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

День 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

День 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

День 2629. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы), которые могут задать на собеседовании. 30. Лидерство и наставничество «Можете ли вы рассказать о своем подходе к лидерству и наставничеству в команде разработчиков? Как вы обеспечиваете положительное влияние вашего лидерства на результаты проекта и на развитие членов команды?» Хороший ответ Мой подход к лидерству и наставничеству в команде разработчиков основан на личном примере, развитии открытой коммуникации и активной поддержке профессионального роста членов команды. Вот как я реализую эти принципы. - Лидерство личным примером: Я верю в демонстрацию поведения и практик, которые ожидаю от своей команды. Это включает в себя соблюдение лучших практик кодирования, поддержание высокого стандарта качества кода и демонстрацию приверженности целям проекта. Например, я обязательно участвую в проверках кода, вношу свой вклад в задачи кодирования и остаюсь в курсе последних технологий .NET, чтобы эффективно руководить командой. - Развитие открытой коммуникации: Я поощряю регулярные командные встречи и индивидуальные консультации, чтобы обеспечить постоянную открытость каналов связи. Это помогает решать любые проблемы на ранних этапах, обмениваться идеями и совместно находить решения проблем. - Профессиональный рост и наставничество: Я привержен профессиональному развитию членов своей команды. Это включает в себя регулярные сессии наставничества, поощрение посещения семинаров и конференций, а также предоставление членам команды возможностей принимать новые вызовы. Начинающих разработчиков я часто объединяю в пары с более опытными коллегами и чередую обязанности в проектах, чтобы расширить их кругозор и навыки. - Обратная связь и признание: Я стараюсь предоставлять конструктивную обратную связь и отмечать достижения. Позитивное подкрепление и конструктивная критика помогают укрепить уверенность и улучшить навыки, что, в свою очередь, повышает производительность команды и моральный дух. Внедряя эти стратегии, я стремлюсь создать благоприятную среду, которая не только способствует успеху проекта, но и развитию каждого члена команды. Часто встречающийся неправильный ответ «Как лидер, я принимаю все ключевые решения и определяю направление проекта. Я ожидаю, что моя команда будет следовать моему руководству и сосредоточится на своих задачах, чтобы проект был завершен вовремя». Почему это неправильно - Авторитарный подход: Этот ответ указывает на нисходящий, командно-административный подход к лидерству, который может подавлять творчество и снижать вовлеченность команды. Современное лидерство в разработке программного обеспечения часто требует инклюзивности и сотрудничества. - Отсутствие делегирования: Не вовлекая команду в процесс принятия решений, лидер упускает ценные идеи и лишает членов команды возможности расти, преодолевая трудности и принимая решения. - Пренебрежение наставничеством: Ответ не затрагивает ни один аспект наставничества или личностного развития членов команды, которые имеют решающее значение для поддержания мотивированной и квалифицированной команды. Эта ошибка обычно проистекает из традиционного взгляда на лидерство, который может не соответствовать коллаборативной и динамичной природе современных команд разработчиков программного обеспечения, особенно в средах, поощряющих инновации и непрерывное обучение. Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md

День 2628. #SystemDesign101 Что Происходит, Когда вы Вводите google.com в Браузере? 1. Вы вводите адрес веб-сайта в адресную
День 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/

День 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

День 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

День 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

День 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

День 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

День 2629. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 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

День 2621. #ЧтоНовенького #МоиИнструменты Bookmark Studio Закладки на Стеройдах в Visual Studio Закладки в Visual Studio всег
День 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/

День 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

День 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

День 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;

-- Либо без ANALYZE (только оценка)
EXPLAIN DELETE FROM orders WHERE …;
Источник: https://medium.com/@reliabledataengineering/15-sql-optimization-tools-that-make-queries-10x-faster-8629ac451d97

День 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;
}
Изолированный Специальный тип класса, который ограничивает иерархию наследования. Это предотвращает создание производных типов, что повышает безопасность кода и позволяет компилятору применять оптимизации производительности. Статический Экземпляр статического класса нельзя создать, и от статического класса нельзя унаследовать. Все члены должны быть помечены статическими.
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

День 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

День 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