понедельник, 31 марта 2014 г.

О книге Джошуа Кериевски «Рефакторинг с использованием шаблонов»

Если посмотреть на опыт использования паттернов проектирования, то он разнится от «это самая ценная штука в арсенале разработчика», до «любителям паттернов проектирования нужен особое место в аду, в котором их ждет синглтонная фабрика со стратегией кипячения в абстрактном котле систем с унаследованным кодом». Если говорить проще, то паттерны – это такой инструмент, который в умелых руках будет помогать, а в неумелых – серьезно осложнит сопровождение полученной системы. (Что, на самом деле, характерно для любого инструмента, а не только для паттернов проектирования, см. Культ Карго в программировании.)

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

Типичный троллинг паттернов головного мозга (оригинал) – Hello, World, Patterns!

public static void Main(String[] args)
{
   
MessageBody mb = new MessageBody
();
    mb
.Configure("Hello World!"
);
   
AbstractStrategyFactory asf = DefaultFactory.
Instance;
   
MessageStrategy strategy = asf.
CreateStrategy(mb);
    mb
.Send(strategy);
}

Ок, паттерны, как и любой другой инструмент легко использовать неправильно. Но при чем здесь рефакторинг? Ответ прост: паттерны и рефакторинг имеют отношение к дизайну, и оба эти понятия весьма популярны. Не удивительно, что в мире появился человек, одолевший Design Patterns «банды четырех» и Рефакторинг Фаулера и решил совместить эти две штуки в одной книге.

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

К моменту написания книги понятие “Emergent Design” еще не вошло в моду (хотя не уверен, насколько оно модно сейчас), но именно эта идея – последовательное улучшение дизайна, конечной целью которого являются конкретные паттерны проектирования – заложена в основу книги Джошуа Кериевски «Рефакторинг с использованием шаблонов».

clip_image002

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

Что понравилось?

ЦИТАТА
К сожалению, когда программисты смотрят на единственную диаграмму, сопровождающую каждый шаблон в книге Design Patterns, они часто приходят к выводу, что приведенная диаграмма и есть способ реализации шаблона. Они бы гораздо лучше разобрались в ситуации, если бы внимательно прочитали самое интересное - примечание к реализации. Многие программисты берут в руки книгу Design Patterns, всматриваются в структурную диаграмму шаблона и начинают кодировать. Полученный код в точности отражает диаграмму, а не реализацию шаблона, наиболее полно соответствующую решаемой задаче.

Прагматичный взгляд на дизайн и рефакторинг.

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

Полезные и поучительные примеры

Некоторые рефакторинги очень полезны, хотя и не всегда приводят к каким-либо паттернам. Compose Method, Introduce Polymorphic Creation with Factory Method, Inline Singleton, Encapsulate Classes with Factory и некоторые другие. Подобные примеры помогут понять назначение соответствующих паттернов проектирования и глубже понять контекст их применения.

Таких полезных примеров не мало, но их нужно выискивать за обилием «императивных шагов» описания рефакторинга, читать которые весьма утомительно. К сожалению, на этом положительные моменты книги заканчиваются и начинаются отрицательные.

Что не понравилось?

Формат описания

Описание любого рефакторинга содержит следующие разделы: диаграмма классов, Мотивация, Механика, Пример. При этом легко себе представить, как выглядят два последних раздела, когда автор пытается детально описать и показать шаги рефакторинга в книге. Это обилие деталей, описывающих шаг за шагом процесс преобразования кода от исходного варианта к целевому.

Есть некоторые вещи, которые проще один раз увидеть, чем десять раз прочитать. И в этом плане, рефакторинг значительно эффективнее показывать с помощью скринкаста, а не в виде текста. Читать такое количество букаф утомительно, особенно когда речь заходит о каких-то тривиальных рефакторингах, таких как замена конструктора фабричным методом.

Отсутствие обобщения

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

Такая проблема очень актуальна для этой книги. Автор описывает пример рефакторинга своих приложений, при этом в десятке случаев показан рефакторинг библиотеки парсинга HTML. Да, это хороший пример, но многим читателям будет очень сложно после прочтения книги понять, а когда же в их приложении нужно применять Limit Instantiation with Singleton или Move Embellishment to Decorator.

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

Наивность примеров

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

Вот например, кто в одном классе делает фасад над несколькими версиями библиотек одновременно? Ведь в большинстве случаев это технически невероятно сложно, так зачем же выносить такой пример в качестве одного из рефакторингов? (см. Extract Adapter).

Есть еще пара неудачных примеров: Replace Type Code with Class, Inline Singleton и Chain Constructors. Они либо привязаны к конкретной проблеме (Inline Singleton показан в контексте паттерна Состояние, в котором избавиться от синглтона легко, но это не так просто сделать в большинстве других случаев), или же к конкретной языковой возможности (Chain Constuctors решает конкретные проблемы языка Java).

Однотипность примеров

«Сквозной пример» - это очень полезный инструмент с педагогической точки зрения, когда одна и та же задача решается и развивается на протяжении всей книги. При его наличии не нужно каждый раз объяснять читателю контекст задачи. У автора не один, а два основных примера – библиотека работы с XML и библиотека разбора HTML, каждый из которых полезен, но далек от идеала. Проблема в том, что оба примера представляют собой библиотечные решения, причем достаточно специфичные, что опять-таки усложняет адаптацию рассмотренных техник для своих решаемых задач.

Тот же паттерн Composite отлично по подходит для унификации работы с XML, но довольно сложно применим в другом «бизнес-контексте».

Обилие ссылок на Рефакторинг Фаулера

Автор сам пишет, что у читателя должна под рукой должна лежать книга Фаулера Рефакторинг и обилие ссылок на нее действительно делает данную книгу не слишком автономной.

В результате

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

Идея у книги хорошая, но реализация, ИМХО, подкачала. Главная проблема в формате: рефакторинг к шаблонам проектирования подразумевает большее влияние на дизайн приложения, по сравнению с большинством типовых рефакторингов Фаулера. А значит тут полезен и другой формат: вместо утомительного описания шагов изменения кода я бы предпочел уделить большее внимание влиянию на дизайн и просто увеличить количество примеров. Пусть они были бы не столь подробными, но зато их разнообразие сделало бы их более применимыми на практике.

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

Оценка: 2/5

З.Ы. Перевод как обычно, читать можно, но приходится постоянно reverse engineer-ить его в оригинал, чтобы понять, что же хочет здесь сказать автор.

Дополнительные ссылки

8 комментариев:

  1. > Перевод как обычно, читать можно, но приходится постоянно reverse engineer-ить его в оригинал
    Да, это сейчас просто беда какая-то всеобщая..

    ОтветитьУдалить
  2. Спасибо, за обзор.
    А подскажи пожалуйста какую-нибудь аналогичную книгу. Просто как раз на днях пришел к мысли, что хочется почитать что-то в духе "практические примеры использования патернов от GoF для рефакторинга" и тут твоя заметка. :)
    Заранее спасибо.
    ЗЫ: язык книги, конечно, не суть важен.

    ОтветитьУдалить
    Ответы
    1. Я не знаю другой подобной книги, которая скрещивает паттерны и рефаторинг. Вполне возможно, это и правильно. Все же рефакторинг к паттернам - это не совсем честное понятие. Ведь рефакторинг все же не подразумевает столь серьезное изменение и развитие дизайна, которое требуется для выделения паттернов проектирования. Ну да ладно.

      Из книг по дизайну и именно по эволюции дизайна я очень бы посоветовал книгу "Growing Object-Oriented Software Guided by Tests". Я уже постил кучу цитат в свой Г+, книга очень хороша. Там нет рефакторинга и не так много паттернов, но вот дизайна и хороших размышлений там очень много.

      Удалить
  3. Сергей, спасибо за пост.

    Вообще, очень часто слышу что паттерны, это костыли порождаемые ООП. Да, конечно это чаще слышу от приверженцев функционального подхода. Хотя сам считаю, что паттерн, это не костыль, а верное применение ООП. Что-то вроде того, что костыль это решение, которое выходит за рамки заложенной архитектуры. В рамках хорошей архитектуры эта проблема должна решаться в "терминах" имеющейся архитектуры. Что думаете по этому поводу? Паттерн скорее костыль или все такие решение?

    ОтветитьУдалить
    Ответы
    1. Вадим,
      Я не согласен с мнением, что паттерны - это костыли, порождаемые ООП. Хотя я сам очень уважаю оба лагеря - ООП и ФП, но я совершенно другого мнения.
      Да, некоторые паттерны не актуальны для некоторых языков программирования, поскольку поддерживаются такими языками из коробки. Так, стратегии или команды обычно выражаются в виде функции, но в некоторых случаях вполне могут быть завернуты во что-то более выразительное, типа записи.
      Паттерн - это типичное решение типичной задачи, в некоторых случаях языки пошли далеко вперед и решают эти задачи из коробки. Но это справедливо лишь к некоторым паттернам, описанным бандой четырех. Сама же идея паттернов существенно более обширная. Есть классические паттерны проектирования, а есть паттерны проектирования, специфичные для конкретного языка или платформы (например, Dispose Pattern). Есть архитектурные паттерны, а есть специфические паттерны проектирования, типа DDD (это тоже паттерны). Есть паттерны рефакторинга, а есть и паттерны поведения.

      Так что паттерн в моем понимании - это не костыль, а именно шаблон или подход к решению определенной проблемы в определенным контексте. И мне сложно представить, как архитектура может решить проблему, решаемую декоратором. Я понимаю, как паттерны могут сделать расширяемый фреймворк или расширяемую архитектуру, но как может архитектура решать эти проблемы сама, я не знаю:))

      Удалить
    2. Сергей, спасибо за ответ. Я всецело поддерживаю Вашу точку зрения.

      Пример с костылем, это небольшая аналогия, которая немного должна была помочь выразить описываемую проблему. Надеюсь что Вы правильно поняли этот момент.

      Но я хочу еще немного углубится. Предположим что у нас появилось требование, которое не решается в рамках тех терминах (используемых решений, паттернов, подходов) архитектуры, которые у нас есть и о которых мы думали. Для решения этой проблемы мы начинаем использовать подход (паттерн или решение), который отличается от всех остальных подходов и решений. Прямо как бельмо в глазу. :) Как Вы считаете, это ошибка спроектированной архитектуры и вынужденный костыль или это нормально?

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

      Поймите меня правильно, я уже не о паттернах, а именно о конкретных вещах. Просто я хочу понимать, где ошибка, а где не ошибка. Как это определить? :)

      Вот Вы являетесь противников повсеместного использования IoC. Я наконец то начал понимать, что Вы абсолютно правы. Пока IoC используется ради использования, от него только вред. Но когда IoC начинает решать какие то задачи (аля использование IoC в Asp.Net MVC для инжектирования в контроллеры), от него появляется польза (хотя не исключаю что и тут Вы не согласитесь с этим). Вот я и хочу понять такие общие границы хорошего и плохого.

      Заранее спасибо.

      Удалить
    3. >Предположим что у нас появилось требование, которое не решается в рамках тех терминах (используемых решений, паттернов, подходов) архитектуры, которые у нас есть и о которых мы думали. Для решения этой проблемы мы начинаем использовать подход (паттерн или решение), который отличается от всех остальных подходов и решений. Прямо как бельмо в глазу. :) Как Вы считаете, это ошибка спроектированной архитектуры и вынужденный костыль или это нормально?

      Во-первых, я не вижу, как эта проблема связана с паттернами. У нас есть система, в рамках которой мы вынуждены принимать решение, которое выделяется из общей картины. Да, это не хорошо, поскольку любое неуклюжее решение усложняет сопровождаемость. Насколько это плохо? It depends! Я всегда задаю себе и своим коллегам такой вопрос: насколько далеко этот "костыль" пускает корни в систему? Если это костыль локальный, то это один разговор, если же он оказывает серьезное влияние на другие компоненты, то это совсем другое дело.

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

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

      Поэтому граница между хорошим и плохим очень нечеткая и сильно зависит от задачи и конкретного человека:)

      Удалить
  4. Сергей, спасибо.

    Да, к паттернам это уже не имело никакого отношения. :)

    ОтветитьУдалить