воскресенье, 8 июня 2014 г.

Is TDD Dead. Часть 5

В то время, как в Виларибо спорят жив ли TDD или мертв, в Вилабаджо думают, а стоит ли компилировать код перед коммитом.

На днях вышла заключительная часть видео-обсуждения о том, а жив ли TDD. Мнение о предыдущих выпусках я публиковал в Google+, но поскольку в этот раз мыслей получилось несколько больше, то решил, что формат блога будет более удачным.

Is TDD Dead. Part V on Youtube. В целом, получилось более затянуто чем обычно (ведь выступление длилось в двое дольше), но, как всегда, весьма любопытно.

Проблема обсуждения вопросов дизайна

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

Обсуждение вопросов дизайна является довольно сложным вопросом и у этого есть как минимум несколько причин. Во-первых, дизайн любой системы постоянно развивается, при этом результат зависит не только от способностей членов команды, но и от исторического контекста (злосчастное, «исторически сложилось»). Это значит, что мы не можем судить о существующем дизайне в вакууме, нам нужно еще и понимать, как мы к нему пришли и какие проблемы считали наиболее приоритетными. Во-вторых, не существует достаточно объективной меры качества дизайна, которая бы четко доказывала, что одно решение действительно лучше другого. В-третьих, при обсуждении вопросов дизайна автор статьи или книги сталкивается с вопросом выбора размера и сложности примера. С одной стороны, нет смысла рассматривать продвинутые техники проектирования на примитивных примерах, ведь примитивные задачи можно решить любым способом. С другой стороны, автор не может давать слишком сложные примеры, поскольку 90% читателей просто не захотят тратить свое время и с ними разбираться.

Это я все к тому, что не стоит ждать идеальной книге о дизайне лично для вас, просто это не тот вопрос, на который вы сможете найти простой ответ. Большинство вопросов дизайна привязаны к контексту и именно поэтому, практически на любой вопрос, типа «А что лучше …?» большинство специалистов используют любимую фразу Кента Бека: “It Depends”.

Дизайн – это же поиск наиболее эффективного компромиссного решения, «эффективность» которого всецело зависит от контекста и ограничений, в которых находится человек (или группа людей), принимающая решение. Именно поэтому люди с разным опытом часто не могут найти общий язык в вопросах дизайна, и именно в этом и заключается причина возникновения обсуждения #IsTTDDead. Мы очень часто обобщаем наш опыт, доказывая, что «TDD не имеет смысла, ведь оно применимо лишь в одном случае из 10!». Но это мнение основано на нашем опыте, который может сильно отличаться от опыта нашего оппонента, который работал над задачами, использование TDD в которых было очень удобным.

Вторая причина столь разных точек зрения на использования инструментов заключается в «спирали обучения». Если у вас больше 5 лет опыта, то вы наверняка замечали, что кривая обучения на самом деле выглядит не совсем так, как обычно принято представлять, а несколько иначе. Я бы сказал, что кривая обучения – это скорее бесконечный процесс, который в лучшем случае будет «сходиться» к точке «идеального использования инструмента»:

clip_image002

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

Разные люди находятся на разных точках «спирали обучения», что также усложняет взаимопонимание. К тому же, у разных людей точка «правильного» понимания (линия «правильного понимания» на графике справа) находится на разном уровне, что проявляется в том, что кто-то полностью отказывается от инструмента («TDD не нужен!», или «IoC не нужен!», «или ОО не нужно» и т.п.), а кто-то продолжает упорно использовать инструмент не по назначению.

Как научиться использовать инструмент правильно?

Дэвид, Мартин и Кент затронули еще одну интересную тему, которая может помочь пролить свет на причины неэффективного использования любого инструмента, включая TDD.

По мнению этих трех уважаемых камрадов, неэффективное и зачастую неверное использование инструментов заключается в непонимании главных целей инструмента. Чтобы правильно использовать TDD нужно не только и не столько вызубрить предложенный Кентом цикл “Red, Green, Refactor”, но и понять, какую именно проблему этот цикл призван решать.

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

Кент предложил этот цикл, поскольку ему лично очень комфортно работать в таком режиме, но какой режим максимально полезен вам – ни я, ни Кент Бек, ни Мартин Фаулер сказать не могут. Тоже самое касается и влияния тестов на дизайн: обдумывание дизайна класса через призму тестов и его открытого интерфейса очень хороший способ прийти к чистому дизайну и простому в использовании API, но никто не говорит, что это единственный способ добиться этого результата, или что этот результат вообще гарантирован!

Именно поэтому меня напрягают некоторые книги, которые не раскрывают контекста, а описывают лишь «что нужно делать», а не «почему нам это нужно делать, и когда». Почему считается, что для новичков именно такой подход является наиболее предпочтительным я не знаю, и тоже самое мнение выразил и Дэвид, говоря о вреде “tweet-size tips”. Мне кажется, что начиная обучение новому инструменту и методологии нужно сразу же давать их фундаментальные идеи и контекст использования, а не сосредотачиваться на частностях. Поскольку советы новичку из «Используй TDD всегда и будет счастье» или «TDD – это бред, никогда этого не делай» легко могут развить культ-карго или отбить охоту от инструмента навсегда.

Martin Fowler: “I don’t like dogmatic statements”

Мартин выразился еще более интересно: каждый раз при описании новой возможности, принципа или метода, у тебя должно быть четкое понимание, когда этим инструментом пользоваться не нужно. Если ты сам не можешь выступить адвокатом дьявола, то ты либо не объективен (“biased”), или сам не до конца разобрался в этой теме.

Semantic Diffusion

Чем шире распространяется технология или методология, тем более разнообразным становятся мнения по поводу того, чем же она является. Достаточно посмотреть на ailge, scrum, TDD, DDD и многое другое. (Это ведь даже к языкам программирования относится: Страуструп ведь явно не предполагал, что шаблоны в С++ могут применяться таким разнообразным образом). Если взять исходные труды по этим темам и сравнить с их «популярными современными» описаниями, то можно найти очень много различий.

Такое разнообразие мнений обусловлено эволюцией, что, в свою очередь приводит к понятию, которое Мартин Фаулер называет «размыванием смысла» (semantic diffusion). Такая «эволюция понятия» затрудняет процесс обучения, а также усложняет и без того не простой процесс оценки результата. Со временем становится чрезвычайно сложным понять, в чем причина успеха/провала использования инструмента на проекте: связан ли провал с непониманием или неверным пониманием того, что такое TDD или же TDD просто не применим в данном контексте?

Есть несколько причин, почему полезно знакомиться именно с оригинальными трудами. Во-первых, как правильно заметил Мейер в своей книге, исходные авторы обычно более адекватны:

The zealots of an idea are often more extreme than its creators - the phase "more royalist than the King" captures that phenomenon - and you will find that foundational agile texts, such as those by Beck, Larman or Cockburn, occupy a higher plane of discourse; in particular they avoid below-the-belt hits at other approaches.

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

Выводы

Is TDD Dead, оказалась замечательной серией выступлений. Она показала, что автор TDD (Кент Бек) очень прагматичный человек, который не считает TDD абсолютно полезным инструментом во всех случаях. Кент неоднократно пытался показать, как он подходит к процессу решения задачи и почему и когда TDD ему помогает, при этом он не отрицает, что есть случаи и люди, для которых этот подход будет далеко не таким эффективным.

Эти выступления показали, что инструменты не нужно мерять по шкале «использовать/не использовать»; нужно понимать, зачем оно тебе нужно и чего ты хочешь добиться, и прекращать пользоваться ими, если они перестают тебе помогать и начинает мешать.

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

З.Ы. Понравился пост? Поделись с друзьями! Вам не сложно, а мне приятно;)

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

  1. Поспамлю немного в комментариях:)

    Для меня TDD умер. Он всегда позволял мне:

    1) Быть уверенным, что написанный код работает.
    2) Совершенствовать код так, что изменение его реализации или функциональности не становится фактическим переписыванием заново с той же черепашьей скоростью и багами. Наоборот, путь от приемлемого к лучшему сокращается до одного шага.
    3) Гордиться в конце дня сделанной на совесть работой.

    Так было до тех пор, пока я однажды не попробовал доказать превосходство TDD на примере простой задачи - реализации двоичного поиска. Говорят, что 90% затрудняется написать корректную реализацию (см. http://habrahabr.ru/post/146228/), я же стремился показать, как с помощью TDD каждый способен быстро реализовать бинарный поиск.
    В результате:

    1) Атрибутов TestCase становилось все больше, затем появились TestCaseSource. С каждым новым тестом уверенности в правильности реализации убавлялось, ведь где гарантии, что имея 100 тестов не потребуется еще один?
    2) Обилие тестов открывало пугающую сложность такой простой задачи.

    И тут случилось то, после чего моя вера в TDD не смогла оправиться.

    Требование: алгоритм должен находить всегда первый элемент, а не произвольный.

    Фрустрация.

    Я вспомнил институт, когда нам объясняли работу логических операций AND и OR. Были таблицы истинности, но мы все-таки мыслили описаниями вида "оба аргумента должны быть истинными", которые, кажется, являются микро-алгоритмами.
    Как были похожи эти таблицы на мое размышление о коде!
    Так вот моя ошибка состояла в том, что наверно нельзя представлять код, как исключительно набор входов и выходов. Веруя в TDD, я, кажется, исключительно этим и занимался. Стройные елочки TestСase(..., Result = ...) даже визуально напоминали таблицу.

    Оказалось (уже без помощи тестов), что если мы будем выбирать правый отрезок исключительно при условии >, то в итоге всегда будем приходить к первому элементу - это как раз то, что нужно и работает почти естественно.
    Помогли ручка и бумага.

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

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

    Понимание, и только потом тест - вот, пожалуй, новый рецепт для себя.

    Почти уверен, что об этом писал и сам Бек в главах о проектировании и обдумывании до написания тестов.

    Так, что наверно умерло ложное понимание TDD, слепая и наивная вера в то, что напиши я сначала атрибут Test, и любая сложная задача решится сама собой, без необходимости глубокого размышления над ней. То, что такое TDD умерло прекрасно!
    Да здравствует TDD истинное!:)

    ОтветитьУдалить
    Ответы
    1. Alex, спасибо за развернутый комментарий.

      Маленькое наблюдение: демонстрировать силу ООП, TDD, BDD, DDD или нечто подобное на задачах, типа двоичного поиска - это epic fail:)

      Как правильно говорил Мейер (как и многие другие), ООП рулит на уровне модулей, но ничем не помогает на уровне деталей реализации, там ФП рулит. Так и здесь, TDD не поможет в решении алгоритмических задач, TDD будет рулить в вопросах дизайна, но никак не в вопросах реализации.

      Удалить
  2. Пока серии видео не смотрел. Но статья понравилась, обязательно посмотрю. Все относительно новомодные течения в разработке ПО, как мне кажется продвигаются разного рода спекулянтами, которые хотят представить себя как "супер-пупер" экспертов и заработать на этом денег. Скрам, канбан, аджаил, TDD и так далее.
    Я не считаю что TDD умер, просто я использую его несколько иначе, сначала бизнес функционал, потом уже обвес тестами по необходимости. Чем "core"-овее функционал тем больше он покрывается тестами. Более того создается инфраструктура, для фиксирования бага: нашли баг, написали тест (он красный), пофиксили багу, тест позеленел. Второй раз эта бага в продакшине уже не стрельнет.
    Никогда не писал сначала тест, потом код. Как мне кажется в общем случае это слишком дорого, хотя наверное есть области где это вполне применимо.

    ОтветитьУдалить
  3. Не мог не отписаться :) TDD реально помогает мне в задачах, где вариантов может быть много и для того чтобы при расширении не сломать что-то старое очень удобно начать именно в таком формате. То есть существует ядро, нужно что-то что использует ядро, хорошо бы знать что я ожидаю от этого, предусмотреть объективные входные данные и сопоставить с ожидаемыми выходными, таким образом вероятность попасть в поле ошибок вне скоупа новой функции сводится к нулю. В случаях когда штампуют тесты на каждую функцию - это расточительство.

    ОтветитьУдалить
    Ответы
    1. Владимир, спасибо за комментарий.
      Но я в нем не увидел, чем же помогает TDD:). Я вижу здесь преимущества юнит-тестов, но не вижу описания преимуществ цикла Green/Red/Refactor.

      Я, например, сам являюсь сторонником юнит-тестов, мне понятна польза от использования небольших шагов в решении. Но вот выгоды от классического TDD я, например, вижу существенно меньше.

      Удалить