Удлинить ли методы расширения?
Все,
Хотелось бы высказать несколько соображений по этому поводу. В последнее время я становлюсь все более и более подписчиком "пуристских" принципов ДИ/МОК при проектировании/разработке. Часть этого (большая часть) связана с тем, что между моими классами существует небольшая связь и что их зависимости разрешаются с помощью конструктора (есть, конечно, другие способы управления этим, но вы получаете идею).
Моя основная предпосылка заключается в том, что методы расширения нарушают принципы DI/IOC.
Я создал следующий метод расширения, который я использую для обеспечения того, чтобы строки, вставленные в таблицы базы данных, были усечены до нужного размера:
public static class StringExtensions
{
public static string TruncateToSize(this string input, int maxLength)
{
int lengthToUse = maxLength;
if (input.Length < maxLength)
{
lengthToUse = input.Length;
}
return input.Substring(0, lengthToUse);
}
}
Затем я могу вызвать свою строку из другого класса следующим образом:
string myString = "myValue.TruncateThisPartPlease.";
myString.TruncateToSize(8);
Справедливым переводом этого без использования метода расширения будет:
string myString = "myValue.TruncateThisPartPlease.";
StaticStringUtil.TruncateToSize(myString, 8);
Любой класс, который использует любой из приведенных выше примеров, не может быть протестирован независимо от класса, содержащего метод TruncateToSize (в стороне от TypeMock). Если бы я не использовал метод расширения, и я не хотел создавать статическую зависимость, это выглядело бы более похоже:
string myString = "myValue.TruncateThisPartPlease.";
_stringUtil.TruncateToSize(myString, 8);
В последнем примере зависимость _stringUtil будет разрешена с помощью конструктора, и класс может быть протестирован без зависимости от фактического класса метода TruncateToSize (его можно было бы легко высмеять).
С моей точки зрения, первые два примера основаны на статических зависимостях (один явный, один скрытый), а второй инвертирует зависимость и обеспечивает уменьшенную связь и лучшую тестируемость.
Таким образом, использование методов расширения противоречит принципам DI/IOC? Если вы являетесь подписчиком методологии IOC, избегайте использования методов расширения?
Ответы
Ответ 1
Я вижу, откуда вы родом, если вы пытаетесь высмеять функциональность метода расширения, я считаю, что вы используете их неправильно. Методы расширения должны использоваться для выполнения задачи, которая будет просто неудобно синтаксически без них. Ваш пример TruncateToLength является хорошим примером.
Тестирование TruncateToLength не включало бы издевательство над ним, это просто связано с созданием нескольких строк и тестированием, что метод действительно вернул правильное значение.
С другой стороны, если у вас есть код на вашем уровне данных, содержащийся в методах расширения, который обращается к вашему хранилищу данных, то да, у вас есть проблема, и тестирование станет проблемой.
Обычно я использую только методы расширения, чтобы обеспечить синтаксический сахар для небольших простых операций.
Ответ 2
Я думаю, что это хорошо - потому что это не нравится TruncateToSize
- это реально сменный компонент. Это метод, который будет когда-либо делать только одну вещь.
Вам не нужно быть в состоянии издеваться над всем - просто сервисами, которые либо нарушают модульное тестирование (доступ к файлам и т.д.), либо те, которые вы хотите протестировать с точки зрения подлинных зависимостей. Если вы использовали его для аутентификации или что-то в этом роде, это было бы совсем другое дело... но просто выполнял прямую операцию строки, которая не имеет абсолютно никакой конфигурации, разных опций реализации и т.д. - нет смысла рассматривать это как зависимость в нормальном смысле.
Другими словами: если TruncateToSize
был подлинным членом String
, вы бы даже подумали дважды об использовании его? Вы пытаетесь издеваться над целочисленной арифметикой, представляя IInt32Adder
и т.д.? Конечно нет. Это одно и то же, это только то, что вы поставляете реализацию. Unit test выберите TruncateToSize
и не беспокойтесь об этом.
Ответ 3
Его боль, потому что их трудно издеваться. Я обычно использую одну из этих стратегий
- Да, отбросьте расширение его PITA, чтобы издеваться.
- Используйте расширение и просто проверяйте его правильность. т.е. передавать данные в усечение и проверять, что он усечен.
- Если это не какая-то тривиальная вещь, и я должен ее высмеять, я сделаю, чтобы мой класс расширения имел сеттер для используемой им службы и устанавливал это в тестовом коде.
то есть.
static class TruncateExtensions{
public ITruncateService Service {private get;set;}
public string TruncateToSize(string s, int size)
{
return (Service ?? Service = new MyDefaultTranslationServiceImpl()). TruncateToSize(s, size);
}
}
Это немного страшно, потому что кто-то может установить службу, когда они этого не делают, но иногда я немного кавалерист, и если бы это было действительно важно, я мог бы сделать что-то умное с флагами #if TEST или ServiceLocator чтобы избежать использования сеттера в производстве.
Ответ 4
Методы расширения, частичные классы и динамические объекты. Мне они очень нравятся, но вы должны осторожно протестовать, здесь есть монстры.
Я бы посмотрел на динамические языки и посмотрел, как они справляются с такими проблемами на повседневной основе, и действительно просвещает. Особенно, когда им нечего им мешать делать глупые вещи, кроме хорошего дизайна и дисциплины. Во время выполнения все динамично, единственное, что их остановить - это компьютер, бросающий большую ошибку времени выполнения. "Duck Typing" - самая сумасшедшая вещь, которую я когда-либо видел, хороший код - это хороший дизайн программы, уважение к другим в вашей команде и доверие к тому, что каждый член, хотя и обладает способностью делать какие-то нелепые вещи, предпочитают не потому, что хорошо дизайн приводит к лучшим результатам.
Что касается вашего тестового сценария с макетными объектами /ICO/DI, вы действительно положили бы какую-то тяжелую работу в метод расширения или просто на некоторые простые статические вещи, которые работают в функциональном стиле? Я склонен использовать их так, как вы бы в функциональном стиле программирования, ввод идет, результаты выходят без магии в середине, просто прямо в рамки классов, которые вы знаете, ребята из MS спроектировали и протестировали: P, на которые вы можете положиться на.
Если вы делаете тяжелую работу с использованием методов расширения, я бы снова посмотрел на ваш проект программы, посмотрел ваши проекты CRC, модели классов, примеры использования, диаграммы действий DFD или все, что вы хотели бы использовать, и выяснить, где этот дизайн вы планировали поместить этот материал в метод расширения вместо подходящего класса.
В конце дня вы можете тестировать только свой дизайн системы, а не код за пределами вашей области. Если вы собираетесь использовать классы расширения, мой совет будет состоять в том, чтобы вместо этого использовать модели компоновки объектов и использовать наследование только тогда, когда есть очень веская причина.
Композиция объектов всегда выигрывает со мной, когда они производят сплошной код. Вы можете подключить их, вынуть их и сделать с ними то, что вам нравится. Имейте в виду, что все это зависит от того, используете ли вы интерфейсы или нет как часть вашего дизайна. Кроме того, если вы используете классы Composition, дерево иерархии классов сглаживается в дискретные классы и меньше мест, где ваш метод расширения будет выбран через унаследованные классы.
Если вы должны использовать класс, который действует на другой класс, как в случае с методами расширения, сначала посмотрите на шаблон посетителя и определите, есть ли его лучший маршрут.