.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
День 1340. #ЗаметкиНаПолях #Testing
F.I.R.S.T. – Акроним для Хороших Тестов
Аббревиатуры, особенно яркие, помогают нам запоминать важные вещи. FIRST – поможет запомнить, какими должны быть ваши юнит-тесты.

1. F - Fast (Быстрые)
Не создавайте тесты, требующие много времени для настройки и запуска: в идеале вы должны иметь возможность выполнить весь набор тестов менее чем за минуту. Если модульные тесты занимают слишком много времени для выполнения, с ними должно быть что-то не так:
- Вы пытаетесь получить доступ к удалённым источникам (реальные API, базы данных и т. д.). Их следует либо замокать, либо использовать в интеграционных или сквозных тестах.
- Система слишком сложна для сборки - слишком много зависимостей.
- Метод делает слишком много вещей. Разделите его на более мелкие.

2. I - Isolated (Изолированные)
Методы тестов должны быть независимыми друг от друга. Обычно тесты исполняются в алфавитном порядке по названию (если отключено параллельное исполнение). Но в общем случае надеяться на это не следует. Создавайте новые экземпляры тестируемых объектов и воссоздавайте нужное состояние системы в каждом тесте. Так изменения в одном тесте не повлияют на другие.

3. R – Repeatable (Повторяемые)
Это означает, что где бы и когда бы вы ни запускали тесты, они должны вести себя корректно. Поэтому вы должны удалить любую зависимость от файловой системы, текущей даты (дня недели) и так далее. Вынесите эти зависимости и используйте заглушки или моки для них.

4. S - Self-validating (Самопроверяющиеся)
Тест должен выполнять операции и программно проверять результат. Если вы тестируете, что вы записали в файл, сам тест должен отвечать за проверку правильности своей работы. Если система генерирует и отправляет email, содержимое которого надо проверить, сделайте мок, который просто будет сохранять текст письма в переменную и проверять её. Никаких ручных операций выполнять не следует. Кроме того, тесты должны обеспечивать явную обратную связь: тест либо проходит, либо не проходит, никаких промежуточных результатов.

5. T – Thorough (Тщательные)
Модульные тесты должны быть тщательными в том смысле, что они должны проверять как удачные, так и неудачные пути. Вы должны тестировать функции как с допустимыми, так и с недопустимыми входными данными, проверить, что будет, если во время выполнения возникнет исключение: правильно ли обрабатываются ошибки?

В этом смысле очень помогают программы (расширения IDE), отслеживающие покрытие кода тестами. Общая цифра покрытия мало о чём говорит. Зато отметки напротив каждой строки кода, исполнялась ли она в тесте явно покажут вам, не забыли ли вы проверить какое-либо условие. Ведь помимо простых ветвлений через if, бывают и switch, и тернарные операторы, и сложные условия, и блоки catch, и т.п. В этом случае программа, отслеживающая покрытие кода тестами, отметит строку обычно зелёным, если она выполнилась в ходе теста, красным, если нет, или жёлтым, если выполнилась только часть условий в ветвлении.

Источник: https://www.code4it.dev/cleancodetips/f-i-r-s-t-unit-tests
👍17
День 1341. #Книги
Сегодня мне пришла книга, над переводом которой я работал совместно с сообществом DotNetRu. Джон П. Смит «Entity Framework Core в действии» — М.: ДМК Пресс, 2022.

Я уже участвовал в переводе книги "ASP.NET Core в действии", но тогда из-за занятости не получилось перевести много. А поскольку с EF Core по работе сталкиваюсь мало (хотя и не сказать, что вообще его не знаю), то решил помочь с переводом этой книги и заодно самому подтянуть знания. Вообще технические книги я читаю достаточно регулярно, но работа над переводом - это немного другое. Внимательно вычитывая и правя текст, сверяя правильность и согласованность использования терминов в разных главах, ты лучше запоминаешь какие-то нюансы, чем если просто прочитаешь готовую книгу. Это получился полезный опыт для меня лично. Но также, надеюсь, что мои усилия помогут русскоязычным читателям получить новые знания.
👍66
День 1342. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 2. Сообщения Об Ошибках. Начало

Часть 1. Идентификаторы Объектов

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

Хорошие сообщения об ошибках — часто недооцениваемая часть API. Но они так же важны для изучения вашего API, как документация или примеры. Вот пример ответа API:
{
status: 200,
body: {
message: "Ошибка"
}
}
Оно кажется странным. Давайте рассмотрим, что здесь не так.

1. Отправляйте правильный код ответа
Выше это ошибка или нет? В сообщении говорится, что да, но код ответа 200 говорит, что всё в порядке. Это не только сбивает с толку, но и опасно. Большинство систем мониторинга ошибок сначала смотрят на код ответа, а затем пытаются проанализировать тело сообщения. Эта ошибка, скорее всего, будет «помещена в папку «OK»» и проигнорирована.
Коды ответа предназначены для машин, сообщения об ошибках — для людей. Необходимо устанавливать соответствующий код ошибки при возврате ответа из API.

2. Добавьте описание
Большинство людей согласятся с тем, что сообщение «Ошибка» так же полезно, как и вообще отсутствие сообщения. Код состояния ответа уже должен сообщить вам, произошла ошибка или нет, сообщение должно быть точным и помогать решить проблему.

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

Изменим сообщение из предыдущего примера:
{
status: 404,
body: {
error: {
message: "Клиент не найден"
}
}
}

- У нас есть соответствующий код ответа: 404, ресурс не найден.
- Сообщение ясно: был запрос, который пытался получить клиента, и он не удался, потому что клиент не может быть найден.
- Сообщение об ошибке заключено в объект ошибки, что немного упрощает работу с ошибкой. Даже без кода ответа вы можете просто проверять наличие body.error, чтобы увидеть, произошла ли ошибка.

Уже лучше, но ещё есть куда расти. Ошибка описывает проблему, но по сути она бесполезна.

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

Источник:
https://dev.to/stripe/designing-apis-for-humans-error-messages-94p
👍6
День 1343. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 2. Сообщения Об Ошибках. Окончание
Начало

Часть 1. Идентификаторы Объектов

3. Добавьте полезной информации
Сообщить, в чем заключалась ошибка, — это минимум, но разработчик захочет знать, как её исправить. «Полезный» API старается устранить любые препятствия на пути решения проблемы.

Сообщение «Клиент не найден» даёт нам некоторую информацию относительно того, что пошло не так, но как разработчики API мы знаем, что могли бы дать здесь гораздо больше информации. Для начала давайте укажем, какой клиент не был найден: "Клиент cus_Jop8JpEFz1lsCL не найден".

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

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

4. Больше эмпатии
Самая неприятная ошибка - 500. Это означает, что что-то пошло не так на стороне API и, следовательно, не по вине клиента. Если конечный пользователь полагается на ваш API в качестве критически важного для бизнеса процесса, то получение таких ошибок очень расстроит клиента.

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

Как и в изначальном примере текст «Ошибка 500» так же информативен, как и его отсутствие. Попытайтесь успокоить клиентов сообщением вроде: «Произошла ошибка. Команда разработчиков проинформирована и разбирается с проблемой. Если это продолжится, свяжитесь с нами по адресу …».

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

Итого
Посмотрим на следующее сообщение об ошибке, когда клиент использует производственный ключ доступа в тестовой среде:
{
status: 404,
body: {
error: {
code: "resource_missing",
doc_url: "https://api.com/docs/errors/resource-missing",
message: "Клиент 'cus_Jop8JpEFz1lsCL' не найден. Он существует в производственной среде, но в этом запросе использовалась тестовая среда.",
param: "id",
type: "invalid_request_error"
}
},
headers: {
'api-version': '3.1.1',
}
}

Здесь мы:
- Используем правильный код ответа HTTP,
- Оборачиваем ошибку в объект «error»,
- Добавляем полезную информацию: код ошибки и тип ошибки,
- Даём ссылку на документацию,
- Сообщаем версию API,
- Предлагаем вариант решения проблемы

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

Источник: https://dev.to/stripe/designing-apis-for-humans-error-messages-94p
👍5
День 1344.
Долго думал, писать ли про это. Но раз уж дневник, то пусть будет дневником.
Я в Грузии. Без семьи. Не знаю, на сколько. Недавние события стали последней каплей. И хотя я вроде под критерии первой очередности не подхожу, решили с женой, что проверять не стоит, и лучше выехать, пока выпускают.
Тем более, что в РФ стало гораздо сложнее получать деньги.

Так что вот. Если вопросы, комментарии какие или личный опыт, делитесь.
👍62👎23
День 1345. #ЗаметкиНаПолях #AsyncTips
Асинхронное Создание Объектов: Паттерн Асинхронной Инициализации

Задача
Вам нужен тип, требующий выполнения некоторой асинхронной работы в конструкторе, но вы не можете воспользоваться паттерном асинхронной фабрики, так как экземпляр создаётся с применением рефлексии (например, IoC-контейнера, связывания данных, Activator.CreateInstance и т. д.).

Решение
В таком сценарии вам приходится возвращать неинициализированный экземпляр, хотя ситуацию можно частично сгладить применением распространённого паттерна асинхронной инициализации. Каждый тип, требующий асинхронной инициализации, должен определять специальное свойство (обычно в интерфейсе-маркере):
public interface IAsyncInit
{
Task Initialization { get; }
}

При реализации этого паттерна следует начать инициализацию, задав значение свойства Initialization в конструкторе. Доступ к результатам асинхронной инициализации (вместе с любыми исключениями) предоставляется через свойство Initialization. Пример реализации:
class MyType : IAsyncInit
{
public MyType()
{
Initialization = InitAsync();
}

public Task Initialization
{ get; private set; }

private async Task InitAsync()
{
// асинхронно инициализируем экземпляр
await Task.Delay(TimeSpan.FromSeconds(1));
}
}

Экземпляр этого типа может быть создан и инициализирован примерно так:
var instance = DIContainer.Resolve<MyType>();
var asyncInit = instance as IAsyncInit;
if (asyncInit != null)
await asyncInit.Initialization;

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

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 11.
👍6
День 1346.
Ребята из IT’s Tinkoff наконец-то выложили материалы сентябрьского митапа, который проходил в Нижнем Новгороде и на котором мне удалось побывать.

Поэтому сегодня предлагаю вашему вниманию доклады.

1. Николай Михалычев «Почему вам стоит начать вести документацию прямо сейчас»
Техническая документация — это та часть деятельности команды разработки, которой пренебрегают чаще всего, и которая первой же попадает под нож, когда нужно снижать стоимость или увеличивать скорость разработки. В то же время, правильно поставленная культура ведения документации позволит вам не только сэкономить время, но и сильно повысить качество вашего продукта. Николай поделился опытом нашей команды по выстраиванию процесса ведения документации, и тем, какие проблемы нам удалось решить с его помощью.
Доклад вызвал больше всего вопросов. И не все желающие успели их задать, т.к. уже крали время от следующего доклада. Но и потом в личной беседе Николая долго спрашивали о тонкостях процесса.

2. Кирилл Бажайкин «Микрооптимизации в .NET»
На примерах с бенчмарками и небольшим количеством теории Кирилл рассказал о микрооптимизации в .NET. Также объяснил почему этим не стоит заниматься, а если заниматься, как делать это правильно.
Наша любимая тема байто- и наносекундо- дрочерства. Автор привёл несколько полезных примеров, рассказал об утилитах мониторинга и анализа кода. Там и я включился с невнятным комментарием)))

3. Никита Сеньков «Неявное использование контравариантных постобработчиков в MediatR»
Никита рассмотрел использование абстракции IRequestPostProcessor для выполнения логики, применимой к нескольким типам команд. А также обсудил, откуда вообще растет такая задача, почему наивное решение не работает из коробки и, как починить это досадное недоразумение созданием еще одного метода расширения для регистрации компонентов MediatR.
Этот доклад можно, наверное, назвать наименее практически полезным из всех. Просто разбор довольно узкоспециализированной функциональности в нелюбимом многими медиаторе. Но знаю, что среди подписчиков есть люди, которые любят покопаться в таких кишочках и явить миру что-нибудь эдакое в виде решения. Тут я тоже влез с глупым вопросом. Умный вопрос пришёл в голову гораздо позже (как это обычно и бывает).

4. Руслан Артамонов «Межсервисные интеграции. Что может пойти не так?»
При написании бизнес-приложений каждый разработчик сталкивается с необходимостью обмениваться данными между сервисами, вызывать удаленные процедуры. В первом приближении такие задачи кажутся довольно тривиальными, пока речь не заходит о надежности.
Как быть до конца уверенным, что данные не потеряются, а удаленные процедуры выполнятся? Наша команда разрабатывает веб-приложение с большим количеством интеграций. В докладе Руслан поделился накопленным опытом и кейсами, а также рассмотрел ошибки, с которыми приходится сталкиваться и описал способы того, как можно их избежать.
👍16
День 1347. #юмор
👍8
День 1348. #Конференция
Команда Dotnetos широко известного в узких кругах дотнетчиков Конрада Кокосы с 10 по 12 октября проводит онлайн-конференцию.

Судя по расписанию докладов, нас ждёт много интересного.

Понедельник, 10 октября
1. 18:00 мск. Raffaele Rialdi «Preemptive monitoring applications in production»

2. 19:00 мск. Stefan Pölz «Never send a human to do a machine's job»

3. 20:00 мск. Miguel Angel Teheran «Getting started with OpenTelemetry in .NET»

Вторник, 11 октября
1. 18:00 мск. Timothy Barnett «A system's perspective – lessons from failures»

2. 19:00 мск. Victor Nicollet «ILPack : saving assemblies to disk»

3. 20:00 мск. Oren Eini «Scalable architecture from the ground up»

Среда, 12 октября
1. 18:00 мск. Rafał Schmidt «Your clients are lying! Adventure with leaky .NET»

2. 19:00 мск. Kevin Gosse «Pushing C# to new places with NativeAOT»

3. 20:00 мск. Krzysztof Stolarz «Road to .NET 6: migration story of hope and despair»

Ставим напоминалки и повышаем квалификацию!
👍12
День 1349. #ЧтоНовенького
Сравнение файлов в Visual Studio
Как разработчикам, нам часто приходится сравнивать два файла, чтобы найти различия. Иногда сравнивать содержимое буфера обмена с содержимым файла на диске, либо сравнивать наши локальные изменения с предыдущими версиями из Git.

Мэдс Кристенсен, известный разработчик многих расширений для Visual Studio, представил новое расширение File Differ как раз для этих целей.

После установки расширения в окне обозревателя решения нам будет доступен пункт всплывающего меню Compare (Сравнить) со следующими вариантами:
- Compare two files in Solution Explorer (Сравнить два файла из Обозревателя Решения),
- Compare file with another file on disks (Сравнить файл с другим файлом на диске),
- Compare file with content of clipboard (Сравнить файл с содержимым буфера обмена),
- Compare file with its unmodified version (Сравнить файл с неизменённой версией)
Также скоро будет доступен вариант Compare with Previous Version (Сравнить с предыдущей версией)

Эти варианты будут активны или неактивны в зависимости от контекста. Например, если вы выделите два файла в обозревателе решения, то будет доступен только пункт Compare > Selected files (Сравнить > Выбранные файлы). А если вы вызовете выпадающее меню на изменённом файле, находящемся под управлением системы контроля версий, будет доступен пункт Compare > File with Unmodified Version (Сравнить > Файл с Неизменённой версией).

Расширение работает и внутри редактора кода. Там также доступен пункт выпадающего меню Compare со следующими вариантами:
- Compare selection with clipboard (Сравнить выделение с содержимым буфера обмена),
- Compare active file with clipboard (Сравнить текущий файл с содержимым буфера обмена),
- Compare active file with saved version (Сравнить текущий файл с сохранённой версией)
- Compare active file with file on disk (Сравнить текущий файл с файлом на диске).

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

Расширение находится в стадии разработки и Мэдс призывает протестировать его и поделиться мыслями в репозитории на GitHub. Я, кстати, добавил туда одну идею. Мне не хватает возможности легко перемещаться по различиям и копировать различия из панели в панель по горячей клавише (напрмер, Alt+стрелка влево/вправо), без необходимости выделять, копировать и вставлять.

Источник: https://devblogs.microsoft.com/visualstudio/comparing-files-in-visual-studio/
👍7
День 1350. #Testing
Не Полагайтесь на Порядок Юнит-Тестов
Одна из распространённых ловушек, в которую может попасть новичок при написании юнит-тестов, — это положиться на их последовательное выполнение. Это ведь имеет смысл. В тестах можно ли создавать, читать и удалять некоторые данные, почему бы не использовать данные из прошлого теста?

Недостатки
1. Вы должны поддерживать состояние, и это очень плохо
Цель юнит-теста — подтвердить, что определённая часть кода выполняет свою работу правильно. Чтобы в этом убедиться, эту конкретную часть надо изолировать от всего остального. Поэтому, если мы полагаемся на то, что другой тест подготовит данные для нас, мы не следуем этому принципу.
Кроме того, очень сложно отслеживать входящие данные теста, если они не очевидны с первого взгляда. Представьте, что вы присоединяетесь к новой команде и должны выяснить, где генерируются данные для теста. Ужас!

2. Один новый тест может сломать все тесты
Что если нам надо добавить новый тест? Если в какой-то момент в тестах нам нужно иметь определённые данные, как мы можем быть уверены, что новый тест их не испортит? А что, если нужно удалить тест, который больше не нужен?

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

4. Увеличивается время отладки
Если юнит-тест падает, мы не сможем сразу же запустить его снова, так как нужно будет запустить все остальные тесты перед ним, чтобы потом его можно было отладить. Если проблему трудно найти, может потребоваться много запусков. А возможно, проблема даже не в этом тесте, она может быть связана с каким-то другим тестом, который неправильно выполнил свою часть.

Как не полагаться на порядок юнит-тестов
В шаблоне Arrange Act Assert (AAA), все данные, необходимые для теста, должны быть подготовлены на этапе Arrange. Некоторые системы тестирования предлагают методы настройки (SetUp). Можно использовать отдельные вспомогательные методы, поскольку это помогает избежать хранения состояния и улучшает читаемость.

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

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

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

Источник: https://intodot.net/unit-testing-best-practices-avoid-relying-on-test-order/
👍14
День 1351. #ЗаметкиНаПолях
Использовать ли Мягкое Удаление?
Удалять ли записи из базы данных или вместо этого использовать мягкое удаление?

На всякий случай, чтоб не было разногласий. Жёсткое удаление – это удаление записи из базы данных, т.к. вызов команды Delete в DBContext или SQL команды DELETE.

Как правило, если вы используете реляционную базу данных, у вас могут быть ограничения внешнего ключа, которые не позволяют вам удалять строки для обеспечения целостности данных. Мягкое удаление решает эту проблему. Чаще всего просто добавляется столбец IsDeleted, чтобы указать, является ли запись/строка «удалённой» или неактивной, со значением false по умолчанию. Аналогично в NoSQL базе данных свойство IsDeleted указывает, удалён ли документ/объект.

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

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

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

Документирование событий
Возможно, мы представляем это в нашем документе/объекте как набор событий найма и увольнения с соответствующими данными. Возможно, работник повторно нанят позже и имеет несколько периодов занятости.

Но ключевым моментом является фокус на происходящих бизнес-событиях. Если вы думаете о событиях как об основном драйвере вашей системы, вы, скорее всего, придёте к мысли о сохранении событий как состояния системы. Это называется Event Sourcing.

CRUD
Можно думать о системе в парадигме CRUD (Create-Read-Update-Delete). Однако в вашей предметной области, как уже упоминалось, вы не услышите, как люди говорят об «удалении». Почти все события имеют некоторый тип компенсирующего действия, которое завершает жизненный цикл процесса или «отменяет» или аннулирует предыдущее действие. Запись этих бизнес-событий и концепций является ключом к построению рабочего процесса в вашей системе. Если вы сосредоточены на CRUD, имейте в виду, что люди в предметной области мыслят бизнес-процессами и рабочими процессами. И ваша CRUD-система в этом случае представляет собой не более чем пользовательский интерфейс для базы данных без реальных возможностей.

GDPR
Можно возразить, что по закону о защите персональной информации может потребоваться удалить данные. Но суть не в этом.
Если вам нужно удалить данные, удалите их. Дело не в том, чтобы не удалять данные; дело в том, что при «мягком удалении» данных вы теряете информацию о бизнес-концепциях/событиях, которые, вероятно, произошли как часть бизнес-процесса или рабочего процесса. Произошедшие события и причины, по которым они произошли, могут быть невероятно ценными при построении надёжной системы, которая может развиваться по мере изменения требований.

Источник: https://codeopinion.com/should-you-soft-delete/
👍10
День 1352. #ЗаметкиНаПолях
Проверка Порядка Выполнения с Помощью Юнит-тестов
Прежде всего, давайте проясним: этот пост не о порядке выполнения юнит-тестов. Никогда не следует рассчитывать на то, что тесты выполняются в каком-либо порядке. Мы же поговорим о проверке порядка выполнения операций внутри определённого метода с помощью тестов. Иногда вам нужно сделать больше, чем просто проверить правильность вызова методов.

Предположим, что мы реализуем сервис-оркестратор, который должен выполнить работу, а затем сообщить об исполнении. Хорошо бы, чтоб уведомление отсылалось после выполнения работы.

Для этого мы можем воспользоваться функцией обратного вызова Moq. Она позволяет нам выполнить код сразу после вызова метода. С её помощью мы можем установить флаг, чтобы отслеживать, когда был вызван метод, а затем использовать этот флаг, чтобы проверить, был ли второй метод вызван в нужное время:
public class JobService
{
private IWorkService ws;
private INotifyService ns;

// внедряем сервисы через DI

public void DoAndNotify()
{
ws.DoJob();
ns.Notify();
}
}

А вот наш тест:
[Test]
public void ShouldNotifyAfterJob()
{
var wsMock = new Mock<IWorkService>();
var nsMock = new Mock<INotifyService>();
var sut = new JobService(
wsMock.Object,
nsMock.Object);

var jobExecuted = false;
var notified = false;
wsMock.Setup(x => x.DoJob())
.Callback(() => jobExecuted = true);
nsMock.Setup(x => x.Notify())
.Callback(() => notified = jobExecuted);

sut.DoAndNotify();

Assert.That(notified, Is.True);
}

Что тут происходит:
- Создаём моки зависимостей и сервис.
- Устанавливаем функцию обратного вызова для метода DoJob, поэтому при его вызове флаг jobExecuted будет установлен в true.
- Устанавливаем функцию обратного вызова для метода Notify, в котором флаг notified будет истинным только, если jobExecuted имеет значение true.

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

Источник: https://intodot.net/verifying-the-execution-order-with-unit-tests/
👍13
LINQ.pdf
3.6 MB
День 1353. #Книги
Сегодня предложу вашему вниманию небольшую книгу Стивена Жизеля «LINQ Explained with sketches» (Объяснение LINQ в зарисовках). Я уже где-то встречал отрывки из этой книги в сети, здесь же они собраны воедино.

Книга хорошо структурирована и содержит отличные пояснения со схемами и примерами на C#, которые помогают чётко понять суть метода и как им пользоваться. Подойдёт как новичкам для изучения LINQ, так и опытным разработчикам в качестве шпаргалки.
👍31
День 1354. #юмор
Все мы иногда этот ИИ.
👍29
День 1355. #Карьера #ВопросыНаСобеседовании
Что Отвечать на Вопрос о Зарплатных Ожиданиях
Наткнулся на интересное видео «Пример лучшего ответа на вопрос о ваших зарплатных ожиданиях». Автор представляет дословный скрипт, куда нужно подставить только цифры. Это мне показалось интересным, поэтому решил «локализовать» для нашей части света.

Для начала несколько советов.
1. Изучите диапазон средних зарплат на вашу должность перед тем, как пойдёте на собеседование.
Данные можно взять, например с Хабр.Карьеры (для РФ) или DOU (для Украины). Добавьте в комментариях, если знаете другие источники.

2. Просите зарплату слегка ниже верхнего предела.
К примеру, если средняя ЗП на уровень мидл разработчика находится в диапазоне от $2000 до $3500, просите $3200.

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

Теперь собственно скрипт:
«Перед тем, как пойти на собеседование я исследовал средние зарплаты по рынку на эту должность. Средние зарплаты колеблются от $2000 до $3500. Несмотря на то, что я хотел бы получать больше, я понимаю, что вы меня не знаете, а мне ещё нужно доказать, что я достоин зарабатывать такие деньги. Учитывая это, комфортным для меня уровнем будет $3200, и я уверен, что моё трудолюбие, ответственность, исполнительность и способность быстро вникать в рабочие процессы докажут вам, что я стою потраченных на меня денег.»

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

Что вы отвечали на вопрос о зарплатных ожиданиях и дали ли вам требуемую сумму? Поделитесь в комментариях.

Источник: https://youtu.be/0k13HVHJoNc
👍8
День 1356. #ЗаметкиНаПолях #AsyncTips
Асинхронные Свойства

Задача:
имеется свойство, которое вам хотелось бы объявить, как асинхронное. Свойство не задействовано в связывании данных.

Решение
Эта проблема часто встречается при преобразовании существующего кода для использования async; в таких ситуациях создаётся свойство, get-метод которого вызывает асинхронный метод. Вообще, такого понятия, как «асинхронное свойство», не существует. Ключевое слово async не может использоваться со свойством, и это хорошо. Get-методы свойств должны возвращать текущие значения; они не должны запускать фоновые операции:
// Чего хотелось бы (не компилируется)
public int Data
{
async get
{
await Task.Delay(TimeSpan.FromSeconds(1));
return 13;
}
}

Когда вам кажется, что вам нужно «асинхронное свойство», в действительности требуется нечто иное. Если ваше «асинхронное свойство» должно запускать новое (асинхронное) вычисление каждый раз, когда оно читается, то, по сути, это замаскированный метод:
public async Task<int> GetDataAsync()
{
await Task.Delay(TimeSpan.FromSeconds(1));
return 42;
}

Вы можете получить Task<int> непосредственно из свойства:
public Task<int> Data
{
get { return GetDataAsync(); }
}

И все же так делать не рекомендуется. Это свойство должно стать методом. Так это ясно показывает, что каждый раз запускается новая асинхронная операция, поэтому API не вводит пользователя в заблуждение.

Стоит задуматься над тем, как состояние соотносится с асинхронным кодом. Это особенно актуально при преобразовании синхронной кодовой базы в асинхронную. Возьмем любое состояние, доступ к которому осуществляется через API (например, через свойства). Для каждой составляющей состояния спросите себя: что считать текущим состоянием объекта с незавершенной асинхронной операцией? Правильного ответа не существует, но важно продумать то, какие семантики вам нужны и как их документировать.

Для примера возьмем объект Stream.Position, представляющий текущее смещение указателя в потоке. С синхронным API при вызове Stream.Read или Stream.Write чтение/запись завершается, а Stream.Position обновляется новой позицией перед возвращением управления методом Read или Write. Для синхронного кода семантика ясна.

Теперь возьмем Stream.ReadAsync и Stream.WriteAsync: когда должно обновляться значение Stream.Position? При завершении операции чтения/записи или до того, как это фактически произойдет? Если оно обновляется перед завершением операции, то будет ли оно обновлено синхронно к моменту возвращения управления ReadAsync/WriteAsync или же вскоре после этого?

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

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 11.
👍12
День 1357. #ЗаметкиНаПолях #DDD
Маппинг Ошибок. Чья Это Ответственность?
Допустим, модель предметной области возвращает ошибку, о которой нельзя сообщать пользователю, поэтому её нужно смаппить в дружелюбное пользователю сообщение или просто очистить от конфиденциальных данных. Должно ли это происходить на уровне домена или в контроллере (на прикладном уровне)?

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

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

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

Это должно происходить на уровне домена.

Маппинг/очистка сообщения об ошибке является проблемой предметной области. Это исходит из бизнес-требований, что означает, что должно быть частью домена. Введите отдельный класс предметной области, такой как ErrorMapper, ErrorCleaner или что-то подобное.

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

Это, кстати, суть паттерна Humble Object. Идея состоит в том, чтобы извлечь важную часть логики (маппер ошибок) из контроллеров, чтобы упростить модульное тестирование этой логики.

Источник: https://khorikov.org/posts/2022-02-28-error-mapping/
👍20
День 1358. #Testing #Benchmark
Руководство по Бенчмарку в .NET. Начало
В этой серии постов рассмотрим, как создать проект .NET для тестирования производительности.

Начнём с чистого листа и создадим проект. Мы протестируем два метода, которые разными способами объединяют строки. Для этого будем использовать пакет BenchmarkDotNet.

Если вы, как я, не можете запомнить структуру проекта с бенчмарком и постоянно её гуглите, можно просто установить шаблоны:
dotnet new install BenchmarkDotNet.Templates

Теперь создадим проект, используя шаблон:
dotnet new benchmark --console-app -f net6.0 -o StringBenchmarks

Здесь мы используем несколько флагов:
--console-app – консольное приложение,
-f net6.0 – целевой фреймворк .NET 6.0,
-o StringBenchmarks – название проекта и папки для него.

Шаблон создаёт 2 класса:
- Benchmarks, в который добавляет 2 метода для тестовых сценариев: Scenario1 и Scenario2, помеченные атрибутом [Benchmark],
- Program, который собственно запускает бенчмарк.

Назовём методы удобными именами и добавим простые методы объединения строк: через конкатенацию и через StringBuilder.
namespace StringBenchmarks;

public class Benchmarks
{
[Benchmark]
public string StringJoin()
{
return string.Join(", ",
Enumerable.Range(0, 10)
.Select(i => i.ToString()));
}

[Benchmark]
public string StringBuilder()
{
var sb = new StringBuilder();
for (int i = 0; i < 10; i++)
{
sb.Append(i);
sb.Append(", ");
}

return sb.ToString();
}
}
Да, метод со StringBuilder выведет лишнюю запятую. Мы к этому ещё вернёмся. Запускаем проект:
dotnet run -c Release

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

Бенчмарк выдаст подробный отчёт. В начале выдаётся информация о системе, в которой проходили тесты, а затем собственно результаты с разными статистическими подробностями. Мы обратим внимание на колонку Mean (Среднее):
|        Method |     Mean |
|-------------- |---------:|
| StringJoin | 69.71 ns |
| StringBuilder | 41.19 ns |

Как видите, метод, использующий StringBuilder, работает заметно быстрее.

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

Источник:
https://blog.nimblepros.com/blogs/benchmarking-in-dotnet/
👍13
Иллюзия идеального выбора lock(_sync), троттлинг запросов, различия в мышлении инженера и архитектора — обо всем этом поговорим на DotNext 2022 Autumn.

Конференция пройдет 3–4 ноября в онлайне и 20 ноября в офлайне.

В программе уже есть первые доклады. Среди них:
✔️ Станислав Сидристый (ЦРТ) — «lock(_sync): иллюзия идеального выбора».
✔️ Евгений Пешков (Тинькофф) — «Алгоритмы троттлинга запросов».
✔️ Дмитрий Сошников (МАИ / НИУ ВШЭ) — «Как научить вашего ребенка программировать (и не только)».
✔️ Дмитрий Таболич (ИТ1) — «Думай как архитектор: майндшифт инженера».

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

Подробности и билеты — dotnext.ru

Если вам хочется на несколько часов отвлечься и побыть среди единомышленников, то приходите на DotNext. А промокод netdeveloper2022JRGpc даст скидку от 20% на билеты из категории «Для частных лиц».
👍5👎1