.NET Разработчик
6.53K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1468. #ВредныеСоветы
11 Способов Усложнить Себе Жизнь в C#. Начало
Программировать тяжело. Большинство из нас работает в командах, создавая ПО, с которым будут работать другие люди. Поэтому важно, чтобы мы старались облегчить жизнь нашей команды. Вот 11 примеров, как НЕ надо делать в C#.

1. Злоупотребление циклами
Цикл for существует во многих языках. Вы видели его миллион раз:
for(int i = 0; i < 10; i++) { }

Делать так нормально, но некоторые слишком творчески подходят к написанию циклов. Для начала рассмотрим структуру цикла for:
for (выражение «до»;
условие;
выражение «после» каждой итерации)
{}
3 выражения в круглых скобках задают условия выполнения цикла. Но это просто операторы, как и любые другие. А значит вы можете написать там всё, что захотите.

Начнём с создания бесконечного цикла, оставив каждое из трех выражений пустым:
// вместо:
while (true) {}
// можно написать:
for (; ;) {}
Хотя это не слишком полезно.

Можно опустить третье выражение и добавить приращение в условное выражение:
for(int i = 0; ++i < 100;) {}

Можно использовать строки. Удалим буквы из строки по одной:
for (string name = "Brendan"; 
name.Length > 0;
name = name.Substring(1))
{
Console.WriteLine(name);
}

Чтобы совсем оторваться, создадим несколько переменных и будем использовать их в цикле! Можно использовать кортежи:
for (
var (number, name) = (1, "Brendan");
number < 7;
name += number.ToString(), number += 1)
{
Console.WriteLine(name);
}

2. Построение и деконструкция кортежа в конструкторе
Кстати, о кортежах. Вы можете конструировать и деконструировать кортежи в одной и той же строке. Удивим команду, поместив весь конструктор в одну строку:
public Person(string prefix, string first, string middle, string last, string suffix, string nickname, DateTime birthday, string favoriteColor)
{
(Prefix, First, Middle, Last, Suffix, Nickname, Birthday, FavoriteColor) = (prefix, first, middle, last, nickname, suffix, birthday, favoriteColor);
}

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

Заметили? Тут есть ошибка.
Да, nickname и suffix перепутаны местами. И тут не будет ошибки компилятора, так как они одного типа.

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

Источник:
https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
👍13
День 1469. #ВредныеСоветы
11 Способов Усложнить Себе Жизнь в C#. Продолжение
Начало

3. Чрезмерное или недостаточное использование var
Var, на самом деле, замечательное дополнение к языку, позволяющее не указывать тип переменной, поскольку компилятор уже знает этот тип. Но просто нигде не указывать тип, а использовать var, - это перебор. Var полезен, когда тип сложный:
// ясно, но длинно
IEnumerable<IGrouping<DateOnly, Person>> peopleGroupedByBirthdate =
people.GroupBy(
p => p.Birthdate,
p => p);

// менее ясно, но короче
var peopleGroupedByBirthdate =
people.GroupBy(
p => p.Birthdate,
p => p);

Именно для таких сложных типов (и анонимных типов) необходим var. На самом деле почти везде, где результат GroupBy хранится в переменной, вы, скорее всего, увидите в коде var. Иногда это связано с тем, что результат должен быть преобразован в новый анонимный тип.

Тогда почему бы просто не использовать его везде? Потому, что var скрывает информацию:
var author1 = GetAuthor1();
var author2 = GetAuthor2();
var authorName = GetAuthorName();
var authorFullName = GetAuthorFullName();

Мы не можем определить тип этих переменных, не наведя на них курсор. Скорее всего, GetAuthorName и GetAuthorFullName одного типа, но вовсе не факт. Мне знакомы люди, которые бросались из крайности в крайность: от полного неприятия var до использования его повсеместно. Как везде, важен баланс. Если вы заметили, что наводите курсор на переменную, чтобы узнать её тип, задумайтесь, может стоит указать его явно?

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

Иногда может быть необходимо не исправлять одно-два предупреждения, поэтому есть параметр NoWarn, который можно установить в проекте, чтобы указать предупреждения, которые следует игнорировать. Если вы действительно хотите развлечь команду, просто добавляйте по одному предупреждению для игнорирования каждый раз, когда с ним сталкиваетесь. Тогда ваш код будет без предупреждений!
<NoWarn>12345,23456,34567,45678,56789</NoWarn>

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

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

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

Источник:
https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
👍13
День 1470. #ВредныеСоветы
11 Способов Усложнить Себе Жизнь в C#. Продолжение
1-2
3-4

5. Работа с disposable-типами без using
Говоря о disposable-типах, мы обычно имеем в виду, что тип реализует интерфейс IDisposable. Когда вы создаёте экземпляр такого типа, он должен очищаться после использования. В помощь вам выражение using:
using (var sw = new StreamWriter(path, true))
{
sw.WriteLine("Test");
}

Если вы хотите расстроить команду, перестаньте использовать using. Что может пойти не так? Многое. Тип реализует IDisposable, чтобы его можно было правильно очистить, т.е. освободить ресурсы (сетевые подключения, дескрипторы файлов и т.д.), которые использовал тип, перед его удалением. Если их правильно не освободить, они останутся открытыми:
SqlConnection conn = new (connString)
SqlCommand command = new (query, conn);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{

}

Здесь объекты SqlConnection и SqlDataReader необходимо очищать. Если вы волнуетесь за вложенность, начиная с C#8 можно использовать декларации using, т.е. не заключать код в фигурные скобки.
using SqlConnection conn = new (connString);

using SqlDataReader reader = command.ExecuteReader();

Здесь переменные conn и reader будут освобождены в конце текущего блока кода (например, при выходе из метода).

См. также «Мои любимые ошибки с IDisposable»

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

Как удивить команду? Ну, в теории можно построить всю логику приложения на исключениях:
private void UpdateProfile(Profile data)
{
if (data is null)
throw new ArgumentNullException(nameof(data));
if (string.IsNullOrWhiteSpace(data.FullName))
throw new ArgumentException("Missing Name", nameof(data));

// сохраняем изменения

throw new UpdatedSuccessfullyException(data);
}

Это примерно как события, если не обработать которые, приложение упадёт.

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

Источник:
https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
👍8
День 1471. #ВредныеСоветы
11 Способов Усложнить Себе Жизнь в C#. Продолжение
1-2
3-4
5-6

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

Даже если аббревиатура распространена в вашей кодовой базе или домене, может возникнуть путаница, о которой вы даже не подумали. Когда у вас есть куча этих внутренних обязательных знаний в предметной области, это значительно усложняет приглашение в вашу команду новых людей, поскольку им придётся выучить этот список сокращений:
var st = new SimpleTransfer();
var st = new ServiceTime();
var st = new SecretTunnel();

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

Вот некоторые аббревиатуры, которые мне встречались в коде, и являлись не тем, чем казалось на первый взгляд:
"E2E" – не End-to-End,
"IRS" – не имело отношения к налогам,
"IBM" – не про компьютерную компанию,
"S3" – не про хранилище AWS,
"DDL" – не DataDefinitionLanguage и не DropDownList,
"DLL" – не DynamicLinkLibrary.

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

1) Классическая i в стандартном цикле for. Если вы не используете саму переменную, а просто считаете, сколько раз должен выполниться цикл, это нормально:
for (int i = 0; i < сount; i++)
{ … }

2) В лямбда-выражениях, где имя коллекции делает переменную x очевидной:
// Это хорошая альтернатива
int max = forecasts.Max(x => x.Temperature);
// этому
int max = forecasts.Max(
forecast => forecast.Temperature);

Практически во всех других случаях, кроме перечисленных выше, использование однобуквенных переменных вызовет недовольство ваших коллег. Если у вас цепочка выражений LINQ, данные часто изменяются по сравнению с первоначальным типом, с которого началась цепочка. В отличие от Fluent- API, где возвращаемое значение часто имеет тот же тип, что и все методы в цепочке, в LINQ возвращаются разные объекты. И типы этих объектов имеют значение. Вот не слишком сложный пример, который показывает, что даже в простых случаях было бы лучше яснее именовать переменные:
var maxTemperatures =
allTemperatures
.GroupBy(x => x.DayOfWeek)
.Select(y =>
new {
DayOfWeek = y.Key,
HighTemp = y.Max(z => z.Temperature)
})
.OrderBy(o => o.HighTemp)
.ToList();

x, y, z и o все разных типов. Даже если использовать g для группировки, есть риск, что она будет интерпретирована неверно.

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

Источник:
https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
👍16
День 1472. #ВредныеСоветы
11 Способов Усложнить Себе Жизнь в C#. Окончание
1-2, 3-4, 5-6, 7-8

9. Сильно вложенный код
Хотите быстрый и простой способ сделать код труднее для чтения? Вложите условные операторы друг в друга несколько раз, проверяя каждое условие отдельно вместо использования return, && или ?. и ?? для null:
if (building != null)
{
if (building.Office != null)
{
if (building.Office.IsAvailable)
{
if (user.CanReserve)
{

}
}
}
}

Вы могли бы просто написать следующее:
if (building?.Office?.IsAvailable == true 
&& user.CanReserve)
{

}

Заметьте явное сравнение с true, т.к. оператор ?. возвращает bool? (Nullable<bool>), а не bool.

10. Использование интерфейса «один к одному» для класса модели
Когда люди узнают об инверсии зависимостей и моках, они бросаются в крайности, добавляя интерфейс к каждому классу независимо от того, будет ли несколько реализаций интерфейса или необходимо ли будет его мокать.

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

Проблема, если вы создаёте IStudent для ученика, ITeacher для учителя и ILesson для урока. Ни один из этих объектов, скорее всего, не нуждается в моке для тестирования, поскольку в тестах вы можете просто создать экземпляры этих моделей. Полезным может быть интерфейс вроде ISchoolMember для учащихся, учителей и администраторов, когда для всех требуется свойство SchoolID.

11. Размещение регионов внутри методов
Худшее я приберёг напоследок. Не буду стыдить за использование регионов в коде, однако почти всегда их лучше заменить изменением кода. Многим нравится отделять блоки кода регионами, но регион внутри метода должен иметь действительно вескую причину для существования. Помечая блок кода регионом, вы кричите о том, что его надо извлечь в метод:
public ProcessResult Update(ChangeLog changes)
{
#region Validate
if (changes == null)
throw ArgumentException(changes);

if (…)
throw …;
#end region

#region Log
logger.Debug(changes);

#endregion


}

Вместо регионов просто создайте отдельные методы для этих блоков:
public ProcessResult Update(ChangeLog changes)
{
Validate(changes);
Log(changes);

}

Источник: https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
👍20