Не бойтесь кода

Всем привет.

Многие проекты с которыми я работал, крупные проекты, превратились в легаси, нераспутываемую бороду, и в итоге развитие становилось крайне сложным. Были случаи, когда проекты просто умирали из-за адской сложности внесения изменений. Сегодня расскажу про мой опыт работы с проектами, и зачем я вообще всё это пишу вам. Всё очень субьективно, конечно.

Так вот, я работал, и я старался, как моряк который хочет выгрести воду и не дать кораблю затонуть, но качество проекта неуклонно падало, и причин было несколько.

Вы их легко узнаете, это постоянные пациенты, просто напомню про них:

  1. Архитектура хромает, внедрить новое решение сложно
  2. Ошибки кодирования
  3. Отсутствует автоматическое тестирование

В итоге, по результатам разработки проекта в течении первых 1-2 лет получается смесь из легаси, хренового кода, запутанных тоннелей с потайными ходами, и наскальные рисунки аля «работает так, почему не знаю».

Я сильно на этом не останавливаюсь, это тема №1 в рефакторинге, поэтому сразу к выводам, которые я сделал.

Разработанный продукт это коллективная экспертиза


Если в команде есть 1 мега-разработчик, реально прокачанный, хорошо работает, то он один не выгребет, потому что команда постоянно делает ошибки и будет неизменно тащить проект вниз.

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

Поэтому радикального апгрейда качества без апгрейда команды ждать не стоит.

Это замкнутый круг №1 – экспертизы команды не хватает, но начинается рефакторинг, и делают его, внимание, те же посоны, которые это и написали.

Как вариант, можно поставить хорошего лида, который будет проводить ревью и эти ошибки выгребать (хоть постарается это сделать), но в итоге это закончится головной болью у тех кто работает хреново, и дальше либо замена разработчиков, либо они сами уйдут, и, снова, получится замена разработчиков.

Получится эдакий естественный отбор на проекте.

Люди боятся менять код


Добро пожаловать, это сеанс психологии для программистов.

Программисты реально боятся менять код, и их можно понять:

  1. Тестов не хватает или нет
  2. Как работает код не понятно
  3. Сроки горят
  4. Ресурсов нет
  5. Менеджмент не поймет (обычный, но если управление огонь, всё будет хорошо)

В итоге старый запутанный легаси код, который тащит проект вниз воспринимают за «это лучше не трогать», и в итоге это «не трогать» лежит веки вечные, и, что еще хуже, вынуждает писать код определенным образом, порождая другие технические проблемы, которые затем тоже тянут проект вниз.

Берем проект, хреновый код, страхи команды, умножаем на время, и получаем огромный технический долг, который встает ребром и не дает команде нормально быстро двигаться и развивать проект.

Проект начинает разваливаться, сроки срываются, новые фичи становятся дико дороже для проекта, разработчики начинают нервничать, некоторые уходят, новых найти сложнее, ну вы поняли…

Так вот, я считаю что это проблема №1, страх перед кодом и рисками.

По опыту замечено, что если оставляешь технический долг, то это потенциальная бомба, ну или хотя бы грабли. Оставьте их 100, 1000, и получите минное поле, на котором не то что идти (развивать проект), ползти не сможешь.

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

Совет огонь, все про него знают, но на деле список выше никуда не делся, поэтому нельзя просто взять и отрефакторить, потому что получите проект, который разваливается, а почему?

Потому, что нет тестов, как работает код не понятно, и в итоге вместо смены чертежей автомобиля и сборки получится что Вася и Петя взяли болгарку, распилили Солярис, и собрали обратно в Таврию, а она не едет. Почему? – ой, а потому что мы не знали про то влияние/поведение/задачи

Да, можно отрефакторить без тестов, и потом стабилизироваться, но может потеряться много пользовательских сценариев, или нужных сценариев выполнения кода, или стабилизация может занять очень долгое время.

Поэтому, еще один замкнутый круг – не трогать код нельзя, но и рефакторить его тоже, как бы, нельзя, потому что есть огромные риски.

Выходит, легаси нам не нужно, но избавиться от него страшно и опасно, поэтому команды часто оставляют легаси как «наименее опасный вариант», чтобы получить потом еще более опасное для проекта решение.

Тесты это ключ


Получается, чтобы разблокировать рефакторинг и сделать его безопасно, нужно сначала покрыть все кейсы тестами, и потом когда мы будет пилить наш солярис и собирать в Таврию, сварка остановится и скажет «алё, нам нужен солярис, ты накосячил вот здесь».

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

Поэтому получаем цепочку:
Тесты -> Рефакторинг -> Прощай борода и легаси

Звучит просто, красиво, но на практике тестов бывает мало. Или вообще не бывает, и причин тому несколько, как обычно:

  1. Разработчики считают тесты отдельной темой и не вкладывают в оценки, пишут отдельно от разработки. Еще сложнее, если так думает управление проекта и хотят урезать тесты чтобы сложиться в сроки.
  2. Тесты это время, а проект нужно сдавать сейчас, некогда писать нам тесты (это по идее тоже самое что и пункт №1)
  3. Проект/компонент простой, зачем там тесты, там всё предельно просто и работает?
  4. Сначала код напишем, потом покроем тестами. Но нет, руки не дошли, проект на месте не стоит, времени не нашлось. Так и лежит эта задача в черном ящике веки вечные.

Причин на самом деле миллион, но факт в том, что это блокирует рефакторинг, и, как следствие, не дает качеству расти вверх.

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

Что делать, Хьюстон?


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

Поэтому всем домашнее задание – возьмите плохо написанный модуль, компонент, что-нибудь, найдите там что вы хотели бы отрефакторить, напишите тесты для этого модуля или компонента, и сделайте рефакторинг.

В результате поймете что тесты это:

  1. Способ изучения кода. Может быть даже намного более эффективный, чем просто его чтение.
  2. Стабильность
  3. Старый код реально можно отрефакторить и поднять качество проекта

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

Всем желаю хорошего кода, тестов, и движения качества вверх – нас именно поэтому берут на работу, давайте работать хорошо.

P.S. Если есть желание, напишите ваш опыт рефакторинга в комментариях, всем будет интересно.
@yakimchuk-ry
18.11.2020 13:14 UTC
Первоисточник

Комментарии

@zloddey
18.11.2020 08:28 UTC
+1

Чем быстрее фидбек, тем быстрее можно получить опыт. Поэтому тренироваться лучше всего на достаточно маленьких кусках кода. Советы в статье правильные, но можно к ним добавить упоминание специальных упражнений — "кат" — для отработки приёмов в безопасных условиях.


Вот классический (но не единственный) пример: https://github.com/emilybache/GildedRose-Refactoring-Kata

@NightShad0w
18.11.2020 09:43 UTC
+1

И после того, как тесты написаны, выводы сделаны и рефакторинг проведет — удалите дублирующиеся, или не обоснованные бизнес-требованиями тесты и функциональность.

@bini1988
18.11.2020 10:36 UTC
+2

А что делать если достался проблемный легаси, который либо сложно либо невозможно покрыть тестами, поскольку писался он без соблюдения модульности, изолируемости и тестируемости компонентов системы?

@yakimchuk-ry
18.11.2020 10:44 UTC
+1

Это тяжелый случай. Я работал с таким – у нас был самописный движок, сотни тысяч строк кода, и полный ад с развитием и поддержкой продукта.


Мы тогда пытались сделать так – обойти грабли, из-за которых до кода было дойти тестами нереально. У нас это была инкапсуляция через локальные и публичные переменные. Создали дыру под тесты, чтобы можно было интегрировать тесты внутрь любого модуля с инкапсуляцией.


Дальше покрытие тестами, рефакторинг.


Но если честно, я видел такой код, просто страшно, и тестами покрыть почти нереально, его даже прочитать и понять было невероятно сложно. Один парень когда работал с ним, реально выпивал после работы, потому что его поставили на проект, отказаться нельзя, а работать с этим просто ад.


Тесты есть не только юниты, почти любой продукт в любом случае можно покрыть функциональными тестами, и рефакторинг в худшем случае сломает технические сущности (но если код уже с такими огромными проблемами, хуже, вероятно, не будет), но качество продукта с позиции клиента не упадет.


Если у кого-нибудь есть варианты лучше, отпишитесь, проблема насущная.

18.11.2020 15:15 UTC
+1
я видел такой код, просто страшно

К сожалению, я тоже иногда проекты с таким кодом встречаю. Подобные встречи вызывают все меньше страха но всё больше печали. Вроде бы интересный проект, а на деле под обёрткой продукта сгнившая мумия в техническом плане.

Нужно трезво оценить объективность страха всё это исправлять и материальную/временнУю осмысленность такого действия. Иногда дешевле действительно распечатать весь текущий код и сжечь, начав самые проблемные места реализовывать заново, с нуля, по современным стандартам в команде с подготовленными/опытными людьми. Возможно даже набрать команду заново, либо «позвать» из других проектов.

Также момент проблемного легаси стоит учитывать при поиске работы/проектов и понимать, что до момента реальной эффективности работника в проекте с нуля до осмысленного и полезного рефакторинга — может пройти не один месяц, а полгода, год, может даже больше. И всё это время будет постоянное ощущение прогулки по минному полю. Не стОит сразу браться за выкашивание проблем, взрощенных другими, без понимания, почему они появились. «Иногда излишнее геройство не нужно и лучше пройти мимо». Рынок со временем добьёт слабые компании, не уделяющие должного внимания качеству командной разработки и управления в целом.
@slava_k
18.11.2020 15:02 UTC
+1
Любой сложный рефакторинг начинается с определения границ изоляции и написания тестов.

Для начала нужно определиться с бизнесс-сценариями, формализовать их, написать актуальные доки. Чтобы было понятно, какие ожидания от тех или иных действий и уровни связности.

Если это какое-то сетевое API, то написать интергационные/smoke тесты как можно подробнее и потом начинать рефакторить по чуть-чуть, одновременно добавляя новые и обновляя старые тесты. Также не забыть про введение версионирования API и тестов, если им пользуются разные сущности (сайт, внешние сервисы, мобильные приложения, etc). Проверять тестами четкий список поддерживаемых версий. Это позволит в будущем относительно безболезненно выкидывать нерабочий код, как из кода API, так и соответствующего кода тестов для таких устаревших версий.

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

Изначальная цель любого рефакторинга «тех долга» должна быть прежде всего в улучшении понимания реализации бизнес-задач в коде и в создании хорошей опоры в виде тестов такого кода. Потом это уже приводит к экономии времени и ресурсов при работе команд с таким кодом. Оптимизация/ускорение чего-либо из функционала кода проекта — это всё не рефакторинг, а, скорее, возможные его последствия. Снижение времени на реализацию функционала кода, скорость его отладки и профилирования — это рефакторинг, его основные цели.

Не бывает невозможного рефакторинга. Бывает только недостаточность на него ресурсов и/или квалификации.
@northmule
18.11.2020 11:08 UTC
0
Часть этих проблем решает отдельный штат специалистов по тестированию (специальные люди + тест кейсы + время + вдумчивое человеческое тестирование функционала). Не все проекты это могут позволить, но всё же это помогает избавиться от страхов изменять код.
@NikolayPyanikov
18.11.2020 15:47 UTC
+1
К сожалению, на легаси код писать модульные тесты очень сложно или невозможно. Другие виды тестов проверят только самые базовые и очевидные сценарии или их будет слишком много и велика вероятность регрессии во многих «неучтенных» сценариях.
@a-tk
22.11.2020 15:42 UTC
0
Тема очень пересекается с тем, что говорил Dylan Beattie на DotNext 2 года назад в докладе «Ctrl-Alt-Del: learning to love legacy code»: www.youtube.com/watch?v=afZGZOL6Fr8