День 1468. #ВредныеСоветы
11 Способов Усложнить Себе Жизнь в C#. Начало
Программировать тяжело. Большинство из нас работает в командах, создавая ПО, с которым будут работать другие люди. Поэтому важно, чтобы мы старались облегчить жизнь нашей команды. Вот 11 примеров, как НЕ надо делать в C#.
1. Злоупотребление циклами
Цикл for существует во многих языках. Вы видели его миллион раз:
Начнём с создания бесконечного цикла, оставив каждое из трех выражений пустым:
Можно опустить третье выражение и добавить приращение в условное выражение:
Кстати, о кортежах. Вы можете конструировать и деконструировать кортежи в одной и той же строке. Удивим команду, поместив весь конструктор в одну строку:
Заметили? Тут есть ошибка.
Да,
Продолжение следует…
Источник: https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
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 (2. Построение и деконструкция кортежа в конструкторе
var (number, name) = (1, "Brendan");
number < 7;
name += number.ToString(), number += 1)
{
Console.WriteLine(name);
}
Кстати, о кортежах. Вы можете конструировать и деконструировать кортежи в одной и той же строке. Удивим команду, поместив весь конструктор в одну строку:
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 полезен, когда тип сложный:
Тогда почему бы просто не использовать его везде? Потому, что var скрывает информацию:
4. Подавление всех предупреждений вместо их исправления
Здесь нечего сказать. Некоторые предупреждения компилятора могут не вызывать проблем, но часто они указывают на место, где ошибка может остаться незамеченной. Некоторые любят установить параметр «обрабатывать предупреждения как ошибки», потому что это заставляет исправлять каждое предупреждение, увеличивая вероятность того, что мы обнаружим ошибки раньше.
Иногда может быть необходимо не исправлять одно-два предупреждения, поэтому есть параметр NoWarn, который можно установить в проекте, чтобы указать предупреждения, которые следует игнорировать. Если вы действительно хотите развлечь команду, просто добавляйте по одному предупреждению для игнорирования каждый раз, когда с ним сталкиваетесь. Тогда ваш код будет без предупреждений!
Если это ваша ситуация, я настоятельно рекомендую настроить метрику для отслеживания их числа и использовать правило скаута: очищать одно или два предупреждения каждый раз, когда вы что-то меняете в файле.
Продолжение следует…
Источник: https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
11 Способов Усложнить Себе Жизнь в C#. Продолжение
Начало
3. Чрезмерное или недостаточное использование var
Var, на самом деле, замечательное дополнение к языку, позволяющее не указывать тип переменной, поскольку компилятор уже знает этот тип. Но просто нигде не указывать тип, а использовать var, - это перебор. Var полезен, когда тип сложный:
// ясно, но длинноИменно для таких сложных типов (и анонимных типов) необходим var. На самом деле почти везде, где результат GroupBy хранится в переменной, вы, скорее всего, увидите в коде var. Иногда это связано с тем, что результат должен быть преобразован в новый анонимный тип.
IEnumerable<IGrouping<DateOnly, Person>> peopleGroupedByBirthdate =
people.GroupBy(
p => p.Birthdate,
p => p);
// менее ясно, но короче
var peopleGroupedByBirthdate =
people.GroupBy(
p => p.Birthdate,
p => p);
Тогда почему бы просто не использовать его везде? Потому, что var скрывает информацию:
var author1 = GetAuthor1();Мы не можем определить тип этих переменных, не наведя на них курсор. Скорее всего, GetAuthorName и GetAuthorFullName одного типа, но вовсе не факт. Мне знакомы люди, которые бросались из крайности в крайность: от полного неприятия var до использования его повсеместно. Как везде, важен баланс. Если вы заметили, что наводите курсор на переменную, чтобы узнать её тип, задумайтесь, может стоит указать его явно?
var author2 = GetAuthor2();
var authorName = GetAuthorName();
var authorFullName = GetAuthorFullName();
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:
См. также «Мои любимые ошибки с IDisposable»
6. Генерация исключений вместо возврата
Вы можете привлечь внимание обзорщика (меня точно) к вашему коду, если в ожидаемой или вероятной ситуации выбросите исключение вместо того, чтобы просто обработать этот вариант и вернуть значение.
Пользователь забыл ввести значение? Это ошибка валидации, а не исключительный случай. Мы можем (и должны) возвращать результат ошибки валидации, чтобы проинформировать пользователя о проблеме. Если же значение отсутствовало во внутренних расчетах, это проблема целостности данных после валидации - исключительный случай, который может потребовать остановки кода, чтобы предотвратить дальнейшие проблемы.
Как удивить команду? Ну, в теории можно построить всю логику приложения на исключениях:
Продолжение следует…
Источник: https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
11 Способов Усложнить Себе Жизнь в C#. Продолжение
1-2
3-4
5. Работа с disposable-типами без using
Говоря о disposable-типах, мы обычно имеем в виду, что тип реализует интерфейс IDisposable. Когда вы создаёте экземпляр такого типа, он должен очищаться после использования. В помощь вам выражение using:
using (var sw = new StreamWriter(path, true))Если вы хотите расстроить команду, перестаньте использовать using. Что может пойти не так? Многое. Тип реализует IDisposable, чтобы его можно было правильно очистить, т.е. освободить ресурсы (сетевые подключения, дескрипторы файлов и т.д.), которые использовал тип, перед его удалением. Если их правильно не освободить, они останутся открытыми:
{
sw.WriteLine("Test");
}
SqlConnection conn = new (connString)Здесь объекты SqlConnection и SqlDataReader необходимо очищать. Если вы волнуетесь за вложенность, начиная с C#8 можно использовать декларации using, т.е. не заключать код в фигурные скобки.
SqlCommand command = new (query, conn);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
…
}
using SqlConnection conn = new (connString);Здесь переменные conn и reader будут освобождены в конце текущего блока кода (например, при выходе из метода).
…
using SqlDataReader reader = command.ExecuteReader();
…
См. также «Мои любимые ошибки с 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. Использование непонятных сокращений для имён переменных
Очень важно, чтобы читатель кода знал, для чего переменная предназначена. Если вы хотите усложнить кодовую базу, вы можете добавить в имена сокращения и аббревиатуры.
Даже если аббревиатура распространена в вашей кодовой базе или домене, может возникнуть путаница, о которой вы даже не подумали. Когда у вас есть куча этих внутренних обязательных знаний в предметной области, это значительно усложняет приглашение в вашу команду новых людей, поскольку им придётся выучить этот список сокращений:
Вот некоторые аббревиатуры, которые мне встречались в коде, и являлись не тем, чем казалось на первый взгляд:
"E2E" – не End-to-End,
"IRS" – не имело отношения к налогам,
"IBM" – не про компьютерную компанию,
"S3" – не про хранилище AWS,
"DDL" – не DataDefinitionLanguage и не DropDownList,
"DLL" – не DynamicLinkLibrary.
8. Использование однобуквенных переменных
Есть только два места, где однобуквенная переменная – это нормально. Но даже в этом случае может быть лучше использовать полное имя.
1) Классическая
Окончание следует…
Источник: https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
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 очевидной:// Это хорошая альтернативаПрактически во всех других случаях, кроме перечисленных выше, использование однобуквенных переменных вызовет недовольство ваших коллег. Если у вас цепочка выражений LINQ, данные часто изменяются по сравнению с первоначальным типом, с которого началась цепочка. В отличие от Fluent- API, где возвращаемое значение часто имеет тот же тип, что и все методы в цепочке, в LINQ возвращаются разные объекты. И типы этих объектов имеют значение. Вот не слишком сложный пример, который показывает, что даже в простых случаях было бы лучше яснее именовать переменные:
int max = forecasts.Max(x => x.Temperature);
// этому
int max = forecasts.Max(
forecast => forecast.Temperature);
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:
10. Использование интерфейса «один к одному» для класса модели
Когда люди узнают об инверсии зависимостей и моках, они бросаются в крайности, добавляя интерфейс к каждому классу независимо от того, будет ли несколько реализаций интерфейса или необходимо ли будет его мокать.
Это не значит, что не может быть интерфейсов для моделей. Могут быть интерфейсы для всех кэшуемых объектов, всех печатаемых объектов и т. д. Но у них наверняка будет несколько реализаций.
Проблема, если вы создаёте IStudent для ученика, ITeacher для учителя и ILesson для урока. Ни один из этих объектов, скорее всего, не нуждается в моке для тестирования, поскольку в тестах вы можете просто создать экземпляры этих моделей. Полезным может быть интерфейс вроде ISchoolMember для учащихся, учителей и администраторов, когда для всех требуется свойство SchoolID.
11. Размещение регионов внутри методов
Худшее я приберёг напоследок. Не буду стыдить за использование регионов в коде, однако почти всегда их лучше заменить изменением кода. Многим нравится отделять блоки кода регионами, но регион внутри метода должен иметь действительно вескую причину для существования. Помечая блок кода регионом, вы кричите о том, что его надо извлечь в метод:
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Заметьте явное сравнение с true, т.к. оператор ?. возвращает bool? (Nullable<bool>), а не bool.
&& user.CanReserve)
{
…
}
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)Источник: https://brendoneus.com/post/Ways-Of-Making-CSharp-Harder/
{
Validate(changes);
Log(changes);
…
}
👍20