.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День девятьсот восемьдесят девятый. #ЗаметкиНаПолях
Основные Заблуждения о Внедрении Зависимостей в
ASP.NET Core. Окончание
Начало
Продолжение

3. Контейнер DI всегда удаляет объекты
Контейнер DI отвечает не только за создание экземпляров объектов, но и за их удаление (dispose). Временные сервисы и сервисы, ограниченные областью действия, удаляются в конце области (в конце веб-запроса в ASP.NET Core), а классы-синглтоны удаляются перед завершением работы приложения. Контейнер DI полностью управляет временем жизни сервисов приложения. Разработчикам не нужно заботиться о вызове метода Dispose.

Однако в редких случаях разработчикам может потребоваться создать экземпляр класса самостоятельно, а затем предоставить экземпляр в контейнер DI:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new Service());
}
В этом случае объект не будет удалён, как могли ожидать разработчики. Здесь им нужно не забыть удалить экземпляр самостоятельно, чтобы избежать проблем, которые могут возникнуть из-за не удалённых ресурсов. Разработчики могут просто ошибочно использовать приведённый выше синтаксис для регистрации объекта вместо следующего:
services.AddSingleton<Service>();

В последнем случае объект будет создан, а затем удалён контейнером DI, что является лучшим вариантом. Оба варианта очень похожи, и легко случайно использовать не тот. Будьте осторожны.

4. Контейнер DI принимает только классы с интерфейсами
Внедрение зависимостей через конструктор довольно часто реализуется с использованием интерфейсов. Вот почему некоторые разработчики ошибочно полагают, что класс должен иметь интерфейс для регистрации в контейнере внедрения зависимостей. Это заблуждение приводит к извлечению интерфейса для классов, которым на самом деле интерфейс не нужен. В следующем примере показано, как зарегистрировать класс, не имеющий интерфейса, в контейнере DI:
public class SomeClass
{
}

public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<SomeClass>();
}

Внедрение класса в сервис:
public class SomeService
{
public SomeService(SomeClass someClass) {}
}

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

Источник: https://levelup.gitconnected.com/top-misconceptions-about-dependency-injection-in-asp-net-core-c6a7afd14eb4
День девятьсот девяностый. #Оффтоп
Гугли Как Профессионал. 10 Советов по Эффективному Поиску
На старте карьеры многие программисты стесняются искать что-то в сети и в основном полагаются на свои скудные знания. Чем больше опыта мы приобретаем, тем больше понимаем, что запоминать детали синтаксиса каждого языка (а обычно за несколько лет карьеры приходится таки попробовать разные языки) или алгоритмы разворачивания деревьев – это пустая трата времени и «памяти». Поэтому всё большую ценность приобретает умение быстро найти решение в гугле.

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

2. Поиск на определённом сайте
Добавьте в строку поиска параметр site:URL, например: nullreferenceexception site:stackoverflow.com

3. Исключить из результатов
Добавьте ключевое слово со знаком минус. Например:
get dom element -jquery
К примеру, так можно исключить имя вашего класса из полного текста ошибки.

4. Новые/старые результаты
Чтобы получать результаты до/после определённого года, можно добавить параметры after:2005 или before:2015. Полезно, чтобы искать, например, документацию по .NET Framework.

5. Любой интервал
Параметры before и after можно использовать совместно, и задать интервал, например, 2013..2019. Хотя, интервал в принципе может быть любым, вроде $1000..$300000.

6. Выбор варианта
Можно использовать знак | для выбора между вариантами. Также, если кроме этих вариантов в запросе есть другие слова, можно заключить выбор в скобки, как в выражении regex:
(A|B) C

7. Wildcard
Может показаться, что использование подстановки для «чего угодно» - бессмысленно, однако это позволяет, например, найти все поддомены у сайта, кроме www:
site:*.microsoft.com -www

8. Тип файла
Тоже довольно широко известный параметр. Добавьте filetype к запросу, чтобы искать только определённый тип файлов, например: filetype:pdf

9. Аналогичные сайты
Используйте параметр related:URL, чтобы найти сайты, аналогичные заданному. Например: related:google.com позволит найти альтернативные поисковики.

10. Поиск в кэше
Параметр cache:url позволяет поискать сайт в кэше. Например, узнать, когда страница в последний раз была кэширована гуглом.

Источник: https://youtu.be/cEBkvm0-rg0
👍3
День девятьсот девяносто первый. #ЗаметкиНаПолях #AsyncTips
Ожидание завершения группы задач

Задача:
есть несколько задач, и нужно подождать, пока они все завершатся.

Решение
Для этой цели существует метод Task.WhenAll. Он получает несколько задач и возвращает задачу, которая завершается при завершении всех указанных задач.
Task task1 = Task.Delay(100);
Task task2 = Task.Delay(200);
Task task3 = Task.Delay(150);
await Task.WhenAll(task1, task2, task3);

Если все задачи имеют одинаковый тип результата и все завершаются успешно, то задача Task.WhenAll возвращает массив, содержащий результаты всех задач:
Task<int> task1 = Task.FromResult(3);
Task<int> task2 = Task.FromResult(5);
Task<int> task3 = Task.FromResult(7);
int[] results = await Task.WhenAll(task1, task2, task3);
// "results" содержит { 3, 5, 7 }

Есть перегруженная версия Task.WhenAll, которая получает IEnumerable с задачами. Тем не менее заметьте, что при использовании LINQ код получается более понятным, когда последовательность явно материализуется:
async Task<string> GetAllAsync(
HttpClient client,
IEnumerable<string> urls)
{
// Определяем действие для каждого URL
var downloads = urls.Select(url => client.GetStringAsync(url));

// Задачи ещё не запущены, т.к.
// последовательность не материализована

// Запускаем загрузку для всех URL одновременно
Task<string>[] downloadTasks = downloads.ToArray();

// Все задачи запущены.
// Асинхронно ожидаем завершения всех загрузок
string[] htmlPages =
await Task.WhenAll(downloadTasks);

return string.Concat(htmlPages);
}

Если одна из задач выбросит исключение, то Task.WhenAll вернёт задачу в статусе Faulted с этим исключением. Если сразу несколько задач выдают исключения, то все они помещаются в задачу, возвращаемую Task.WhenAll. При ожидании этой задачи будет выдано только одно из них:
try
{
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
// "ex" – исключение либо из task1, либо из task2
...
}

Обычно первого «попавшегося» исключения достаточно, но, если вам нужны все исключения, проверьте свойство Exception задачи, возвращаемой Task.WhenAll:
Task allTasks = Task.WhenAll(task1, task2);
try
{
await Task.WhenAll(allTasks);
}
catch
{
AggregateException allExceptions = allTasks.Exception;
...
}

Если ни одна задача не выбросила исключения, но хотя бы одна задача была отменена, то задача, возвращённая Task.WhenAll, будет в статусе Canceled.

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 2.
👍1
День девятьсот девяносто второй. #ЗаметкиНаПолях #AsyncTips
Отмена Группы Задач
Вчерашний пост вызвал в чате обсуждение. Возник такой вопрос: если одна из ожидаемых задач выбросит исключение, можно ли отменить остальные? Уточню вводные:
- задачи формально не связаны, но результат нужен или их всех, или никакого;
- задачи нужно выполнять параллельно.

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

Итак, у нас есть «хорошие» и «плохие» задачи (передаём в них время задержки delay для удобства тестирования):
async Task<object> DoAsync(string id, 
CancellationToken token, int delay)
{
await Task.Delay(delay, token);
return id;
}

async Task<object> BadAsync(string id,
CancellationToken token, int delay)
{
await Task.Delay(delay, token);
throw new Exception(id);
}

Следующий метод «оборачивает» задачу, перехватывая исключения в ней и вызывая отмену остальных:
async Task<T> Wrap<T>(Task<T> task,
CancellationTokenSource cts)
{
try {
return await task;
}
catch {
if (!cts.IsCancellationRequested)
cts.Cancel(); // отменяем остальные
throw;
}
}

Теперь собственно метод, выполняющий все задачи:
public async Task DoWorkAsync(CancellationToken t)
{
var tasks = new List<Task<object>>();
var cts = new CancellationTokenSource();
var ct = cts.Token;

try {
using (t.Register(() => cts.Cancel())) {
// параллельно запускаем задачи
tasks.Add(Wrap(DoAsync("1", ct, 500), cts));
tasks.Add(Wrap(DoAsync("2", ct, 1000), cts));
tasks.Add(Wrap(DoAsync("3", ct, 1500), cts));
tasks.Add(Wrap(BadAsync("Bad", ct, 700), cts));

await Task.WhenAll(tasks.ToArray());
}
}
catch (Exception e) {
Console.WriteLine(e.Message);
}

Console.WriteLine("Results:");
tasks.ForEach((task) =>
Console.WriteLine(task.Status));
}

Здесь мы используем внешний токен отмены t, чтобы иметь возможность отменить все задачи извне. В директиве using регистрируем действие, которое будет выполнено при отмене внешнего токена.
Далее создаём несколько задач, «оборачивая» их в наш метод Wrap. Передаём в них один токен отмены и разные времена задержки, имитируя асинхронную работу. Наша «плохая» задача выбросит исключение после задачи 1, но перед завершением задач 2 и 3, метод Wrap перехватит исключение и отменит задачи 2 и 3.

Запускаем:
var cts = new CancellationTokenSource(10000);
DoWorkAsync(cts.Token).Wait();

//Вывод (в порядке регистрации задач):
RanToCompletion
Canceled
Canceled
Faulted

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

Источник: https://stackoverflow.com/questions/19394150/tpl-execute-dependent-task-and-stop-all-task-when-some-task-fails#19397190
👍2
День девятьсот девяносто третий. #Testing
7 Принципов Тестирования ПО
Опять про тестирование? Да. На этот раз глазами QA. Однако сегодня рассмотрим не советы о том, как тестировать, а общие принципы тестирования. То, чего следует и не следует ожидать при написании тестов.

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

2. Исчерпывающее тестирование невозможно
У вас, вероятно, никогда не будет времени протестировать все возможные сценарии в продукте. Представьте себе небольшую форму с 3 полями выбора, каждое с 10 вариантами. Чтобы проверить все возможные сценарии, вам потребуется 1000 тестов.
Мы можем использовать метод тестирования, называемый эквивалентным разбиением. Вкратце, он состоит из разделения тестовых данных на блоки таким образом, что ожидается, что все элементы одного блока будут обрабатываться одинаково.
А при острой нехватке времени на тесты рекомендуется провести анализ рисков и расставить приоритеты, какие тесты писать в первую очередь с учётом более высоких рисков для бизнеса.

3. Раннее тестирование экономит время и деньги
Работа по тестированию должна начинаться как можно раньше в проекте. Действия по тестированию не ограничиваются выполнением или автоматизацией end-to-end тестов, когда функционал готов. Тестировщик также может просматривать документацию и требования, чтобы не допустить перехода ошибок на более поздние этапы жизненного цикла разработки. Помните, что чем позже вы обнаружите проблему, тем больше будет стоить её устранение.

4. Дефекты объединяются
Этот принцип является применением закона Парето: для многих исходов примерно 80% последствий происходят от 20% причин.
При тестировании ПО высока вероятность того, что 80% проблем можно найти в 20% модулей/компонентов. Так что будьте осторожны, когда найдёте ошибку, потому что в той же функции могут скрываться и другие.

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

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

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

Источник
День девятьсот девяносто четвёртый. #ЗаметкиНаПолях
Сравнение Строк Сложнее, Чем Кажется. Начало
Сравнение строк отличается от сравнения чисел. 2 числа равны, если их значения идентичны. Для строк всё сложнее. Например, вы хотите сравнение с учетом регистра? А как насчёт разных способов написать одну и ту же букву? Например, в немецком языке часто встречается буква ß, но её также можно написать ss, так как это проще на многих клавиатурах.

В .NET есть 6 режимов сравнения строк:
1. Ordinal:
выполняет простое сравнение байтов независимо от языка. Наиболее удобен для сравнения ресурсов, чувствительных к регистру, таких как пароли.

2. OrdinalIgnoreCase: обрабатывает символы в строках для сравнения, как если бы они были преобразованы в верхний регистр с использованием соглашений инвариантного языка и региональных параметров, а затем выполняет простое сравнение байтов, которое не зависит от языка. Подходит для сравнения строк, созданных программно, когда регистр не важен, таких как пути и имена файлов.

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

4. InvariantCultureIgnoreCase: сравнивает строки лингвистически релевантным способом, игнорируя регистр.

5. CurrentCulture: может использоваться, когда строки имеют лингвистическое значение (например, отображаются для пользователя или являются результатом взаимодействия с пользователем).

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

Важно явно указывать режим сравнения, чтобы избежать неожиданного поведения или, что еще хуже, проблем с безопасностью. Например, если вы сравните 2 пароля, используя текущую культуру или инвариантную культуру, пароли могут быть одинаковыми, тогда как на самом деле они могут быть разными!

Примеры
Понять разницу между Ordinal и OrdinalIgnoreCase довольно просто. Сложнее понять культурно-зависимые сравнения.
Примечание: В примерах ниже последним параметром идёт перечисление StringComparison. Для краткости я оставил лишь значение перечисления, поэтому подразумевается, что везде используется using static System.StringComparison;

// false
string.Equals("ss", "ß", OrdinalIgnoreCase);
// true в Windows / false в Linux (WSL) – об этом позже
string.Equals("ss", "ß", InvariantCulture);

\0 и ещё некоторые символы игнорируются в лингвистических сравнениях:
// false
string.Equals("a\0b", "ab", Ordinal);
// true
string.Equals("a\0b", "ab", InvariantCulture);
// true
string.Equals("a\0b", "ab", CurrentCulture);

Сравнение с учетом культуры сравнивает графемы. Например, строка A\r\nB разделена на A, \r\n и B. Это означает, что \n не является частью этой строки. Но \r или \r\n являются частью строки:
// true
"A\r\nB".Contains("\n", Ordinal);
// false
"A\r\nB".Contains("\n", InvariantCulture);
//true
"A\r\nB".Contains("\r", InvariantCulture);

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

Источник:
https://www.meziantou.net/string-comparisons-are-harder-than-it-seems.htm
👍1
День девятьсот девяносто пятый. #ЗаметкиНаПолях
Сравнение Строк Сложнее, Чем Кажется. Продолжение
Начало

NLS и ICU
В прошлом API глобализации .NET использовали разные базовые библиотеки на разных платформах. В Unix использовались международные компоненты для Unicode (ICU), а в Windows использовалась поддержка национальных языков (NLS). Это привело к некоторым различиям в поведении нескольких API глобализации при запуске приложений на разных платформах. В .NET 5.0 поведение среды выполнения изменилось. API глобализации теперь по умолчанию используют ICU на всех поддерживаемых платформах. Это позволяет приложениям избегать различий между платформами.

Обратите внимание, что ICU также используется в Windows. DLL-библиотеки ICU поставляются с Windows, начиная с Windows 10 версии 1703. Многие приложения уже начали использовать ICU в Windows. Таким образом, переход на использование ICU происходит повсюду, а не только в .NET.
.NET Framework 1.0-4.8: NLS в Windows
NET Core 1.0-3.1: NLS в Windows, ICU в Linux
.NET 5.0+: ICU* в Windows, ICU в Linux
* Можно принудительно использовать NLS с помощью флага System.Globalization.UseNls в файле конфигурации.

Если в версии Windows нет ICU, она автоматически возвращается к NLS. NLS и ICU в некоторых крайних случаях ведут себя по-разному (см. ниже). Кроме того, новая версия библиотеки может вносить изменения в поведение.

Обратите внимание, что переход с NLS на ICU не является критическим изменением. Изменения данных глобализации происходят даже в NLS, и поведение API не следует считать стабильным.

У методов разные режимы сравнения по умолчанию
Вы всегда должны явно указывать компаратор, поскольку значения по умолчанию в разных методах не согласованы. Например, string.IndexOf использует текущий язык и региональные параметры, тогда как string.Equals использует Ordinal. Поэтому всегда используйте перегрузку метода с StringComparison, IEqualityComparer<string> или IComparer<string>, когда это возможно:
// true, т.к. используется режим сравнения Ordinal
"A\r\nB".Contains("\n");

IndexOf по причинам, описанным выше, в .NET 5+ выдаёт -1, т.к. используется текущая культура, а до .NET 5 в результате будет 2:
"A\r\nB".IndexOf("\n"); 

Это также относится к Equals и CompareTo (и всем операторам ==, <, >, и т.п.):
// false
"encyclopædia".Equals("encyclopaedia");

A CompareTo опять же выдаёт 0 (равны) до .NET 5 и 1 в .NET 5+:
"encyclopædia".CompareTo("encyclopaedia");

Защита от неожиданного поведения
Анализаторы кода могут обнаруживать места потенциальных ошибок. Чтобы защититься от неожиданного поведения, рекомендуется включить в проекте анализаторы кода Roslyn. Они помогут пометить код, который может непреднамеренно использовать лингвистический компаратор, когда вероятнее всего подразумевался порядковый компаратор. Следующие правила должны помочь выявить эти проблемы:
- CA1307: Specify StringComparison for clarity (для ясности укажите StringComparison)
- CA1309: Use ordinal StringComparison (используйте порядковый режим сравнения)
- CA1310: Specify StringComparison for correctness (укажите StringComparison для корректности)

Эти особые правила не включены по умолчанию. Чтобы включить их и отображать любые нарушения как ошибки сборки, установите следующие свойства в файле проекта:
<PropertyGroup>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<WarningsAsErrors>
$(WarningsAsErrors);CA1307;CA1309;CA1310
</WarningsAsErrors>
</PropertyGroup>

Источники:
-
https://www.meziantou.net/string-comparisons-are-harder-than-it-seems.htm
-
https://docs.microsoft.com/en-us/dotnet/standard/base-types/string-comparison-net-5-plus
День девятьсот девяносто шестой. #Оффтоп
Противоречивые Мнения Разработчиков ПО
Отличной пятницы вам, господа. И у меня для вас сегодня вот такая тема для обсуждения. Периодически мы сталкиваемся с такими рассуждениями из уст даже уважаемых в сообществе людей. Приведу несколько примеров из видео по ссылке ниже (кроме тех, которые на канале и так широко обсуждались), а в комментариях расскажите, с чем согласны, с чем нет. И предлагайте свои варианты, от которых у вас «пригорает» больше всего.

1. Программисты, которые не пишут код в свободное время для развлечения, никогда не станут такими же хорошими, как те, кто это делает
Даже самые умные и талантливые люди никогда не станут по-настоящему хорошими программистами, если они не будут относиться к этому как к чему-то большему, чем работа. То есть они они делают небольшие проекты на стороне или просто возятся с множеством разных языков и идей в свободное время. Это не значит, что хорошие программисты ничего не делают, кроме программирования, но они делают больше, чем просто пишут код с 9 до 5.

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

3. ИТ специальность делает вас более разносторонним программистом
Формальное образование открывает вам другие возможности мышления и даёт отличный базис. То, что вы написали код лучше, чем бакалавр в ИТ, не значит, что вы лучше него. Он сможет легко наверстать упущенное, а вот наоборот – вряд ли. Наличие квалификации также показывает целеустремлённость, настойчивость и тот факт, что человек сделает всё возможное, чтобы стать лучше.

4. Тесты не нужно писать заранее, а иногда и вовсе не нужно
Разработчики плохо тестируют собственный код. Вот почему обычно есть группы QA. В большинстве случаев код, который мы пишем, переплетается с другим кодом, который нужно тестировать отдельно, поэтому мы в итоге обмазываемся паттернами, чтобы обеспечить тестируемость. Не то чтобы паттерны – это плохо, но иногда они могут добавлять ненужной сложности, и все ради тестирования… что часто всё равно не работает. Чтобы написать исчерпывающий тест, требуется много времени. Часто больше времени, чем мы готовы выделить. И чем более комплексным является тест, тем более хрупким он становится. Если интерфейс объекта, который он тестирует, изменяется, мы вынуждены переписать тест, который больше не срабатывает. А если меняются требования к системе…

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

6. Не страшно чего-то не знать. Но тебя уволят, если ты даже не сможешь это нагуглить
Интернет (и гугл в частности) - это инструмент. И эффективное его использование – это один из важных навыков, которым должен обладать разработчик.

Источник: https://youtu.be/goy4lZfDtCE
👍1
День девятьсот девяносто седьмой. #ПолезныйКод
Загрузка сертификата SSL/TLS в .NET
Недавно мне потребовалось настроить оповещение, когда срок действия сертификата подходит к концу или когда он использует небезопасный алгоритм подписи. Первый шаг - получить сертификат. В .NET для этого вы можете использовать класс SslStream.

static class CertificateDownloader
{
//Этот метод обратного вызова нужен для проверки безопасности сертификата
//Но здесь нам не нужна проверка, мы только получаем данные сертификата
private static readonly
RemoteCertificateValidationCallback
сallback = (_, _, _, _) => true;

public static async Task<X509Certificate2?>
GetCertificateAsync(string domain, int port = 443)
{
using var client = new TcpClient(domain, port);
using var sslStream =
new SslStream(client.GetStream(), true, сallback);

// Инициируем соединение, чтобы скачать сертификат
await sslStream
.AuthenticateAsClientAsync(domain)
.ConfigureAwait(false);

var cert = sslStream.RemoteCertificate;
if (cert != null)
return new X509Certificate2(cert);
return null;
}
}

Использование:
static async Task Main()
{
var cert = await
CertificateDownloader.GetCertificateAsync("www.google.com");

Console.WriteLine($"Subject: {cert?.Subject}");
Console.WriteLine($"Issuer: {cert?.Issuer}");
Console.WriteLine($"NotBefore: {cert?.NotBefore}");
Console.WriteLine($"NotAfter: {cert?.NotAfter}");
Console.WriteLine($"Algorithm: {cert?.SignatureAlgorithm.FriendlyName}");
}

Далее можно настроить повторяющуюся задачу в Azure Pipelines или GitHub Actions для ежедневной проверки сертификатов, но это выходит за рамки этого поста.

Источник: https://www.meziantou.net/downloading-a-ssl-certificate-in-dotnet.htm
День девятьсот девяносто восьмой. #ЗаметкиНаПолях #AsyncTips
Обработка задач при завершении

Задача
Имеется коллекция задач, которые будут использоваться с await; требуется организовать обработку каждой задачи после ее завершения. При этом обработка каждой задачи должна происходить сразу же после завершения, без ожидания других задач.

Допустим, у нас есть некоторый набор однотипных задач. Для простоты возьмём задачу, которая просто ожидает заданное количество секунд:
async Task<int> DelayAndReturnAsync(int value)
{
await Task.Delay(TimeSpan.FromSeconds(value));
return value;
}

Создадим коллекцию этих задач:
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
Task<int>[] tasks = new[] { taskA, taskB, taskC };

Можно ожидать каждую задачу в цикле, но тогда они будут обрабатываться последовательно в порядке их нахождения в коллекции:
// Выводит "2", "3" и "1" вместо "1", "2" и "3".
async Task ProcessTasksAsync(Task<int>[] tasks)
{
foreach (Task<int> task in tasks)
{
var result = await task;
Trace.WriteLine(result);
}
}

Решение
Решение заключается в добавлении оборачивающего асинхронного метода, который обеспечивает ожидание задачи и обработку ее результата. Этот метод можно записать как асинхронную лямбду:
async Task ProcessTasksAsync(Task<int>[] tasks)
{
Task[] processing = tasks.Select(async t =>
{
var result = await t;
Trace.WriteLine(result);
}).ToArray();

// Ожидаем завершения всей обработки
await Task.WhenAll(processing);
}

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

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 2.
День девятьсот девяносто девятый. #ЧтоНовенького
Hot Reload Убрали Отовсюду, Кроме Visual Studio 2022… Но Потом Вернули

It’s been 0 days since .NET had drama.
Такое сообщение выдаёт недавно созданная шуточная утилита dotnet-drama, которую успели написать за выходные.

Суть
В прошлую среду Microsoft объявили о последних подготовках к выходу .NET 6 (кстати, презентация пройдёт на .NET Conf 2021, начало 9 ноября в 19:00 по Москве). Отдельным большим постом было объявление о релизе Hot Reload, из которого стало известно, что широко разрекламированную утилиту внезапно решили выпилить отовсюду, кроме Visual Studio 2022. То есть, она не работала бы ни в VS Code, ни в Rider, ни в консольных утилитах вроде dotnet watch. Причём, поначалу было не ясно, почему было принято такое решение. Но, судя по объяснениям от Microsoft (а точнее по их отсутствию), скоро стало понятно, что решение было не техническим, а чисто менеджерским. То есть, готовую рабочую утилиту, которой многие уже начали пользоваться даже в режиме превью, решили убрать… «просто потому что».

Развитие драмы
Решение было быстро замечено и раскритиковано чуть менее, чем всем .NET сообществом. Вот например тред от Microsoft MVP Шона Киллина. Основной посыл критиков в том, что такое решение подрывает доверие ко всей экосистеме. Более того, этот поступок был практически бессмысленным, т.к. весь код утилиты был открыт, и ничто не мешало энтузиастам взять его и запилить собственные версии. А JetBrains уже в пятницу объявили об обновлении Rider, поддерживающем Hot Reload.

В результате в субботу в Microsoft, что называется, «дали заднюю». Директор по управлению программами .NET Скотт Хантер принёс извинения сообществу и сообщил, что пулл реквест по возвращению Hot Reload в .NET SDK будет удовлетворён.

Что это было?
Да чёрт его знает. Мотивы поступка менеджеров Microsoft до сих пор не ясны. Стимулировать использование новой VS 2022? По слухам Microsoft особой прибыли от IDE не получают. Основной поток идёт от Azure. Да и вряд ли это стало бы решающим фактором при выборе рабочего процесса.

В общем очередная победа гражданского общества над корпорацией зла… Хотя, может на это и был изначальный расчёт – нагнать хайпа перед релизом .NET 6?

Источник: https://devblogs.microsoft.com/dotnet/net-hot-reload-support-via-cli/
День 1000.
И вот так незаметно прошла 1000 дней. Кстати, мне порядком надоело писать числа прописью, тем более что теперь это будет совсем длинно. Поэтому дальше будут цифры.

К юбилею вот подогнали книжку, над переводом которой работал, поэтому вот хвастаюсь. Купить можно тут.

Спасибо, что читаете. Отдельное спасибо за комментарии и обсуждения в чате.

Может, пора уже какой-нибудь мерч выпустить, что скажете? Давайте для пробы начнём с кружек. Напишите в комментариях, кто хочет кружку. Среди желающих выберу случайным образом несколько человек и отправлю им сувениры. Только сразу оговорюсь, будьте готовы сообщить мне реальные ФИО и адреса, куда прислать.
День 1001. #юмор
Да, да, именно так.
День 1002. #ProjectManagement
Почему Плохое ПО Случается с Хорошими Людьми. Начало
Плохое ПО - одна из немногих вещей, которую нельзя решить с помощью денег. У огромных авиакомпаний приложения для поиска рейсов часто уступают студенческим проектам. Корпоративные ИТ-системы - это монстры с огромными бюджетами, создаваемые в течение многих лет. Как бы то ни было, причина плохого ПО не в недостатке финансирования.

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

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

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

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

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

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

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

Источник:
https://www.csc.gov.sg/articles/how-to-build-good-software
День 1003. #ProjectManagement
Почему Плохое ПО Случается с Хорошими Людьми. Окончание
Начало

2. ПО ограничено сложностью
Полезность ПО обычно ограничивается его сложностью, а не объемом ресурсов, вложенных в его создание.

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

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

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

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

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

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

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

Источник: https://www.csc.gov.sg/articles/how-to-build-good-software
День 1004. #NetBasics
Чем Отличается SDK от Runtime?
Официальная страница .NET предлагает 2 опции для скачивания:
- SDK (Software Development Kit) – комплект для разработки ПО со средой исполнения,
- Runtime – только среду исполнения.

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

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

Ещё один момент — это слабая взаимосвязь между разными языками .NET. Как видно на картинке ниже, C#, F# и VB.NET находятся на разных этапах своей жизни и, следовательно, имеют разные версии. Языки могут развиваться независимо и вводить новые языковые функции по своему усмотрению до тех пор, пока связанный компилятор переводит исходный код в действительный IL.

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

Для справки: не для каждого языка программирования требуется SDK и среда исполнения. Например, такие языки, как Rust или Go, которые напрямую компилируются в собственный машинный код, не требуют среды исполнения. Для этих языков доступен только один вариант загрузки, который обычно представляет собой SDK для создания ПО. .NET же похож, например, на Java, в которой также есть JDK (Java Development Kit) для создания программного обеспечения и JRE (Java Runtime Environment) для исполнения кода.

Источник:
https://dusted.codes/dotnet-basics
1005.png
286.5 KB
День 1005. #NetBasics
Очень понравилась картинка, поэтому решил её перевести. Надеюсь, Кокоса не будет против. Все ссылки на авторство сохранил.

Источник: https://goodies.dotnetos.org/
День 1006. #Testing
Видимое и Невидимое в Модульном Тестировании
В экономике существует понятие «видимого и невидимого», что означает, что при реализации какой-либо политики необходимо сосредоточить внимание на обеих его сторонах:
- Желаемые результаты, которые являются видимой частью.
- Непредвиденные последствия - невидимая часть.

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

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

Видимая часть — это преимущества, которые предоставляют вам модульные тесты. Невидимая часть — это (часто скрытые) затраты.

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

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

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

Есть и эффекты второго порядка: вы начинаете терять доверие к своим тестам, что приводит к меньшему количеству рефакторингов и, в итоге, к ухудшению кода.

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

Источник: https://enterprisecraftsmanship.com/
Автор оригинала: Vladimir Khorikov