Рефакторить или не рефакторить?



Мне нравится рефакторинг. Нет, не так. Я люблю рефакторинг. Не, даже не так. Я чертовски люблю рефакторинг.

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

Если вы хоть немного разделяете мои ощущения, то нам есть о чём поговорить. Дело в том, что со временем что-то внутри меня начало подсказывать, что рефакторить всё подряд, везде и всё время — не самая лучшая идея. Поймите меня правильно, код должен быть хорошим (а лучше бы ему быть идеальным), но в условиях суровой реальности не всегда разумно постоянно заниматься улучшением кода. Я вывел для себя несколько правил о своевременности рефакторинга. Если у меня начинают чесаться руки что-нибудь улучшить, то я оглядываюсь на эти правила и начинаю думать: «А действительно ли сейчас тот момент, когда нужно нарефакторить?». Давайте порассуждаем о том, в каких же случаях рефакторинг уместен, а в каких — не очень.

Дисклеймер. Скорее всего, многим захочется после прочтения поста сразу сказать: «Да это уже 600 раз обсуждалось!» или «Это же настолько очевидно, зачем же об этом писать?». Возможно, вы и правы, но только вот какой момент: в окружающем мире по-прежнему творится хаос. Вроде бы всем всё понятно, но на деле получается, что не так уж и понятно. Поэтому я думаю, что не будет слишком вредно ещё разок взглянуть на эту тему. Но если конкретно у вас проблем с рефакторингом нет, то можете просто пропустить этот пост, у вас уже всё хорошо.

Слишком ранний рефакторинг

Можете ли вы припомнить, когда у вас последний раз было постоянное ТЗ, которое месяцами не менялось? У меня вот такое вспомнить не очень получается. Мы живём в реальном мире, требования всё время меняются. Причём это не обязательно внешние требования — это могут быть ваши собственные требования к проекту. Поясню мысль на примере: допустим, вы взяли задачку среднего размера на один-два дня. Первые несколько классов уже написаны, но запустить пока нечего — идёт процесс написания суровой архитектурной части. И тут вы замечаете, что одна из частей проекта написана не особо универсально: «А вот если через полгода понадобится сделать X, то все будут страдать». Вполне разумно, что вам не хочется отправлять в репозиторий хреновый код, чтобы другие разработчики потом вспоминали вас плохим словом. И вы начинаете рефакторить ещё не готовую фичу. Иногда это оправдано, но на подобном пути следовало бы повесить табличку «ОПАСНОСТЬ». Вот поправите вы одну штуку, потом другую, потом третью. Неделя прошла, фича всё ещё не запускается, а вы говорите: «Как-то всё неправильно сделано. Но теперь я точно понял, как надо делать. Сейчас быстренько всё перепишу с нуля». Основная проблема заключается в том, что фидбека по фиче ещё не получено, а вы уже начали работать над улучшением кодовой базы. Подобный подход редко приводит к успеху. Не знаю, как у вас, а у меня часто бывает, что после реализации фичи я начинаю понимать, что работать всё должно несколько иначе. И это не из-за того, что я такой глупый, заранее не смог нормально продумать. Просто некоторую функциональность нужно «пощупать», чтобы понять как всё должно быть в релизе. Иногда нужен небольшой прототипчик (пусть даже с говнокодом и багами), чтобы обсудить фичу с коллегами. Иногда нужно что-то показать заказчику, чтобы он мог сказать: «Не, ну я не так хотел, всё должно быть наоборот». Порой пользователями не нравятся нововведения, они хотят всё как было. Проблема новых фич в том, что сложно предсказать их судьбу. Нередко случается так, что все наработки отправляются в помойку, т. к. после обсуждения первой версии коллектив принял решение делать всё иначе. Общий вывод: не стоит рефакторить код слишком рано, особенно если вы не уверены, что этот код 100 % останется в проекте.

Нецелевой рефакторинг

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

Рефакторинг ради рефакторинга

Ок, вы пришли к выводу, что нужно обязательно отрефакторить часть проекта. Хорошо, давайте отрефакторим. Вроде бы запланированные улучшения выполнены, но тут возникает мысль: «А что я могу ещё улучшить? Ага, вон ту штуку». А после вон той штуки появится вот эта штука, а потом ещё одна, а потом ещё и т. д. Нужно понимать, что есть плохой код, есть хороший код, есть идеальный код. Последнего в большом проекте у вас никогда не будет. Это не значит, что не нужно к нему стремиться, но нужно понимать его недостижимость. Обычно задача стоит в написании хорошего кода, а не идеального. Допустим, после рефакторинга у вас получился вполне читаемый код, который работает более или менее очевидным образом, в котором нет костылей и которым не так сложно пользоваться. Задайте себе вопрос: «А может, пора остановиться?». Да, код можно улучшать. Причём в достаточно большом проекте его можно улучшать до бесконечности. Но вот прямо сейчас он справляется со своими функциями, им удобно пользоваться, он практически не вызывает у вас дискомфорта. Очень важно определить для себя приемлемое качество кода, после которого вы перестанете его улучшать (до тех пор, пока свойство приемлемости не будет утрачено). Вспомните, что есть ещё так много разных клёвых штук, которые можно дописать. Не нужно рефакторить ради самого рефакторинга, ради идеального кода. Нужно рефакторить, когда у вас есть веские причины на это: код сложно прочитать, код сложно поддерживать, код сложно развивать, код сложно использовать и т. п. Если ни одного «сложно» не возникает, то веских причин тратить время на рефакторинг у вас нет.

Рефакторинг за день до релиза

Бывает так, что релиз послезавтра/завтра/сегодня/должен был быть вчера (нужное подчеркнуть). Это важный момент в жизни проекта. Нужно уделить особое внимание тестированию, фиксам критических багов, финальным доделкам. Поверьте, это действительно плохая идея — перерабатывать кодовую базу (а ещё хуже — качественно перерабатывать) в тот момент, когда нужно отдавать проект в продакшн. Опытная практика подсказывает, что нужно зарелизиться, а потом с чистой совестью спокойно улучшать код. Некоторые спросят: «А почему?». Если такой вопрос возник, то, наверное, вам ещё не приходилось делать сложный рефакторинг. Подскажу: при переписывании код не всегда обязательно улучшается — иногда он может сломаться. Да что там сложный рефакторинг — бывает, поправишь один метод на пять строк, не уследишь за какой-нибудь зависимостью, а на другом конце проекта выползет бага, с которой сразу же встретятся ваши любимые пользователи. Вот вроде бы ничего плохого и не делаешь, а тут внезапно на тебя нападает зверь «Это было неочевидно» и топит тебя в пруду ложной первоначальной оценки. Хотя, может, это просто я такой плохой программист — люблю что-нибудь сломать. Вполне возможно, что вы всегда рефакторите всё абсолютно правильно и с полным контролем всего проекта. В таком случае я могу вас поздравить, но от совета с запретом предрелизного рефакторинга всё-таки не откажусь. Поверьте, за несколько дней рефакторинг никуда не убежит, а сон у всей команды будет чуточку, но спокойней.

Рефакторинг очень старого кода

Вопрос тяжёлый, очень тяжёлый. Ситуация: есть огромное количество ужасных строк кода, которые достались вам от старых разработчиков (возможно, этими старыми разработчиками были вы несколько лет назад, ещё до того, как научились писать всё правильно и сразу). Код приходится поддерживать. То там, то тут возникают костыли и дублирования, энтропия растёт. С каждым днём всё больше хочется выкинуть всё и переписать с нуля. В такой момент нужно очень хорошо подумать о рисках. Да, вполне вероятно, что в перспективе такая деятельность будет полезна. Но в какой именно перспективе и насколько полезна? Скорее всего, в процессе большого рефакторинга или переписывания отдельных частей вы замените старый работающий говнокод новым, идеально написанным кодом, но с багами. И вовсе не из-за того, что вы плохой программист и пишете плохо. Просто вы можете не знать этот код в достаточной мере. Вы можете не знать, почему автор написал всё именно так, а ведь причины могли быть. Иногда приходится писать очень странный и кривой код. Я могу придумать очень много примеров: подавление хитрых оптимизаций процессора, подстройка под баги сторонней библиотеки, подавление каких-нибудь многопоточных косяков и т. д. Я не говорю, что нельзя все эти проблемы решить нормально. Просто иной раз при переписывании казалось бы абсурдного кода на нормальный появляются баги. Да, можно было всё сделать нормально, но вы можете просто не осознать всё величие шалаша из костылей вместо палок, если не узнаете у автора кода, почему он написал именно так (а подобная возможность предоставляется далеко не всегда). Будьте осторожны, когда переписываете старый код, который понимаете не до конца (а особенно, если думаете, что понимать там нечего).

А когда рефакторить-то?

Я прошу прощения, если из этой статьи у вас сложилось впечатление, что от рефакторинга одни проблемы. Я всё ещё настаиваю на том, что код должен быть читаемым и красивым, им должно быть удобно пользоваться, его должно быть легко развивать. Позитивный подход лучше негативного, так что относитесь к рефакторингу не как к источнику проблем, а как к своему очень хорошему другу, который выручит вас в трудную минуту. Более того, этот друг может сделать так, чтобы в вашем светлом будущем трудных минут было бы поменьше. Хотелось бы в завершении обозначить несколько моментов, когда рефакторинг действительно уместен.

  • Нечего делать. Бывают спокойные времена простоя, когда все важные задачи закрыты, а новых пока не поставлено. Ну, не то чтобы совсем нечего делать, но определённое количество свободного времени есть. Потратьте его на улучшение кода. Дайте сущностям понятные имена, избавьтесь от дублирования, перепишите неудачный кусок архитектуры. Хоть нового функционала и не добавляется, но вы вкладываете свою лепту в душевное спокойствие разработчиков, которые будут продолжать проект. Поверьте, это важно.

  • Каждодневные страдания. А бывает так, что есть кусок проекта, который заставляет вас тяжело вздыхать каждый день. А из-за соседних столов слышатся тяжёлые вздохи ваших коллег. Конечно, хоть и релиз и не завтра, но важных задач хватает. Тем не менее, за неделей проходит неделя, а смотреть на этот кусок проекта всё грустнее. Скажите себе: «Хватит это терпеть!». Если бизнес-план составляет начальник, то объясните ему, что этот код просто необходимо переписать. Если работаете на заказчика, то убедите его, что потраченная на рефакторинг неделя позволит сэкономить много времени на разработку в будущем. Если работаете на пользователей, то примите решение о том, что лучше бы этим пользователям в этот раз подождать релиза на недельку больше, но зато потом наслаждаться стабильным ПО и регулярными обновлениями. Да, договориться со всеми и с самим собой не всегда просто, но вы уж постарайтесь.

  • Проблема запоздалого рефакторинга. Не стоит абсолютизировать правило про слишком ранний рефакторинг. Некоторые считают так: «Я вот сейчас наулучшаю чего-нибудь, а оно не пригодится — обидно же будет». Нужно понимать, что в ядре программы вполне могут быть важные части, которым лучше бы быть написанными хорошо. Помните, что чем позже вы проводите рефакторинг — тем выше его цена, ведь вы потратите больше времени и сил на переписывание. Критические базисные вещи (которые используются по всему проекту) должны быть в максимально хорошей форме постоянно. Будет просто прекрасно, если в вашей команде будет работать тезис «Рефакторинг не опаздывает. Он приходит строго тогда, когда считает нужным».

  • Встреча с монстром. Вы начинаете пилить новый функционал, который должен использовать старый кусок проекта, который выглядит как настоящий монстр: вам становится страшно только при взгляде на внешний интерфейс. Если время позволяет, то поправьте сперва базовую часть кода, чтобы потом сосредоточиться на новом функционале и не отвлекаться на то, чтобы вбить пяток костылей ради повторного использования кода.

  • Разумный перфекционизм. Заметили плохой кусок кода? Хочется его поправить? Очень хочется его поправить? Ну, если прям очень хочется, то можно. Но обратите внимание на слово «разумный». Соотносите время, потраченное на рефакторинг, с выгодой от улучшения кода. Не стоит срывать сроки и зарываться в бесконечные улучшения. Однако если своевременно и в меру заниматься рефакторингом, то проекту от этого станет только лучше.

Вместо заключения

Всё вышеперечисленное является чисто субъективным обобщением опыта работы над рядом проектов. Разумеется, я покрыл далеко не все жизненные ситуации. В каждой команде свои требования к коду, свой бизнес-план и свои правила. Уверен, что у многих найдётся пяток историй из серии «А вот у меня был случай, когда все эти советы не работают». Это абсолютно нормально, так и должно быть. Нет универсальной серебряной пули для определения количества усилий на улучшение кода («Мы будем каждый день 47 минут 23 секунды заниматься рефакторингом — и всё у нас будет хорошо»). Вам нужно исходя из собственного опыта в вашем конкретном проекте, в вашей конкретной команде попытаться найти золотую середину между написанием нового кода и улучшением старого. Я агитирую только за то, чтобы ко всему было рациональное отношение без фанатизма («Зачем улучшать код, нового функционала от этого не появится» / «Нужно срочно весь код сделать идеальным, чтобы потом с ним можно было нормально работать»). Подходите разумно к распределению времени на работу над существующим кодом — и всё у вас будет хорошо.

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

Оригинал на Хабрахабре

Поделиться:
Исходный код поста находится на GitHub:
https://github.com/AndreyAkinshin/aakinshin.net/blob/master/ru/_posts/dev/refactoring.md