.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
День девятьсот шестьдесят четвёртый. #TipsAndTricks
Уменьшаем Длину GUID в URL и Json
GUID обычно используются в качестве ключа в базе данных. Если вы хотите создать URL-адрес для определённой записи, вы можете использовать GUID в нём. Но как отобразить GUID в URL-адресе? Есть несколько вариантов:
1. Guid.ToString() – 36 символов
00000000-0000-0000-0000-000000000000

2. Guid.ToString("N") – 32
00000000000000000000000000000000
Уберёт лишние символы, но всё равно результат довольно длинный.

3. Convert.ToBase64String – 24
0000000000000000000000==
Кодировка base64 небезопасна для URL-адресов, поскольку может содержать символ /. Это означает, что вам нужно закодировать значение с помощью Uri.EscapeDataString. Кроме того, в конце строки есть символы-заполнители.

4. WebEncoders.Base64UrlEncode - 22
0000000000000000000000
Этот метод преобразует массив байтов в строку base64, которую можно безопасно использовать в URL-адресе.

Вот пример кода для добавления кодирования и декодирования GUID в качестве безопасной строки URL-адреса base64:
using Microsoft.AspNetCore.WebUtilities;

string EncodeGuid(Guid guid)
{
Span<byte> bytes = stackalloc byte[16];
guid.TryWriteBytes(bytes);
return WebEncoders.Base64UrlEncode(bytes);
}

Guid DecodeGuid(string guid) =>
return new Guid(WebEncoders.Base64UrlDecode(str));

Привязка модели ASP.NET Core
Чтобы легко использовать короткие идентификаторы GUID в приложении ASP.NET Core, вы можете создать структуру для обёртывания идентификатора GUID:
public readonly struct ShortGuid
{
private readonly Guid _value;

public ShortGuid(Guid value) =>
_value = value;

public static implicit operator
Guid(ShortGuid sGuid) => sGuid._value;

public static implicit operator
ShortGuid(Guid guid) => new(guid);

public static ShortGuid Parse(string input) =>
new Guid(WebEncoders.Base64UrlDecode(input));

public override string ToString()
{
Span<byte> bytes = stackalloc byte[16];
_value.TryWriteBytes(bytes);
return WebEncoders.Base64UrlEncode(bytes);
}
}

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

Источник: https://www.meziantou.net/reducing-the-string-length-of-guids-in-urls.htm
День девятьсот шестьдесят пятый. #ЧтоНовенького
Быстрые Действия для Типичных Задач с Помощью IntelliCode
IntelliCode теперь может определять, типичные паттерны в коде и рекомендовать правильное быстрое действие прямо во время набора текста. На данный момент IntelliCode может обнаруживать и предлагать быстрое действие для создания конструктора и добавления параметра в конструктор. Но сценарии будут расширены в будущих версиях. В Visual Studio 2022 Preview 4 эти новые возможности предложений IntelliCode включены по умолчанию.

Раньше такие подсказки показывались в виде лампочки слева от строки. Теперь, пока вы печатаете, IntelliCode проверяет изменения в коде и, используя технологию PROSE, сопоставляет их с распространёнными паттернами и предлагает варианты завершения.

Источник: https://devblogs.microsoft.com/visualstudio/discover-quick-action-intellicode/
День девятьсот шестьдесят шестой. #TypesAndLanguages
Ключевые различия между C# и F#. Начало
И C#, и F# по-своему уникальны. В чём их различия и что сделать проще в C#, а что в F#?

В чём C# превосходит F#?
1. Асинхронность
Асинхронный код в C# выполняется быстрее, чем в F#. Это в первую очередь потому, что асинхронные конструкции изначально поддерживаются компилятором и генерируют оптимизированный код. Как только в F# появится аналогичная поддержка, эта разница уменьшится. Кроме того, она не очень важна для типичного бизнес-приложения. Мы можем написать библиотеку C# и вызвать ее из F# для реального кода, чувствительного к производительности.

2. Взаимодействие с библиотеками .NET
Поскольку большинство библиотек .NET написано на C#, разработчику проще работать с ними на C# по сравнению с F#.

3. Ранний возврат
В C# для возвращения из метода используется ключевое слово return. Это невозможно в F#. В глубоко вложенных блоках кода эта функция действительно полезна.

4. Ко-/контравариантность
Вариантность поддерживается в C#, но на данный момент не поддерживается в F#. Это особенно полезно для библиотечного кода, который имеет дело с обобщениями.

5. ООП в общем
В C# работать с защищёнными классами проще, так как в нём есть ключевое слово protected, которого нет в F#. Кроме того, реализация типов внутренних классов, неявных интерфейсов и частичных классов возможна в C#, но не в F#.

6. Неявное приведение
Неявное приведение типов поддерживается в C#, но не поддерживается в F#. В F# не поддерживается ни неявный апкаст, ни неявный даункаст. Следовательно, в C# проще использовать библиотеки, полагающиеся на неявное приведение типов.

7. Генераторы кода
Генераторы исходного кода недоступны для F#. Хотя есть Myriad.

8. Упорядочивание файлов
В C# упорядочивание файлов и пространств имён возможно любым способом. В F# строгий порядок (снизу вверх).

9. Инструменты и поддержка в IDE
Инструменты и поддержка IDE в C# лучше по сравнению с F#.

10. Отладка
Во всех IDE процесс отладки проще в C#, чем в F#. Асинхронные рабочие процессы особенно сложно отлаживать на F#.

11. Низкоуровневый код
Небезопасный код и Invoke/P, span и закреплённые указатели также поддерживаются только в C#.

12. WinForms и WPF
C# начинался с разработки клиентов WPF или Winform. Это не важная область для F#.

13. Entity Framework
В мире .NET Entity Framework - очень популярен. Разработчикам инстинктивно приходит в голову не использовать его в F#, потому что его дизайн противоречит F#.

14. Асинхронный Main
В методе Main async может использоваться в C#, в то время как для F# в нём используется Async.RunSynchronously.

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

Источник:
https://www.partech.nl/nl/publicaties/2021/06/key-differences-between-c-sharp-and-f-sharp

UPD: поскольку я не спец в F#, оказалось, что некоторые пункты из поста не совсем верны. Спасибо @Lanayx за комментарий
День девятьсот шестьдесят седьмой. #TypesAndLanguages
Ключевые различия между C# и F#. Окончание
Начало

В чём F# превосходит C#?
1. Неизменяемость по умолчанию
В F# всё неизменяемо, если вы не используете ключевое слово mutable. Неизменяемость помогает упростить распараллеливание и предотвратить дефекты.

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

3. Всё - выражение
Понимание кода становятся проще при использовании выражений. Также упрощается отладка кода.

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

5. Разграниченные объединения (discriminated unions)
Разграниченные объединения обеспечивают поддержку значений, которые могут быть одним из нескольких именованных вариантов, возможно, каждое с разными значениями и типами. Они присутствуют в F#, что позволяет лучше моделировать бизнес-домен при использовании классов, записей, перечислений и интерфейсов. Лучшее моделирование приводит к простому коду и меньшему количеству дефектов.

6. Вычислительные выражения (computation expression)
Вычислительные выражения объединяют различные аспекты, такие как async, Option, Result, Validation и т.д. удобным для программиста способом. Это широко распространено в F#. Многие выражения представлены в библиотеке FsToolkit.

7. Сопоставление по шаблону
Активные шаблоны в F# позволяют выделить часть шаблона, задать ей имя и использовать в других шаблонах, что упрощает чтение сложных шаблонов. Хотя C# в последнее время улучшил сопоставление по шаблону, но F# всё ещё впереди в этом.

8. Единицы измерения (Measure)
Использование единиц измерения подключает компилятор к проверке, что значения в арифметическом выражении имеют правильные единицы, что помогает предотвратить ошибки и делает код более выразительным.

9. Частичное применение (partial application) и композиция
Частичное применение широко используется в F#. Идея заключается в том, что, если вы фиксируете первые N параметров функции, вы получаете новую функцию для остальных параметров. Это помогает создавать дизайн на основе композиции функций.

Сравнение кода
C#:
public static class SumOfSquares
{
public static int Square(int i)
=> i * i;

// без LINQ
public static int Sum(int n)
{
int sum = 0;
for (int i=1; i<=n; i++)
sum += Square(i);
return sum;
}

// с LINQ
public static int SumLinq(int n)
=> Enumerable.Range(1, n).Select(Square).Sum();
}

var x = SumOfSquares.Sum(100);

F#:
let square x = x * x;
let sumOfSquares n =
[1..n] |> List.map square |> List.sum

sumOfSquares 100

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

Источник: https://www.partech.nl/nl/publicaties/2021/06/key-differences-between-c-sharp-and-f-sharp
День девятьсот шестьдесят восьмой. #Оффтоп #Обучение
Как Быть Лучше Всех как Разработчик? Начало
Иногда мне кажется, что я бегу по рельсам, меня преследует скоростной поезд, на котором написано что-то вроде «новинки в технологиях». Я не могу остановиться и изменить направление, и у меня нет выбора, кроме как есть на бегу. В другие дни я смотрю на технологическую новинку, и она вся такая красивая и блестит на солнце. И тогда всё остальное теряет значение, даже приближающийся поезд. Я хочу эту блестяшку. Как же нам совладать с этими, казалось бы, противоположными потребностями?

Хватай блестяшку!
Мы все через это проходили. Мы смотрели на крутую новинку, которая обещала решить все наши проблемы. Мы хотели протянуть руку, схватить её и изучить. Остальное не важно. Конечно, для её изучения надо было выделить время. И если бы мы это сделали, мы (по крайней мере, в глазах некоторых людей) стали бы крутыми гуру. А другие бы подумали, что мы зря тратим время и ресурсы. Как обычно бывает в нашей отрасли, здесь следует найти компромисс. Давайте копнём глубже.

Технологии развиваются быстро, мы все это знаем. Как же нам оставаться впереди остальных, но не гнаться за каждой блестящей новинкой? Интересно, что погоня за каждой новинкой имеет негативный оттенок, а пребывание на острие технологий - позитивный. Я думаю, что эти вещи тесно связаны. Изучение новинок часто предвестник того, что вы в курсе текущих трендов. Конечно, если окажется, что новинка – это какой-нибудь Silverlight, вам нужно иметь возможность бросить её так же быстро, как вы за неё ухватились.

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

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

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

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

Наша потребность оставаться впереди всех, по крайней мере отчасти, вызвана страхами. Однако есть и другие движущие силы. Иногда мы стремимся быть впереди всех, потому что:
- мы хотим зарабатывать больше денег,
- мы хотим, чтобы нас считали успешными по каким-то показателям,
- мы получаем удовольствие от новейших технологий и получаем удовольствие от их изучения: «Ух, ты! Блестяшка!»

Всё это реальные опасения и реальные мотиваторы. У вас может быть ещё много других. Но как же выбрать стоящую новинку?

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

Источник:
https://helenjoscott.iss.onedium.com/how-can-you-stay-ahead-of-the-curve-as-a-developer-a27d728980a2
Автор оригинала: Helen Scott
День девятьсот шестьдесят девятый. #Оффтоп #Обучение
Как Быть Лучше Всех как Разработчик? Окончание
Начало

Как узнать, какую новинку выбрать?
Да никак! Мы, вероятно, можем предположить, что новинка потенциально может решить наши проблемы. Однако мы не можем изучать все новинки и таким образом быть в курсе всех возможных трендов. Поэтому нам нужно с умом выбирать новинки, особенно в сфере технологий. Выберите, в чём хотите быть впереди, и сфокусируйтесь на этом. Нужно сосредоточиться на этой сфере, но всегда быть готовым изменить направление, поскольку ветер изменчив, и новинка, на которую вы так надеялись, может потерять популярность.

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

Как оставаться впереди?
Банальный ответ: упорным трудом. Однако давайте разберёмся, потому что есть некоторые вполне реальные и осязаемые вещи, которые мы можем делать. Вот те, которые подходят мне, ваш список может быть длиннее или короче.
Часто мы можем подогнать их под наш график. Мы можем:
- читать посты в блогах,
- смотреть видео,
- слушать доклады на конференциях.

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

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

…Но делать это активно. То есть использовать полученные знания и применять их к чему-то в реальном мире. Поиграть с новой игрушкой - это первый шаг. Такие вопросы как:
- Какие проблемы она решает?
- Чью жизнь облегчает (допустим, что облегчает)?
- Где её можно применить в нашем мире?
- Какие есть конкуренты?
- Где её нельзя применить или в каких случаях она не сработает?

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

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

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

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

Источник: https://helenjoscott.iss.onedium.com/how-can-you-stay-ahead-of-the-curve-as-a-developer-a27d728980a2
Автор оригинала: Helen Scott
День девятьсот семидесятый. #NuGet
Генерация QR Кодов в .NET
QR коды сегодня повсюду, поэтому часто требуется возможность их генерировать. Сегодня расскажу вам про полезный пакет Net.Codecrete.QrCodeGenerator, позволяющий генерировать QR коды из текста или массива байт буквально в 2 строчки. Библиотека создана для .NET Standard 2.0 и поэтому работает на большинстве современных платформ .NET (.NET Core, .NET Framework, Mono и т.д.).

Основные особенности
- Поддерживает кодирование всех 40 версий (размеров) и всех 4 уровней коррекции ошибок в соответствии со стандартом QR Code Model 2.
- Форматы вывода: чистые пиксели QR символа, строка SVG XML, растровое изображение.

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

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

Добавим в проект NuGet пакет:
dotnet add package Net.Codecrete.QrCodeGenerator --version 1.6.1

Создадим простую страничку с формой, принимающей текст, который мы хотим видеть в QR коде, и отображающей сам код после отправки формы:
@model Byte[]

@using (Html.BeginForm(FormMethod.Post))
{
<label>Текст для QR кода:</label><br />
<input type="text" name="qrText" size="40" />
<button>Сгенерировать</button>
}

@if (Model != null)
{
<label>QR Код Успешно Сгенерирован:</label><br />
<img src="@string.Format("data:image/png;base64,{0}",
Convert.ToBase64String(Model))" />
}

QR код передаётся в представление в виде массива байтов и отображается как изображение base64. А вот код метода действия контроллера, обрабатывающего форму:
using System.Drawing.Imaging;
using System.IO;
using Net.Codecrete.QrCodeGenerator;

[HttpPost]
public IActionResult Index(string qrText)
{
if (string.IsNullOrEmpty(qrText))
return View();

var qr = QrCode.EncodeText(qrText, QrCode.Ecc.Medium);
var img = qr.ToBitmap(6, 1);

using var stream = new MemoryStream();
img.Save(stream, ImageFormat.Bmp);

return View(stream.ToArray());
}

В методе используем статический метод EncodeText для преобразования текста в QR код. Затем преобразуем изображение в поток байт и отправляем в представление. Получаем результат, как на картинке ниже.
День девятьсот семьдесят первый. #Оффтоп
Советы для Продвинутого Пользователя Git. Начало
Хорошо это или плохо, но Git превзошёл другие системы контроля версий, такие как Subversion, Perforce и ClearCase, и стал стандартом системы управления исходным кодом для современного разработчика. Если вы не знакомы ни с одной из этих систем, считайте, что вам повезло жить в эпоху простых локальных ветвлений, поддержки нескольких рабочих процессов и множества распределённых платформ хостинга репозиториев.

Хотя Git легко изучить, его постоянное развитие и широкий спектр возможностей усложняют освоение его продвинутого использования. В результате как новички, так и опытные программисты частенько (вероятно, матерясь) гуглят «как мне это сделать в Git».

В этой серии постов рассмотрим несколько команд для некоторых продвинутых сценариев при работе с репозиторием Git.

1. Клонирование и удалённые URL
Git поддерживает учётные данные Secure Shell (SSH), уникальный криптографический идентификатор для клиентов, при этом большинство ключей создается с использованием алгоритма RSA. Ключи SSH действуют как имя пользователя и пароль для удалённого экземпляра репозитория кода. Для программиста это значит, что имя пользователя и пароль придётся вводить гораздо реже.

При работе с удалёнными репозиториями вы можете случайно клонировать репозиторий, используя HTTPS URL-адрес вместо SSH. Хотя через HTTPS всё равно можно работать, но все пуши в репозиторий потребуют от вас ввода имени пользователя и пароля. Чтобы проверить адрес удалённого репозитория, вы можете использовать Git CLI:
> git remote -v
origin https://github.com/user/Repo.git (fetch)
origin https://github.com/user/Repo.git (push)

Чтобы изменить URL для удалённого источника, вы можете использовать команду set-url. URL-адрес SSH можно найти у вашего Git провайдера. В случае репозиториев GitHub он обычно следует шаблону
[email protected]:<username>/<repository_name>.git:
> git remote set-url origin [email protected]:user/Repo.git

Вы можете снова запустить команду git remote -v, чтобы убедиться, что URL-адрес обновлён:
> git remote -v                                                        
origin [email protected]:user/Repo.git (fetch)
origin [email protected]:user/Repo.git (push)

При работе в Visual Studio или Rider вы можете использовать меню Git > Manage Remotes.

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

Источник:
https://blog.jetbrains.com/dotnet/2021/09/13/advanced-git-workflow-tips/
День девятьсот семьдесят второй. #Оффтоп
Советы для Продвинутого Пользователя Git. Продолжение
Начало

2. Очистить хранилище от всех неотслеживаемых артефактов.
Репозитории проектов могут быстро съедать дисковое пространство из-за артефактов сборки, сторонних зависимостей и неотслеживаемых файлов. Раздутие папок особенно актуально для долгосрочных проектов, которые претерпели множество итераций на вашем компьютере. Для возврата к «чистому» состоянию в папке вашего репозитория можно использовать команду git clean.

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

> git clean -xdf

Рассмотрим, что означает каждый из флагов:
- x - Обычно команда clean игнорирует файлы и папки из файла .gitignore репозитория. Этот флаг указывает команде удалить все неотслеживаемые файлы, даже если они игнорируются.
- d - сообщает команде о необходимости рекурсивного просмотра всех каталогов, независимо от того, отслеживаются они или нет.
- f - заставляет команду удалить файлы и каталоги.

Если вы не хотите запускать команду clean, не зная, что она сделает с вашим репозиторием, добавьте флаг n вместе с другими, чтобы выполнить пробный запуск:
> git clean -xdfn 
Would remove .idea/.idea.Repo/.idea/workspace.xml
Would remove bin/
Would remove obj/

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

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

После коммита неверного сообщения вы можете использовать следующую команду, чтобы изменить его:
// Откроет редактор Git по умолчанию 
> git commit --amend
// Сразу заменит сообщение коммита
> git commit --amend -m "исправлена ошибка зависимости"

При использовании Visual Studio или Rider вы можете отметить флажок Amend рядом с сообщением коммита в окне Git Changes (VS) или Commit (Rider). Кроме того, вы можете изменить сообщение коммита в окне Git, где показана история репозитория.

Обратите внимание, что эта команда перезаписывает историю Git, поэтому вам придется использовать force push, если вы уже отправили ошибку в удалённый репозиторий.

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

Источник:
https://blog.jetbrains.com/dotnet/2021/09/13/advanced-git-workflow-tips/
День девятьсот семьдесят третий. #Оффтоп
Советы для Продвинутого Пользователя Git. Окончание
Начало
Продолжение

4. Добавить файлы в предыдущий коммит
Как и при исправлении орфографических ошибок в сообщении коммита, вы иногда можете забыть добавить файл в конкретный коммит. Вместо добавления нового коммита в историю Git вы можете изменить предыдущий коммит, не изменяя сообщение. Сначала нужно добавить все файлы, которые вы хотите добавить в предыдущий коммит.
> git add .

А затем выполнить команду commit с параметрами amend и no-edit:
> git commit --amend --no-edit

В Visual Studio или Rider также можно использовать флажок Amend, как и в предыдущем совете. Также, как и предыдущем совете, эта команда изменяет историю Git и может потребовать принудительного пуша, если изменения уже были отправлены в удалённый репозиторий.

5. Отменить изменения в ранее зафиксированных файлах
Вы можете начать вносить изменения, а потом обнаружить, что изменения вам больше не нужны. К счастью, вернуться к заведомо исправному состоянию просто:
> git co Program.cs
Updated 1 path from the index

Если вы хотите откатить изменения в IDE:
- В Visual Studio нажмите правой кнопкой мыши на файл в окне Solution Explorer и выберите пункт меню Git > Undo changes.
- В Rider нажмите правой кнопкой мыши на файл в окне Commit и выберите пункт Rollback.

6. Отменить последний локальный коммит Git
Вы работали над важным функционалом, а потом случайно сделали коммит в основную ветку, когда надо было создать новую ветку для функции? Чтобы отменить коммит локально, выполните следующую команду:
> git reset --soft HEAD~1

Git сравнит текущий коммит в HEAD (ваш последний коммит) с коммитом перед ним (предыдущим) и вернёт файлы в статус предыдущего коммита. На этом этапе у вас будет возможность создать новую ветку и правильно сохранить изменения.

Если вы хотите откатить изменения в IDE:
- В Visual Studio в окне Git с историей репозитория нажмите правой кнопкой мыши на предыдущий коммит и выберите Reset. Вам будет предложено 2 варианта: Keep changes (--mixed), который сохранит изменения, или Delete changes (--hard), который удалит все изменения до выбранного коммита.
- В Rider в окне Git выберите коммит и в выпадающем меню выберите Reset Current Branch To Here. Rider предложит дополнительные варианты: soft (вернёт изменения в статус staged), keep (откатит файлы до выбранного коммита, но сохранит локальные изменения).

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

Источник: https://blog.jetbrains.com/dotnet/2021/09/13/advanced-git-workflow-tips/
День девятьсот семьдесят четвёртый. #ЧтоНовенького
Глобальные Директивы Using в C #10
Размер поста в телеграмме ограничен, поэтому я часто пытаюсь сократить его, насколько это возможно. Больше всего проблем доставляют примеры кода. С одной стороны, хочется показать пример, который можно было бы скопировать и выполнить, и всё бы работало. С другой стороны, C# довольно многословен, в отличие от того же F# или чисто скриптовых языков, например. Поэтому приходится удалять из кода всё лишнее. Это касается в том числе директив using в начале файла. Часто они просто загромождают код и редко добавляют ему какую-то ценность.

В .NET 6 и C #10 появились глобальные директивы using. Теперь вы можете поместить все общие директивы using (в основном, System) в один файл, который будет автоматически доступен для использования в рамках всего проекта.

Помните, что эта функция доступна только в .NET 6. Кроме того, на данный момент вам может потребоваться отредактировать файл .csproj, чтобы разрешить функции в предварительном просмотре (см. LangVersion):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>

Синтаксис для добавления глобальных директив using на самом деле довольно прост: добавьте ключевое слово global перед любым using в любом месте, чтобы сделать директиву глобальной.
global using System;

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

При этом некоторые используют файл GlobalUsings.cs в корне проекта, содержащий что-нибудь вроде:
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;

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

Публичное обсуждение функционала ведётся здесь: https://github.com/dotnet/csharplang/issues/3428

Источник: https://dotnetcoretutorials.com/2021/08/19/global-using-statements-in-c10/
День девятьсот семьдесят пятый. #Оффтоп
Интервью - Это Плохой Способ Нанять Программиста
Давненько мы ничего не обсуждали по поводу собесов. А тут первый день октября, пятница. Почему бы и нет?
Тему на этот раз дал ютубер и программист из Нидерландов Алексей Корепанов. Вот его канал, если что.

Тема такая: "Собеседования - это плохой способ найти подходящего программиста". Но другие - ещё хуже. Что не так с техническими интервью и есть ли им какая-то замена?

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

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

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

Есть, конечно, вариант тестового задания. Но тут перекос в другую сторону. Либо оно будет настолько тривиальное, что не позволит адекватно оценить навыки. Либо это будет разработка полноценного (пусть небольшого) приложения, на которое кандидат должен будет потратить несколько часов, а то и весь день (читал про такой случай, по-моему, на Хабре, после чего кандидата просто отшили без особых пояснений). А если оно оплачиваемое, то зачем писать код, который потом всё равно выкинут? Понятно, что есть нюансы о коммерческой тайне, но думаю, их можно утрясти или не допускать кандидатов к секретным частям.

Что скажете? Были ли у вас интересные собеседования, которые хотелось пройти ещё раз или чем бы вы заменили собеседования?

Источник: https://www.youtube.com/watch?v=GdV-H0uDHZc
День девятьсот семьдесят шестой.
Юнит-Тесты Приватных Методов и Абстракций
Большинство специалистов советуют проводить юнит-тесты только публичного API класса. Тестирование закрытых методов приводит к привязке ваших тестов к деталям реализации, что приводит к ложным срабатываниям, что, в свою очередь, снижает точность и ценность теста.

Ценность теста = сигнал/шум,
где
- сигнал – количество найденных ошибок,
- шум – количество ложных срабатываний.

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

Однако тогда возникают вопросы:
- Что, если закрытые методы содержат важную логику, требующую тестирования?
- Что, если эта логика слишком сложна и её тестирование как часть наблюдаемого поведения класса не обеспечивает достаточного покрытия?
- Как тогда её протестировать?

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

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

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

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

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

Другими словами, дизайн кода всегда идёт на первом месте. Модульные тесты просто помогают выявить проблемы с этим дизайном. Даже без модульных тестов было бы неплохо выделить сложный закрытый метод в отдельный класс. Тесты просто явно указывают на это.

Источник: https://www.getdrip.com/deliveries/kjdniknl44vo4fplhd26
Автор оригинала: Vladimir Khorikov
День девятьсот семьдесят седьмой. #ЧтоНовенького
Неявные Директивы Using в .NET 6
Недавно мы говорили о возможности использовать global using в C #10. Так почему бы не включать эту функцию сразу же при создании нового проекта в .NET 6? Если вы создаёте новый веб-проект, появляется много автоматически сгенерированных как часть шаблона файлов. Логично было бы явно создавать файл GlobalUsings.cs, верно?

В .NET 6 решили проблему немного по-другому. Новый функционал неявных директив using создаёт скрытый автоматически сгенерированный файл внутри папки obj, который за кулисами объявляет глобальные директивы using. После сборки проекта в obj/Debug/net6.0 появится файл
<имя_проекта>.ImplicitNamespaceImports.cs, со примерно таким содержимым:
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;


Заметьте, что т.к. это автоматически созданный файл, мы не можем его здесь редактировать. Но мы видим, что он объявляет за нас целую кучу глобальных директив using. Каждый тип проекта будет иметь свой набор глобальных директив. Например, в веб проект добавятся
System.Net.Http.Json
Microsoft.AspNetCore.Hosting
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
и т.п.

Если мы попытаемся импортировать пространство имен, которое уже есть в этом файле, мы получим обычное предупреждение:
CS0105: The using directive for 'System' appeared previously in this namespace

Отключение
Если вы используете .NET 6 и C #10, то эта функция включена по умолчанию. Но что, если вы захотите отключить её? Это может быть полезно, если автоматически импортируемое пространство имён имеет тип, который конфликтует с типом, который вы сами хотите объявить. Это можно сделать в файле .csproj. Можно либо отключить функцию полностью:
<PropertyGroup>

<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
</PropertyGroup>

Либо выбрать, какие пространства имён включать, а какие нет:
<ItemGroup>
<Import Remove="System.Threading" />
<Import Include="Microsoft.Extensions.Logging" />
</ItemGroup>

Это также может быть заменой обсуждённому в предыдущем посте файлу GlobalUsings.cs, но в этом варианте, в недрах файла .csproj, это более «скрыто».

Это хорошая функция?
Сложно сказать. Придумывали её люди намного умнее меня, и наверняка они знают, что делают. Когда появлялись такие вещи, как дженерики, лямбда-выражения, async/await, они тоже были непривычны и непонятны.

С одной стороны, мне нравится, что каждый тип проекта имеет свой набор подключённых по умолчанию пространств имён. Если вы, например, активно используете async/await, то импорт System.Threading.Tasks потребуется чуть ли не в каждом файле, поскольку ваши методы должны возвращать тип Task.

При этом настораживает «скрытая» сущность функционала. Я почти уверен, что StackOverflow будет переполнен вопросами, почему компилятор говорит, что System уже импортирован, хотя его импорта нигде не найти. Кроме того, после обновления существующего решения с .NET 5 на .NET 6 функция автоматически включится… ну такое.

При этом, может быть, через годы мы просто привыкнем к этому, и это просто станет «частью .NET», как и многие другие вещи. Что думаете?

Источник: https://dotnetcoretutorials.com/2021/08/31/implicit-using-statements-in-net-6/
День девятьсот семьдесят восьмой. #ЗаметкиНаПолях #AsyncTips
В этой серии постов будут выдержки из книги Стивена Клири. Просто разнообразные советы по работе с асинхронным кодом.

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

Решение:
1. Для возврата значения - использовать Task.FromResult:
public Task<int> GetValueAsync()
=> Task.FromResult(42);

2. Для методов, не имеющих возвращаемого значения - Task.CompletedTask:
public Task DoSomethingAsync()
=> Task.CompletedTask;

3. Для возврата исключения - Task.FromException:
public Task<T> ThrowsAsync<T>()
=> Task.FromException<T>(new Exception());

4. Для отменённых задач - Task.FromCanceled:
public Task<int> GetValueAsync(CancellationToken ct) {
if (ct.IsCancellationRequested)
return Task.FromCanceled<int>(ct);

return Task.FromResult(13);
}

Если в синхронной реализации может произойти отказ, перехватывайте исключения и используйте Task.FromException:
public Task DoSomethingAsync() {
try {
DoSomethingSynchronously();
return Task.CompletedTask;
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}

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

Если вы регулярно используете Task.FromResult, задачу можно кэшировать, чтобы не создавать объекты Task каждый раз:
private static readonly Task<int> zeroTask 
= Task.FromResult(0);

Task<int> GetValueAsync()
=> zeroTask;

На логическом уровне Task.FromResult, Task.FromException и Task.FromCanceled являются вспомогательными методами и сокращёнными формами обобщённого типа TaskCompletionSource<T>. TaskCompletionSource<T> представляет собой низкоуровневый тип, полезный для взаимодействия с другими формами асинхронного кода. В общем случае следует применять сокращенную форму Task.FromResult и родственные формы, если хотите вернуть уже завершённую задачу. TaskCompletionSource<T> используется для возвращения задачи, которая завершается в некоторый момент в будущем.

Помимо этого, иногда требуется протестировать асинхронное поведение метода. В этом поможет Task.Yield. Заметьте, что в отличие от вышеупомянутых методов, этот метод помечен как асинхронный и содержит await:
public async Task<int> DoSomethingAsync()
{
// Принудительно включить асинхронное поведение
await Task.Yield();
return 13;
}

Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 2.
День девятьсот семьдесят девятый. #Оффтоп #CodeReview
Обзоры Кода. Передовой Опыт. Начало

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

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

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

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

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

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

4. Мотивация к прогрессу
Очевидно, что, если вы знаете, что ваш код будет проверяться коллегами, вы уделите ему больше внимания. Вы потратите время на то, чтобы правильно назвать идентификаторы, написать тесты, ввести хорошо продуманные абстракции и тщательно использовать корпоративные шаблоны.

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

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

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

Источник:
https://blog.ndepend.com/what-is-code-review-guidelines-best-practices/
👍1
День девятьсот семьдесят девятый. Часть 2.
Конференция Dotnetos 2021
Вопреки обыкновению, второй пост за сегодня. Потому что раньше я это как-то упустил, а завтра уже будет поздно. Расписание семинаров конференции Dotnetos 2021 от Конрода Кокосы и ко.

Вторник, 5 октября:
5 PM - 5:45 PM CEST (18:00 - 18:45 мск)
Kevin Gosse - Debugging one layer deeper
https://www.youtube.com/watch?v=nahz_LCSo0U

6 PM - 6:45 PM CEST (19:00 - 19:45 мск)
​Jared Parsons - Performance features in C#
https://www.youtube.com/watch?v=Q_tvcyl-e60

7 PM - 7:45 PM CEST (20:00 - 20:45 мск)
David Fowler - Turtles all the way down: You don’t need to understand the details of .NET. Until you do
https://www.youtube.com/watch?v=Uyg4_4TZINE

8 PM - 8:45 PM CEST (21:00 - 21:45 мск)
Jiří Činčura - How I put .NET into Firebird database engine
https://www.youtube.com/watch?v=5Qw6l8M-nJU

Среда, 6 октября:
5 PM - 5:45 PM CEST (18:00 - 18:45 мск)
Oren Eini - Performance Architecture
https://www.youtube.com/watch?v=KIErf6b2RoI

6 PM - 6:45 PM CEST (19:00 - 19:45 мск)
​Oleg Karasik - Making fun with parallelism in .NET
https://www.youtube.com/watch?v=nEFwG3jpUiQ

7 PM - 7:45 PM CEST (20:00 - 20:45 мск)
Reuben Bond - A Deep Dive into Orleans
https://www.youtube.com/watch?v=kgRag4E6b4c

8 PM - 8:45 PM CEST (21:00 - 21:45 мск)
Adam Furmanek - Hacking C# from the inside
https://www.youtube.com/watch?v=uYdmtqFbU8M
День девятьсот восьмидесятый. #Оффтоп #CodeReview
Обзоры Кода. Передовой Опыт. Продолжение
Начало

Как проводить обзор кода
1. Уведомление о том, что код готов к обзору
Часто обзор кода проводится неформально. Когда разработчик завершает какую-то часть кодирования, он уведомляет одного или нескольких членов команды, чтобы они проверили код, когда смогут. Неплохо для начала, но такие обзоры не являются систематическими, плохо отслеживаются и не поддаются измерению.

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

3. Парное программирование
Парное программирование - это метод гибкой разработки программного обеспечения, пришедший из XP (Extreme Programming). Парное программирование предполагает команду из двух разработчиков, работающих вместе на одном компьютере. Они объединяют свои усилия для написания кода и тестов. Это является одной из форм проверки кода, поскольку клавиатура одна: один проверяет код, написанный другим.
Парное программирование имеет несколько преимуществ. Это отличный способ наставить младшего разработчика и как можно раньше выявить некоторые дефекты.
С другой стороны, разработчики часто достигают пика своей продуктивности, когда они одни, «в потоке», и когда их не отвлекают. Таким образом, систематическое парное программирование снижает продуктивность и снижает энтузиазм разработчиков. Код, написанный одним разработчиком «в потоке», все равно может быть пересмотрен и улучшен позже другими.

4. Инструменты для автоматической проверки кода
Инструменты автоматической проверки кода помогают серьёзно улучшить процесс обзора кода. С помощью инструментов обзоры могут стать систематическими. Их можно отследить, и можно сделать вывод о том, как улучшить процесс на основании некоторых показателей. Пул-реквесты в Github сейчас популярный способ проведения большинства проверок. Существуют также более сложные инструменты, такие как SmartBear Collaborator с широким спектром поддерживаемых систем управления версиями.
Однако обратная сторона обзора кода - его высокая стоимость, поскольку он требует больших человеческих усилий. Вот почему команда должна бороться за автоматизацию большинства проверок, чтобы максимально снизить человеческое участие. Другой инструмент – NDepend - анализирует многие аспекты кода, такие как именование, сложность, дизайн, запахи кода, покрытие кода тестами и т.п. При этом он создаёт моментальные снимки кода. Два моментальных снимка можно сравнить между собой. Инструмент также поддерживает LINQ-подобные запросы для оценки качества кода. Например, перед любой проверкой кода человеком, можно внедрить правило проверки на сложность методов с помощью запроса ниже:
// <Name>Avoid complex methods</Name>
warnif count > 0
from m in JustMyCode.Methods
where m.CyclomaticComplexity > 20
select new { m, m.CyclomaticComplexity, m.NbLinesOfCode }

Строка warnif count > 0 автоматически переводит запрос в правило, которое выдаст предупреждение компилятора.

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

Источник:
https://blog.ndepend.com/what-is-code-review-guidelines-best-practices/