Может ли цепочка методов С# быть "слишком длинной"?
Не с точки зрения удобочитаемости, естественно, поскольку вы всегда можете расположить отдельные методы в отдельных строках. Скорее, опасно ли по какой-либо причине объединять слишком много методов? Я использую метод chaining, прежде всего, для экономии места при объявлении отдельных одноразовых переменных и традиционно используя методы возврата вместо методов, которые изменяют вызывающего. За исключением строковых методов, те, кого я беспорядочно цепляю. В любом случае я иногда беспокоюсь о влиянии использования исключительно длинных цепочек методов в одной строке.
Скажем, мне нужно обновить значение одного элемента на основе имени пользователя. К сожалению, самый короткий метод для получения правильного пользователя выглядит примерно так:
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+? Или нет никакого вреда в использовании действительно очень длинных цепочек методов?
Ответы
Ответ 1
Нет технических ограничений на то, как долго может быть цепочка методов.
Однако три области, которые могут стать проблематичными, - это отладка, обработка исключений и удаление ресурсов.
Отладка осложняется тем же фактом, что делает цепочку настолько элегантной - отсутствие промежуточных временных переменных. К сожалению, без временных переменных проверка промежуточных результатов при отладке становится болезненной.
Обработка исключений осложняется тем фактом, что вы не можете изолировать исключения, созданные из одного метода по сравнению с другим. Обычно это не проблема, если вы не можете сделать что-то значимое в ответ на исключение - просто позвольте ему распространить цепочку вызовов. Однако, если вы понимаете, что вам нужна обработка исключений, вам нужно реорганизовать синтаксис цепочки, чтобы иметь возможность вставлять соответствующие обработчики try/catch.
Подобно обработке исключений - это случай детерминированного удаления ресурсов. Самый простой способ в С# выполнить это с помощью using()
- к сожалению, синтаксис цепочки исключает это. Если вы вызываете методы, возвращающие одноразовые объекты, вероятно, рекомендуется избегать синтаксиса цепочки, чтобы вы могли быть хорошим "гражданином кода" и распоряжаться этими ресурсами как можно раньше.
Синтаксис цепочки методов часто используется в бедных API, где он позволяет синтаксису вашего кода более точно отражать последовательность операций, которые вы выполняете намерены. LINQ - один из примеров в .NET, где часто встречается сильное синтаксическое поведение/цепочка.
Ответ 2
Чтение является самой большой проблемой, но часто это не проблема.
Вы также можете описать синтаксис запроса LINQ как (под всем этим) именно такую настройку. Это просто делает его более красивым; -p
Одна из возможных проблем заключается в том, что вам нужно вводить такие вещи, как using
или lock
; с беглым API вы можете испытывать соблазн просто отказаться от этих компонентов, но это может привести к странностям при вызове исключения.
Другая возможная мысль заключается в том, что вы можете захотеть иметь более гранулированную обработку исключений по некоторым вызовам; но вы всегда можете просто сломать поток:
var foo = bar.MethodA().MethodB(...).MethodC();
try {
foo.MethodD();
} catch (SomeSpecificException) {
//something interesting
}
Или вы даже можете сделать это в методе расширения, чтобы сохранить свободный вид:
bar.MethodA().MethodB(...).MethodC().MyExtensionMethodD();
где MyExtensionMethodD
- это тот, который вы добавляете со специальной обработкой (исключения, блокировки, использование и т.д.).
Ответ 3
Это считается запахом кода некоторыми, а не другими. Каждый раз, когда вы видите следующее:
Foo.getBar().getBlah().getItem().getName();
вы действительно должны думать: "Чего я действительно хочу?" Вместо этого, возможно, ваш метод должен содержать вызов функции:
String getName(Int _id, String _item)
{
return myBar.getName( _id, _item );
}
который затем делегирует вниз, среди классов. Затем, если что-то изменится в одном из ваших классов при последующем обновлении, вы увидите, где именно оно происходит, и можете изменить его в одном месте.
Ответ 4
Единственное, что вы должны учитывать относительно этого (если вы игнорируете читаемость) - это обработка ресурсов и GC.. Вопросы, которые вы должны задать, - это.
- Являются ли вызовы я возвращаю объекты, которые должны быть удалены?
- am Я начинаю много вещей внутри одной области, а не с меньшими областями, с которыми GC может реагировать более эффективно? (GC планируется запускать каждый раз, когда вы покидаете область действия, хотя планирование и когда оно выполняется на самом деле - это две разные вещи: -p).
Но на самом деле.. погода вы вызываете по одному методу в строке или строкой их всех тогеров, все заканчивается тем же, что и в IL (или почти одинаково).
Джош