Лучшие практики написания кода на C#

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

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

Использование методов расширения для лучшей обработки ошибок

Методы расширения, вероятно, являются одной из наиболее часто используемых мной возможностей C#. Я склонен создавать различные методы расширения для сценариев, чтобы справляться с ошибками, внедрять зависимости и т. д. Один из распространенных вариантов использования и лучшая практика — создать метод для проверки null на ваших коллекциях.

Приведенный ниже пример можно использовать в качестве справки

public static void Main(string[] args)
{ try { var importName = GetNamesFromThirdPartyAPI(); importName.EmptyIfNull().ForEach(x => { Console.WriteLine(x); }); Console.WriteLine("Hello World!"); } catch(Exception ex) { Console.WriteLine(ex.Message); } finally { Console.ReadLine(); }
} private static IEnumerable<string> GetNamesFromThirdPartyAPI()
{ return null;
}

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

public static IEnumerable<T> EmptyIfNull<T> (this IEnumerable<T> data)
{ return data ?? Enumerable.Empty<T>();
}

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

importName.EmptyIfNull().ToList().ForEach(x =>
{ Console.WriteLine(x);
});

Обработка логических переменных в условиях

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

Итак, вместо

var items = GetAllItems();
if(items.Any() == true)
{ ShowDetails.Visible = true;
}
else
{ ShowDetails.Visible = false;
}

мы должны делать

var items = GetAllItems();
if(items.Any())
{ ShowDetails.Visible = true;
}
else
{ ShowDetails.Visible = false;
}

даже лучше было бы

var items = GetAllItems();
ShowDetails.Visible = items.Any();

Сравнение строк

Вместо прямого сравнения двух строк с equality check operator == следует использовать .Equals() свойство, поскольку оно предоставляет гораздо более эффективные конфигурации, которые могут помочь выполнять точные сравнения, такие как регистр, язык и региональные параметры и правила сортировки. Этим традиционным способом сравнения придется управлять вручную, преобразуя обе строки в нижний регистр и затем выполняя сравнение.

Пустая проверка строки

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

Теперь, если мы будем следовать традиционному способу проверки, это будет примерно так:

string shortName = GetShortNameBasedOnId(itemId);
if(shortName != null && shortName != "")
{
    
}

С IsNullOrWhiteSpace все эти проверки лучше выполнять сразу, так как это указывает, является ли указанная строка null, empty или состоит из white-spaces только символы.

string shortName = GetShortNameBasedOnId(itemId);
if(!string.isNullOrWhiteSpace(shortName)

Безопасная проверка свойств с помощью ?

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

Давайте обновим метод возврата в нашем предыдущем коде, чтобы понять это на примере.

private static List<Offer> GetNamesFromThirdPartyAPI() { Store store = null; return new List<Offer>() { new Offer() { OfferName = "New Year Offer", Details = "New year bonanza offer",
                    ApplicableStore = store
                }
            };
        }

Здесь объект предложения будет содержать значения, но объект хранилища имеет значение null. Теперь, если мы прочитаем это напрямую, это сломает наш код, или мы сможем продолжать использовать старый if(store !=null) способ. Однако разумнее было бы использовать следующий

var importName = GetNamesFromThirdPartyAPI(); importName.EmptyIfNull().ToList().ForEach(x =>
                {
                    Console.WriteLine(x.OfferName);
                    Console.WriteLine(x.Details);
                    Console.WriteLine(x.ApplicableStore?.StoreName);

                });

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

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

Что мы хотим сделать, так это поставить чай на плиту и дать ему закипеть, а тем временем поджарить хлеб, а затем намазать его маслом. Теперь чайная часть — это наша асинхронная операция, которая займет время. Представим это с помощью Task.Delay() так что это занимает некоторое время, чтобы закончить.

public static async Task<string> TimeTakingTask() { Console.WriteLine("tea is being prepared"); await Task.Delay(5000); return "Tea is ready";
        }

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

var tea = await TimeTakingTask(); Console.WriteLine("Toasting Bread"); Console.WriteLine("Applying Butter"); Console.WriteLine( tea);``

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

var tea = TimeTakingTask();
Console.WriteLine("Toasting Bread");
Console.WriteLine("Applying Butter");
Console.WriteLine( await tea);

Интерполяция строк с использованием $

Несмотря на то, что сейчас он довольно старый, но все же многие разработчики используют традиционный способ объединения строк либо с помощью + или рискованный string.Format.

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

string name = "Hashnode";
string title = "C# best practices";
string author = "Me myself..."; var oldestWay = string.Format("Blog has been written on {0}, with the title {1} by {2}.", name, title, author);
var oldWay = "Blog has been written on " + name + ", " + "with the title " + title + " " + "by " + author + ".";
var mostReadableWay = $"Blog has been written on {name}, with the title {title} by {author}.";

Console.WriteLine(oldestWay);
Console.WriteLine(oldWay);
Console.WriteLine(mostReadableWay);

Некоторые дополнительные однострочные советы

  • Использовать .Any вместо того Count
  • Избегайте объявления переменной, если она просто используется для установки значения, а затем возвращает его из метода, вместо этого напрямую возвращайте значение
  • В основном 99,9% случаев внедрения зависимостей достигаются с помощью шаблона внедрения конструкции. Старайтесь избегать двух других способов.
  • Используйте правильное соглашение об именах внутри lambda функция вместо обычной x
  • С использованием ternary оператор вместо стандартного if else так как это резко уменьшает размер блока кода
  • Моя сборка SonarLint однажды потерпела неудачу, так как у меня было более 8 параметров в методе. В идеале предполагается, что при таком количестве параметров лучше создать модель и передать ее, а не передавать отдельные поля.

гифка

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

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *