Может ли цепочка методов C# быть слишком длинной?

Не с точки зрения читабельности, естественно, так как вы всегда можете расположить отдельные методы в отдельные строки. Скорее, опасно ли по какой-либо причине связывать чрезмерно большое количество методов вместе? Я использую цепочку методов в первую очередь для экономии места при объявлении отдельных одноразовых переменных и традиционно использую методы возврата вместо методов, которые изменяют вызывающую программу. За исключением строковых методов, я безжалостно привязываю их к цепочке. В любом случае, меня иногда беспокоит влияние использования исключительно длинных цепочек методов в одной строке.

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

SPWeb web = GetWorkflowWeb();
SPList list2 = web.Lists["Wars"];
SPListItem item2 = list2.GetItemById(3);
SPListItem item3 = item2.GetItemFromLookup("Armies", "Allied Army");
SPUser user2 = item2.GetSPUser("Commander");
SPUser user3 = user2.GetAssociate("Spouse");
string username2 = user3.Name;
item1["Contact"] = username2;

Все с 2 или 3 длится только один вызов, поэтому я мог бы сжать его следующим образом (что также позволяет мне избавиться от лишней 1):

SPWeb web = GetWorkflowWeb();
item["Contact"] = web.Lists["Armies"]
                     .GetItemById(3)
                     .GetItemFromLookup("Armies", "Allied Army")
                     .GetSPUser("Commander")
                     .GetAssociate("Spouse")
                     .Name;

По общему признанию, это выглядит намного длиннее, когда все в одной строке и когда у вас есть int.Parse(ddlArmy.SelectedValue.CutBefore(";#", false)) вместо 3. Тем не менее, это одна из средних длин этих цепочек, и я могу легко предвидеть некоторые исключительно длинные отсчеты. За исключением удобочитаемости, есть ли что-то, о чем я должен беспокоиться об этих 10+ цепочках методов? Или нет ничего плохого в использовании действительно очень длинных цепочек методов?


person Grace Note    schedule 17.05.2010    source источник
comment
Десять это ничто. Если бы у вас было больше, скажем, десяти тысяч, то вы могли бы начать сталкиваться с проблемами. Вы сказали, что делаете это для экономии места. Я не понимаю, какое пространство, по вашему мнению, вы экономите, или почему вы считаете это пространство дефицитным ресурсом, требующим осторожного присмотра. Можете ли вы объяснить, какие эмпирические данные подтверждают ваше мнение о том, что вы экономите на использовании дефицитного или дорогого ресурса?   -  person Eric Lippert    schedule 17.05.2010
comment
Я не эксперт, но я считаю, что ваш второй пример намного читабельнее. Если бы мне поручили поддерживать этот пример, я бы гораздо быстрее понял, что делает второй. Там гораздо меньше лишнего кода. Я полагаю, что удобочитаемость должна быть здесь главной заботой. Если ваши цепочки легче читать, то это хорошо   -  person Jeremy B.    schedule 17.05.2010
comment
@ Эрик Я ничего не называл дефицитным или дорогим ресурсом. Пространство, которое я экономлю, — это пара паршивых байтов файлового пространства в файле кода для состояния ;\r\nCLASS bar = foo между цепочками методов, что, в свою очередь, кажется мне более чистым. Учитывая, что для меня он выглядит чистым, это вопрос удобочитаемости, а я явно исключаю удобочитаемость как проблему, с чего вы взяли, что я нахожу это место требующим тщательного контроля?   -  person Grace Note    schedule 17.05.2010
comment
@Jeremy По иронии судьбы, я разместил этот второй пример для удобства чтения только на этом сайте. Основываясь на том, какое свойство на самом деле назначается цепочкой-возвратом, я обычно могу сказать, что представляет собой вся цепочка, не прокручивая ее, когда я перечисляю ее в одной строке.   -  person Grace Note    schedule 17.05.2010
comment
Меня беспокоит, когда чьей-то основной инженерной задачей является оптимизация использования неважного ресурса. Это кажется плохой инженерной практикой, поэтому я и назвал это. Если ваша главная задача на самом деле состоит не в том, чтобы сделать код небольшим, а в том, чтобы сделать код более читабельным (что разумно, хотя лично я бы сосредоточился на правильности i> до удобочитаемости), тогда зачем подчеркивать аспект размера в вашем вопросе? Ваш вопрос, насколько я понимаю сейчас, заключается в том, что я нахожу эту технику более читаемой; есть ли скрытые технические расходы? Нет, здесь нету.   -  person Eric Lippert    schedule 17.05.2010
comment
@ Эрик Меня вообще не волнует оптимизация. На самом деле я подчеркиваю правильность: я хочу знать, есть ли реальные проблемы (как бы неправильность в функциональности), связанные с очень длинными цепочками методов. Непосредственный пример, который я получил из своего кода, состоит всего из 6 методов, которые, как я ожидаю, тривиальны по количеству, поэтому я хотел подчеркнуть, что вопрос заключается в том, существует ли такая вещь, как слишком долго? и не является ли моя цепочка из 6 методов слишком длинной?   -  person Grace Note    schedule 17.05.2010
comment
В то время как вторая форма выглядит красивее, я считаю, что первую намного легче отлаживать. Однако использование var может немного облегчить глаза. Если что-то сломается на третьем шаге, красота меня никуда не приведет. Я впадаю в припадки, когда пытаюсь отлаживать такие вещи, как: return Method1(Method2(Method3(my_value.Method4())));   -  person Jim B-G    schedule 17.05.2010


Ответы (5)


Технических ограничений на длину цепочки методов нет.

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

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

Обработка исключений усложняется тем фактом, что вы не можете изолировать исключения, вызванные одним методом, по сравнению с другим. Обычно это не проблема, если вы не можете сделать что-то значимое в ответ на исключение — просто позвольте ему распространяться по цепочке вызовов. Однако, если вы позже поймете, что вам нужна обработка исключений, вам придется реорганизовать синтаксис цепочки, чтобы иметь возможность вставлять соответствующие обработчики try/catch.

Аналогично обработке исключений случай детерминированного удаления ресурсов. Проще всего в C# сделать это с помощью using() - к сожалению, синтаксис цепочки исключает это. Если вы вызываете методы, которые возвращают одноразовые объекты, вероятно, лучше избегать синтаксиса цепочки, чтобы вы могли быть хорошим «гражданином кода» и избавляться от этих ресурсов как можно раньше.

Синтаксис цепочек методов часто используется в флуктуальных API, где он позволяет более эффективно использовать синтаксис вашего кода. точно отражать последовательность операций, которые вы намереваетесь. LINQ — это один из примеров в .NET, где часто встречается свободный синтаксис.

person LBushkin    schedule 17.05.2010
comment
согласна, я как раз собиралась написать то же самое - person SirLenz0rlot; 17.05.2010
comment
Существуют технические ограничения, связанные с инструментами. См. мой ответ: stackoverflow.com/a/67034345/64334 - person Ronnie Overby; 10.04.2021

Удобочитаемость является самой большой проблемой, но часто это вообще не проблема.

Вы также можете описать синтаксис запроса LINQ как (под всем этим) точно такую ​​настройку. Это просто делает его красивее ;-p

Одна возможная проблема заключается в том, что вам нужно ввести такие вещи, как using или lock; с плавным API у вас может возникнуть соблазн просто удалить эти компоненты, но это может привести к странностям, когда выдается исключение.

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

var foo = bar.MethodA().MethodB(...).MethodC();
try {
    foo.MethodD();
} catch (SomeSpecificException) {
    //something interesting
}

Или вы могли бы даже сделать это в методе расширения, чтобы сохранить плавный внешний вид:

bar.MethodA().MethodB(...).MethodC().MyExtensionMethodD();

где MyExtensionMethodD — это тот, который вы добавляете со специальной обработкой (исключения, блокировки, использование и т. д.).

person Marc Gravell    schedule 17.05.2010

Некоторые считают это запахом кода, а другие нет. Каждый раз, когда вы видите следующее:

Foo.getBar().getBlah().getItem().getName();

Вы действительно должны думать: «Чего я действительно хочу?» Вместо этого, возможно, ваш метод должен содержать вызов функции:

String getName(Int _id, String _item)
{
    return myBar.getName( _id, _item );
}

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

person wheaties    schedule 17.05.2010

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

  • Являются ли вызовы, которые я делаю, возвращающими объектами, от которых следует избавляться?
  • Я инициирую много вещей внутри одной области, а не в меньших областях, на которые GC может быть более реактивным? (GC планируется запускать каждый раз, когда вы покидаете область действия, хотя планирование и время его фактического запуска — это две разные вещи :-p).

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

Джош

person Josh Handel    schedule 17.05.2010
comment
Сборщик мусора не запускается каждый раз, когда вы покидаете область — он запускается, когда в управляемой куче нет свободного места. - person Lee; 17.05.2010
comment
так ли это? Я знаю, что когда я тестировал использование диструкторов для управления пулом синглетонов (примерно 3 года назад), диструктор вызывался, когда я выходил из области видимости, независимо от размера кучи (т.е. почти ничего). И я знаю, что диструктор вызывается (когда присутствует) при первом проходе GC.. - person Josh Handel; 17.05.2010

В дополнение к другим ответам, есть техническое ограничение, с которым вы можете столкнуться: очень длинные цепочки методов приведут к сбою служб, предоставляющих IntelliSense, а затем и их хост-процессов (Linqpad 6, Visual Studio 2019 и, возможно, другие).

Я обнаружил это, когда преобразовал сценарий, который я использую для отслеживания своей личной работы, из API на основе коллекции в цепочку вызовов Fluent API. Я обнаружил, что код, который у меня был, приведет к сбою в любой среде IDE, в которую я его вставлю. Исследования привели меня к этой давней проблеме с github: https://github.com/dotnet/roslyn/issues/9795

Достаточно просто разбить очень длинные цепочки на отдельные операторы.

person Ronnie Overby    schedule 10.04.2021