Чему не учат разработчиков: советы для начинающих
Иногда меня спрашивают, как стать разработчиком? Сложно ли это, что для этого нужно, какой язык выбрать? Спрашивают друзья, родственники, знакомые. Далёкие от сферы информационных технологий или забросившие когда-то программирование и подумывающие о возвращении (читай дауншифтеры). Я не нашёл статьи, которую мог бы скидывать в таких случаях, и решил её написать. Здесь должна быть шутка про 14 конкурирующих стандартов, но как солдат не смеётся в цирке, так и программист не улыбается, говоря о конкурирующих стандартах.
Часто бывает, что начинающие разработчики, уже неплохо зная язык, сталкиваются с тем, что непосредственно программирование занимает небольшую долю рабочего времени, и многих это демотивирует. Постараюсь рассказать, почему так получается и как можно этого избежать.
Также довольно часто я встречаю команды, которые трудятся не покладая рук, но такой показатель как time to market (время которое проходит от начала реализации до того, как фича выкатывается на живых пользователей) у них непомерно высокий. Выпуск новой фичи, рассчитанный на две недели, часто связан с колоссальным напряжением разработчиков, большими задержками, урезанной функциональностью и месяцами исправления ошибок.
Кажется, что у этих проблем мало общего, но кое-что их объединяет. Разработчиков учат писать код, понимать сложные алгоритмы, но их не учат культуре разработки и управления проектами. Команды, которым повезло, — которые доходят до этого сами со стороны разработки, и при этом не встречают сопротивления со стороны менеджмента (некоторым даже везёт настолько, что менеджмент осознаёт проблему и помогает), — работают эффективно. Команды, которым везёт меньше, тонут в болоте бесконечного багфиксинга с его неизменными спутниками: выгоранием и текучкой кадров.
Прежде чем перейти к сути, пару слов о себе. Впервые я столкнулся с программированием на Basic/Pascal/C/Assembler/Perl в конце прошлого века, учась сначала в школе, потом в институте. Много времени посвятил администрированию Linux-серверов, периодически возвращался к разработке (обычно на других языках, изучая их в процессе), но в итоге остановился всё же на backend-разработке высоконагруженных сервисов, и сегодня пишу на языке Go в команде календаря VK.
Следующий раздел посвящён началу карьеры разработчика с чистого листа. В нём много размышлений, и если вы обладаете некоторым опытом, то можете его безболезненно пропустить. Для тех, у кого есть время или нет выбора, — продолжим.
Обучение программированию с нуля
Итак, начало карьеры в ИТ. Здесь я не буду затрагивать выбор направления. Информационные технологии — гигантская область, тут есть и frontend-разработка, и backend, и мобильная, машинное обучение, а ещё есть такие штуки, как графический дизайн, дизайн интерфейсов, аналитика, управление проектами, маркетинг, администрирование и DevOps (что бы это ни значило). Вопросы, с которыми сталкиваюсь в жизни я, чаще всего связаны именно с backend-разработкой. Возможно, ответы на них будут не так далеки от frontend-а или мобильщиков, но вряд ли помогут тем, у кого душа лежит к прорисовке персонажей для игр, хотя, безусловно, это тоже сфера ИТ.
С этой оговоркой предположим, что вы решили попробовать себя в backend-разработке, но ещё особо ничего не знаете. Тут нужна вторая оговорка. Очень часто, когда меня спрашивают про карьеру разработчика, используют отговорки: «Я бы стал разработчиком, если бы у меня была голова как у тебя», или «Если бы не мой возраст, я бы обязательно стал разработчиком», или «Я бы стал разработчиком, если бы было кому меня научить». Это именно отговорки, которыми люди закрывают себе путь в новую жизнь. Отговорки, которые позволяют остаться в зоне комфорта, даже если это муравейник. Человек может бесконечно жаловаться, как больно сидеть голым задом на муравейнике, и продолжать ждать, когда же произойдёт что-то, что заставит его подняться и идти вперёд. Могу только сказать, что ожидание бесполезно. Не произойдёт. В сегодняшней реальности человек отвечает за себя сам. Хочешь стать разработчиком — встань и иди.
Для этого в сегодняшнем мире не существует преград кроме, пожалуй, инвалидности. Если у вас нет рук, или вы не видите символы на экране, то вам будет сложно стать разработчиком. Во всех остальных случаях это возможно и зависит от вашего желания и энергии, но не от возраста, технического склада ума, связей, наследственности, места жительства. Чтобы начать, вам нужен только ноутбук или компьютер, не обязательно свежей модели. Огромная потребность в кадрах привела к высокому спросу на рынке труда. Эта тенденция вряд ли изменится в ближайшие десять лет. Некоторые компании даже берут на стажировку людей, которые в своей жизни ничего не программировали, и учат их, выплачивая при этом зарплату за решение тестовых заданий и сдачу экзаменов.
Итак, дальше читают только те, кто готов что-то делать, изучать, тратить время, но не знает, куда податься. Погуглив, можно легко найти несколько рекламных статей в стиле «хочешь стать программистом быстро — приходи на наши курсы». Также есть ворох статей, в которых рассказывают, чем отличается backend от frontend-а, что такое аналитик и как составить резюме. Не скажу, что они совсем бесполезны, но оставляют некую пустоту. Где-то между выбором направления и написанием хорошего резюме лежит тот самый вопрос: куда податься, чтобы научиться разработке?
ВУЗы
Традиционный совет, который сегодня можно услышать от бабушки или дедушки, — пойти в профильный ВУЗ. Я с этим в корне не согласен. Во времена молодости моих родителей ВУЗ был источником знаний, почерпнуть которые было больше просто негде, или, как минимум, очень сложно. Мой отец ночевал в вычислительном центре, если очень надо было прогнать программу с перфокарты, и любит вспоминать жёсткие диски размером с тумбочку и ёмкостью в 10 мегабайтов. Всё это он видел в институте или на стажировке, на которую попал благодаря учёбе в институте. Больше это было увидеть негде. Литературу по интересующему вопросу можно было найти только в библиотеке, а туда ещё не всех пускали. В таких условиях ВУЗ действительно давал человеку широкие возможности.
Во времена моей молодости (напомню, это было в прошлом веке) высшее образование ещё сохраняло какую-то актуальность, но постепенно её утрачивало. Программа, по которой обучали студентов, безнадёжно устаревала к концу обучения. ВУЗы постепенно превращались в способ откосить от армии для одной части человечества и удачно выйти замуж для другой. Осознав этот факт, я бросил институт на этапе защиты диплома. В моей зачётке проставлена преддипломная практика, после чего я был отчислен за непредоставление в срок дипломного проекта. Самого диплома у меня, конечно же, нет. И я ни разу об этом не пожалел за последние двадцать лет.
Сегодня в подавляющем большинстве ВУЗов бывшие школьники, покалеченные ЕГЭ, продолжают тратить свою жизнь на копипаст информации, которая им никогда не пригодится. Часто на платной основе. Часто не получая знаний. Если раньше студента, который не учится должным образом, просто отчисляли (и это гарантировало какой-то набор знаний у человека, который всё же получил диплом), то сегодня его просто дотягивают до конца обучения, потому что ВУЗу это выгодно, а за плохой результат никого не наказывают. Исключения есть, их достаточно много, но общее правило такое.
И самое важное — информация стала доступна. Если отбросить такие специальности как медицина, биология (мы же тут говорим про ИТ), то всё, что нужно для успешного развития, сегодня можно найти на Youtube. С другой стороны, ВУЗы помимо необходимого включают в программу технических специальностей всякий треш вроде экономики, социологии, психологии, политологии. Не то чтобы это плохие знания, но сегодня мир очень динамичен. Время, которое человек тратит на изучение чего-то одного, он не тратит на изучение чего-то другого, и нужно очень аккуратно относиться не только к предмету изучения, но и к качеству его преподавания, а также к целям приобретения знаний. Здесь я рекомендую пользоваться методом Талеба, описанным в книге «Антихрупкость»: если какая-то тема представляет интерес, то надо найти посвящённую ей книгу и читать. Если книга неинтересная, отложить и перейти к другой. Повторять до достижения необходимого результата либо до потери интереса к теме.
Небольшая оговорка. Если вас всё же угораздило связать свою жизнь с порядочным ВУЗом (МГУ, Бауманка и ряд других) и вам интересна работа в ИТ, то постарайтесь попасть в образовательный проект. Тот же VK делает очень крутые программы для будущих разработчиков, и те, кто пришёл к нам на стажировку из проектов вроде Техносферы или Технопарка, поражают даже бывалого разработчика уровнем своих знаний и опыта. Я могу вспомнить нескольких стажёров, которые пришли еще в Mail.Ru Group, а сегодня уже в VK они ведущие разработчики, тимлиды, архитекторы. Впрочем, чтобы попасть в образовательный проект надо сдать вступительный экзамен, поэтому мечтать будем позже, а пока читаем дальше о том, как можно подтянуться до необходимого уровня.
Курсы
Курсов сейчас очень много. Это, в общем-то, хорошо. Проблема в том, что отличить хорошие от плохих довольно сложно. Практически, это лотерея, в которой за 150–200 тысяч рублей и полгода жизни вам предлагают освоить ту или иную специальность. Если вы дауншифтер и у вас в кармане много лишних денег, то это неплохой способ. Может, не идеальный, но неплохой.
Самый лучший вариант сегодня это пройти бесплатный онлайн—курс по конкретной тематике. В нашем случае — по языку программирования. Таких курсов можно найти множество на обучающих платформах вроде https://stepik.org/catalog. Вводим название языка, получаем несколько вариантов курса, изучаем описание, отзывы, записываемся, и погнали. Принцип тут такой же, как с книгами у Талеба: если в процессе стало неинтересно или не нравится стиль изложения — бросаем и переходим на другой.
Выбор языка
Я специально отложил напоследок вопрос выбора языка программирования. Если он для вас ещё не решён, то мой совет — не особенно заморачиваться. Серьёзный разработчик за свою жизнь обязательно выучит несколько языков, и если даже первый оказался не тот, то его изучение будет связано с общим пониманием того, как выполняется код, что такое алгоритм и т.д. То есть большой пласт информации, необходимой на начальном этапе, вообще не привязан к языку.
Если надо выбрать один язык, то я бы рекомендовал начать с Java. Считаю его не очень подходящим для современной серверной разработки, но в то же время это язык, который:
работает в большинстве операционных систем одинаково;
приближен по синтаксису к языкам низкого уровня вроде C++;
используется для мобильной разработки под Android (что лично мне однажды очень пригодилось, потому что я смог для тестирования своего сервиса быстро запилить мобильное приложение);
а ещё самый лучший курс по алгоритмам из известных мне (про него позже) предполагает использование Java как основного инструмента для упражнений.
Обычно существует несколько версий очень базового курса для начинающих (чаще всего для школьников) под любой язык. Рекомендую начать с такого, но не останавливаться на нём. Подобные курсы помогают втянуться, но оставляют за бортом очень много полезного, а качество чаще всего посредственное. С другой стороны, серьёзные курсы обычно подразумевают, что у студента уже есть базовые навыки в программировании (например, все курсы от VK, которые я видел, именно такие). В этот момент, кстати, можно переключиться на другой язык, если вдруг первый вариант не зашёл.
Алгоритмы
Время для изучения алгоритмов мне сложно рекомендовать однозначно. Это точно не то, с чего надо начинать, но и сильно затягивать не стоит. Самое ценное тут даже не столько алгоритмы (немногие разработчики применяют на практике эти знания), сколько понимание сложности конкретных алгоритмов, и сфер их применения. Это чаще всего нужно при выборе тех или иных архитектурных решений, или понимания ограничений разных систем, таких как базы данных и различные библиотеки.
Лучший курс из всего, что мне попадалось, это https://www.coursera.org/learn/algorithms-part1. Он англоязычный, при желании можно найти на Youtube, но я рекомендую смотреть именно на Coursera, потому что там есть домашние задания, а они много дают для понимания.
Иностранные языки, терминология и жаргон
Незнание английского языка может стать определённым препятствием на пути разработчика. С одной стороны, для получения базовых навыков достаточно русскоязычной литературы и знания латинского алфавита (чтобы буквы на клавиатуре различать). В то же время, понимание значения таких слов как if, function, routine, try, catch гораздо полезнее тупого заучивания последовательностей символов и их связи с синтаксисом конкретного языка программирования. Поэтому тем, кто по какой-то причине не изучил английский в школе, я рекомендую этим заняться. Но это можно делать не в первую очередь, а параллельно, по мере необходимости. Лучший известный мне способ (им пользовался я сам) это чтение технической документации. Различные руководства, справочники функций, статьи. Я просто читал, их держа под рукой словарь. Что-то понимал интуитивно, что-то запоминалось после множества повторений (очень больно искать слово в бумажном словаре, мозг предпочитает это слово побыстрее запомнить). Бегло говорить по-английски и воспринимать разговорную речь я не умею до сих пор, однако тексты читаю без проблем, и этого достаточно.
Множество терминов, используемых в ИТ, пришли к нам из английского языка. Часть из них имеют русские аналоги, более или менее удачные, часть просто заимствуется. Но всё же полезно понимать, что «фикс» (англ. fix) это «исправление», «приведение к «правильному» состоянию», а кейс (case) в зависимости от контекста может иметь самые разные значения, от usecase-ов в проектировании до testcase-ов в тестировании и условных операторов в языках программирования. Такие слова как баг (bug) и фича (feature), кажется, сегодня известны даже домохозяйкам и пенсионерам. В реальной работе приходится сталкиваться с самыми разными вариациями подобных названий, используемых в качестве профессионального жаргона (сленга). Есть такие жаргонизмы как «пофиксить», «зафиксить», «дофиксить». «Хотфикс» (быстрое исправление или дополнение функциональности), «багфикс» (исправление бага — ошибки, неточной реализации алгоритма и т.д.). Тот же хотфикс часто называют быстрофиксом. Редко кто употребляет термин «исключение», вместо этого говорят «эксепшен» (англ. exception). Все эти подробности довольно полезно знать, чтобы понимать, о чём идет речь на встречах и в докладах. При достаточном уровне английского разобраться в сленге существенно легче, поэтому ещё раз призываю дружить с английским. И ещё раз напоминаю, что это не обязательно делать сразу. Вообще, с течением времени разработчику приходится много учиться. Это нормально, и чаще всего интересно. Главное — не бояться, не убеждать себя в том, что это сложно. Важен настрой, и если он есть, то знания приобретаются гораздо легче.
Стажировка и джуниорство
Теперь самое интересное. Здесь я рассказываю для тех, кто уже имеет некоторый опыт программирования, но пока находится в начале пути разработчика. Под разработкой будем понимать весь процесс до выкатки функциональности на живых пользователей. Помимо программирования разработка включает в себя проектирование решения, прототипирование и проверку гипотез, тестирование, передачу в эксплуатацию, сопровождение.
В сопровождение входит и исправление ошибок, а это намёк на то, что очень часто разработчик не занимается последовательно одной задачей за другой. В реальности чаще бывает так:
Разработчик занимается задачей, и тут в него прилетает баг, который затрагивает живых пользователей (например, через обращение в техподдержку).
Разработчик бросает текущую задачу на середине, разбирается с багом, выпускает исправление.
А в это время разные люди ему задают вопросы:
тестировщик спрашивает про предыдущую задачу (функциональность уже отдана в тестирование, но ещё не ушла в эксплуатацию);
менеджер проекта попросит оценить разработку следующей фичи;
разработчик из соседней команды спрашивает про параметры запроса, с которыми он должен обращаться к нашему сервису, или как отреагирует сервис на такие-то значения параметров. Часто на этот вопрос нельзя просто ответить, надо залезть в код, который писал даже не ты, и разбираться в нём.
То есть штатный режим разработчика это такое жонглирование несколькими разными процессами, переключение внимания с одной задачи на другую. Сам процесс перехода от одной задачи к другой известен как переключение контекста, и интернет полон рассказов о том, что частые переключения могут быть очень неэффективны и отнимать львиную долю рабочего времени.
Такова общая картина работы в среднестатистической ИТ—компании. Обычно стажёра прикрывают от горящих задач, дают то, что попроще, в чём меньше вероятность накосячить или цена ошибки невысока. Но тот, кто идёт в разработку (даже в целом — в ИТ) должен готовиться к подобному режиму. Фактически начинающий разработчик отличается от опытного тем, что последний умеет эффективно работать в описанном выше режиме многозадачности и достигать поставленных бизнесом целей, невзирая на применяемые средства (правильнее сказать, применяя любые средства), а первый — умеет читать и писать код.
Сглаживаем эффект переключения контекста
Итак, мы немного забежали вперёд и выяснили, что одним из главных врагов разработчика является переключение контекста. Можно также сказать, что одним из самых распространённых переключателей контекста является коммуникация, то есть взаимодействие двух людей. Иногда она вынужденная (пришёл менеджер и начал задавать вопросы), а иногда разработчику для выполнения задачи надо пойти к кому-то и задать вопрос.
В первом случае помогает тайм-менеджмент (буквально, управление временем, но я предпочитаю использовать устоявшийся термин). Например можно использовать технику помидора (pomodoro). Разработчик квантует своё время, работает непрерывными интервалами, закрывает все мессенджеры, телефоны и прочие отвлекающие устройства и приложения, через которые до него можно достучаться. В прежние времена ещё приходилось найти укромное местечко, потому что особо активный коллега мог и ногами до тебя дойти, если ты не ответил через две минуты. Но сейчас широко применяется удалённая работа и стало гораздо проще. Так вот, выделяем себе какой-то промежуток времени, например 20 минут. Убираем все источники прерываний, ставим будильник (у автора техники будильник был в форме помидора, отсюда и название), и работаем без перерыва. Когда сработал будильник, можно (и нужно) прерваться: посмотреть в мессенджеры, проверить почту и так далее. В подавляющем большинстве случаев никто за это время не умрёт. Если вы ответите на вопрос менеджера через 20 минут, ничего страшного. Если 20 минут будет лежать сервис на проде, это уже покритичнее, но достаточно оставить включённым телефон, куда в случае проблем вам прилетит либо SMS, либо звонок от дежурного. Секретные техники помогают, но они не избавляют полностью от всех проблем.
На самом деле важнее избавляться от второго типа коммуникации. Старайтесь как можно раньше получить как можно больше информации по задаче, над которой вам предстоит работать. Нет, не в смысле о чём думал менеджер, когда ставил задачу, по которой позже нашли баг и сейчас вас просят его поправить. Но если это баг, то вы должны чётко понимать, в чём он заключается и как его воспроизвести. Какое поведение считается багом, и каким оно должно стать после того, как вы сделали задачу? Очень часто задача сформулирована нечётко. Или она понятна человеку, который давно работает в компании, но непонятна новому члену команды. Я в таких случаях обычно начинаю дополнять описание (если задачу никто ещё не делал), или добавляю комментарий, в котором перевожу авторскую формулировку в ту, которая позволит мне через неделю вспомнить, о чём речь в задаче. И не только мне, но и тестировщику, который будет после меня проверять, исправлена ли ошибка. В идеале я довожу описание до состояния zero communication. Это означает, что если мне на голову упадёт кирпич, то другой разработчик возьмёт задачу, и ему для её выполнения не придётся задавать дополнительных вопросов кому-либо ещё. Ноль коммуникаций. Не всегда получается довести до идеала, но чем ближе, тем лучше.
Кроме того, что я застраховал компанию от падения кирпича на мою голову (также широко используется понятие bus factor — количество людей, которых должен сбить автобус по дороге на работу, чтобы выполнение задачи было нарушено), я застраховал себя самого в будущем. Ведь этим другим разработчиком вполне могу оказаться я. Всегда может прибежать менеджер и сказать, что есть более срочная задача, которой надо заняться. Займёт она, допустим, пару недель. А вернувшись к этой, я уже могу и не вспомнить подробностей. Придётся шариться по переписке, вспоминать нюансы и так далее. И этот случай не гипотетический, поэтому я рекомендую сохранять контекст задачи в виде комментариев, не только в части постановки, но и в части прогресса: что сегодня удалось сделать, какие проблемы всплыли, какие решения приняты, какие договорённости достигнуты, с кем и почему.
Бывает и так, что через полгода к тебе приходят и спрашивают: «Помнишь, ты делал задачу, а почему решили вот так? Вы же нам поломали вот эту функциональность». И если я постарался подробно сформулировать задачу, то будет хорошей практикой сразу после этого попросить всех заинтересованных прочитать описание и написать свой «ок» в комментариях. Или нажать кнопку Approve, в зависимости от того, как устроен процесс в вашей компании. В таком случае, если ко мне приходят с вопросами, я могу на них ответить. — Кто сказал, что делаем так? — А вот, тимлид сказал. Иногда бывает смешнее: смотришь в задачу, а сказал это тот, кто сегодня с вопросом прибежал. Сам не подумал про граничные случаи, сам потом удивился.
Подведём итог сказанному. Чёткое описание задачи и согласование реализации с возможно большим количеством заинтересованных сторон на ранней стадии избавляет разработчика от потерь времени:
на переписывание функциональности, понятой не так;
на претензии и вопросы, требующие длительного поиска ответов в будущем;
на походы к смежникам с вопросами и необходимости потом вспоминать, кто что и почему сказал, почему выбрано то или иное решение.
Это не требует каких-то особенных способностей, нужно просто выработать привычку сначала убедиться в правильном понимании желаемого результата, и только потом двигаться к его достижению. Самое сложное — заставить себя поверить в то, что затраты времени на выяснение подробностей и их документирование себя оправдывают. Но по опыту могу сказать, что лучше тратить много времени на согласования и выяснения подробностей, чем на разработку функциональности, работающей не так, как нужно.
Test Driven Development
Следующий очень важный аспект — Test Driven Development. Тема вызывает очень много споров, но не для меня. Впервые я попробовал TDD где-то в 2010 году, и с тех пор не раз убеждался: использование TDD влечёт за собой снижение показателя «time to market». Напомню кратко суть процесса, а потом разберёмся в деталях, которые определяют, к чему приведёт написание тестов для вашего кода, — к ускорению доставки работающей функциональности до пользователей или к написанию очередной статьи, почему тесты это плохо.
Test Driven Development подразумевает написание сначала теста, а потом кода, который проверяется этим тестом. Последовательность такая:
Пишем один тест, запускаем его, тест кончается неудачей (это важно).
Пишем код, минимально необходимый для успешного выполнения теста.
Пишем следующий тест, запускаем ВСЕ тесты, один не проходит.
Меняем код так, чтобы все тесты проходили.
Повторяем, пока вся функциональность не будет готова.
Это классика, вокруг которой обычно идут жестокие холивары. На что надо обратить внимание, чтобы извлечь пользу из методики TDD:
Перед началом разработки фичи запускать все тесты, чтобы убедиться, что они не поломаны. В командах со здоровой культурой тестирования такой проблемы не возникает, но часто можно встретить команды, в которых часть разработчиков считает, что тесты не нужны, и вообще не заморачиваются проверками. Кроме того, бывают зависимости от окружения и флапающие тесты (выполняющиеся нестабильно, чаще всего успешно, но иногда нет). Это всё плохие факторы, с которыми надо бороться, но такая борьба чаще всего ведётся не один день и лучше перепроверить. Если тесты сломаны, надо их починить, даже если это не ваши тесты. Лучший вариант — разобраться, кто сломал, и его попросить починить.
Тесты должны проходить быстро. Я всё ещё говорю о unit-тестах (unit testing, используется также термин модульное тестирование, но мне он не нравится, поскольку unit — термин почти однозначный, а модуль— нет) и TDD. Интеграционное тестирование может занимать и пять, и двадцать пять минут, но unit-тесты должны отрабатывать секунд за десять, хотя бы при частичном запуске сценариев, относящихся к разрабатываемой функциональности. Суть в том, что TDD эффективно работает, когда можно поправить одну строку в коде, запустить тесты, понять, что ничего не сломалось (либо что-то сломалось), поправить или написать ещё пару строк, и так далее. Это способствует концентрации внимания на задаче. Если тесты гоняются хотя бы минуту, то могут возникнуть две крайности:
Перевешивает соблазн пошариться по мессенджерам, пока «оно компилируется». Кончается всё большими задержками (ушёл на минуту, вернулся через пять — десять изменений сожрут час вместо десяти минут) и потерей концентрации.
Начинаем писать жирные тестовые сценарии, которые пытаются за один заход проверить несколько вещей, а потом пишем большие куски кода. Это усложняет сопровождение в будущем и, опять же, приводит к потере концентрации.
Тесты это тоже код, и они могут превращаться в говнокод. Этого надо избегать. Избегать повторений, стремиться к хорошей читаемости, к тому, чтобы человек, который через полгода будет читать эти тесты с намерением поменять функциональность, мог, во-первых, разобраться в намерениях — что проверяет данный тест-кейс, —, а во-вторых, легко изменил пачку тест-кейсов, относящихся к разным случаям тестирования одного кода. Этот пункт выполнить довольно сложно, но возможно, если об этом думать с самого начала. И очень важно его выполнять, иначе TDD превращается в ад.
Как сказанное ложится на начало карьеры? Необходимо приучать себя к написанию тестов как можно раньше. Теоретически обосновать пользу TDD крайне сложно. Если же начать использовать TDD на практике, то понимание приходит само. Я говорю сейчас о том, что лучше всего начинать писать тесты ещё на этапе изучения языка. Выполнять все упражнения, покрывая код тестами. Для этого не обязательно использовать сразу готовые тестовые фреймворки (их ведь ещё надо изучить). Можно просто писать тестовые программки, использующие разработанную функциональность, примерчики. Это занимает чуть больше времени, но зато под силу каждому. По мере изучения языка надо, конечно, переходить к использованию тестовых фреймворков. И в какой-то момент вы просто не сможете больше писать без тестов, потому что поймёте на собственном опыте, что TDD экономит время и силы.
Приходя в проект с историей, очень часто можно встретить либо полное отсутствие unit-тестов, либо подход, при котором функциональность пишется отдельно, а потом покрывается тестами. Конечно, в последнем случае покрытие будет не просто неполным, а катастрофически далеким от полного. Расскажу, как я действую в такой ситуации. Будет немного непонятно для тех, кто только начинает, но вернитесь к этому разделу, когда дойдёте до стадии «научился программировать, хочу научиться разрабатывать».
Когда я беру в работу задачу и понимаю, что мне необходимо изменить какой-то кусок кода, а он не покрыт тестами, я начинаю с рефакторинга. Сначала пишу хоть какие-то тесты, проверяющие небольшую часть функциональности при успешных исходах. В идеале стараюсь, глядя на код, понять, что может пойти не так, и написать тесты, которые покроют любые входные данные.
Часто это оказывается невозможно, потому что тестируемая функция занимает несколько экранов. Каждый if в ней вдвое увеличит необходимое количество тестовых сценариев, сложность очень быстро станет неконтролируемой, даже при удачных случаях, когда функция получает параметры извне, а не вычисляет их в ходе работы.
Так вот, уперевшись в такую ситуацию, я начинаю очень осторожно разбивать функцию на несколько маленьких, следя за тем чтобы заменять куски кода вызовами мини-функций без потери эквивалентности. Буквально делаю коммит за коммитом, каждое изменение начинается из состояния «все тесты работают» и заканчивается им же. Важно не разбивать сразу в один заход, а понемногу подтачивать, выносить кусочек за кусочком. Иначе эквивалентность с большой вероятностью потеряется, а это больно. Надо гарантировать, что новый код работает точно так же, как и старый, просто написан по-другому. Не надо чинить обнаруженные баги и оптимизировать что-то на этом этапе.
По мере разбиения на мелкие функции (хорошо порезанная функция видна целиком на экране ноутбука) я покрываю их полноценными тестами, стараясь, чтобы входные параметры позволяли без тяжёлых операций (походов в сеть или на диск) протестировать важнейшие вариации входных данных и ожидаемых результатов.
В какой-то момент тест-кейсов становится довольно много и я начинаю видеть закономерность в их написании, позволяющую заменить их табличными тестами, в которых входные данные и желаемый результат описываются простыми структурами, а процесс подготовки, выполнения кода, сравнения результата с ожиданиями заворачивается во вспомогательную и достаточно универсальную функцию. Это позволяет в будущем при изменении функциональности вносить изменения в большой набор сценариев с минимальными мучениями. При правильном использовании TDD такое состояние достигается автоматически. При написании тестов после кода (это не является TDD, хотя относится к тестированию) к идеалу можно приблизиться, но чаще всего результат оказывается хуже. И дольше, конечно, но отсутствие тестов ещё хуже.
На этой стадии получается код, покрытый тестами, который работает так же, как и до внесения изменений. То есть задачу я делать ещё не начал, но провёл рефакторинг. После этого я начинаю работать по канонам TDD. Добавляю или меняю существующие сценарии — они, естественно, падают при запуске. Я меняю код и постепенно довожу задачу до логического завершения. Все коммиты, включая промежуточные, я делаю в состоянии, в котором тесты проходят успешно. Исключения случаются, но чем их меньше, тем лучше. И у меня обязательно есть коммит, который отражает состояние сразу после рефакторинга.
Может показаться, что это слишком сложно. Особенно начинающему разработчику, которому задачи достаются не сильно мудрёные, часто связанные с изменением одной строчки кода или разработкой простой утилиты. Тем не менее привыкать к хорошему я рекомендую с самого начала. Даже самые простые задачи с непривычки заставляют помучиться с тестированием. У новичков возникает множество вопросов из серии «зачем так сложно». Однако, полезнее и эффективнее учиться именно на простых задачах. Даже они часто «стреляют» с точки зрения внезапных изменений в постановке задачи (начинали делать одно, но в процессе переиграли, и теперь надо сделать другое) или неочевидных частных случаев. На таких задачах как раз и приходит понимание того, как должно выглядеть TDD здорового человека.
Попытки внедрять TDD в запущенных проектах при решении сложных задач у новичков почти всегда обречены на неудачу. Тем не менее, даже если проект, команда или менеджмент не позволяют использовать TDD в полном объёме, определённые выгоды из тестирования можно для себя извлечь в любом случае.
Повторяемость результатов и воспроизводимость окружения
Код, прежде чем попасть на серверы, обслуживающие живых пользователей (известные также как прод, production, живое или бой), запускается на машине разработчика в различных окружениях: CI, тестовых и препродовых (staging) стендах. Бывает, что разработчик работает на ноутбуке под Windows или MacOS, сервер на Linux, а тестовые окружения — обрезанный Linux (образы для Docker или Kubernetes).
В зависимости от языка, версий установленных библиотек, зависимостей, параметров ядра, различных железок, настроек сети, содержимого конфигов и т.д. один и тот же код может работать очень по-разному. Соответственно, очень часто случается так, что разработчик приносит админу новую версию софта, админ начинает катить на прод, а код не катится. В лучшем случае просто падает при попытке обновить пакет или образ,