Несколько месяцев назад я работал в аутстафе. Это не то место, где нужен энтузиазм и вера в великую цель проекта. Меня вместе с командой просто продавали заказчикам, а на митингах было важно, сколько тикетов я закрыл. Приступы перфекционизма — скорее вредная штука для такого места, но я ничего не мог с ними сделать. За один из них я знатно поплатился, попал в адский кранч и провел худшие две недели в моей жизни.
Моя компания попросила меня приехать на две недели в Москву, в главный офис, поработать вместе с командой. Просьба разумная, я согласился. За несколько дней до отъезда я взял очередной тикет. Нужно было добавить фичу к SDK для десятка проектов, над которыми мы работаем. Эта SDK — непролазное легаси, которую в здравом уме никто не трогает. Там такое болото, что даже прожженные инженеры суеверно крестятся — лишь бы все работало, как работает. Когда нам нужно что-то пофиксить или добавить фичу, мы осторожненько подпираем код ещё одним костылём, и не дыша отправляем её в руки CI и QA.
Я планировал поступить так же. Суть задачи была простая. Сама SDK — обёртка над очень умным сишным бинарём, который умеет поднимать VPN. Она же общается с нашим беком, получает всякие конфиги, авторизует пользователя, фетчит список стран для VPN и так далее.
Мне просто нужно было добавить ещё один запрос на бэкенд, который получает список серверов. Выглядит это крайне просто — добавил к публичному API метод, положил внутрь запрос на нужную урлу, сляпал модель, распарсил, отдал. Потом научил клиентские приложения использовать этот запрос, и всё готово. Я проэстимировал таску в два дня.
Очень большая ошибка. Не бывает простых задач. Одним глазом я уже перебирал московские бары, где мне надо выпить все пиво, другим — увидел первую проблему. Мы не просто получаем список серверов, мы ещё используем один из них для того, чтобы поднимать VPN-туннель. Раньше мы использовали для этого только страны. Но какой-то гений решил, что будет отличной идеей хранить страны как список строк. Само собой, сущность VPN node — слишком ёмкая штука для того, чтобы уместить её в одну строку.
В итоге в SDK набралась куча методов, которые принимают строку (код страны) и что-то там делают, а теперь все эти методы должны принимать код страны, или объект сервера — и что-то там делать. Всё, что мы делали в коде SDK и приложений со странами, теперь нужно делать и со странами, и с серверами.
Когда я это понял, то почему-то обрадовался. Скоуп задачи теперь позволял мне слегка отрефакторить болото, полное говна, в котором я всё время плаваю. Я заявил о своем желании, и теперь точно знаю — мне дали добро с выражением, как будто впарили инвалидку по цене Мазератти. Я согласовал недельный бюджет и принялся за дело.
До отъезда была пара дней. Неделя же выглядела как целая бесконечность. Я решил, пока не буду писать код, просто утрясу решение в голове, а как приеду, быстренько все раскидаю.
Когда нормальный человек едет в командировку, он заранее покупает билеты, решает вопрос с жильём, предупреждает жену, и вообще всё делает правильно. Я недальновидный идиот, который поперся в Москву с женой и двумя детьми. Жену пришлось брать, потому что я рассказал ей про поездку за три часа до отъезда. За два часа — только начал искать водителя с минивэном, потому что моя машина сломалась. Квартиру я снял в тот же момент. Шесть часов пути с орущими детьми и недовольной женой закончились так, как и должны были — квартира оказалась ровно в две тысячи раз меньше, чем выглядела на фотках. Хлеще всего был запах — там воняло травой. Горничная, которая нас принимала, сказала, что это они травили тараканов (тараканов!!!). Жена ей поверила, а говорить жене, что я отличаю запах травы от тараканьего яда, не в моих интересах.
Я пришёл в офис, мне выдали ноут. Пол дня я устанавливал среду, со всеми знакомился. Когда всё скачалось и установилось, начал работать. Злоба от ужасного переезда еще не остыла. Я открыл проект с SDK, в очередной раз крепко выругался про себя, и подумал: «А какого хера? Да тут всё к чертям собачим надо выкидывать, и переделывать».
Я сказал об этом лиду, а он, конечно, ответил: «Согласен, фигачь». Проект на 15 тысяч строк, каждая из которых — преступление против человечества. Я собрался его исправить за две недели. А будь у меня десять часов дороги и трое детей, что бы я предложил лиду? Изобрести за неделю новый ЯП специально для проекта?
Но мне показалось, если я приведу в порядок хоть что-то — мне станет легче. Сейчас распинаю этот говнокод, сделаю всё как надо, заживём. Я когда-то читал книги про то, как нужно производить глубокий рефакторинг. Там было много умных мыслей про то, что ты расписываешь шаги, пишешь тесты, продумываешь свои действия так, чтобы на каждом шаге иметь уверенность — проект работает, ничего не сломано. Книги и лучшие практики, это очень круто, но я никогда не учусь на чужих ошибках. В самом начале карьеры я очень много читал про всякие базовые штуки, но не верил в них, пока мне не понадобилось расширить функционал своего первого проекта.
Задача любых граблей — дать тебе по лбу. Пока твои неудачные решения не насрали тебе лично, ты не будешь считать их неудачными.
Свой рефакторинг я стал делать не опираясь на практики. Я мысленно разделил проект на большие логические куски. В его структуре файлов это не было отражено, поэтому я снёс эту структуру файлов к чертям собачьим. Насоздавал папочек верхнего уровня: вот тут у нас работа с бэком, тут работа с VPN, тут хелперы, тут исключения. Существующие юнит тесты я тоже забраковал. Тогда мне казалось, что говнокод не имеет права на жизнь, а сейчас я понимаю — я выпрыгнул из самолёта, и не взял парашют, потому что его уложили с нарушением инструкции.
Я начал переносить говнокод из старых папочек в новые, попутно приводя его в порядок. Ничего особенного, просто следуешь хорошим практикам. Есть класс, описывающий модель пользователя, у него все свойства опциональные, все с сеттерами. Превращаешь его в пацанскую иерархию наследования. Всё ридонли, всё через конструктор. Так, чтобы пользователи этого класса всегда точно знали, что у них в руках. Есть говнометод на четыреста строк — бьешь на сабметоды. Видишь файл с тысячей обязанностей — превращаешь в тысячу файлов с одной обязанностью. Есть класс, у которого метод Initialize, который всегда надо вызывать сразу после создания класса — применяешь паттерн «состояние»
Понятия хорошего и плохого кода очень размытые. Я всю свою жизнь буду учиться различать их. Но вот откровенно дерьмовый код я могу видеть уже сейчас. Видеть и исправлять. Я старался не сильно перелопачивать архитектуру верхнего уровня и как можно меньше менять внешнее API SDK.
Задерживаться на работе на пару часов, когда тебя ждёт жена в чужом городе, в самой худшей халупе в мире, а у неё двое маленьких детей, которые от стресса ревут не переставая — плохое решение. Я задержался на семь часов. В первый же день работы я просидел в офисе до часа ночи и переписал от силы процента два.
«Дома» меня ждал заслуженный скандал, утром — дерьмовый растворимый кофе. Я снял квартиру в пяти минутах пути от офиса, но мне было так странно и страшно в этом ужасном городе, что первые три дня я ездил на работу на такси.
В офисе все были очень радостные — народ собирался пить вечером. У этих москвичей так каждый день. Меня тоже позвали. Я обещал постараться, хотя сам точно знал — нет опций. Снова принялся за работу.
Тогда мне казалось, что во всем бытовом дерьме виноват я сам, потому что пустил дела на самотек. Надо было лучше искать квартиру, лучше готовиться, предусмотреть все проблемы с семьей и бытом. Туча проблем, которых могло не быть. Взглянув на код я подумал — вот к чему приводят полумеры.
Быстро решил, что мой вчерашний подход слишком компромиссный. Если уж переделывать, то вообще всё. Я перестал ограничиваться переименованиями и разбиениями. Каждый файл, который я собирался переработать, подвергался тщательному анализу. Я уже довольно долго на этом проекте, я хорошо понимаю, чего тут хочет бизнес, и что должен делать наш код.
Но здесь я стал постоянно встречать код, который я не понимаю — какую проблему он решает и зачем вообще был написан. И я принял решение — если я не понимаю, зачем нужен код, значит код не нужен. Я с радостью стал отправлять этот цифровой мусор в преисподнюю.
Такой подход породил новые проблемы. Публичное API проекта было неправильным. Там было много методов c нелепой сигнатурой (например метод принимает строку и объект, который содержит ту же строку). У многих методов были идиотские названия. И в целом, если используешь эту SDK с тем API, которое у неё было — можешь сломать работу приложения десятью тысячами способов.
К этому моменту я зашёл уже достаточно далеко. Проект не собирался, и не было шанса, что он будет собираться в ближайшие несколько дней. Всё, что я в нём меняю, я смогу проверить только в конце работы. А раз я поменял уже так много, почему бы и не поменять тогда публичное API? Хорошо, мне хватило ума сделать копии репозиториев с SDK и приложений, так я мог дублировать изменения API в этих копиях и находить ошибки. Ведь основную версию SDK я даже не мог скомпилить.
Проблемы начались сразу. Изменил сигнатуру одного метода? Лови плюс две сотни ошибок времени компиляции в приложениях. Из-за неправильных сигнатур методам передавались неправильные параметры, а приложения работали. При этом ты работаешь в четырёх окнах IDE. После того, как я поменял три метода, и вылечил все ошибки с ними, подумал — раз работа уже проделана, глупо будет поменять только часть дурацкого API — надо переделывать дальше. И полез в процесс еще глубже.
Мне постоянно приходилось принимать решения, по большей степени этические. Вот я знаю, как должно что-то работать, и вот я вижу код, который должен это делать. Я читаю его и понимаю, что он этого не делает. Но этот код в проде, он прошёл QA. И кто тут дурак? Как я могу брать на себя эту ответственность? Поначалу я долго думал над каждым таким кейсом, консультировался с лидом и командой. Но это занимало слишком много времени.
Я понял, что я работал медленно, потому что боялся что-то сломать, пытался где-то разобраться, быть аккуратным. Тогда я сказал себе: «Я умнее чем тот, кто писал это говно, я знаю, что должно быть сделано, у меня есть задача и бюджет на рефакторинг и я имею право на всё». Разбираются и переживают слабаки, мужчина знает чего хочет, и делает, не задавая лишних вопросов.
В какой-то момент я вошёл в странное состояние робота, который методично перерабатывает один файл с кодом за другим. Ничего не проверяя, ни в чём не сомневаясь. Вижу плохой код — меняю. Это по цепочке поднимает новые проблемные места, чиню их. В каждом заменяю говнокод на что-то сносное.
Это похоже на блиц в шахматах. Ты играешь в игру, где самое главное — продумывать все возможные варианты, но у тебя так мало времени, что ты не можешь даже подумать, какие варианты стоят того, чтобы продумать их. Я всегда проигрываю в блиц. За второй день я сделал очень много. Вот только код, который ты не можешь проверить — это не код. И я об этом знал.
В один из вечеров я вспомнил, что обещал жене съездить в Икею. Квартира, в которой мы жили, была ужасна, и жена хотела напокупать всякой уютной херни. Я весь день писал код, мой
Вдавил тормоз. Естественно, не той ногой, ведь в дурацком автомате всего две педали, и мой
Кое-как я выполз из тупичка, и понял — вокруг меня все машины мира, а я понятия не имею, куда и как мне ехать. Я доехал до «дома», забрал жену, полночи ездил по самому ужасному городу на земле, пропустил десяток поворотов, оставил целое состояние в икее.
Мы вернулись часа в четыре утра, мой
В восемь утра я снова был на работе. Там снова все счастливые, обсуждают свои московские дела и бары, до которых мне не добраться. У меня снова SDK. С тяжелой головой я бегло просмотрел изменения, которые наделал. Всё очень плохо. Уже завтра митинг по таске, а мне нужно ещё три сотни лет, чтобы всё закончить.
У меня есть хорошее умение — когда я получаю большой тикет, я всегда декомпозирую его на несколько маленьких. Отсюда моё рабочее флоу. Один тикет, один-два коммита, один пулл реквест. Поэтому я в принципе не умею в процессе кодирования разбивать задачу на несколько коммитов — мне это обычно не нужно. Но стресс меня добивал, и умение подвело. В процессе работы я превратил маленькую задачу в гигантскую, но начал над ней работать так, как работал бы над маленькой. Без системы, тупо где увидел проблему, там и починил. Не стал дробить процесс на логические шаги, а просто как последний джун хватался за всё сразу.
Сделанного не воротишь. Главная проблема оставалась — я не мог собрать и запустить всё решение, и понятия не имел, как оно заработает. Это очень пугало, в последний день я посмотрел на диффы и понял, что я переписал почти весь проект. Было чертовски страшно.
Все всегда сползает на последний день. Командировка кончалась, мое растянутое время на рефакторинг — тоже. Жена позвонила, и сказала, что нас выселяют в час дня, хотя я договаривался на поздний вечер. Естественно, это означало, что доработать последний день до конца я не мог.
Вместе с тремя галлонами холодного пота до меня доходило то, что должно было дойти, когда я взялся за этот таск, задумал рефакторинг, согласился на командировку, взял детей, снял притон на две недели — я самый главный идиот на всем белом свете. Разбираться в моих мотивах стоит психиатрам, но я решил идти до конца. То ли чтобы самому себя наказать, то ли на какой-то иррациональной вере, что все делаю правильно.
С горящей жопой я мёржу всё за час, запускаю CI, и сваливаю домой. Всю дорогу я прокручиваю в голове свой код, и я абсолютно уверен — ничего не выйдет. Слишком много изменений, будет сотня багов и мы откажемся от этого рефакторинга.
Когда я вернулся домой, меня ждала новость — проект работает. Лид поправил пару мелочей, мои изменения прошли автотесты, и прямо сейчас успешно проходят ручное тестирование. Я не поверил. Спулил код на домашнюю тачку, собрал и вручную проверил все сценарии, которым не доверял. Оно работало. Работало!!!
Я был так горд собой. Качество кодовой базы выросло в разы, появилось несколько фич, я выпилил кучу багов, до которых ещё не успели добраться тестеры. Либой можно было комфортно пользоваться, а я доказал себе свою профпригодность.
Порывы энтузиазма и самопожертвенного труда — удел наивных глупцов. Я был бы рад доказать себе этой историей обратное. Но через несколько дней после возвращения меня ждала еще новость.
Заказчик решил отказаться от проекта. Почти сотня человек в аутстафе (половина всей компании) оказалась «в ожидании нового проекта». Считай — лимбически безработными. А удаленщиков при таких делах режут в первую очередь. Мне сказали переезжай в Москву насовсем и переходи на другой проект. Либо мы тебя уволим.
Я спросил, что будет со старым проектом.
В такие моменты люди становятся болезненно честными. Лесть, приукрашивания и корпоративная этика становятся никому не нужны. Мне сказали, этот код больше никому в целом мире не нужен, отправлен на свалку истории. Самими продуктами ещё очень долго будут пользоваться, но поддерживать кодовую базу не станет никто. Всё вот это качество и читаемость кода, масштабируемость, удобство в использовании, однозначность поведения — всё сделано зря.
Я слушаю и понимаю, что мог просто сделать фичу, и ходить по барам. Но я сидел ночами, не спал, терпел скандалы ради хорошей кодовой базы, которая больше никому не нужна. Энтузиазм — лживая штука. Ты говоришь, что готов работать, даже если в этом нет выгоды. Но когда тебе говорят, что твоя высшая безвозмездность действительно не приносит выгоды — это почему-то убивает. Ты-то надеялся изменить своим порывом всю вселенскую неправильность.
Мне кажется, я все отлично понимал с самого начала. Просто я не хотел быть прагматичным парнем.
Поэтому я сказал, что могут увольнять меня к чертовой матери, а в сраную Москву пусть перевозят своих мамаш, чтобы их жирные задницы порвали нерезиновую на миллиард маленьких ошметков. Вежливо об этом написал, и остался дома. В мире хватит тасок, которые просто надо сделать.
Автор: Philipp Ranzhin