Обучение
Позднее этой теме будет посвящена целая глава. А пока позвольте мне просто сказать, что ответственность за обучение менее квалифицированных программистов возлагается на их опытных коллег. Учебных курсов недостаточно. Книг недостаточно. Ничто не приведет молодого разработчика к высокой производительности быстрее, чем его собственное желание в сочетании с эффективным обучением со стороны старших товарищей. Итак, еще одна сторона профессиональной этики проявляется в том, что опытные программисты берут под опеку молодых программистов и обучают их. Аналогичным образом профессиональная обязанность неопытных программистов заключается в том, чтобы найти наставника и перенять его опыт.
5
Разработка через тестирование

Методология разработки через тестирование, или TDD (Test Driven Development), появилась в нашей отрасли уже более 10 лет. Изначально она применялась на волне экстремального программирования (XP, eXtreme Programming), но с тех пор была принята на вооружение Scrum и практически всеми остальными гибкими (Agile) методологиями. Даже группы, не использующие гибкие методологии, применяют TDD.
Когда в 1998 году я впервые услышал о "упреждающем тестировании", я отнесся к нему скептически. Да и кто бы поступил иначе? Кто начинает работу с написания модульных тестов? Кто будет делать подобные глупости?
Но к тому времени у меня был уже 30-летний опыт профессионального программирования; я видел, как в отрасли появляются и исчезают новые идеи. Я прекрасно понимал, что ничего не стоит отвергать заранее, особенно если рекомендует такой человек, как Кент Бек.
Так в 1999 году я отправился в Медфорд, штат Орегон, чтобы встретиться с Кентом и научиться у него новой методологии. Результат был просто поразительным!
Мы с Кентом сели у него в офисе и начали программировать простую задачу на Java. Я хотел просто написать свой примитивный код, но Кент воспротивился и провел меня по всему процессу шаг за шагом. Сначала он написал крошечную часть модульного теста, которую и кодом-то нельзя было назвать. Затем он написал код, достаточный для того, чтобы тест компилировался. Затем он написал еще один тест и еще немного кода.
Такой рабочий цикл полностью противоречил всему моему опыту. Я привык писать код не менее часа, прежде чем пытаться откомпилировать или запустить его. Но Кент буквально выполнял свой код каждые 30 секунд или около того. Это было невероятно! Но самое интересное, что этот рабочий цикл был мне знаком! Я сталкивался с ним много лет назад, когда еще ребенком программировал игры на интерпретируемых языках вроде Basic или Logo. В этих языках не было сборки как таковой: вы просто добавляли строку кода и запускали программу. Рабочий цикл проходил очень быстро. И по этой причине программирование на этих языках бывало очень производительным.
Но в настоящем программировании такой рабочий цикл казался абсурдным. В настоящем программировании вы тратили много времени на написание кода, а потом еще больше времени на то, чтобы заставить его компилироваться. И еще больше времени на отладку. Я ведь был программистом C++, черт побери! А в C++ процессы сборки и компоновки могли длиться минутами, а то и часами. Тридцатисекундные рабочие циклы казались немыслимыми. Тем не менее передо мной сидел Кент, который писал свою программу на Java с 30-секундными циклами – и без малейшего намека на то, что работа замедлится. И тогда до меня дошло, что эта простая методология позволяет программировать на настоящих языках с продолжительностью рабочего цикла, типичной для Logo! И я капитально "подсел" на TDD!
Вердикт вынесен
С того времени я узнал, что TDD – нечто много большее, чем простой трюк для сокращения рабочего цикла. Методология обладает множеством преимуществ, которые будут описаны ниже.
Но сначала я должен сказать следующее.
• Вердикт вынесен!
• Дебаты завершены.
• Команда GOTO вредна.
• И TDD работает.
Да, за прошедшие годы о TDD было написано много противоречивых статей и блогов. На первых порах встречалась серьезная критика и сомнения, но в наши дни все дискуссии завершены. Кто бы что ни говорил, TDD работает.
Я знаю, что это утверждение кажется слишком жестким и односторонним, но в конце концов, хирургам уже не нужно доказывать полезность мытья рук. И я не думаю, что программистам нужно защищать TDD.
Как можно называть себя профессионалом, если вы не знаете, что весь ваш код работает? А как можно знать, что весь ваш код работает, если вы не тестируете его при каждом внесении изменений? А как тестировать код при каждом внесении изменений, не имея автоматизированных модульных тестов с очень высоким покрытием? Но можно ли создать автоматизированные модульные тесты с очень высоким покрытием без применения TDD?
Впрочем, последнее предложение стоит рассмотреть более подробно. Что же это, собственно, такое – TDD?
Три закона TDD
1. Новый рабочий код пишется только после того, как будет написан модульный тест, который не проходит.
2. Вы пишете ровно такой объем кода модульного теста, какой необходим для того, чтобы этот тест не проходил (если код теста не компилируется, считается, что он не проходит).
3. Вы пишете ровно такой объем рабочего кода, какой необходим для прохождения модульного теста, который в данный момент не проходит.
Эти три закона заставляют вас использовать рабочий цикл продолжительностью около 30 секунд. Сначала вы пишете маленькую часть модульного теста. За эти считанные секунды вы упоминаете в коде имя класса или функции, которые еще не были написаны; естественно, модульный тест не компилируется. Следовательно, далее вы должны написать рабочий код, с которым тест откомпилируется. Но писать больше кода нельзя, поэтому вы переходите к написанию дополнительного кода модульного теста.
Цикл прокручивается снова и снова. Добавляем небольшой фрагмент в тестовый код. Добавляем небольшой фрагмент в рабочий код. Два кодовых потока растут одновременно, превращаясь во взаимодополняющие компоненты. Соответствие между тестами и рабочим кодом напоминает соответствие между антителом и антигеном.
Длинный перечень преимуществ
Уверенность
Разработчик, принявший TDD как профессиональную методологию, пишет десятки тестов каждый день, сотни тестов каждую неделю, тысячи тестов каждый год. И все эти тесты постоянно находятся "под рукой" и запускаются при каждом внесении в код каких-либо изменений.
Я являюсь основным автором и ответственным за сопровождение FitNesse – системы приемочного тестирования на базе Java. На момент написания книги код FitNesse состоял из 64 000 строк, из которых 28 000 содержались в 2200 отдельных модульных тестах. Эти тесты обеспечивают покрытие по меньшей мере 90 % рабочего кода, а их выполнение занимает около 90 секунд.
Каждый раз, когда я изменяю какую-либо часть FitNesse, я запускаю модульные тесты. Если они проходят, то я практически полностью уверен, что изменения ничего не нарушили. Насколько "практически полностью"? Достаточно, чтобы опубликовать обновленную версию!
Весь процесс контроля качества FitNesse сводится к команде ant release. Эта команда собирает FitNesse "с нуля", а затем запускает все модульные и приемочные тесты. Если все тесты проходят успешно, я публикую результат.
Снижение плотности дефектов
FitNesse не является критически важным приложением. Если в FitNesse закрадется ошибка, никто не умрет и никто не потеряет миллионы долларов. Исходя из этого, я могу себе позволить опубликовать новую версию на основании только прохождения тестов. С другой стороны, у FitNesse тысячи пользователей, и при том, что за последний код кодовая база расширилась на 20 000 строк, мой список дефектов состоит только из 17 позиций (многие из которых имеют чисто косметическую природу). Таким образом, я знаю, что плотность дефектов в FitNesse чрезвычайно низка.
И этот эффект не уникален. Существенное снижение количества дефектов при использовании TDD описано в ряде отчетов и исследований. От IBM до Microsoft, от Sabre до Symantec – компании и группы сообщают о снижении количества дефектов в 2, 5 и даже 10 раз. Настоящий профессионал не может игнорировать такие показатели.