Как стать автором
Обновить

Комментарии 19

Немножко дико выглядит код после return. Вы по какой-то причине так писали?
Мне кажется, для читабельности лучше было бы использовать локальную функцию после её объявления.

Это перевод msdn. А так — как раз удобнее, если привыкнуть. Более лучше видно, что вы не проваливаетесь в тело функции из общего потока.
А мне нравится: сначала идет основная логика, сразу ясно, что выполняет функция. Детали — позже.

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


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


Но если такую практику принять, то и локальные функции должны располагаться после основного блока кода.

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

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

Некоторые вообще следуют принципам SOLID и выделяют публичную часть класса в отдельный интерфейс.


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

При использовании IDE этой проблемы нет. В VS вообще можно прыгать через Alt+F12 с сохранением истории переходов.

Некоторые вообще следуют принципам SOLID и выделяют публичную часть класса в отдельный интерфейс.

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


При использовании IDE этой проблемы нет. В VS вообще можно прыгать через Alt+F12 с сохранением истории переходов.

Тем не менее, для аккуратности и читаемости кода лучше методы группировать.
Сейчас в тренде группировать по области видимости в порядке, начиная с публичных, хотя раньше было не так.
Это прослеживается и в новых MS исходниках, например, шаблоне Web API Core приложения.
В целом, это дело моды и вкуса.

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

Какую именно часть деталей реализации вы хотите увидеть первой?
Не уверен в ценности опроса, поскольку полезность фичи зависит от контекста. По моим наблюдениям, малые команды обычно пишут малые и типовые проекты, часто без активного использования async и Enumerable (везде используется List).

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

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

Небольшой нетехнический момент: в раздел 101 можно добавить пункт о форматировании кода. Например, в коде есть публичный метод на 30 строк, разбитый на
public void DoSomething()
{
  DoFirstPart();
  DoSecondPart();
  DoThirdPart();
}
..
// Private methods Do*Part here

В итоге в классе лежат 4 метода. Если вынести Do*Part методы в локальные функции — остается лишь один метод, сор из DoSomething не выносится.
часто без активного использования async и Enumerable (везде используется List).

То есть, Linq вообще не используют?

В итоге в классе лежат 4 метода. Если вынести Do*Part методы в локальные функции — остается лишь один метод, сор из DoSomething не выносится.

А вот это, ИМХО, перебор, 4 локальных функции…
То есть, Linq вообще не используют?


Наверное имелось в виду, что yield return не используют.
Linq используют, но из любого потенциального IEnumerable метода возвращают result.ToList();
Как выше заметил Naglec — не используют yield return.

По поводу функций вопрос открыт. В любом случае у нас есть три выделенных куска кода. Какая разница где их хранить, в теле класса как приватные методы, или в теле метода как локальные функции? В первом случае мы «засоряем» интерфейс класса, пускай и в приватной части, во втором случае мы «засоряем» лишь метод. В случае ЛФ сразу видно где код используется, в случае приватных методов проще переиспользовать код.
Мы не засоряем интерфейс класса, т.к. функции приватны.
Конечно, это выбор отдельно взятого человека/команды. Но на мой вкус — это перебор.
В первом случае мы «засоряем» интерфейс класса, пускай и в приватной части, во втором случае мы «засоряем» лишь метод. В случае ЛФ сразу видно где код используется, в случае приватных методов проще переиспользовать код.


Но в случае ЛФ сразу возрастает уровень вложенности, что тоже читабельности не добавляет.

Лично я пока вижу адекватный сценарий использования ЛФ только в виде описания небольшой логики (в пределах 5-10 строк), которая нигде за пределами метода использоваться не будет. И, наверное, не более одной ЛФ на метод.

Спасибо за подробный обзор локальных функций!


Локальные функции и анонимные блоки кода имеют разную семантику.
Анонимные блоки на то и анонимные, чтобы использовать их как лямбда-функции (передавать как стратегии в метод или класс).
А локальные функции это локальные функции.
И в силу отличий в семантике и технических причин, анонимные блоки не могут заменить собой локальные функции во всех случаях.


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


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


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


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


  1. В основном методе есть повторяющиеся участки, которые нецелесообразно выносить во внешний метод, т.к. новый (локальный) метод имеет смысл только в контексте основного.
    Вынесение такого участка по внешний метод привносит "замусоривание" кода класса и риск того, что в дальнейшем кто-то может посчитать такой метод самодостаточным, дорабатывать и вызывать его в других методах класса.
  2. Бывает, что код метода нельзя сделать короче одного-двух экранов, но в нем просматриваются отдельные блоки, которые для читаемости основной логики есть смысл вынести в отдельные методы.
    Тогда можно провести декомпозицию основного метода, вынеся блоки в локальные методы (локальные — по тем же причинам).
И в обоих случаях проблема решается созданием private nested класса. Вообще, локальные функции — шутка очень удобная. Но с дополнительным классом код получается более аккуратным.
Создавать еще одну сущность (пусть и private nested) ради метода? о_О Это еще менее логично, чем просто private метод, который используется только один раз.
Ну да. Я не понимаю боязни создания новых сущностей. И почему ради метода? Ради нескольких методов («в нём просматриваются отдельные блоки, которые для читаемости основной логики есть смысл вынести в отдельные методы»).

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

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


Соответственно, и для случая, когда декомпозируется метод на методы, имеющие смысл только в его контексте, можно реализовать декомпозицию двумя способами (при условии, что не выносим на верхний уровень "подметоды"):


  • Локальные методы.
  • Отдельная сущность, для решения задачи, которую до этого решал большой метод. И поскольку сущность эта будет небольшая (разделили метод на 2-3-4 метода и вынесли их в новую сущность), то и вопрос группировки становится неактуальным.

Какой метод выбрать — видимо, решать на месте, в зависимости от количества и объема "подметодов".

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории