the mine universe

Показаны сообщения с ярлыком Design. Показать все сообщения
Показаны сообщения с ярлыком Design. Показать все сообщения

вторник, 4 августа 2009 г.

Будьте очень осторожны! Unity application block

Почти сразу после публикации своей заметки о сложностях с Unity, мне попался пост Ивана (Merle) об func – такая другая реализация IoC и DI концепции с поинтом на производительность и совместимость с Compact Framework. В нем – теже лямбды в регистрации. Помимо сложностей с добавлением новых реализаций «Lifetime Manager», позволю себе обратить внимание на менее очевидные трудности в Unity.

Не буду писать про недоразумение с RegisterInstance и PerThreadLifetimeManager. Очень кратко опишу концептуальные джоки (jokes), которые меня вводят в ступор после тсчательного знакомства с Unity:

  • Named Resolving - это самый верный способ запутать себя, коллег, и пользователей вашей библиотеки. Почему? Вспомним что такое Unity прежде всего - IoC и DI. Метод Resolve, во-первых, возвращает экземпляр определенного поведения. Этот экземпляр, прежде всего, исполняет некую роль в приложении, в системе, в библиотеке. Не побоюсь сделать на этом акцент - два вызова Resolve с одинаковыми аргументами должны возвращать две ссылки на экземпляры с одинаковым поведением. А что значит в таком свете Named Instances - это значит, что Resolve возвращает экземпляр как бы другого поведения!!! Т.е. Resolve<IService>("foo") и Resolve<IService>("bar") возвращают экземпляры IService разного поведения. Пока все хорошо? Ничуть! Если вы следите за моей мыслью, у вас должен возникнуть вопрос – «С какого перепуга параметризация поведения должна описываться единственной строкой?» Ни-с-ка-ко-го!!! Очень редко поведение можно параметризировать (ну и слово) строкой. Для пользователя вашей системы или библиотеки будет понятней, когда параметры будут явно описаны где-то в интерфейсе IService - будет видно и от каких именно параметров, и что именно зависит от параметров.

  • Injection на рефлексии. Ctrl-Shift-G в студии не покажет buildup-зависимости, спрятанные с помощью Unity. Unity придется подождать, пока Re# и VS научатся разглядывать Unity.

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

пятница, 10 июля 2009 г.

Будьте осторожны: Unity Application Block

Столько шума и публикаций вокруг Unity не оставили меня равнодушным. Мне лично захотелось поближе познакомиться с Unity, когда я узнал что в Unity 1.2 появился Per Thread Storage Lifetime Manager. Подумать только! Для каждого потока возвращается отдельный экземпляр в методе Resolve. Не так давно коллега прислал ссылку на Unity без коментариев – дескать в продукте надобы использовать его вместо Dictionary<Type, object>, который выполняет роль контейнера зарегистрированных сервисов. Пришлось познакомиться с Unity поближе. И мое терпение лопнуло. Из моего поста вы узнаете, что Unity – это плохой, неудачный дизайн, его сложно использовать в реальных ситуациях, и сам Application Block “плохо пахнет”.

Для начала – почему Unity это over-design. Ответ - анонимный делегат. Да! Проще всего сконфигурировать контейнер и инвертировать зависимости с помощью анониного делегата в методе Register. Сразу отпадает необходимость в развесистой и неудобной иаерархии с красивым названием “Lifetime Managers”. А вместе с ней "в топку" отправим и другую избушку на курьих ножках (e.g. кривая, неудобная реализация) вокруг концепции "Injection of Contructor, Properties и Initializers".

Transient Lifetime Manager

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        return new SampleClass();
    });

Singleton (aka RegisterInstance)

SampleClass instance = new SampleClass();
ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        return instance;
    });

Injection of Contructors, Properties and Initialisers

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        SampleClass ret = new SampleClass(12, "Bye-bye, Unity!");
        ret.MyProperty = new object();
        ret.MyStringProperty = "Some Text";
        ret.InitializeMe(42.0m, container.Resolve<ILogger>());
        return ret;
    });

Для всех кто не читает справку приведу пример из справки Unity. Почувствуйте разницу:

IUnityContainer myContainer = new UnityContainer();
myContainer.Configure<InjectedMembers>()
  .ConfigureInjectionFor<MyObject>(
    new InjectionConstructor(12, "Hello Unity!"),
    new InjectionProperty("MyProperty"),
    new InjectionProperty("MyStringProperty", "SomeText"),
    new InjectionMethod("InitializeMe", 42.0, 
            new ResolvedParameter(typeof(ILogger), "SpecialLogger"))
  );

PerThreadLifetimeManager

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        const string key = "Sample Interface Implementation";
        LocalDataStoreSlot slot = Thread.GetNamedDataSlot(key);
        SampleClass ret = (SampleClass)Thread.GetData(slot);
        if (ret == null)
        {
            ret = new SampleClass();
            Thread.SetData(slot, ret);
        }
        return ret;
    });

TimeLimitedLifetimeManager

И в заключении – немного о том почему Unity сложен в использовании. Здесь я приведу очень древний пример реализации конецепции Lifetime Manager. Реализация древняя настолько, насколько древний сам .Net. Я пользуюсь реализацией ниже со времен появления .Net в 2002-м году. Для тех, кто не знает, напомню – Unity вышел в 2008 году. Сама концепция описывается одним простым предложением – Объекты “живут” в контейнере строго ограниченное время. Т.е. по истечении определенного времени объект из контейнера освобождается. Итак – сама реализация:

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        const string key = "Sample Interface Implementation";
        SampleClass ret = (SampleClass)HttpRuntime.Cache.Get(key);
        if (ret == null)
        {
            ret = new SampleClass();
            HttpRuntime.Cache.Insert(
                key,
                ret,
                null,
                DateTime.Now.AddMinutes(60),
                Cache.NoSlidingExpiration);
        }
        return ret;
    });

Дополнение: Lazy Singleton Lifetime Manager

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

 ConfigurationContainer container = new ConfigurationContainer();
 container.Register<ISampleInterface>(
     delegate
     {
         return Lazy.Get(
             "Sample Interface Implementation",
             () => new SampleClass(container.Resolve<IFoo>(),"2");
             );
     });

Продолжение

вторник, 28 апреля 2009 г.

Никогда не планируйте Unit-тестов

Question: Do I need a Unit test or Functional test?
Answer: Yes
(источник)

Без юнит-тестов никак не обойтись и в этом заголовке шутка. На мой пост меня натолкнула заметка Сергея о Как тестировать приватные функции. У него можно найти бесспорный ответ на вопрос как: "Только через публичный контракт этого класса"

Можно ли обойтись без юнит-тестов? Чаще можно ответить да чем нет. Что бы понять ответ достаточно определить о каком коде идет речь:

  • Или бизнес-код
  • Или инфрастуктура (framework)

Бизнес-код,
прикладной код

Библиотека,
инфрастуктура

Количество зависимого кода

Определено и ограничено

Неопределено и недоступно

Доступность зависимого кода

Весь код в одном VS-решение

Недоступен

Окружение

Известное и ограниченное - определенный продукт или inhouse-система

Неограниченное, неопределенное множество. Т.е. зависимый код будет когда-то кем-то написан

Тестирование

функциональные тесты с помощью инженера по качеству

Юнит-тесты

Источник знаний о коде

Зависимый код

Юнит-тесты

Текучесть требований

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

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

Верификация результатов рефакторинга.

Компиляция кода, регресионные тесты инженером по качеству.

Регрессионные юнит-тесты

Ну таблица, и что дальше? А дальше - цитата. Brad Abram в одном из своих постов об обновлении Framework Design Guidelines цитирует Фила, разработчика ASP.NET MVC: "Well-Designed Frameworks Are Testable." Мне так и не удалось найти ответ почему?, без которого любой пост становится похож на догму. Но ведь Фил прав! Попробую ответить за Бреда, почему Ваш framework становится хорошим, когда он легко тестируется (unit-тестами):

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

В защиту заголовка:

  • Не бывает вопроса как протестировать код
  • Не бывает вопроса нужно ли тестировать этот код?
  • Не бывает вопроса зачем тестировать код?
На все вопросы ответ известен раньше чем будет написана первая строчка кода - см. выше

Ярлыки (Tags)