Примечание от переводчика: мой опыт знакомства с разработкой через тестирование во многом схож с тем, что описывает автор (хотя и начался на несколько лет позже). Я начинал изучать TDD самостоятельно, на работе, исправляя баги и создавая новые модули с ноля. Эффект от применения TDD произвёл на меня настолько мощное впечатление, что породил желание делиться умением применять эту технику с другими. Я также проводил Code Retreat-ы внутри и вне своей компании. И я вижу те же проблемы в своих тренингах — очень сложно взять и «впихнуть» понимание сути TDD в чужие головы.
Поэтому в данной статье я вижу свежий взгляд на проблему, который, возможно, даст новый толчок в изучении TDD мне и моим коллегам. Думаю, она пригодится и прочим интересующимся разработкой через тестирование. Буду рад увидеть Ваши комментарии.
Я использую TDD как инструмент для изучения и преподавания основ модульного дизайна, но должен заметить, что эффективность обучения сильно зависит от дисциплины студентов. Я хочу делать своё дело всё лучше и лучше, и поэтому постоянно ищу новые способы планировать критические шаги1 в преподавании TDD. Думаю, я нашёл микротехнику, которая помогает в этом деле, и хочу немедленно поделиться ей с вами.
TL;DR?
Многие сторонники TDD рекомендуют подход под названием «интенсивная практика», но я догадываюсь, что у Вас не будет возможности тратить много рабочего времени на практику. Я советую людям «применять TDD осознанно», но до сих пор не знал хорошего способа достаточно доступно объяснить смысл этих слов, что снижало ценность моего совета. Вы можете начать применять оба подхода (интенсивный и осознанный) одновременно, если начнёте исправлять баги через тесты. Даже если Вы до сих пор не умеете проектировать софт на экспертном уровне, то, по крайней мере, Вы уже можете учиться как эксперт. И исправление багов через тесты даст Вам естественную и не слишком рискованную возможность делать это. У Вас будет возможность практиковаться в TDD усердно и осознанно. Если у Вас есть возможность исправлять баги на работе в одиночку, то Вы можете использовать эти практики, не привлекая лишнего внимания, которое обычно возникает при разговорах об «интенсивной практике». Просто говорите всем, что Вы исправляете баги. Это всем понравится.
Вам всего лишь надо начать с написания теста, который будет падать до тех пор, пока Вы не исправите баг. Если Вам нужно больше подробностей, прежде чем начать, продолжайте чтение.
Подробности
Во время чтения книги Sources of Power Гэри Кляйна (Gary Klein), в которой описывается, как люди учатся принимать решения, моё внимание привлекла глава об интенсивной практике. (Я прокомментирую несколько цитат со 104-й страницы этой книги.)
Поскольку ключом к эффективному принятию решений является построение экспертизы, одним из соблазнов будет создание тренинга, учащего людей думать как эксперты.
Мне представляется, что множество людей подвергаются этому искушению и строят свои тренинги так, чтобы максимизировать объём информации экспертного уровня, которой они могут нагрузить участников. Я тоже так делал, но остановился, когда сам побывал на одном из них обычным участником. Если Вы откроете кран и начнёте заливать в меня десятки правил и выводов, я «переполнюсь» всего за пару часов, а затем перестану слушать.
В большинстве случаев это будет слишком долгим и дорогим удовольствием.
Ага.
Однако, если мы не можем научить людей думать как эксперты, возможно, у нас получится научить их учиться как эксперты.
Продолжай…
Изучив литературу, я выявил несколько способов, как могут обучаться эксперты в различных областях:
Они принимают участие в интенсивной практике так, что каждое практическое занятие имеет цель и оценочные критерии.
Они нарабатывают обширный пласт опыта.
Они получают обратную связь: точную, диагностирующую, и своевременную.
Они обогащают свой опыт, проводя «разборы полётов», чтобы извлечь уроки из своих ошибок.
Что-то из этого звучит знакомо, а что-то представляет собой новые возможности извлечь больше из практик по TDD.
Три из четырёх — не так уж и плохо
Ранее я уже упоминал об интенсивной практике. В «Don’t Waste Your Golden Learning Opportunity!» я писал о том, что к новые практики можно рассматривать как этюды. Эта метафора взята из музыки, где этюды широко применяются для отработки техники. Спортсмены, в свою очередь, могут думать о практике как об игровой тренировке. Но, так как большинство программистов вряд ли будут практиковаться вне рамок своей дневной работы, есть смысл рассмотреть такие режимы практики, которые можно применять во время «обычной работы».
Я и сам изучал сперва подход «сначала тесты» (test-first), а потом и «разработку через тесты» (test-driven) в горячке ежедневной работы, когда время ограничено, и от меня ждут реальных результатов. Сейчас, оглядываясь назад, я понимаю, что делал три из четырёх вещей, которые Кляйн перечисляет в качестве источников обучения экспертов.
Я нарабатывал обширный пласт опыта, используя TDD для написания почти всего моего рабочего кода в период с начала 2000-го года и до моего ухода из IBM в ноябре 2001-го.
Я получал точную, диагностирующую и своевременную обратную связь, задавая тысячи вопросов и участвуя в серьёзных дискуссиях в различных почтовых рассылках. (Тогда ещё не было StackOverflow.) Я также отвечал на множество вопросов других людей, что требовало от меня чтения, написания и улучшения большого количества кода.
Отвечая на вопросы в списках рассылки, на конференциях, на встречах пользователей, я часто касался старых тем снова и снова, что позволяло оттачивать мои старые ответы, а также испытывать новые озарения. Весь этот опыт со временем превратился в мою первую книгу: JUnit Recipes: Practical Methods for Programmer Testing.
Но даже тогда, когда я практиковался интенсивно и усердно (и даже осознанно), я не заходил так далеко, чтобы сформулировать явную цель и оценочный критерий для моих практических занятий. Точнее, порой заходил, но чаще нет. Как правило, я не ставил таких целей явным образом, а если ставил, то это происходило случайно. Сейчас же настало время вспомнить мой былой опыт и извлечь из него новый урок.
Интенсивная практика TDD
Если Вы хотите попрактиковаться в TDD как в инструменте качественного проектирования программных систем, попробуйте заняться этим на работе.
Возьмитесь исправить какой-нибудь баг. Скорее всего, Вы уже знаете, как исправлять баги, используя принцип «сперва тесты» (test-first), но на всякий случай я кратко опишу этот процесс:
Напишите интеграционный тест, который падает из-за этого бага. Тест не обязательно должен проходить до конца, но Вы можете продолжить его так далеко, как Вам требуется. Главное, чтобы бы он падал из-за существующего бага.
Начинайте писать всё меньшие и меньшие (более сфокусированные) тесты, которые приближают Вас к месторасположению бага. Можете использовать для этого приём «тиски Саффа» (Saff Squeeze) или любой аналогичный. Можете даже использовать отладчик, если хотите.
Остановитесь тогда, когда у Вас есть минимальный тест (или несколько тестов, если у бага есть несколько причин), который описывает суть ошибки, вызывающей этот баг.
Исправьте ошибку, удостоверьтесь, что все тесты проходят, и затем закомитьте все Ваши изменения.
Налейте себе кружку кофе.
А теперь давайте вернёмся к четырём пунктам Кляйна и посмотрим, как этот метод поможет Вам учиться проектированию софта на экспертном уровне.
Похоже ли это на интенсивную практику? Да. Когда я сажусь исправлять баг этим методом, я отношусь к нему как к возможности потренироваться в написании тестов, возможности учиться меньше полагаться на отладчик и отслеживание кода в голове, возможности учиться лучше выражать и документировать (через тесты) моё знание о коде, который я отлаживаю.
Есть ли у меня ясная цель и оценочные критерии? Я вижу ясную цель: исправить баг, научиться писать тесты, научиться писать более сфокусированные тесты, а также задокументировать приобретённое знание о коде, чтобы это знание не испарилось к тому моменту, когда я буду отправлять свои изменения в общий репозиторий. А в качестве оценочных критериев я могу выбрать что-то из следующего:
Насколько легко мне вернуться к этим тестам спустя какое-то время после исправления бага и понять, что я делал?
Насколько легко кому-то другому прочитать мои тесты и понять, что я делал?
Насколько легко мне перемещаться от больших тестов к меньшим?
Теряется ли какая-то информация, когда я перехожу от больших тестов к меньшим?
Насколько я уверен, что этот конкретный баг не повторится?
Насколько спокойно и комфортно я себя ощущаю во время борьбы с багом?
Не знаю, назовёте ли Вы эти критерии достаточно ясными, но от них, по крайней мере, уже можно отталкиваться. Возможно, вместе мы сможем выработать более удачные оценочные критерии.
Поможет ли это мне наработать обширный пласт опыта? Поможет, если я исправлю таким образом 50 багов в течении нескольких следующих месяцев. Как минимум, если я решу исправлять все баги таким образом, я неуклонно наработаю существенный объём опыта. Особенно если я буду исправлять баги во всех частях системы. А уж тем более, если в нескольких системах.
Получу ли я точную, диагностирующую, своевременную обратную связь? Думаю, да. Каждый раз, когда я пишу новый тест, он либо пройдёт, либо упадёт. Поэтому я, по крайней мере, могу понять, что начал приближаться к сломанной части кода. Я не могу быть уверен на 100%, что я нашёл тот кусок кода, который вызывает падение, пока мои тесты не станут достаточно маленькими, чтобы доказать это тем или иным способом, и я постепенно двигаюсь в этом направлении.
Поможет ли это мне пересмотреть мой старый опыт, чтобы получить новые озарения? Да и нет. Нет, потому что (как я надеюсь) мне не придётся исправлять один и тот же баг дважды; но да, потому что я работаю таким образом с каждым новым багом, так что мне придётся сталкиваться с теми же частями системы по многу раз. Чем больше багов я исправляю в какой-либо части системы, тем больше возможности у меня будет вернуться к моим прошлым тестам и сравнить свой новый опыт со старым. Если я потрачу немного времени, сравнивая, что я делаю по-другому по сравнению с прошлыми разами, то это даст мне возможность получить новые озарения.
Так что, мне кажется, что подобный метод исправления багов поможет практикующемуся учиться как эксперт (по крайней мере, в данной интерпретации как метода, так и модели обучения экспертов, описанной Кляйном). Просто попробуйте сами и оцените этот подход.
За пределами работы с багами
Как можно использовать TDD для изучения принципов модульного дизайна на экспертном уровне? Я должен упомянуть одну часто встречающуюся небрежность на этом пути. Это не то чтобы серьёзная небрежность, скорее следствие благих намерений и в то же время недопонимания. Я всегда считал использование «осознанного подхода» особенно важным. Поэтому, когда кто-либо начинает заявлять, что разработка через тесты «не работает» или «причиняет страдание» или «вредит», я включаюсь в дискуссию и раз за разом повторяю одну и ту же идею:
Правила ничего не делают сами; всё делают люди. TDD, как и любая другая практика, требует от практикующего включать мозги. Он должен упражняться осознанно.
Это было верно, но достаточно бесполезно… до сегодняшнего дня. Я уверен, что Кляйн открыл мне более точный и применимый в деле способ объяснять, что такое «осознанная практика». Я собираюсь использовать его в моих тренингах, начиная с сегодняшнего дня. Похоже, я неявно использовал его в изучении TDD (и модульного дизайна), но до сих пор у меня не было легко запоминающегося способа его сформулировать. Спасибо, Гэри Кляйн.
Как практиковаться в TDD «осознанно»
Возьмите паузу в несколько секунд и подумайте о том, когда и как Вы собираетесь изучать TDD. На каком коде? В какое время? В каких условиях? Не обязательно жёстко ограничивать себя этими правилами, но они помогут упорядочить процесс. Очень важно определить для себя, в каких случаях Вы готовы использовать TDD, а в каких нет. Определите это, пока что, только для себя. Договориться с коллегами можно и позже2.
Как только Вы определили рамки, в которых Вы будете упражняться в TDD, Вы можете задать себе цели и критерии оценки для этих тренировок. Подкину несколько идей.
Цели для тренировок по TDD
Создайте через тесты точку интеграции с библиотекой, с которой Вы не работали ранее.
Отделите несколько большую часть Вашего кода от интеграции с фреймворком.
Напишите поменьше тестов, которые требуют фреймворк-ориентированной библиотеки тестирования (например, Robolectric, NUnitASP, rspec-rails, or JSFUnit), и побольше тестов, которые используют обычные библиотеки тестирования (обычные JUnit, RSpec или pytest).
Уменьшите время, которое требуется на создание следующего теста.
Оценочные критерии для тренировок по TDD.
Сделал ли я эту часть системы более простой, чем обычно?
Являются ли имена в этой части системы более понятными другим, помогают ли они понять, как изменять этот код?
Могу ли я расширить эту часть системы, просто добавляя новый код, а не изменяя существующий?
Зависит ли код, необходимый для настройки окружения моих тестов, только от тех частей системы, которые я только что создал?
Если мой тест содержит несколько проверок, предполагаю ли я, что они сильно связаны друг с другом?
Достаточно ли быстро проходят мои тесты? Или они хотя бы стали быстрее, чем раньше?
Если мы посидим вместе хотя бы десять минут, то наверняка придумаем ещё дюжину подобных полезных критериев. Выберите несколько и держите их в голове в течение следующего сеанса программирования, во время которого Вы будете отрабатывать TDD.
А мне позвольте перейти к вопросу получения новых озарений из старого опыта, потому что я часто сталкиваюсь с этим, обучая других и практикуясь с ними. Чтобы перерабатывать свой опыт, не обязательно самому проводить какие-то занятия. Можно, к примеру, просто отвечать на чужие вопросы. Например, я раз за разом отвечаю на одни и те же вопросы, приходящие разными людьми. (Обычно я терпеть не могу повторяться, но сейчас я понял, что эти повторения на самом деле гораздо ценнее.) Чем больше я отвечаю на эти вопросы, тем больше я рефлексирую над моим пониманием. Со временем мои идеи сталкиваются с разнообразными новыми испытытаниями и либо укрепляются, либо дают жизнь новым, более удачным. Я также принимаю участие в Code Retreat-ах специально для того, чтобы работать над одними и теми же небольшими задачками снова и снова с новыми людьми. Каждый год я испытываю новые озарения, работая над игрой «жизнь».
А сейчас я хотел бы перейти к одному важному пункту в модели Гэри Кляйна, к пункту, который сподвиг меня написать эту статью именно сегодня.
Обширный пласт опыта
Я годами описывал TDD как «технику для обучения проектированию», в которой мы можем использовать механизм, схожий с механизмом наработки языковых навыков. Как Вы учились говорить на своём родном языке? Скорее всего, это выглядело как-то так:
Сначала, в течение долгого времени. Вы только слушали речь людей вокруг Вас.
В какой-то момент Вы начали говорить очень простые фразы.
Когда вы начали произносить эти фразы, Вы замечали, как окружающие реагируют на Вашу речь.
Иногда Вас поправляли, объясняя, что надо говорить так, а не этак.
Из этих объяснений Вы извлекали правила и принципы построения «правильных» фраз.
Вы опирались на эти правила и принципы в построении новых предложений.
И этот процесс шёл, не прекращаясь. Язык, на котором Вы разговариваете, превратился в инструмент непрерывного общения с другими людьми, включающий эксперименты, оценку, взаимное влияние, попытки исправлений, извлечение новых правил и принципов… — и так до самой смерти.
На мой взгляд, TDD позволяет изучать дизайн кода аналогичным образом. Он позволяет выйти за пределы обычного программирования, предоставляя программисту возможность оценивать код в критериях высокого уровня, а-ля «я легко могу протестировать это», или «я могу отделить этот код из его контекста, чтобы запускать в изолированном окружении» или «я могу добавить новое поведение через добавление кода, а не через изменение существующего». Вы можете возразить, что такие оценки можно получить, даже просто читая и обсуждая код, но именно TDD мотивирует программиста вести диалог с кодом, обдумывать «как должен вести себя этот кусок?» или «как мне следует спроектировать эту часть?» с разных сторон. Я думаю, что такой диалоговый стиль создания кода позволяет нарабатывать опыт куда эффективнее и быстрее, нежели традиционный подход «пиши много, читай редко». Как минимум, TDD мотивирует программиста видеть код под разными точами зрения.
Главную роль в этом подходе играет рефакторинг. Во время рефакторинга требуется оценить существующий код, понять и выявить его проблемы, предложить способ его улучшения, а затем изменить в соответствии с этим способом. (Не хотелось бы мне проводить крупный рефакторинг без тестов, а если бы я не писал тесты в первую очередь, то вряд ли бы их набралось достаточно много, чтобы я чувствовал себя достаточно уверенно при рефакторинге.) Процесс рефакторинга позволяет программисту видеть разные варианты дизайна одного и того же кода, каждый из которых может либо иметь определённые проблемы, либо не иметь; это позволяет сравнивать различные варианты дизайна и выбирать из них наиболее достойные. У программиста появляется возможность сравнивать состояния «до» и «после» рефакторинга и оценивать, какой эффект дают его улучшения кода.
Можно сравнить дизайн «до рефакторинга» с некорректными либо корректными-но-плохо-сформулированными выражениями языка. А различные техники микро-рефакторинга (например, «замена наследования делегированием» или «введение переменной») — с правилами и принципами, по которым эти выражения можно исправлять и улучшать. В таком случае сам акт рефакторинга будет схож с актом применения этих правил и принципов для исправления выражений (кода). И Вы можете оценить свои изменения, через представление того, как будет воспринимать их программист, которому нужно прочитать и понять, что Вы сделали. Если Вы не можете оценить свою работу самостоятельно или чувствуете неуверенность, пробуйте предлагать свои варианты другим, просите их оценки. Они могут поправить Вас или, хотя бы, указать на плохой стиль. Точно так же, как Вы применяли этот метод для изучения своего родного языка, применяйте его для изучения языка модульного дизайна кода.
1. Почитайте 3 главу книги Switch, чтобы узнать больше о том, как помогать людям изменять своё поведение.
2. Я обучаю команды технике применения новых практик через безопасные эксперименты, что помогает им улучшать их работу. Я ещё не писал об этом подробно, но обещаю это сделать. Если Вы не можете дождаться новой статьи и хотите узнать об этом как можно скорее, свяжитесь со мной, и мы поработаем так вместе.