За счет чего TDD “драйвит” разработку

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

Поэтому я не хотел писать еще одну статью с описанием техники Red-Green-Refactor. Мне хотелось взглянуть на TDD немного глубже и описать, как и почему TDD влияет на поведение человека.

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

46e939aaa921e91ed0127f08346183b5

Мои первые шаги в TDD

Я работаю web-разработчиком 12 лет. Первые 10 я выполнял задачи на php для CMS-систем Joomla и Bitrix. Как я сейчас вижу, до развития качества и практик чистого кода не особо то и доходило. Но мне удалось вырваться из западни работы с CMS и последние 2 года мой стек в основном это javascript (React).
За все эти года у меня никогда не было ни ментора, чтобы направить или объяснить, ни кумира, чтобы копировать поведение, стремясь стать лучше, не понимая, как это сделать. Немного обидно и, для кого-то, удивительно, но я познакомился с TDD совсем недавно, хотя многие из статей, которые я читаю, датируются 2013 годом. Честно говоря, я не понимаю, как это возможно, столько лет старательно работать и так много не знать о своей же профессии, но это факт.
И в итоге, моим ментором за последний год стал Скрамгайд, который, кроме описания процессов, делает еще и акцент на техническом качестве продукта. Большую часть базового понимания о стандартах разработки, в том числе о TDD, я получил из подготовки к сертификации Professional Scrum Developer на scrum.org.

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

В какой-то момент я оказался один на один с целью прочитать книгу «Test Driven Development: By Example» от Kent Beck. В тот момент у меня было некое понимание, что такое TDD, и оно преимущественно совпадало с коллегами, которые также что-то слышали о нем, но толком не пробовали. В двух словах, я думал, что «TDD — это те же самые юнит тесты, только написанные до имплементации». Звучит немного отпугивающим и сложным, но мне понравилась идея. И я начал читать…

В районе 50-ой страницы ко мне пришло озарение, насколько ложным и неправильным было мое прежнее понимание. Тесты, написанные при TDD, — это другие тесты, категорически и совершенно другие тесты… по их логике, по их коду, по их смыслу. Если вкратце, то такой тест не должен соответствовать и проверять требование задачи, его цель — проверить только следующий маленький шаг, которые разработчик собрался реализовать в ближайших строках кода в следующие 2–5–15 минут. Пример, как это может выглядит — Example of TDD by H. Koehnemann, и обратите внимание, что acceptance test пишется уже в самом конце.

Но это не все. Я осознал, что TDD базируется на тех же психологических принципах и лайфхаках, которые я уже использую в своей работе и своей жизни. И я начал об этом говорить с коллегами, а позже родилась мысль написать об статью о механиках, которые лежат под капотом TDD и объясняют, почему TDD стимулирует (драйвит) разработку кода.

И вот они:

Верхнеуровневый список задач (todo list)

Есть кое-что, что постоянно упускается из вида при обсуждении TDD. Это список шагов/подзадач. Физический список. Любая пришедшая в голову в процессе разработки идея, если она не может быть легко и быстро реализована прямо сейчас, не нарушая текущий ход мышления, обязана быть внесена в этот список.

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

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

Внезапно появилась новая гениальная идея? Не переключайтесь на неё, отправьте её в список. Потом к ней вернётесь.

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

Test-First Thinking

Test-first мышление — это уже нечто большее чем техника — это сдвиг в видении задач и подхода к их решению. Обычно, перед началом имплементации, разработчик задается вопросом «как я реализую эту функцию?». Основная идея test-first подхода в том, что такой вопрос смещает фокус с задачи на имплементацию этой задачи. Это смещение может привести к выстраиванию «воздушных замков», излишней преждевременной оптимизации, нарушения принципа о простоте из Agile манифеста, не говоря о конкретных YAGNI и KISS правилах разработки. Но даже если этого не произойдет и код не будет нарушать эти принципы, это все равно не ответит на вопрос «как я узнаю, что я действительно достиг своей цели?».

Измерение достижения цели — это то, что делает цель целью. Без измерения это уже не цель, это только желание, неформализованная хотелка. Бывало ли у вас, что вроде бы все шаги сделаны, а понимания, что желаемое получено, — нет, и удовольствие от достижения цели отсутствует? Это происходит тогда, когда не были зафиксированы критерии достижения цели. Когда в процессе деятельности понимание цели видоизменилось, желание рассеялось, и возможно, цель вообще прошла мимо первоначальной её постановки. Это расстраивает, демотивирует. Потому что человеку крайне необходимо созерцать результаты своей работы, которые приводят к выбросу эндорфинов и мотивируют двигаться дальше (Что создаёт нам хорошие ощущения от работы).

И это именно то, что означает литера M в аббревиатуре S.M. A.R.T. постановке целей.

Но есть путь, который позволит избежать этой ловушки — Test-First Thinking. Не задавайтесь вопросом об имплементации. Спросите себя «Как я смогу кому-то продемонстрировать выполненную задачу?», «Как я могу протестировать, что все выполнено правильно?», «Как я узнаю тот момент, когда работа сделана?». Вопросы такого типа провоцируют дополнительные мыслительные цепочки, которые позволят схватить нюансы, которые обычно теряются при мыслях только о реализации. Это поможет отделить зерна от плевел и более четко определить, что на самом деле нужно, а что сейчас избыточно. Это сместит фокус с написания кода на достижение результата, что в конечном счете и приводит чувству удовлетворения.

Понятная задача

Если перед человеком стоит задача Важная и задача Срочная, какую он начнет делать?
Какую обычно выбираете вы?

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

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

Но что же с этим делать? И опять это возвращает нас к GTD, которое гласит, что нужно определить ближайший шаг, приближающий к достижению цели, и выписать его в правильной формулировке. После этого даже не нужно прилагать силу воли, мозг сам переключится на такую задачу, начнёт о ней думать, а человек — делать.

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

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

Правильное наименование

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

1. Тест должен звучать как ответ на вопрос «Что делает» в полной формулировке, как будто задача уже сделана и является спецификацией для другого человека. Ведь если отвлечься, то мозг выпадет из контекста и понять, что надо сделать, уже тяжелее;

2. Название теста должно начинаться с глагола.

Пример получше:

describe(‘Функция factorial’, () => {
  it(‘возвращает 0 при отрицательном входном параметре’, () => {
    …
  })
})

Пример похуже:

describe(‘factorial’, () => {
  it(‘проверка на 0’, () => {
    …
  })
})

Эти правила опять же из описания GTD. Конкретно я об это почерпнул из Джедайских техник М. Дорофеева (Глава 3).

Прерывания

Сейчас (и уже давно) принято считать и громогласно говорить о высокой стоимости прерывания разработчика от рабочего процесса. Обычно речь идет о воздействии менеджеров на мыслительный процесс программиста. Например, THIS IS WHY YOU SHOULDN«T INTERRUPT A PROGRAMMER и The Cost of Interruption for Software Developers.

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

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

Отсюда вытекает очень интересный и эффективный трюк — завершайте работу на красном тесте (вечером, на обед, перед встречей). Каждый раз, возвращаясь к работе, это позволит практически моментально вернуться в контекст задачи одной командой.

Научение через обратную связь

Человек не может учиться эффективно без получения обратной связи на его действия. Это базовый психологический момент природы человека в принципе (Как стать лучшим в своем деле? — А. Курпатов) и это же наиболее эффективный способ обучения.

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

Тест coverage

Представьте, разработчик дописывает последние строки кода имплементации, и … все. Работа сделана. Ему не надо заставлять себя писать тесты на то, что по его твердому убеждение безоговорочно работает. Потому что все тесты уже написаны. Это сохраняет ресурс силы воли, который достаточно ограничен и применение которого потребляет еще и приличное количество ментальной энергии.

Более того, эти тесты, написанные по ходу создания имплементации поставленной задачи, служили помощью в написании кода. Это явно должно увеличить тестовое покрытие проекта полезными тестами.

Рефакторинг

Я не буду уделять много внимания на вполне понятный профит для рефакторинга при наличии качественных автотестов (Начинаем писать тесты (правильно) — Кирилл Мокевнин [Хекслет]). Это действительно сильно уменьшает дискомфорт и страх и позволяет разработчику более удобно и легко перелопачивать уже написанный код. Но про это говорится почти всегда, когда речь заходит про TDD, и, честно говоря, в контексте рефакторинга я не вижу большой разницы между TDD и тестами, написанными после имплементации.

Дисциплинированный разработчик

На мой субъективный взгляд, самая ключевая ценность TDD в том, что при его использовании разработчик неосознанно использует классические приемы самоуправления применительно к процессу написания кода. А это, определенно, требует дисциплины. Конечно, любой может быть организованным в работе, вне зависимости от использования TDD. Но тот, кто использует TDD в правильной интерпретации, автоматически будет организованным и дисциплинированным хотя бы применительно к написанию кода. Я считаю это очень важной характеристикой в текущее время печенек и PS-ок в офисе (до 2020) и удаленной работы в 2020.

Минусы TDD

Простите, но в контексте вышеописанного я их не вижу.

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

1. другой формат мышления при подходе к реализации задачи. Часть предпочитает строить верхний слой архитектуры решения, а потом спускаться на нижние уровни и к детальной имплементации мелких функций. Если вы относите себя к таким людям, то, вероятно, TDD вам с первых попыток не понравится. Но, лично для меня, такой подход является анти-паттерном. Мне просто не хватает силы сознания держать в голове так много параметров, кейсов, объектов.

2. принуждение к TDD. Часть разработчиков подвергалась давлению и принуждению к использованию TDD. В случае неиспользования их оценивали как «недо-программист». Это конечно ужасно, но я пишу эту статью в совершенно зеркальных условиях, когда попытки применять TDD воспринимается коллегами как нелепость и непрофессионализм (ведь я же не могу держать в голове все ветвления логики, кейсы, объекты).

Итог

Да никакого итога. Мысли вслух. Скомпоновал как смог.
Если кто-то нашел в этой статье пищу для размышлений, то я доволен. Стремление к горстке признания это ведь так по-человечески.

© Habrahabr.ru