Сделайте ваши запросы LINQ читабельными, как хорошую книгу

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

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

1. Делайте перерыв после каждого утверждения

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

2. Отступ для каждого вложенного слоя

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

Конструктор ReceivedMessage — это новый логический уровень, который получает новый уровень отступа, и, при желании, вы также можете разбивать и отступать свои аргументы.

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

3. Извлеките более крупные методы

Иногда в предложениях Select/SelectMany могут быть сложные правила преобразования. У вас может возникнуть соблазн просто написать весь код внутри вашей лямбда-функции. Однако следующий разработчик, скорее всего, не поймет, что там происходит. Так почему бы просто не выделить эту часть кода в отдельный метод. Вы даже можете не использовать фигурные скобки и шаблонный код лямбда-функции, так как вы можете написать короткие лямбда-выражения следующим образом:

4. Извлечение сложных предложений Where

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

5. Создайте свои собственные методы расширения

LINQ уже поставляется с чрезвычайно полезными методами расширения. Тем не менее, все еще могут быть некоторые функции, которые очень полезны и еще не включены в библиотеку по умолчанию. Например, я создал метод расширения DistinctBy() задолго до того, как Microsoft включила его в .NET 6.

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

Они значительно упрощают работу с асинхронным LINQ, как вы можете видеть здесь:

6. Используйте отдельные символы для ваших лямбда-переменных

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

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

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

7. Всегда ожидайте IEnumerable в качестве параметра

Каждый метод в вашем проекте, который ожидает какой-либо IEnumerable, должен всегда указывать, что он ожидает это. Никогда не ждите Array или List или чего-то еще, если они вам на самом деле не нужны (хотя вы все равно можете передать их методу). Единственные исключения, которые я нашел в своих проектах, это HashSets и Dictionaries. Кроме того, когда вы явно хотите уже перечислить IEnumerable, вы можете ожидать определенный тип по своему вкусу.

Вы должны установить правило никогда не передавать null как IEnumerable. Вы можете просто передать пустой IEnumerable с Enumerable.Empty<T>(). Таким образом, вы значительно уменьшите количество исключений NullReferenceException, и ваш код не сломается. Обычно вам даже не нужно беспокоиться о пустых перечислениях, так как ваши запросы LINQ также будут работать с пустыми.

8. Всегда преобразовывать неизменяемое

Неизменяемость — чрезвычайно важная концепция для чистого кодирования. В основном это означает, что вы не можете изменить состояние любого объекта по дизайну. Иногда конечно можно, но нельзя! Особенно в ваших запросах Select() вы должны обратить внимание на то, чтобы не изменять какое-либо состояние вашего источника. Таким образом вы убедитесь, что можете перечислить запрос несколько раз без изменения результата. Взгляните на следующий пример:

Если вы перечислите это в первый раз, потоки будут прочитаны, а затем удалены, что изменит состояние источника! Если вы попытаетесь перечислить это во второй раз, вы получите ObjectDisposedException() и не слишком очевидно, где вы ошиблись.

(9.) Обратите внимание на исполнение-производительность

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

Легко может случиться так, что вы переберете все IEnumerables в несколько раз больше, чем вам действительно нужно. Многие упражнения Leetcode включают оптимизацию итераций и, таким образом, уменьшают сложность вашего времени и пространства. Хороший вопрос, который следует задать себе, если вы хотите оптимизировать запрос:

Могу ли я использовать здесь HashSet или Dictionary?

Часто вы можете уменьшить временную сложность, просто используя один из них, поскольку они имеют O(1) временную сложность.

Кроме того, хороший способ значительно повысить производительность ваших запросов LINQ — просто использовать .AsParallel(). Это знакомит ваш запрос LINQ с запросом PLINQ, в котором все выполняется параллельно на вашем ЦП. Здесь следует обратить особое внимание на неизменность и не зависеть от других изменяющихся состояний, поскольку распараллеливание можно эффективно использовать только на независимых запросах.

Вы должны использовать PLINQ только тогда, когда у вас очень длинные запросы, так как он может даже замедлить определенные запросы, которые в противном случае были бы быстрее.

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

Если вы заинтересованы в том, чтобы быть в курсе последних тенденций, советов и приемов для чистой архитектуры, чистого кодирования и новейших технологических стеков, особенно в контексте C #, .NET и Angular, я был бы признателен. если вы решили следовать за мной.

Удачного дня!

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

Присоединиться к Medium сейчас

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