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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2423. #BestPractices
Вещи, Которые Я Делаю в Каждом Проекте .NET. Продолжение

1-2
3
4-6

7. Не внедряйте IOptions
Проблема с параметрами IOptions в том, что вам нужна зависимость от Microsoft.Extensions. И если вы используете IOptions в других проектах ниже по стеку вызовов, придётся добавлять зависимость во все эти проекты. И тестировать параметр типа IOptions может быть непросто. Кроме того, везде приходится использовать .Value вместо просто имени параметра:
public class MyController(
IOptions<AppSettings> appSettings)
{
private readonly AppSettings _appSettings =
appSettings.Value;

}

Решение - регистрируйте класс IOptions напрямую, так вы получите нужный класс значений в DI контейнере, который уже можно внедрять:
services.Configure<AppSettings>(
Configuration.GetSection("AppSettings"));
services.AddSingleton(s =>
s.GetRequiredService<IOptions<AppSettings>>().Value);



public class MyController(AppSettings appSettings)
{

}

Если вы хотите, чтобы параметры изменялись без перезагрузки приложения, это тоже будет работать. Регистрируете значения как AddScoped и используйте IOptionsSnapshot<T> вместо IOptions<T>.

8. Запахи кода
Во-первых, возьмите за правило располагать «счастливый путь» в конце метода. Если вы смотрите на незнакомый код, как быстро узнать, что происходит, когда всё идёт хорошо? Проще всего – когда нужно всегда смотреть в конец.
Чтобы этого добиться, используйте технику раннего возврата. Вместо нескольких вложенных if-else, просто возвращайте ошибку (или любой другой результат), если что-то идёт не так. Таким образом, в начале метода следуют необходимые проверки, которые завершаются ранним возвратом, если они не проходят, а в конце (если все проверки прошли) – возврат результата, когда всё хорошо:
public ActionResult SendMessage(Message msg)
{
if(!ModelState.IsValid())
return View(msg);

if(emailSender.Send(msg) != Result.OK)
{
_logger.Log(…);
return LocalRedirect("/message/error");
}

return LocalRedirect("/message/success");
}

Это не только позволяет быстро найти «счастливый путь», но и избавляет от избыточной вложенности конструкций if.

И некоторые не жёсткие правила, а просто предупредительные сигналы о запахах кода:
- метод длиннее 20 строк,
- класс длиннее 200 строк,
- использование областей (#region).
Это всё сигналы о том, что метод или класс можно разбить на части.

9. Новые файлы решений
Это относительно новая функция. Технически она всё ещё в стадии превью версии, но сейчас довольно стабильна. Если коротко, новые файлы .slnx – это старый добрый XML, они гораздо короче и понятнее. Обновить файл решения можно, просто перейдя в папку решения и выполнив:
dotnet sln migrate

Затем не забудьте удалить старый файл .sln. Он больше не нужен, но утилита его не удаляет.

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

Источник:
https://youtu.be/SvcRvolP2NE
👍21
День 2424. #BestPractices
Вещи, Которые Я Делаю в Каждом Проекте .NET. Окончание

1-2
3
4-6
7-9

10. HTTP-заголовки безопасности
Они сообщают браузеру о необходимости применения дополнительных правил. Это означает, что вы можете предотвратить определённые типы атак, такие как «человек посередине», кликджекинг, кросс-скриптинг и многие другие. Однако будьте осторожны. Можно, например, запретить загрузку JavaScript отовсюду, кроме вашего домена, но если вы при этом используете CDN, то ваше приложение сломается. Есть стратегии по постепенному внедрению HTTP-заголовков в существующее приложение. Если ваше приложение сканируется на безопасность внешними аналитиками, они всегда проверяют это. Есть публичные сайты, вроде securityheaders.com, которые просканируют ваш сайт и сообщат о состоянии дел. Примерно половина веб-сайтов вообще не имеет заголовков безопасности. И только у 10% рейтинг A или A+. У Эндрю Лока есть NuGet-пакет для реализации некоторых заголовков безопасности.

11. Валидация при сборке
Эта функция проверяет, что ваш DI контейнер настроен правильно. Синглтоны зависят только от синглтонов, а не принимают scoped-объекты. Иначе возникает проблема захвата зависимости. ASP.NET Core обнаруживает это, в режиме локальной разработки, но не в других средах. Но вы можете заставить это работать всегда:
builder.Host.UseDefaultHostProvider(config =>
{
config.ValidateOnBuild = true;
});


12. Автоматизированные тесты
Надеюсь, не нужно объяснять, почему нужно писать тесты? Здесь рассмотрим один нюанс. Слышали выражение «чеховское ружьё»? Если кратко, Чехов писал, что если в главе 1 у вас на стене висит ружьё, то в последующих главах оно должно выстрелить. Иначе нечего его изначально «вешать». То есть, у всего должно быть предназначение. Вот как это касается тестов.
[Fact]
public void EmptyLastNameValidationTest()
{
var customer = new Customer
{
FirstName = "John",
LastName = "",
Address = "123 1st Street",
Born = new DateOnly(2000, 1, 1)
}

var result = new CustomerValidator()
.Validate(customer);

result.Errors.Should().Contain(
e => e.ErrorMessage == "Last Name is required");
}

Что здесь не так? Имя, дата рождения и адрес не имеют значения для этого теста. Фамилия — единственное, что мы проверяем. Мы можем вынести ненужные конструкторы в общий код:
[Fact]
public void EmptyLastNameValidationTest()
{
_customer.LastName = "";

var result = _customerValidator
.Validate(_customer);

result.Errors.Should().Contain(
e => e.ErrorMessage == "Last Name is required");
}

Нижний тест более ясно показывает, что мы тестируем.

13. Централизованное управление пакетами
Проблема, которую решает централизованное управление пакетами, в том, что у вас может быть один пакет в нескольких проектах в решении, и может быть раздражающим поддерживать их синхронизацию. В .NET 6 добавили эту функцию, с помощью которой вы можете создать directory.packages.props в корне и определить там пакеты и версии, которые вам нужны, а затем вы удаляете все версии пакетов в файлах .csproj проектов, и все проекты используют одну версию пакета (хотя, её можно переопределить в любом проекте при необходимости).

Источник: https://youtu.be/SvcRvolP2NE
👍17
День 2425. #ЗаметкиНаПолях
Тестирование Текущего Времени с Помощью TimeProvider и FakeTimeProvider
В тестировании сложно использовать то, что зависит от конкретных данных. Представьте себе файловую систему: для корректной работы тестов необходимо убедиться, что файловая система структурирована именно так, как вы ожидаете. Похожая проблема возникает и с датами: если вы создаёте тесты, основанные на текущей дате, при следующем запуске они не пройдут. Нужно найти способ абстрагировать эти функции, чтобы сделать их пригодными для использования в тестах. Сегодня рассмотрим класс TimeProvider, как его использовать и как его имитировать.

Раньше: интерфейс вручную
Раньше самым простым способом абстрагирования управления датами было ручное создание интерфейса или абстрактного класса для доступа к текущей дате:
public interface IDateTimeWrapper
{
DateTime GetCurrentDate();
}

И стандартная его реализация, использующая дату и время в UTC:
public class DateTimeWrapper : IDateTimeWrapper
{
public DateTime GetCurrentDate()
=> DateTime.UtcNow;
}

Или аналогичный подход с абстрактным классом:
public abstract class DateTimeWrapper
{
public virtual DateTime GetCurrentDate() => DateTime.UctNow;
}

Затем нужно просто добавить его экземпляр в движок DI, и всё готово. Единственная проблема - придётся делать это для каждого проекта, над которым вы работаете.

Сейчас: класс TimeProvider
Вместе с .NET 8 команда .NET выпустила абстрактный класс TimeProvider. Помимо предоставления абстракции для локального времени, он предоставляет методы для работы с высокоточными временными метками и часовыми поясами. Важно отметить, что даты возвращаются как DateTimeOffset, а не как экземпляры DateTime. TimeProvider поставляется «из коробки» с консольным приложением .NET:
DateTimeOffset utc = TimeProvider.System.GetUtcNow();
Console.WriteLine(utc);

DateTimeOffset local = TimeProvider.System.GetLocalNow();
Console.WriteLine(local);

А если вам нужно использовать внедрение зависимостей, нужно внедрить его как синглтон:
builder.Services.AddSingleton(TimeProvider.System);

// Использование
public class Vacation(TimeProvider _time)
{
public bool IsVacation
=> _time.GetLocalNow().Month == 8;
}


Тестируем TimeProvider
Мы можем использовать NuGet-пакет Microsoft.Extensions.TimeProvider.Testing, который предоставляет класс FakeTimeProvider, выступающий в качестве заглушки для абстрактного класса TimeProvider. Используя класс FakeTimeProvider, вы можете установить текущее время UTC и местное время, а также настроить другие параметры, предоставляемые TimeProvider:
[Fact]
public void WhenItsAugust_ShouldReturnTrue()
{
// Arrange
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(
new DateTimeOffset(2025, 8, 14,
22, 24, 12, TimeSpan.Zero));
var sut = new Vacation(fakeTime);

Assert.True(sut.IsVacation);
}

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

Источник: https://www.code4it.dev/csharptips/timeprovider-faketimeprovider/
🔥23👍1
День 2426. #TipsAndTricks
Перемещение Файлов и Папок в Корзину в .NET
При работе с файлами и папками в приложениях .NET в Windows может потребоваться перемещать элементы в корзину вместо их безвозвратного удаления (File.Delete, Directory.Delete). Это позволит пользователям восстановить случайно удалённые элементы. Вот как это можно сделать с помощью API Windows Shell:
static void MoveToRecycleBin(string path)
{
if (!OperatingSystem.IsWindows()) return;

var shellType = Type.GetTypeFromProgID(
"Shell.Application", throwOnError: true)!;
dynamic shellApp =
Activator.CreateInstance(shellType)!;

// https://learn.microsoft.com/en-us/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants?WT.mc_id=DT-MVP-5003978
var recycleBin = shellApp.Namespace(0xa);

// https://learn.microsoft.com/en-us/windows/win32/shell/folder-movehere?WT.mc_id=DT-MVP-5003978
recycleBin.MoveHere(path);
}


Теперь можно использовать этот метод для перемещения файлов и папок в Корзину:
MoveToRecycleBin(@"C:\path\to\file.txt");
MoveToRecycleBin(@"C:\path\to\directory");


Источник: https://www.meziantou.net/moving-files-and-folders-to-recycle-bin-in-dotnet.htm
👍26
День 2427. #ЗаметкиНаПолях
Особенность ToDictionaryAsync в Entity Framework

В Entity Framework Core ToDictionaryAsync (и, конечно же, его синхронный аналог ToDictionary) извлекает весь объект из БД.

Вот определение ToDictionaryAsync:
public static async Task<Dictionary<TKey, TElement>> ToDictionaryAsync<TSource, TKey, TElement>(
this IQueryable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey>? comparer,
CancellationToken ct = default)
where TKey : notnull
{
Check.NotNull(keySelector);
Check.NotNull(elementSelector);

var d = new Dictionary<TKey, TElement>(comparer);
await foreach (var element in
source.AsAsyncEnumerable()
.WithCancellation(ct)
.ConfigureAwait(false))
{
d.Add(keySelector(element), elementSelector(element));
}

return d;
}


Мы видим, что он принимает лямбды для выбора ключей и значений, а также видим, что этот выбор выполняется на клиенте после получения всего объекта! Поэтому, если у вас есть сущность вроде такой:
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Content { get; set; }
public string Author { get; set; }
}

И вы выполняете какой-то такой запрос:
return await dbContext.BlogPosts
.Where(...)
.ToDictionaryAsync(k => k.Author, v => v.Title);

Тогда вы извлекаете из базы данных не только автора и заголовок, но и, описание (Description), и содержание (Content). Решение проблемы описано в этом тикете: используем Select для выбора только нужных полей, так как он выполняется на стороне сервера:
return await dbContext.BlogPosts
.Where(...)
.Select(s => new { Author = s.Author, Title = s.Title })
.ToDictionaryAsync(k => k.Author, v => v.Title);


Источник: https://steven-giesel.com/blogPost/1af57355-7978-40e6-a0f1-3d0ba2c6e1bc/todictionaryasync-retrieves-the-whole-object-from-the-database-in-entity-framework
👍41
День 2428. #Оффтоп
Странная Ошибка Компиляции
Пишу экспорт отчёта в CSV. До этого использовали библиотеку EPPlus (её же используем для экспорта в Excel), но из-за большого размера отчёта оно периодически падает с Out of memory.

Свойства объектов экспортируемой коллекции помечены атрибутами EPPlus для форматирования. Типа [EpplusIgnore] для игнорирования свойства, [EpplusTableColumn(Header="Заголовок", Order = 1)] - для имени заголовка колонки и порядка. См. первую картинку.

Понятно, что EPPlus при экспорте сам эти атрибуты обрабатывает, а мне в самодельном экспорте надо их ручками вытягивать через рефлексию. См. вторую картинку. Обращаюсь через
Attribute.GetCustomAttribute(prop, typeof(…))

или
prop.CustomAttributes.Any(a => a.AttributeType == typeof(…))

И вот когда в typeof(…) кидаю EpplusIgnore, всё работает, а когда кидаю EpplusTableColumn - пишет
The type or namespace name 'EpplusTableColumn' could not be found (are you missing a using directive or an assembly reference?)


В обоих файлах ссылка на нугет есть, юзинг неймспейса есть. И EpplusIgnore, и EpplusTableColumn находятся в одной сборке, в одном неймспейсе, и как атрибуты модели работают прекрасно.

Предложения по исправлению ничем не помогают. Там только что-то в духе «Создать новый класс 'EpplusTableColumn'…».

Есть предположения, в чём проблема?

Чуть позже выложу ответ.

UPD: довольно быстро догадались, ответ в комментариях.
👍2
День 2429. #ЗаметкиНаПолях
Управляем Временем Жизни DbContext. Начало
DbContext — это сердце EF Core, но его легко использовать неправильно.

Основные правила:
- DbContext представляет собой единицу работы и должен существовать недолго.
- Он не является потокобезопасным; никогда не используйте один экземпляр совместно для параллельных операций.
- В ASP.NET Core стандартным и, как правило, правильным выбором является DbContext с ограниченной (scoped) областью действия для каждого запроса. Для задач вне области действия запроса (фоновые сервисы, одиночные объекты, UI-приложения) используйте фабрику для создания новых контекстов по требованию.
Рассмотрим, как правильно подключать DbContext, когда выбирать каждый вид регистрации и каких ловушек следует избегать.

Виды регистрации
1. AddDbContext (Scoped – по умолчанию, для каждого запроса)
// Program.cs
builder.Services
.AddDbContext<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));

Когда использовать: контроллеры MVC/минимальные API, Razor Pages, хабы SignalR — всё, что находится внутри веб-запроса.
Почему: Вы автоматически получаете отдельный контекст на каждый запрос; EF Core эффективно обрабатывает использование подключений.

2. AddDbContextFactory (Transient - фабрика для контекстов по требованию)
builder.Services
.AddDbContextFactory<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));

Использование:
public sealed class ReportService(
IDbContextFactory<AppDbContext> factory)
{
public async Task<IReadOnlyList<OrderDto>>
GetAsync(CancellationToken ct)
{
await using var db = await
factory.CreateDbContextAsync(ct);
return await db.Orders
.Where(…)
.Select(…)
.ToListAsync(ct);
}
}

Когда использовать:
- Фоновые сервисы (IHostedService, BackgroundService),
- Любые синглтон-сервисы, которым требуется DbContext,
- Десктопные/Blazor-приложения, где требуется свежий контекст для каждой операции.
Почему: Фабрики создают чистые, кратковременные контексты, не полагаясь на внешние области видимости.

3. AddDbContextPool (Scoped с пулингом)
builder.Services
.AddDbContextPool<AppDbContext>(o =>
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default")
));

Когда использовать: Высокопроизводительные API, когда конфигурация контекста стабильна и не сохраняет состояние.
Зачем: Повторное использование экземпляров DbContext из пула для снижения затрат на выделение ресурсов.
Внимание: не сохраняйте состояние запросов в контексте; экземпляры в пуле сбрасываются и используются повторно.

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

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

Источник:
https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
1👍36
День 2430. #ЗаметкиНаПолях
Управляем Временем Жизни DbContext. Окончание

Начало

Примеры из реального мира
1. Контроллер / Минимальные API (Scoped)
DI даёт вам scoped-контекст, привязанный к запросу. Одна единица работы, никаких утечек:
app.MapGet("/orders/{id:int}", 
async (int id,
AppDbContext db,
CancellationToken ct) =>
{
var order = await db
.Orders.FindAsync([id], ct);

return order is null
? Results.NotFound()
: Results.Ok(order);
});


2. Фоновые сервисы
Фоновые сервисы не имеют области видимости запроса, вы должны создать область видимости:
public sealed class CleanupService(
IServiceScopeFactory scopes,
ILogger<CleanupService> log)
: BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken stop)
{
while (!stop.IsCancellationRequested)
{
using var scope = scopes.CreateScope();
var db = scope
.ServiceProvider
.GetRequiredService<AppDbContext>();

// Одна короткоживущая единица работы
// … используем db …
await db.SaveChangesAsync(stop);

await Task.Delay(TimeSpan.FromHours(6), stop);
}
}
}


Либо использовать фабрику:
public sealed class CleanupService(
IDbContextFactory<AppDbContext> factory,
ILogger<CleanupService> log)
: BackgroundService
{
protected override async Task
ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await using var db = await
factory.CreateDbContextAsync(ct);
// … та же логика, что и выше …
}
}
}


3. Синглтон-сервис, которому нужна БД
Синглтоны не должны захватывать DbContext. Создание нового при каждом вызове помогает избежать проблем:
csharp 
public sealed class PricingCache(
IDbContextFactory<AppDbContext> factory,
IMemoryCache cache)
{
public async Task<decimal>
GetPriceAsync(int id, CancellationToken ct)
{
if (cache.TryGetValue(id, out var price))
return price;

await using var db = await
factory.CreateDbContextAsync(ct);
price = await db.Products
.Where(p => p.Id == id)
.Select(p => p.Price)
.FirstAsync(ct);

cache.Set(id, price, TimeSpan.FromMinutes(10));
return price;
}
}


4. Высокопроизводительные APIs (пулинг)
builder.Services
.AddDbContextPool<AppDbContext>(o =>
{
o.UseNpgsql(
builder
.Configuration
.GetConnectionString("Default"));
// Не используйте состояние в опциях
// Избегайте изменяемого состояния
});

Совет: Пулинг улучшает пропускную способность. Он не делает DbContext потокобезопасным. По-прежнему один контекст на запрос.

Распространённые ошибки
1. Совместное использование одного DbContext между потоками
Симптом: "A second operation started on this context before a previous operation was completed." (Вторая операция началась в этом контексте до завершения предыдущей операции).
Решение: один контекст на единицу работы; не выполняйте параллельные запросы в одном и том же контексте.

2. Внедрение DbContext в синглтоны
Решение: внедрение IDbContextFactory или IServiceScopeFactory и создание контекстов по требованию.

3. Долгоживущие контексты
Решение: делайте контексты короткоживущими, иначе раздуваются трекеры изменений и удерживаются соединения.

4. Использование пула с состоянием запросов
Решение: не добавляйте пользовательское состояние к контексту (например, поле CurrentUserId). Контексты в пуле переиспользуются.

5. Попытка «ускорения» путём одновременного выполнения нескольких запросов в одном контексте.
Решение: либо сериализуйте работу, либо создайте несколько контекстов. DbContext не является потокобезопасным.

Итого
Думайте о DbContext как о листке-черновике: вы берёте лист, используете для коротких записей, и выбрасываете его, когда закончите. Не передавайте его по всему офису и не пытайтесь писать на нём двумя ручками одновременно.

Источник: https://thecodeman.net/posts/managing-ef-core-dbcontext-lifetime
👍26
День 2431. #ЗаметкиНаПолях
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Начало

Если вы уже какое-то время работаете в мире .NET, то знаете, что эта среда выполнения полна скрытых жемчужин. Некоторые из них отточены и дружелюбны, разработаны для того, чтобы новички чувствовали себя в безопасности. Другие — сырые, но мощные инструменты, которым «не обучают в школе», но они дают вам невероятную мощь, если вы знаете, когда и как их использовать. Разберём 5 малоизвестных API, которые могут помочь вам выжать гораздо больше производительности из кода, интенсивно использующего память. Эти функции имеют реальное значение в высокопроизводительных сервисах, конвейерах с малой задержкой и любых приложениях, которые используют много памяти.

1. CollectionsMarshal.AsSpan() для доступа к списку
Обычно, работая с List<T>, вы представляете его себе как удобную и безопасную абстракцию над массивом. Добавляете данные, проходите по списку. Но что, если вам нужен прямой доступ к базовому массиву без копирования?
Стандартный способ:
var numbers = new List<int> { 1, 2, 3, 4, 5 };

// Нужен Span? Придётся делать копию
Span<int> span = numbers.ToArray().AsSpan();
// вариант 2, но всё равно копия
Span<int> span1 = [.. numbers];

// … работа со span’ом

ToArray() создаёт полную копию содержимого списка, тем самым удваивая размер используемой памяти.

Способ лучше:
using System.Runtime.InteropServices;

// Span напрямую над массивом
Span<int> span = CollectionsMarshal.AsSpan(numbers);

Почему: здесь мы работаем непосредственно с внутренним хранилищем списка. Никакого копирования, никаких лишних выделений памяти. Есть одна проблема: если список изменит размер (например, при добавлении нового элемента), span станет недействительным. Именно поэтому этот API находится в System.Runtime.InteropServices.
Когда использовать: при обработке больших буферов или выполнении коротких циклов. Это избавляет от ненужных копий, снижает нагрузку на сборщик мусора и повышает производительность.
Выигрыш: в 2 раза по памяти.

2. CollectionsMarshal.GetValueRefOrNullRef для доступа к словарю
Словари повсюду в приложениях .NET. И чаще всего мы просто делаем что-то вроде этого:
if (dict.TryGetValue("foo", out var value))
{
value++;
dict["foo"] = value;
}

Выглядит безобидно, но мы ищем ключ дважды: в TryGetValue и в индексаторе.

Способ лучше:
using System.Runtime.InteropServices;

ref int valueRef =
ref CollectionsMarshal
.GetValueRefOrNullRef(dict, "foo");

if (!Unsafe.IsNullRef(ref valueRef))
valueRef++;

Почему: С помощью GetValueRefOrNullRef вы получаете ссылку прямо на хранилище словаря. Без повторного поиска, без избыточного вычисления хэша и без лишних записей.
Когда использовать: при работе с большими словарями или циклами, критически важными для производительности (например, с таблицей символов компилятора или кэшем), эти ненужные поиски накапливаются. Этот подход быстрее, но, опять же, слово Unsafe разработчиками языка используется не просто так. Обращайтесь с ref’ами осторожно.
Выигрыш: примерно в 2 раза.

См. также Альтернативный Доступ к Коллекциям в C#13

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

Источник:
https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
1👍33
День 2432. #ЗаметкиНаПолях
Экономим Память, Как Профессионалы. 5 Продвинутых Техник. Окончание

Начало

3. GC.AllocateUninitializedArray<T> для более быстрой работы с массивами
При создании нового массива в .NET среда выполнения обнуляет его. Обычно это хорошо, предсказуемое поведение, никаких мусорных данных. Но что, если вам не нужно изначальное содержимое, ведь вы всё равно перезапишете каждый элемент? Обнуление — пустая трата времени.
Стандартный способ:
// Автоматически обнуляется
int[] arr = new int[1000];
for (int i = 0; i < arr.Length; i++)
arr[i] = i; // Перезаписываем всё


Способ лучше:
int[] arr = GC.AllocateUninitializedArray<int>(1000);
for (int i = 0; i < arr.Length; i++)
arr[i] = i;

Почему: здесь среда выполнения пропускает очистку памяти. Это ускоряет выделение памяти, особенно для больших массивов.
Когда использовать: требуется дисциплина. Если вы читаете массив перед записью, вы получите мусорные значения. Но в таких сценариях, как сериализация, повторное использование буфера или краткие циклы с вычислениями, это может сэкономить время.
Выигрыш: ~15% для 1000 элементов.

4. ArrayPool<T>.Shared и IMemoryOwner
Если вы многократно выделяете большие массивы в цикле, сборщик мусора вас возненавидит. Именно здесь на помощь приходит ArrayPool<T> — общий пул массивов, который можно арендовать и вернуть.
Стандартный способ:
for (int i = 0; i < 1000; i++)
{
// аллокации каждый раз
var buffer = new byte[1024];
DoSomething(buffer);
}

Это приведёт к тысяче аллокаций, которые захламят кучу.

Способ лучше:
using System.Buffers;

for (int i = 0; i < 1000; i++)
{
using IMemoryOwner<byte> owner =
MemoryPool<byte>.Shared.Rent(1024);
var memory = owner.Memory;
DoSomething(memory.Span);
}

Почему: мы арендуем буфер из пула, используем его и возвращаем при уничтожении IMemoryOwner<T>. Никакие выделения памяти не перегружают сборщик мусора.
Когда использовать: особенно актуально в сценариях с высокой пропускной способностью, таких как сетевые серверы, конвейеры или обработка видео. Кроме того, это безопаснее, чем просто арендовать и возвращать массивы, поскольку IMemoryOwner<T> гарантирует корректную очистку.
Выигрыш: более чем в 2 раза быстрее и 1 аллокация вместо 1000.

5. ObjectPool<T> для дорогостоящих объектов
Создание некоторых объектов требует больших затрат. Если вы постоянно создаёте и уничтожаете их, вы тратите не только память, но и ресурсы процессора. Поможет ObjectPool<T>.
Без пулинга:
for (int i = 0; i < 100; i++)
{
var sb = new StringBuilder(1024);
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
}

Это создаст 100 экземпляров StringBuilder. Они будут собраны GC, но зачем создавать столько?

Пулинг:
using Microsoft.Extensions.ObjectPool;
var provider = new DefaultObjectPoolProvider();
var pool = provider.CreateStringBuilderPool();

for (int i = 0; i < 100; i++)
{
var sb = pool.Get();
sb.Clear();
sb.Append("Hello ").Append(i);
Console.WriteLine(sb.ToString());
pool.Return(sb);
}

Почему: мы переиспользуем небольшой пул объектов StringBuilder. Выделение памяти значительно сокращается, и мы избегаем постоянного обращения к GC.
Когда использовать: фреймворки логирования, библиотеки сериализации или любые другие рабочие нагрузки, требующие создания множества временных объектов.
Выигрыш: примерно в 4 раза быстрее и в 50 раз меньше по памяти.

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

Источник: https://blog.stackademic.com/stop-wasting-memory-5-advanced-net-tricks-nobody-teaches-you-47376c1339e7
👍47
День 2433. #SystemDesign101 #Microservices
Как Выглядит Типичная Микросервисная Архитектура?


Балансировщик нагрузки: устройство или приложение, которое распределяет сетевой или прикладной трафик между несколькими серверами.

CDN (Сеть Доставки Контента): группа географически распределённых серверов, которые обеспечивают быструю доставку статического и динамического контента. С CDN пользователям не нужно загружать контент (музыку, видео, файлы, изображения и т. д.) с исходного сервера. Вместо этого контент кэшируется на узлах CDN по всему миру, и пользователи могут загружать его с ближайших узлов CDN.

API-шлюз: обрабатывает входящие запросы и направляет их соответствующим сервисам. Он взаимодействует с провайдером идентификации и выполняет обнаружение сервисов. См. подробнее.

Провайдер идентификации: отвечает за аутентификацию и авторизацию пользователей.

Регистрация и обнаружение сервисов (Service Registry и Service Discovery): Service Registry — это база данных, которая хранит информацию о сервисах и их экземплярах, а Service Discovery — это механизм, использующий этот реестр для автоматического обнаружения, регистрации и отслеживания доступности сервисов в распределенной системе, что особенно важно для микросервисных архитектур, где сервисы могут динамически масштабироваться. API-шлюз ищет соответствующие сервисы в этом компоненте для взаимодействия с ними.

Менеджмент: этот компонент отвечает за мониторинг сервисов.

Микросервисы: микросервисы разрабатываются и развёртываются в разных доменах. Каждый домен имеет свою собственную базу данных. API-шлюз взаимодействует с микросервисами через REST API или другие протоколы, а микросервисы в пределах одного домена взаимодействуют друг с другом с помощью RPC (удалённого вызова процедур).

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

Источник: https://blog.bytebytego.com
👍8
День 2434. #ЧтоНовенького
Срок Поддержки STS Релизов Продлён до 24 месяцев
Microsoft объявили о продлении срока поддержки релизов .NET Standard Term Support с 18 до 24 месяцев. Изменение политики, вступающее в силу с .NET 9, поддержка которого продлена до 10 ноября 2026 года, совпадает с датой окончания поддержки .NET 8, версии с долгосрочной поддержкой (Long Term Support, LTS).

Как заявили в Microsoft, компания будет придерживаться установленного ежегодного графика выпуска релизов каждый ноябрь. Чётные релизы продолжат получать статус долгосрочной поддержки на 3 года, в то время как нечётные релизы обозначены как версии со стандартной поддержкой (STS). Ранее релизы STS получали обновления в течение 18 месяцев, и прекращали поддерживаться через 6 месяцев после выпуска следующей версии.

Основной причиной этого изменения названы проблемы управления зависимостями. Для таких релизов, как .NET Aspire, Microsoft.Extensions.AI и C# Dev Kit, иногда требуются обновлённые версии пакетов из более новых ежегодных выпусков. Эта ситуация создавала сложности для компаний, придерживающихся политики «только-LTS», поскольку они могли непреднамеренно включить компоненты STS при установке этих релизов, что потенциально сокращало сроки их поддержки. Т.е., когда организации, использующие LTS-релизы, устанавливали компоненты, требующие более новых версий пакетов, они непреднамеренно переводили части своей среды выполнения из статуса LTS в статус STS. Продлённый период поддержки решает эту проблему, обеспечивая поддержку пакетов из .NET 9 до той же даты, что и компонентов из .NET 8.

Что касается отзывов сообщества разработчиков, они выявили различные точки зрения на объявление. Некоторые отметили, что продлённый период поддержки повышает жизнеспособность релизов STS в производственных средах. Например, это изменение затрудняет обоснование отказа от запуска новых проектов на последней версии, независимо от её статуса STS или LTS.

Другие предложили альтернативные решения. Несколько разработчиков предложили Microsoft рассмотреть возможность продления цикла выпуска до 18 месяцев вместо ежегодного. Как отметил один из комментаторов, зрелость фреймворка снизила влияние новых функций, сделав более длинные циклы выпуска более приемлемыми для команд разработчиков.

Изменение политики особенно выгодно компаниям со строгими требованиями к развертыванию «только-LTS», одновременно способствуя внедрению новых возможностей .NET. Как заявили в Microsoft, организациям, планирующим миграцию с .NET 9 на .NET 10, следует продолжить придерживаться текущего графика обновлений, поскольку новые релизы обеспечивают повышение производительности и дополнительные функции.

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

Изменённая модель поддержки вступила в силу немедленно с релизом .NET 9. Microsoft подтвердили, что сохранят существующее трёхлетнее обязательство по поддержке для всех выпусков с долгосрочной поддержкой.

Источник: https://www.infoq.com/news/2025/09/microsoft-extends-dotnet-sts/
👍11
День 2435. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Начало
LLM, такие как GhatGPT и Claude, изменили наше взаимодействие с компьютерами. Однако они сталкиваются с фундаментальными ограничениями, которые не позволяют им быть немедленно полезными во многих бизнес-контекстах.

Некоторые ограничения:
- Знания LLM часто заморожены на момент окончания обучения. Если мы спросим GPT-4 о событиях, произошедших после сбора обучающих данных, он не узнает об этом, если не подключится к Интернету для сбора информации.
- LLM не имеют доступа к закрытым данным компании. Когда сотрудники задают вопросы о политике компании, а клиенты интересуются конкретными продуктами, стандартный LLM может давать только общие ответы, основанные на общих закономерностях, которые он, возможно, изучил из общедоступных интернет-данных.
- LLM страдают от галлюцинаций (генерируют правдоподобно звучащую, но неверную информацию). Они могут уверенно приводить выдуманные факты, придумывать цитаты или создавать вымышленные ситуации.
- Даже если у LLM есть соответствующие знания, их ответы могут быть скорее общими, чем конкретными для требуемого контекста.

RAG решает эти проблемы, предоставляя ИИ доступ к конкретным документам и данным.

Что это?
По сути, RAG (Retrieval Augmented Generation) — это метод, объединяющий два различных процесса в одну систему:
- Извлечение релевантной информации из коллекции документов.
- Создание точного ответа на основе этой информации.
Представьте, что вы заходите в библиотеку и задаёте библиотекарю конкретный вопрос о местном налоговом кодексе. Обычный библиотекарь может поделиться общими знаниями о налогах, но библиотекарь, имеющий доступ к конкретным налоговым документам города, может подойти к нужной полке, вытащить соответствующее руководство, прочитать нужный раздел и дать точный ответ, основанный на этих официальных документах. Так работает RAG.

Когда мы спрашиваем стандартную LLM, например, о политике компании в отношении отпусков, она может ответить общей информацией о типичных правилах, с которыми она ознакомилась во время обучения, вроде: «Многие компании предлагают 2 раза по 2 недели оплачиваемого отпуска», потому что это распространённая практика. При использовании RAG система сначала извлекает справочник, находит раздел о политике отпусков, а затем генерирует ответ на основе этого документа. Ответ будет следующим: «Согласно справочнику «…», штатные сотрудники могут планировать отпуска по своему усмотрению, но не более 28 оплачиваемых дней отпуска в год».

Упрощённый принцип работы RAG показан на рисунке ниже.

RAG более полезен, чем стандартный LLM в следующих случаях:
1. Когда сценарий использования включает в себя часто меняющуюся информацию, например, данные о товарных запасах, ценах или новостях.
2. При работе с личной или конфиденциальной информацией, которая не была частью обучающих данных модели, например, внутренней документацией, записями клиентов или конфиденциальными исследованиями.
3. Когда точность критически важна, а галлюцинации недопустимы, например, в юридических, медицинских или финансовых приложениях.
4. Когда важно предоставить ссылки или доказать источник информации. Система может указать на конкретные документы и отрывки, обеспечивая прозрачность и контролируемость, которые невозможны при использовании стандартных ответов LLM.
5. Когда приложению необходимо обрабатывать большие коллекции документов, которые было бы непрактично включать в каждое сообщение.

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

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

Источник:
https://substack.com/home/post/p-174052561
👍8
День 2436. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение

Начало

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

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

1) Сбор и обработка документов. Каждый документ (PDF, Word, веб-страница или запись БД) должен быть преобразован в обычный текст. Процесс обрабатывает различные форматы и гарантирует чёткое отделение фактического содержимого от форматирования и метаданных.

2) Разделение текста на мелкие фрагменты. Документы обычно слишком длинные для обработки как единое целое. Размер фрагментов имеет значение: слишком маленький — они теряют контекст, слишком большой — они становятся менее точными. Большинство систем используют фрагменты по 500–1000 слов, часто с некоторым перекрытием между последовательными фрагментами для сохранения контекста.

3) Фрагменты преобразуются в числовые представления – эмбеддинги - список чисел, отражающих его семантическое значение, например, [0,23, -0,45, 0,67, …] с сотнями или тысячами измерений. Числа кодируют смысл текста так, что это позволяет проводить математические сравнения. Схожие понятия порождают схожие числовые шаблоны, что позволяет системе находить связанный контент даже при использовании разных слов.

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

2. Обработка запросов
Этот этап должен быть быстрым и эффективным, поскольку пользователи ожидают быстрых ответов. См. схему 2 ниже.

1) Вопрос пользователя попадает в систему и проходит тот же с использованием той же модели эмбеддинга, которая использовалась для обработки документов.

2) Система ищет в векторной БД наиболее похожие фрагменты документов. Этот поиск быстр, т.к. использует математические операции, а не сравнение текстов. Обычно система извлекает от 3 до 10 наиболее релевантных фрагментов.

3) Извлечённые фрагменты подготавливаются для языковой модели. Система объединяет их в контекст, часто ранжируя по релевантности и фильтруя на основе метаданных или бизнес-правил.

4) Языковая модель получает исходный вопрос пользователя и извлеченный контекст. Промпт может содержать:
- Предоставленные документы контекста.
- Конкретный вопрос пользователя.
- Инструкции по ответу на основе предоставленного контекста.
- Рекомендации по обработке информации, отсутствующей в контексте.

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

6) Ответ часто проходит постобработку: добавление ссылок на исходные документы, форматирование ответа для лучшей читаемости или проверку на соответствие заданному вопросу. Некоторые системы также логируют запрос, извлечённые документы и ответ для аналитики и улучшения.

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

Источник:
https://substack.com/home/post/p-174052561
👍11
День 2437. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Продолжение

Что такое RAG
Как работает RAG

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

Представьте себе разнообразие способов задать вопрос о проблеме с компьютером: «ноутбук не запускается», «компьютер не загружается», «система не включается» или «ПК не работает». В этих фразах практически нет общих слов, но все они описывают одну и ту же проблему. Система, основанная на ключевых словах, воспримет их как совершенно разные запросы и пропустит руководства по устранению неполадок, использующие другую терминологию.

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

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

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

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

Интересно, что мы не до конца понимаем, что представляет собой каждое измерение. Когда модель встраивания выдаёт 768 чисел для фрагмента текста, мы не можем просто сказать, что измерение 1 представляет «формальность», а измерение 547 — «техническую сложность». Эти измерения возникают естественным образом в процессе обучения, когда модель определяет закономерности, которые ей необходимо отслеживать для эффективного понимания языка. Некоторые измерения могут слабо коррелировать с человеческими понятиями, такими как тональность или тема, а многие отражают абстрактные закономерности, которые не соответствуют ни одному понятию, для которого у нас есть слова.

Модели эмбеддинга и большие языковые модели (LLM) служат разным целям в системе RAG. Модель эмбеддинга небольшая, специализирована для одной задачи: преобразования текста в числа. Модели LLM массивные, предназначены для понимания и генерации текста, похожего на человеческий. В то же время они дорогие. Поэтому системы RAG используют две отдельные модели. Модель эмбеддинга эффективно преобразует все документы и запросы в векторы, обеспечивая быстрый поиск по семантическому сходству. Затем LLM берёт найденные релевантные документы и генерирует интеллектуальные, контекстные ответы.

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

Источник:
https://substack.com/home/post/p-174052561
👍13
День 2438. #ЗаметкиНаПолях #AI
Как RAG Позволяет Использовать ИИ для Ваших Данных. Окончание

Что такое RAG
Как работает RAG
Эмбеддинги

Создание RAG-системы

1. Понимание требований.
Разрабатывается ли система для внутренних сотрудников, для которых важна точность и надежность? Или для внешних клиентов, для которых важнее скорость и взаимодействие с UI?

2. Структура документов.
Масштаб имеет значение. Различные объёмы требуют разных стратегий хранения и поиска. Возможные типы контента определяют конвейеры загрузки и предварительной обработки.

3. Шаблоны запросов.
Являются ли большинство запросов простыми поисковыми запросами, например, «Какова наша политика в отношении отпусков?», или требуют сложных рассуждений, например, «Сравните стратегии продаж в III и IV кварталах»? Ожидают ли пользователи точных цитат или просто ответов в разговорном формате? Это определяет, насколько сложной должна быть система.

4. Технологический стек.
Вот некоторые популярные инструменты и технологии.
- LLM. С закрытым исходным кодом (GPT-4 от OpenAI, Claude от Anthropic, Gemini от Google), которые легко внедряются и обеспечивают высокую производительность, но привязаны к поставщику и имеют проблемы конфиденциальности данных. Модели с открытым исходным кодом (Llama 3, Mistral, или специализированные BioBERT для медицины и FinBERT для финансов), обеспечивают больший контроль и гибкость, но требуют инфраструктуры графических процессоров и наличия собственных специалистов для масштабирования.

- Модель эмбеддинга. Распространенные варианты: text-embedding-3 от OpenAI или embed-v3 от Cohere или бесплатные альтернативы с открытым исходным кодом. Специализированные модели, такие как E5 или Instructor, могут дополнительно повысить точность, специфичную для предметной области. Модели LLM и эмбеддинга не обязательно должны предоставляться одним и тем же поставщиком.

- Векторная БД, в которой хранятся и ищутся эмбеддинги: Pinecone, Weaviate Cloud или Qdrant Cloud, отлично подходят для быстрого начала работы и плавного масштабирования, хотя и стоят дороже. Решения с собственным хостингом, такие как ChromaDB, Milvus, Elasticsearch с поиском векторов или расширение PostgreSQL pgvector, обеспечивают больший контроль и могут быть дешевле в долгосрочной перспективе, но требуют инвестиций в DevOps. Правильный выбор зависит от объёма данных (сотни тысяч против миллиардов векторов), нагрузки запросов (десятки против десятков тысяч запросов в секунду) и бюджета.

- Фреймворк для оркестровки. Немногие команды разрабатывают всё с нуля. LangChain — самый популярный фреймворк с широкой экосистемой и абстракциями практически для каждого компонента, хотя в простых случаях он может показаться слишком громоздким. LlamaIndex разработан специально для приложений RAG с большим объёмом документов и предлагает чистый приём данных и конвейеры запросов. Haystack ориентирован на производство и обладает мощной поддержкой сложных рабочих процессов.

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

Источник:
https://substack.com/home/post/p-174052561
👍4
День 2439. #ЗаметкиНаПолях
Используем insteadOf в Git для Замены HTTPS-адресов на SSH
При работе с Git-репозиториями вы часто сталкиваетесь с URL-адресами в формате HTTPS, особенно при клонировании из GitHub, GitLab или других хостинг-провайдеров. Однако, если вы предпочитаете использовать SSH для аутентификации (что часто удобнее с аутентификацией по ключам), ручное изменение URL-адресов может быть утомительным. Кроме того, это может быть ещё сложнее при работе с субмодулями.

Конфигурационная опция Git insteadOf предлагает элегантное решение, автоматически переписывая URL-адреса «на лету»:
git config --global url."git@github.com:".insteadOf "https://github.com/"
git config --global url."git@gitlab.com:".insteadOf "https://gitlab.com/"
git config --global url."git@bitbucket.org:".insteadOf "https://bitbucket.org/"


Вы также можете настроить более конкретную проверку и выбрать только определённые репозитории или сервисы. Например, если вы хотите заменить HTTPS-адреса некоторых репозиториев, вы можете сделать следующее:
git config --global url."git@github.com:username/".insteadOf "https://github.com/username/"


После настройки Git будет автоматически переписывать URL:
# Следующая команда
git clone https://github.com/user/repo.git

# Станет эквивалентной
git clone git@github.com:user/repo.git


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

Ещё один вариант использования insteadOf — добавление аутентификации к URL. Например, если вы хотите использовать определённого пользователя для всех запросов Git, вы можете сделать следующее:
git config --global url."https://<token>@github.com/".insteadOf "https://github.com/"


Источник: https://www.meziantou.net/using-git-insteadof-to-automatically-replace-https-urls-with-ssh.htm
👍16
День 2440. #Книги
«.NET 8: инструменты и навыки. Лучшие практики и паттерны проектирования, отладки и тестирования.» (Прайс М. — Астана: «Спринт Бук», 2025).

Ещё одну книгу издательство «Питер» прислали (точнее, передали на DotNext) мне на обзор.

В обзоре предыдущей книги я упоминал, что она вторая из серии книг Марка Прайса. Сегодня напишу про третью. В третей части 800 страниц, и, в отличие от второй, где сделан упор на обзор технологий, существующих в экосистеме .NET, в третей рассказывается о лучших практиках, которые можно применять как при создании приложений, так и для продвижения по карьерной лестнице. Вот некоторые темы:
- Эффективная работа с IDE;
- Работа с Git;
- Отладка;
- Ведение журналов, метрик и трассировок;
- Документирование кода и API;
- Рефлексия и динамическая загрузка сборок;
- Криптография;
- Создание ИИ-чатбота;
- Внедрение зависимостей;
- Тестирование: модульное, интеграционное, нагрузочное, функциональное;
- Контейнеризация;
- .NET Aspire;
- Паттерны и принципы проектирования;
- Основы архитектуры ПО;
- Командная работа и собеседования.

Как и в предыдущей книге, автор на практических примерах показывает наиболее интересные на его взгляд приёмы, которые используют многие .NET разработчики: горячие клавиши и функции IDE, команды Git, стратегии отладки, настройки телеметрии, тесты и т.п. Автор попытался охватить ВСЕ лучшие практики. Конечно, подробно в рамках одной книги это сделать нереально, поэтому ко всем перечисленным выше темам стоило бы добавить слово «Основы …». Но информации по каждой теме вполне достаточно, чтобы создать прочный базис, и автор не скупится на ссылки с дополнительной информацией. Кстати, плюсик в карму издательству: где это возможно, ссылки ведут на русскоязычные версии страниц документации.

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

Очень интересная 19я глава про работу в команде и карьеру. В ней описаны хард- и софт-скилы, которых ждут от кандидатов, специализации, которые есть в современных вакансиях, роли в командах разработки. Далее довольно подробно описан процесс поиска работы, составления резюме и подготовки к собеседованию (ещё один плюсик издательству – добавили информацию об особенностях российского рынка разработки). Причём, описаны пути не только для новичков, но и для претендентов на старшие и руководящие должности. Описаны различные сценарии проведения собеседований, рассматриваются частые вопросы, которые задают кандидатам, в том числе поведенческие вопросы.

Интересный факт, одним из научных редакторов книги был «широко известный в узких кругах» Милан Йованович.

Ещё раз спасибо за подарок издательству «Питер». Присылайте что-нибудь ещё 😊
👍25