.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
День 1330. #CodeReview
Допрашиваем Незнакомый Код. Продолжение
Начало

3. Рефакторинг имён локальных переменных и методов
Иногда часть кода настолько расплывчата или вводит в заблуждение, что её трудно осмыслить. Один практически безрисковый способ — переименовать локальные переменные и приватные методы, чтобы более точно описать, что они делают. Эти изменения не повлияют ни на что за пределами файла, с которым вы работаете, и не вызовут ошибок, если вы будете осторожны, чтобы избежать конфликтов имен. Если возможно, используйте инструменты рефакторинга IDE, а не поиск и замену текста.

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

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

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

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

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

Полнотекстовый поиск — самая простая версия. Если вы хотите сузить результаты, поищите с помощью регулярного выражения. Также стоит потратить время на изучение некоторых продвинутых методов. Многие программисты предпочитают инструменты командной строки Unix, такие как grep и awk, или, в Windows, рукописные сценарии PowerShell.

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

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

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

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

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

Источник:
https://stackoverflow.blog/2022/08/15/how-to-interrogate-unfamiliar-code/
👍3
День 1331. #CodeReview
Допрашиваем Незнакомый Код. Окончание
Начало
Продолжение

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

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

8. Поиск в базе знаний
Если ваша команда использует базу знаний, такую ​​как Stack Overflow for Teams, Confluence или GitHub-вики, вы уже должны иметь довольно хорошее представление о том, какие термины или понятия можно использовать для поиска соответствующей документации. Имейте в виду, что документация не должна быть вашим единственным источником правды. Она начинает устаревать в момент публикации, и единственное, на что вы можете полностью положиться, чтобы узнать, как ведёт себя часть кода, — это сам код. Тем не менее, даже устаревшая документация может дать достаточно исходной информации и контекста, чтобы помочь вам избежать ошибочных выводов.

Документация может объяснить скорее не «как», а «почему» код работает так, а не иначе. Иногда вы понимаете, что делает фрагмент кода, но что-то в нем кажется неправильным. Прежде чем изменить его, вы должны приложить все усилия, чтобы понять, чем руководствовался первоначальный программист.

9. Используйте аннотацию контроля версий (git blame)
Последний способ собрать контекст — отследить исходного автора, коммит и тикет, связанный с этим кодом.

В любой системе контроля версий есть инструмент, который раскрывает автора и коммит для любой строки кода в кодовой базе. В Git это команда git blame. Так вы сможете найти исходный запрос, который включал код, и возможно, ссылку на исходный тикет, в котором содержится описание функции или ошибки. Возможно, придется просмотреть историю изменений файла, чтобы найти коммит, в котором было сделано нужное изменение.

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

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

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

Источник: https://stackoverflow.blog/2022/08/15/how-to-interrogate-unfamiliar-code/
👍10
День 1332. #ЗаметкиНаПолях #DDD
Всегда Валидная Модель Домена
Инвариант — это условие, которое всегда должно выполняться. Например, треугольник имеет 3 стороны. Условие edges.Count == 3 по своей сути верно для всех треугольников.

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

Наличие инвариантов — это то, что требует введения правил валидации. Без таких инвариантов, как edges.Count == 3, вам не нужно было бы проверять входные данные от внешних клиентов.

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

Это различие приводит к различному обращению с нарушениями этих бизнес-правил. Нарушение инварианта в модели предметной области — это исключительная ситуация, и на неё следует генерировать исключение и полностью останавливать текущую операцию (принцип отказоустойчивости).

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

Можно предположить, что разница между валидациями и инвариантами в том, что валидации могут меняться в зависимости от бизнес-правил. Например, треугольник имеет 2 инварианта:
- ровно 3 стороны,
- каждая сторона больше нуля.

Но в нашей модели предметной области есть ещё условие:
- каждая сторона долна быть больше 10 см.

Это правило валидации (в отличие от инвариантов) можно изменить или удалить из нашей модели предметной области.

Действительно, интуитивно эти два условия не кажутся одинаковыми: наличие 3 сторон и требование, чтобы все стороны были больше 10 см. Одно условие необходимо для треугольников, а другое явно нет. Но это только потому, что мы привносим наш реальный опыт в область моделирования предметной области.

Какова цель моделирования предметной области? Максимально приблизиться к физическому миру? Сделать модель максимально реалистичной?

Нет.

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

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

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

Конечно, требования могут измениться, и условие 10 см может превратиться в 5 или 20 (или даже стать настраиваемым), но это регулярный процесс уточнения, когда вы лучше понимаете домен по мере продвижения проекта. Это не означает, что исходное условие не было инвариантом. Было. Так же, как новое является инвариантом сейчас.

Источник: https://khorikov.org/posts/2022-06-06-validation-vs-invariants/
👍9
This media is not supported in your browser
VIEW IN TELEGRAM
День 1333. #ЧтоНовенького
Улучшения Табличного Визуализатора Данных в VS 2022
Табличный визуализатор данных (DataTable visualizer) появился в версии 17.3 preview 3 для IEnumerable, а начиная с версии 17.4 preview 2 позволяет также просматривать содержимое объектов DataTable, DataSet, DataView или DataViewManager. Вызывается он по щелчку на увеличительное стекло в отладчике (см. видео).

Поддерживается
- Фильтрация данных по строке фильтра.
- Сортировка по возрастанию или убыванию.
- Экспорт данных в формат Excel (xlsx) или CSV. Также работает с отсортированным и отфильтрованным представлением.
- Тема основного окна Visual Studio.

Источник: https://devblogs.microsoft.com/visualstudio/datatable-visualizer-improvements/
День 1334. #ЧтоНовенького
Атрибут StringSyntax
Visual Studio ещё с версии 2019 поддерживала подсветку синтаксиса в регулярных выражениях или подсказки при форматировании даты. Исторически это был жёстко закодированный список методов, где было известно, что строковые аргументы в действительности будут регулярными выражениями или форматом даты. Однако это решение не масштабировалось. Существует много API, принимающих строки, которые должны соответствовать определенному синтаксису, например, JSON, XML, формат даты, Guid и т.п.

В .NET 7 представлен новый атрибут StringSyntax, который используется в .NET 7 для более чем 350 параметров типа string, string[] и ReadOnlySpan<char>, свойств и полей, чтобы обозначить для клиента, какой синтаксис ожидается для передачи или установки. Теперь любой метод, который хочет указать, что строковый параметр, принимает регулярное выражение, добавить атрибут на параметр:
void MyMethod(
[StringSyntax(StringSyntaxAttribute.Regex)]
string input)

Visual Studio 2022 обеспечит ту же проверку синтаксиса, подсветку синтаксиса и IntelliSense, что и для всех других методов, связанных с Regex.

Строковые параметры, свойства и поля во всех основных библиотеках .NET теперь помечены атрибутами, чтобы обозначить, являются ли они регулярными выражениями, JSON, XML, строками составного формата, URL, строками числового формата и так далее.

Подробнее об этом в недавнем видео от Ника Чапсаса https://youtu.be/Y2YOaqSAJAQ

Источник: https://devblogs.microsoft.com/dotnet/regular-expression-improvements-in-dotnet-7/#stringsyntaxattribute-regex
👍8
День 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