.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
День 1335. #ЗаметкиНаПолях
Разработка API для Людей.
Часть 1. Идентификаторы Объектов
Про выбор идентификатора между целым числом и GUID, я уже писал ранее. Сегодня посмотрим, как делать ID более удобочитаемыми для людей.

Вот, например, ID в платёжной системе Stripe:
pi_3LKQhvGUcADgqoEM3bh6pslE

Этот формат более понятен для человека:
pi_3LKQhvGUcADgqoEM3bh6pslE
└─┘└──────────────────────┘
└─ Префикс └─ Случайные символы

Ничего не зная об идентификаторе, мы можем сразу же понять, что здесь мы говорим об объекте PaymentIntent, благодаря префиксу pi_. Когда вы создаёте PaymentIntent через API, вы фактически создаёте или ссылаетесь на несколько других объектов, включая Customer (cus_), PaymentMethod (pm_) и Charge (ch_). С помощью префиксов вы можете сразу различить все эти разные объекты:
var pi = 
Stripe.PaymentIntents.Create(@"{
Amount = 1000,
Currency = 'usd',
Customer = 'cus_MJA953cFzEuO1z',
PaymentMethod = 'pm_1LaXpKGUcADgqo'
}");

Это помогает сотрудникам Stripe так же, как и разработчикам, интегрирующимся со Stripe. Например, вот фрагмент кода, который нужно отладить:
var pi = 
Stripe.PaymentIntents.Retrieve(
id: id,
stripeAccount: "cus_1KrJdMGUcADgqoEM"
);

Код пытается получить PaymentIntent из подключённой учетной записи, однако, даже не глядя на код, вы можете сразу заметить ошибку: вместо идентификатора учетной записи (acct_) используется идентификатор клиента (cus_). Без префиксов это было бы намного сложнее отлаживать.

Полиморфный поиск
При создании PaymentIntent вы можете дополнительно указать параметр paymentMethod, чтобы указать, какой тип платежного инструмента вы хотите использовать. Вы можете указать здесь идентификатор источника (src_) или карты (card_) вместо идентификатора PaymentMethod (pm_):
var pi = 
Stripe.PaymentIntents.Create(@"{
Amount = 1000,
Currency = 'usd',
Customer = 'cus_MJA953cFzEuO1z',
// Здесь может быть
// PaymentMethod, Card или Source ID
PaymentMethod = 'card_1LaRQ7GUcA'
}");

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

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

Предотвращение человеческой ошибки
Есть и другие менее очевидные преимущества, одно из которых — простота работы с идентификаторами, когда вы можете определить их тип по первым нескольким символам. Например, на сервере Stripe Discord используется функция Discord AutoMod, чтобы автоматически помечать и блокировать сообщения, содержащие живой секретный ключ API Stripe, который начинается с sk_live_. Утечка такого конфиденциального ключа может иметь серьёзные последствия для бизнеса. Поскольку ключи начинаются с sk_live_, написать регулярное выражение для фильтрации случайных утечек несложно.

Говоря о ключах API, префиксы live и test — это встроенный уровень защиты, который защищает вас от их смешивания. Те, кто особенно заботится о безопасности, могут настроить проверки, чтобы убедиться, что вы используете ключ только для соответствующей среды:
if (!app.Environment.IsDevelopment())
{
if (Regex.IsMatch("sk_live", "<API_KEY>"))
throw new Exception("Live key detected! Aborting!");
}

Источник: https://dev.to/stripe/designing-apis-for-humans-object-ids-3o5a
👍6
День 1336. #ЗаметкиНаПолях #DesignPrinciples
OCP Против YAGNI. Начало
В этой серии постов рассмотрим противоречия между принципом Open/Closed и принципом «Вам это не понадобится».

OCP
Принцип Open/Closed гласит, что:
«Программные сущности (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации.»

В настоящее время существует две интерпретации этого определения:

1. Дядюшка Боб Мартин говорит о предотвращении волновых эффектов. Когда вы изменяете часть кода, вам не нужно вносить изменения во всю базу кода, чтобы приспособиться к этой модификации. В идеале вы должны иметь возможность добавлять новые функции, ничего не меняя в существующем коде.
Обычно это реализуется с помощью полиморфизма. Например, следующий пример нарушает версию OCP Мартина:
public void Draw(Shape shape)
{
switch (shape.Type)
{
case ShapeType.Circle:
DrawCircle(shape);
break;
case ShapeType.Square:
DrawSquare(shape);
break;
default:
throw new
ArgumentOutOfRangeException();
}
}

Здесь, чтобы ввести новую форму, вам нужно будет изменить метод Draw. Чтобы исправить это, вы можете создать абстрактный класс Shape, а затем перенести логику рисования в подклассы:
public abstract class Shape
{
public abstract void Draw();
}

public class Circle : Shape
{
public override void Draw()
{ … }
}

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

2. Бертран Мейер же говорит об обратной совместимости.
Когда есть несколько взаимозависимых модулей, вы не можете просто изменить свой модуль, когда хотите, вам нужно учитывать его клиентов.

Например, для библиотеки с открытым методом
CreateCustomer(string email)
вы не можете просто добавить новый обязательный параметр:
CreateCustomer(string email, string accountNumber)

Это будет критическим изменением для клиентского кода, который уже привязан к исходной версии метода. Это можно делать во время разработки, но не после публикации. Если нужно внести изменение после публикации, вы создаёте новый модуль (версию модуля). Однако вы по-прежнему можете изменять реализацию, если она не меняет API. Вся тема управления версиями веб-API — это, по сути, принцип OCP Мейера.

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

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

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

Источник:
https://enterprisecraftsmanship.com/posts/ocp-vs-yagni/
👍19
День 1337. #ЗаметкиНаПолях #DesignPrinciples
OCP Против YAGNI. Продолжение
Начало

YAGNI
“You ain’t gonna need it” (Вам это не понадобится) означает, что вы не должны тратить время на функциональность, которая сейчас не нужна. Вам не следует разрабатывать эту функциональность, а также изменять существующий код с учетом её появления в будущем. Два основных момента, которые объясняют, почему это хорошая идея:
1. Требования бизнеса постоянно меняются. Если вы тратите время на функцию, которая не нужна бизнес-людям в данный конкретный момент, вы крадёте время у тех функций, которые им нужны прямо сейчас. Более того, когда им наконец-то понадобится разработанный функционал, их взгляд на него, скорее всего, изменится, и вам всё равно придётся вносить в него коррективы. Такая деятельность расточительна и приводит к чистым убыткам, поскольку было бы выгоднее просто реализовать функцию с нуля, когда в ней возникнет реальная потребность.

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

Бывают ситуации, когда YAGNI неприменим.
Например, вы проектируете функциональность, которую трудно изменить в будущем. Это ориентированные на клиента API, сторонние библиотеки, фундаментальные архитектурные решения, пользовательские интерфейсы (их может быть трудно изменить, поскольку пользователи неохотно принимают новый внешний вид). В таких ситуациях стоит потратить некоторое время, чтобы попытаться предсказать, как будущие функции будут сочетаться с решениями, которые вы принимаете сейчас. Например, заранее продумать систему управления версиями веб-API, потому что после публикации API изменить её будет невозможно. Аналогично, публичный метод или класс в общедоступной библиотеке должен оставаться там для обратной совместимости, даже если вы решите, что он больше не нужен. Менять такие вещи сложно.

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

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

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

Источник:
https://enterprisecraftsmanship.com/posts/ocp-vs-yagni/
👍10
День 1338. #ЗаметкиНаПолях #DesignPrinciples
OCP Против YAGNI. Окончание
Начало
Продолжение

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

Вернёмся к коду метода Draw, использующего switch, из первой части. С одной стороны, у нас есть YAGNI, который говорит, что с этим оператором switch всё в порядке, если полученный код прост, его легко понять и поддерживать. С другой стороны, у нас есть OCP Боба Мартина, в котором говорится, что нам нужно иметь возможность расширять его без изменения имеющегося кода, то есть без изменения самого оператора switch.

Что выбрать?

Обратите внимание, что мы говорим о противоречии между YAGNI и версии OCP Боба Мартина, а не о версии Бертрана Мейера. Это потому, что YAGNI не противоречит последней, они в принципе говорят о разных вещах.

Что касается версии Боба Мартина, то её можно рассматривать с двух разных точек зрения.

1. Когда вы являетесь и автором, и «клиентом» кода, который пишете, YAGNI имеет приоритет над OCP. Потому что YAGNI, наряду с KISS, является самым важным принципом в разработке ПО. Следование ему должно быть первоочередной задачей любого программного проекта. Зачем преждевременно закладывать точки расширения в свой код, если это приведет к чрезмерному усложнению? Действительно ли рефакторинг switch в иерархию классов стоит усилий и дополнительных затрат на обслуживание? Конечно нет. Гораздо лучше закладывать точки расширения постфактум, когда уже есть полная картина и когда вы видите, что оператор switch стал слишком раздутым. В этом случае вы можете отрефакторить код и извлечь иерархию классов. Но не раньше, чем потребность в этом станет очевидной.

2. Когда нужно опубликовать свой код для внешнего использования.
В данном случае YAGNI неприменим, поскольку стоимость изменения уже реализованного функционала слишком высока. Вы не можете просто отрефакторить свой код, потому что вы не единственный его потребитель. В такой ситуации вам необходимо определить потенциальные точки вариаций и создать вокруг них интерфейс, который позволит потребителям расширять ваши классы, а не изменять их. В примере с методом Draw, если он открыт для клиентов, и вы хотите предоставить средства для его расширения, лучше заранее реализовать его в базовом классе Shape и позволить вашим потребителям создавать собственные формы.

Версия OCP Боба Мартина имеет гораздо больше смысла, если поместить её в контекст точки зрения Бертрана Мейера. Точки расширения стоит закладывать только тогда, когда вам придётся публиковать свой код для внешнего использования в том или ином виде. В любом другом случае придерживайтесь YAGNI и не вводите дополнительную гибкость без реальной необходимости.

Источник: https://enterprisecraftsmanship.com/posts/ocp-vs-yagni/
👍13
День 1339. #ЗаметкиНаПолях
Пустые Переменные в Лямбдах
В твиттере задали интересный вопрос:
Имеет ли смысл использование пустой переменной (дискарда) `_` в выражении типа?
( _ , args) =>{…}
Я ожидаю параметр, но не буду с ним ничего делать.
— Layla #WomenOfDotNet (@LaylaCodesIt)

Короткий ответ: да. Но кому нужны простые ответы? Пустые переменные появились в C# недавно. А это значит, что не всё так просто, как кажется.

В C# есть несколько мест, где мы используем _ как пустую переменную. В примере ниже _ технически не является ей:
Func<int, int, int> f = (_, x) => 2 * x;

Лямбда здесь имеет два параметра, первый называется _, а второй называется x. C# принимает _ в качестве идентификатора в соответствии с правилами языка. Даже Visual Studio при наведении курсора на _ показывает подсказку «(parameter) int _». Мы можем продемонстрировать это, используя оба параметра:
Func<int, int, int> f2 = (_, x) => _ * x;

Следующий код не работал в C#8, но C#9 позволяет так писать:
Func<int, int, int> f3 = (_, _) => 42;

Здесь символы подчеркивания действительно являются пустыми переменными. (Правило состоит в том, что если лямбда имеет несколько параметров с именем _, то все они отбрасываются). A Visual Studio при наведении курсора на _ показывает подсказку «(discard) int _». Однако это работает только для лямбда-выражений.

Зачем это нужно?
Иногда мы вынуждены принимать аргументы, которые не будем использовать. Неиспользуемые параметры выглядят так, как будто это может быть ошибкой:
void ShowNumber(int i)
{
Console.WriteLine(42);
}

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

Поэтому принято соглашение об использовании _ в качестве имени параметра, который бы намеренно игнорировался. Если вы измените предыдущий пример, переименовав параметр i в _, вы увидите, что компилятор перестанет выдавать сообщение IDE0060, поскольку он знает об этом соглашении:
void ShowNumber(int _)

Но, если у вас есть два параметра, которые вы хотите игнорировать:
void ShowNumber(int _, int _)
это вызовет ошибку компиляции CS0100 из-за дублирующего имени параметра _, т.к. пустые переменные можно использовать только в лямбдах.

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

Всё становится ещё интереснее, когда _ используется в качестве имени локальной переменной. Например, что, по-вашему, выведет код с картинки ниже в C#9+?

Источник: https://endjin.com/blog/2022/09/csharp-lambda-discards
👍5
Что выведет код с картинки выше?
Anonymous Quiz
24%
Ошибку в строке 2
41%
Ошибку в строке 3
11%
23
2%
29
7%
63
15%
69
👍3
День 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