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

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

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

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

Мои первые шаги в 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 воспринимается коллегами как нелепость и непрофессионализм (ведь я же не могу держать в голове все ветвления логики, кейсы, объекты).

Итог

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

Средняя зарплата в IT

110 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 8 889 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 21

    +4
    Минусы TDD


    TDD и есть тот минус.
      +2

      Вот все было хорошо, пока top-down подход антипаттерном не назвали :)) ИМХО одно другому не мешает, нужно совмещать

        0
        Спасибо. Справедливо.
        Перефразировал, подчеркнув акцент на контр-продуктивности персонально для себя.
        +4

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

          0
          TDD это как степень по гендерным наукам. Все о ней говорят, но никто не видел, чтоб это работало на практике или приносило деньги. Приходится продавать семинары по TDD.
            0
            Все эти подходы просто придумываются людьми совершенно далекими от разработки. Они просто не понимают, что в реальной жизни никто не даст переписывать ни код ни тесты по десять раз. Бизнес заказчику невозможно объяснить, зачем нужен рефакторинг и почему важно обновлять кодовую базу на новые версии фреймворков. Люди не пишут тесты не потому, что им лень, а потому, что на таску требующую пары дней работы выделяют пару часов. А потом приходят университетские работники и начинают разглагольствовать, что хорошо бы писать код правильно. Да, неплохо бы. Только, к сожалению, за это денег не платят.

              +5

              Кому-то дают, кому-то не дают. Те, кому не дают, обычно в итоге тратят больше времени на фикс багов.

                +1
                Без тестов, обновления версий фреймворка и рефакторинга можно двигаться быстрее, но только первое время. А дальше вы поневоле начнете платить по техническим долгам. И более-менее адекватный бизнес это понимает.
                  +2
                  В Гугле, в Андроиде есть баги не пофикшеные с 2016 и до сих пор актуальные. Я на своих 15 годах опыта тоже таких адекватных не встречал. Так что где таких взять — я не знаю.

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

                    0
                    В Гугле, в Андроиде есть баги не пофикшеные с 2016 и до сих пор актуальные.

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

                      +1

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

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

                      Думаю, если бы в гугле не было тестирования и прочих практик повышения надежности кода, багов у них было бы гораздо больше. И если честно не знаю, как там в больших компаниях, но в местах, где я работал, тесты либо спускались сверху, либо начальство не сильно противилось их написанию. Хотя бы для самых частых кейсов.
                        +1
                        Нет, почему, это абсолютно релевантные для бизнеса вещи. С накоплением технического долга начинает падать скорость деливера фич, равно, как их качество, начинаются сложности с поиском новых сотрудников на легаси. Это всё ощутимо для бизнеса, и бизнес не сможет закрывать на это глаза. Другое дело, что на первых этапах стартапа порой действительно лучше делать по принципу «тяп-ляп и в продакшен», просто чтобы оценить потенциал идеи. Но потом рефакторить всё это бывает уже очень больно.
                          0

                          В гугле пишут тесты. Есть даже такая книжка "How Google Tests Software". То, что есть какие-то баги не значит, что тестов не пишут.

                      +1
                      никто не видел, чтоб это работало


                      Я видел. Качество кода повысилось, багов стало меньше, поддерживать и дорабатывать продукт легче. А каких еще результатов вы ожидаете от tdd?
                      +3

                      TDD отлично работает для задачи, для которой уже заранее примерно ясно, как будет выглядеть решение. Желательно чтобы еще и зависимостей у решения не было. Хороший пример — всякие алгоритмические задачки типа leetcode и того, что на интервью спросят.


                      Зачастую это не так. Пишешь-пишешь, оппа, оказывается есть такой подводный камень и пол решения надо переделывать, включая интерфейс взаимодействия с внешним миром. Половина тестов к коту под хвост.


                      Или вот про зависимости. Начали по ТДД, раз тест, два тест, три. А потом добавили зависимость — и все предыдущие тесты надо поправить. Потом снова. А затем переосмыслил подход и избавился от зависимости — снова меняем ранее написанные тесты.


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

                        +2
                        Пишешь-пишешь, оппа, оказывается есть такой подводный камень и пол решения надо переделывать, включая интерфейс взаимодействия с внешним миром

                        Так это и есть принцип TDD: вместо того, чтобы сначала всё продумать, создать API, написать тесты (test-first), затем под них код, TDD заставляет выкинуть всё это из головы и двигаться маленькими шажками. Естественно, в этом случае API будет по многу раз переписываться.

                          +3
                          Так зависимостями надо управлять. И TDD, кстати, помогает бороться с соблазном плодить зависимости.

                          отдельный тип задач, где ТДД (а точнее, написания теста до реального кода) помогает

                          Автор статьи совершенно верно заметил, что TDD — это совсем не написание тестов до реального кода. То, что вы описываете, — это, спору нет, тоже полезно, но это не TDD.
                            0

                            Да, TDD хорош тем, что принуждает к написанию кода в соответствии с принципами написания хорошего кода: модульность, изолированность. Очень хорошо помогает новичкам в программировании.

                            +2
                            Иногда проще сначала более-менее определиться с реализацией, а потом уже писать ее начисто уже с тестами. А иногда наоборот, например в сложных модулях, писать маленькими шажками через test first. Но главное, на мой взгляд, что в конце у вас окажется набор тестов, более-менее покрывающий все приложение, на который в дальнейшем вы можете опираться.
                            0
                            Я когда-то на RSDN писал про TDD:

                            Сейчас наконец пересилил себя и взялся за задачку, которую откладывал уже пару месяцев, и понял:

                            Test-driven development — это воплощение идеи «ленивых вычислений» в самом программисте.

                            Тебе облом. Тебе ОБЛОМ. Тебе облом в кубе. Но надо делать. Ты смотришь на обломки старых недоделок. Ты понимаешь, что нет никаких сил разбираться во всём этом, тебе абсолютно по барабану, что внутри делается каким кодом и почему. Но тебе НАДО это сделать.

                            Ты хочешь, чтобы оно хоть что-то сделало. Ты пишешь тест, чтобы оно хоть чихнуло. Ты его запускаешь… кодишь… запускаешь… кодишь… запускаешь… кодишь… запускаешь… кодишь… и всё это через большой, тотальный ОБЛОМ.

                            Наконец заработало, ты его спихиваешь и с чистой душой идёшь в отпуск.

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

                            И как ни странно, потом оно работает. До следующего изменения требований.:))

                            Да здравствует лень.


                            Но всё это совершенно не значит, что надо точно следовать этому при написании кода; наоборот (по крайней мере мне) это убивание энтузиазма и замедление работы, за счёт дёрганий между прикидкой требований (в виде теста) и написанием, и хорошо идёт тогда, когда в основном понятно, где, что и как будет делаться, неясны только детали. И это никак не поможет полностью пионерской разработке (для автора кода — даже если это простейшая школьная лабораторная).

                            > его цель — проверить только следующий маленький шаг, которые разработчик собрался реализовать в ближайших строках кода в следующие 2–5–15 минут

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

                            > человек чаще всего неосознанно выбирает задачу понятную

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

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

                            Самое читаемое