15

Все,Удлинительные методы Скрыть зависимости?

Хотелось бы высказать несколько соображений по этому вопросу. В последнее время я становлюсь все более и более подписчиком «пуристских» принципов ДИ/МОК при проектировании/разработке. Часть этого (большая часть) связана с тем, что между моими классами существует небольшая связь и что их зависимости разрешаются с помощью конструктора (есть, конечно, другие способы управления этим, но вы получаете идею).

Моя основная предпосылка заключается в том, что методы расширения нарушают принципы 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? Если вы являетесь подписчиком методологии МОК, избегайте использования методов расширения?

+0

Ваш метод расширения не проверяет нулевой ввод! – canon

+0

@antisanity: AFAIK логически невозможно, чтобы параметр экземпляра метода расширения (* input * в этом случае) был пустым. –

+3

Да, Фил ... он может быть нулевым. – canon

ответ

15

Я вижу, откуда вы родом, если вы пытаетесь издеваться над функциональностью метода расширения, я считаю, что вы используете их неправильно. Методы расширения должны использоваться для выполнения задачи, которая будет просто неудобно синтаксически без них. Хорошим примером является ваш TruncateToLength.

Тестирование TruncateToLength не предполагает издеваться над ним, это просто связано с созданием нескольких строк и проверкой того, что метод действительно вернул правильное значение.

С другой стороны, если у вас есть код в вашем слое данных, содержащийся в методах расширения, который обращается к вашему хранилищу данных, то да, у вас есть проблема, и тестирование станет проблемой.

Я обычно использую только методы расширения, чтобы обеспечить синтаксический сахар для небольших простых операций.

18

Я думаю, что все в порядке - потому что это не так, как TruncateToSize - это реально сменный компонент. Это метод, который только когда-нибудь понадобится сделать.

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

Другими словами: если TruncateToSize были подлинным членом String, вы бы даже подумали об использовании его? Вы пытаетесь издеваться над целочисленной арифметикой, представляя IInt32Adder и т. Д.? Конечно нет. Это то же самое, только то, что вы осуществляете реализацию. Unit test the heck из TruncateToSize и не беспокойтесь об этом.

+0

Такая же идея, как и мое сообщение, согласился – LorenVS

+0

Итак, я не согласен с вашим ответом - это очень прагматично. Вы можете определенно взять МОК (или любую конструкторскую теорию) слишком далеко. Для аргументации: если вы привержены IOC, у вас не будет проблем со вторым примером реализации (явная зависимость от статического класса)? Я никогда не читал статью хардкор-защитника МОК, которая заявила, что статические зависимости приемлемы. –

+0

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

0

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

  1. Да, лом расширения его ЛАВАШ издеваться из
  2. Используйте расширение и просто проверить, что он правильно сделал. т.е. передавать данные в усечение и проверять, что он усечен
  3. Если это не какая-то банальная вещь, и я должен издеваться над этим, я сделаю, чтобы мой класс расширения имел сеттер для используемой им службы и установил это в тесте код.

т.е.

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, чтобы избежать использования сеттера в производстве.

0

Методы расширения, частичные классы и динамические объекты. Мне они очень нравятся, но вы должны осторожно протестовать, здесь есть монстры.

Я бы взглянул на динамические языки и посмотрел, как они справляются с такими проблемами на повседневной основе, и в самом деле они просвещают. Особенно, когда им нечего им мешать делать глупые вещи, кроме хорошего дизайна и дисциплины. Во время выполнения все динамично, единственное, что их остановить - это компьютер, бросающий большую ошибку времени выполнения. «Duck Typing» - самая сумасшедшая вещь, которую я когда-либо видел, хороший код - это хороший дизайн программы, уважение к другим в вашей команде и доверие к тому, что каждый член, хотя и обладает способностью делать какие-то нелепые вещи, предпочитают не потому, что хорошо дизайн приводит к лучшим результатам.

Что касается вашего тестового сценария с макетными объектами/ICO/DI, вы действительно положите какую-то тяжелую работу в метод расширения или просто на простые статические вещи, которые работают в функциональном стиле? Я склонен использовать их так, как вы бы в функциональном стиле программирования, ввод идет, результаты выходят без магии в середине, просто прямо в рамки классов, которые вы знаете, ребята из MS спроектировали и протестировали: P, на которые вы можете положиться на.

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

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

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

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