переписывание кода как называется
Дорабатывать или переписывать
Основная мысль этой статьи: переписывайте с нуля работающий и внедренный проект только под дулом пистолета.
Далее, представим, что вы работаете один, и ни с кем не делитесь печеньками.
Ситуэйшн №1: маленький проект
Представьте, что вы студент. Пишете ваш первый студенческий проект – больше, чем лабораторная, но меньше, чем реальный коммерческий проект. Размер примерно 1000 строк кода.
Вы написали его и захотели улучшить. Но вдруг обнаружили, что в архитектуре у вас ошибка, и исправление проблемно. Переписали. Потом еще раз. Станете ли вы переписывать его в четвертый раз? Возможно.
Небольшие проекты можно переписывать бесконечно, пока они не будут доведены до совершенства. Но вот проблема: где эта грань между совершенством и сумасшествием?
Я думаю, что хоть немного средний проект нельзя довести до совершенства, ведь сегодня вам кажется, что совершенны одни методы, а завтра – другие. А время будет потеряно. В конце концов вы можете сдать полурабочий проект, который застрял на стадии третьего переписывания (вы ведь студент, и в первом проекте решили не использовать VCS, ведь это мейнстрим).
Ситуэйшн №2: свободное плавание
Теперь вы уже продвинутый студент, и захотели пофрилансить. А может просто создать свой проект, свою игру, свой аукцион с блекджеком и… ну вы поняли.
Есть отличная идея, вы ее описали, запрограммировали, сделали, оптимизировали. Дали протестить тестерам. Казалось бы, ничего не предвещало беды.
Но вот вы обнаруживаете, что встраивать новые возможности вам все сложнее, а где-то в глубине души вас грызет червь переписывания.
Подумайте, цените ли вы свое время и результаты своей работы, чтобы переписать ваш чудесный проект размером 5000 строк кода. Если не цените, то пожалуйста, начинайте с чистого листа. Помните, что в этом случае, ваш проект врядли будет закончен.
Ситуэйшн №3: чужой код
Это самый опасный путь, скользкая, мерзкая и пакостная дорожка. Итак, представьте, что свой студенческий проект вы кое как сдали, свой личный проект забросили (извините, конечно же отложили, чтобы вернуться, когда будет время!).
И вот, к вам попадает чужой код в 10-15 тысяч строк. Говнокод. По-вашим представлениям. Внедрение обновлений равносильно выстрелу себе в ногу. Переписываем?
Не торопитесь. Сначала оцените свои возможности. Сколько времени вам потребуется, чтобы это переписать? Как это будет оплачено? Заложено ли нужное время?
Переписывание чужого кода чревато несколькими проблемами.
Первая проблема: код, скорее всего, уже внедрен и работает. В этом случае, вам не только нужно будет заново разработать весь его функционал, но и учесть все костыли, на которые наткнулся бывший говнокодер.
Вторая проблема: вы можете не потянуть проект, начать прокрастинировать, и в конечном итоге, окончательно завалите проект.
Третья проблема: почему вы так уверены, что вы сами не напишете такой же говнокод?
Ситуэйшн №4: устаревший проект или специфичные инструменты
Возможно, это единственное частичное оправдание полному переписыванию проекта. Известно, что сейчас многие работающие программы стары, как дерьмо мамонта, но люди продолжают ими пользоваться. Потому что они работают, отлажены, и ошибки в них возникают известно где. Такой проект действительно можно переписать с нуля, при учете жестко и четко сформулированных требований, моря тестов и многих часов отладки. Однако помните, что даже в этом случае нет гарантий, что проект заработает сразу, как нужно. Возможно поэтому до сих пор существуют программы, старые как… ну вы поняли.
Итак, когда не надо переписывать проект:
1) Когда вы дорожите своим временем, и временем других людей, которое они вложили в проект;
2) Когда вы не уверены, что сможете точно повторить функционал проекта;
3) Когда вы не до конца разобрались с проектом, не знаете его особенностей;
4) Когда вы говнокодер;
5) Когда никто не угрожает вашему любимому хомячку.
6) Размер проекта превышает 10000 кода, без учета инструментов и фреймворков. Даже этого будет много чтобы оценить, стоит ли овчинка выделки.
Когда, возможно, стоит переписать проект:
1) Когда внедрение доработок занимает чрезвычайно много времени и/или очень много средств из-за чрезмерной сложности системы;
2) Когда вы изучили проект досконально и знаете каждую его деталь;
3) Когда вам доставляет удовольствие в пятидесятый раз переписывать свой любимый калькулятор;
4) Когда заказчик пришел к вам домой и настойчиво утверждает, что ваш или чей-то чужой говнокод очень плохо работает, и он хочет, чтобы вы сделали все замечательно с первого раза;
5) Когда вам предлагают невообразимую кучу денег за небольшой проект.
И еще помните, что серьезные люди считают, что переписывание проектов с нуля – это признак непрофессионализма.
Удачи.
Когда стоит переписывать код проекта и как это донести до заказчика
Елена Шаровар, Lead Node.js developer в Waverley Software, в своей статье на DOU.UA рассмотрела ситуацию, когда программисты говорят: «Нужно все переписать», но это тяжело донести до заказчика.
Давайте разберемся, как помирить программистов и бизнес, если вы — Lead проекта, Project Manager или тот самый программист, который хочет улучшений, а вас как будто не слышат. Скорее всего, об этой проблеме вы знаете не понаслышке: такое случается то с вами, то с вашими друзьями или коллегами, и в очередной раз начинается обсуждение: а что же делать?
Каким бы ни было ваше итоговое решение — не переписывать, переписывать или сделать это частично, — я надеюсь, этот подробный разбор поможет сделать его мудро и взвешенно, а также донести свою идею до других стейкхолдеров.
Стейкхолдеры проекта
Чем больше заинтересованных лиц в проекте, тем более разнообразные у них цели. Давайте определим всех стейкхолдеров, участвующих в проекте. Заметьте, что с некоторыми из них вы, возможно, даже не знакомы. Но они есть, и они имеют влияние на принятие тех или иных решений.
Как проект выглядит для всех, кроме программистов
Извините, но это все! Магия, которая происходит внутри, интересна только программистам. Пока программа делает то, что нужно — она устраивает владельца продукта, инвесторов и даже пользователей.
Почему программисты хотят переписать код проекта
Во-первых, потому что у нас есть потребность делать хорошо. К сожалению, понятие «хорошо» у всех разное. Например, код хочется переписать тогда, когда достался legacy-проект, а у тех, кто писал его — понятие, что такое «хорошо», было другим, отличающимся от вашего. Или если вам достался прототип, который делался в спешке, и программисты и сами понимали, что работают не очень качественно.
Во-вторых, потому что во время работы мы загружаем информацию в память. И нам гораздо проще держать в голове структурированный код, чем кашу из файлов и функций.
В-третьих, для работы нам нужно понимать то, с чем работаем. Когда ты изменяешь то, что не понимаешь — это вызывает стресс. Кому хочется работать в постоянном стрессе во время разработки, тестирования, деплоя?
Где интересы программистов и других стейкхолдеров пересекаются
Кроме функциональных характеристик, у программ есть нефункциональные характеристики. К ним относятся:
Первые четыре характеристики очень интересуют пользователей, они заметны невооруженным глазом. Точнее, эти характеристики не интересуют пользователей, пока не станут плохими.
Остальные характеристики — начиная с расширяемости — интересуют владельца продукта и программистов, но они необходимы, чтобы поддерживать первые четыре на должном уровне. А также чтобы вовремя выпускать новые фичи.
Так вот, именно в зоне нефункциональных характеристик находится точка пересечения интересов программистов, продакт-оунера и пользователей. Когда просите рефакторинг — указывайте, какие именно нефункциональные характеристики в данный момент вас не устраивают.
Если в проекте очень сложный и запутанный код, то, скорее всего, хромает maintainability, скорость разработки и тестируемость. Если там старые библиотеки и фреймворки, то, скорее всего, у вас проблемы с расширяемостью. Если архитектура не предполагала большого количества запросов, а они ожидаются — это грядущие проблемы с scalability и performance. Если вы тратите половину рабочего времени на поиск причин багов — явно стоит улучшать auditability & traceability.
Ваши аргументы должны основываться на том, какие проблемы есть у проекта в данный момент или какие проблемы появятся в ближайшем будущем, если ситуацию не изменить (превентивный risk management).
Почему стороны не могут договориться
На одном из тренингов «успешного успеха» нас учили в любой конфликтной ситуации выписывать желания и опасения сторон. Техника хорошая, и я просто оставлю здесь таблицу, которую вы сможете заполнить исходя из вашей ситуации:
Стороны | Желания | Опасения |
Инвесторы | Получить хороший IRR и много платящих пользователей. | Впустую слить бюджет. |
Пользователи | Иметь надежный продукт, решающий их бизнес-задачи. | Нестабильная работа продукта в тот самый момент, когда он очень нужен. |
Product Owner | Идти в соответствии с разработанным roadmap, превосходить конкурентов, выпускать те фичи, которые имеют наибольшую конечную ценность. | Думает об Opportunity Cost — убытках, которые понесет компания, если будет делать переписывание вместо новых фич. |
Программисты | Получать удовольствие от работы на проекте, понимать систему, программировать, работать с новыми технологиями. | Нестабильная программа, в которой приходится чинить баги по ночам или выходным. |
Конфликт чаще всего случается там, где желания одной стороны пересекаются с опасениями другой. Из-за этого столкновения стороны не могут договориться. То, чего хотят одни стейкхолдеры, вызывает опасения у других.
Note: если во всем этом еще участвует аутсорсинг-компания, то к опасениям инвесторов добавляется страх, что им просто хотят продать дополнительные человеко-часы разработки. Я не вижу другого способа развеять этот страх, кроме как заказать аудит проекта у третьей незаинтересованной стороны, которой доверяет Product Owner.
Что по поводу переписывания говорят эксперты
Joel Spolsky написал 20 лет назад:
«Фундаментальный закон программирования состоит в том, что код сложнее читать, чем писать. Любой уже написанный код кажется программисту слишком сложным, поэтому он хочет написать его заново. Писать свои собственные функции проще и веселее, чем разбираться в существующем коде».
Проблема в том, что когда придет следующий программист — он снова сочтет код сложным, и снова захочет его переписать. В доказательство этой аксиомы [о том, что код проще писать, чем читать] — спросите почти любого программиста о том коде, с которым он сейчас работает. Он ответит, что это та еще каша.
«Никто не хочет делать те части работы, которые не веселые. Чинить баги — это не весело. А писать с нуля — весело», — пишет Jamie Zawinski.
Так что же, совсем не переписывать?
Неверный вывод. Принцип «работает — не трожь» также не подходит. В книге «Object-Oriented Reengineering Patterns» упоминается, что если система функционирует, но вы не можете ее ни поддерживать ни расширять, она сломана.
Компания Basecamp выпускала три версии продукта: Basecamp, Basecamp 2 и Basecamp 3. Они не уничтожали и не изменяли глобально предыдущую версию продукта, а просто выпускали новую.
Примерно то же самое произошло с AngularJS — когда возникло желание его переписать, был выпущен новый фреймворк Angular, и таким образом риски сломать то, что уже работает, были нивелированы.
Microsoft переписала Visual Studio и выпустила VSCode, которым многие, я думаю, пользуются. Google переписала свой Inbox, однако оставила работать обе версии — старую Inbox и новую GMail.
Достойные причины переписать проект
Недостойные причины переписать проект
Подводные камни
Переписать или отрефакторить?
Переписывание (rewrite from scratch) — это когда вы заново, с нуля, пишете код, используя старый только для чтения. Рефакторинг — это когда путем последовательных преобразований старого кода вы приходите к новому его виду.
Интересный вопрос, на который я сейчас не дам ответа, но, может, вы дадите в комментариях: а можно ли к одному и тому же результату прийти путем рефакторинга вместо переписывания? Насколько я понимаю — можно, просто иногда переписать — быстрее, чем отрефакторить. Или кажется, что быстрее.
Но рефакторинг считается более безопасным по таким причинам:
А где ваш план!
Вы делали ремонт? Помните, что вас интересовало? Цена, качество и сроки, верно? Здесь то же самое. Чтобы заказчик сказал вам «да» — предоставьте ему информацию о цене, качестве и сроках.
Цена и сроки
Цена и сроки тесно связаны. Для того чтобы максимально точно сделать эстимейт, вам нужен план работ. Для того чтобы сделать план работ — вам нужно четкое понимание, что же вы хотите получить в итоге:
С этим планом вы можете обращаться к стейкхолдерам с предложением переписать.
Качество
Качество чаще всего гарантируется вашей репутацией. Если вы — те самые ребята, которые написали плохо, где гарантия, что в этот раз вы сделаете хорошо? Для того чтобы вам разрешили делать столь рискованные вещи с проектом, вам нужно иметь высокую степень доверия от стейкхолдеров. Если не дают добро — значит, заказчику не донесена информация о ценности, он считает цену слишком высокой или не доверяет. Се ля ви.
Пару слов об инженерной этике
При вступлении в профессию врачи дают клятву Гиппократа. В ней несколько пунктов, и один из них — «доминанта интересов больного» — в процессе лечения врач обязуется следовать интересам больного, а не своим. Будет ли он это делать на самом деле — вопрос воспитанности врача.
При вступлении в профессию программиста мы не даем [еще пока] никаких клятв, но кодекс этики программиста существует здесь и здесь, и похожий пункт там тоже есть: «Principle 2: Software engineers shall act in a manner that is in the best interests of their client and employer consistent with the public interest»
Преследовать свои личные цели, а не цели бизнеса — неэтично.
Дайте цифры
Есть люди, которые принимают решения на основе цифр, а не абстрактных рассказов, как все будет хорошо. Если ваш Product Owner именно такой, предоставьте расчеты, где вы сравниваете стоимость переписывания, стоимость поддержки новой версии продукта и стоимость поддержки старой версии продукта.
Стоимость переписывания посчитать проще всего, она прямо пропорциональна времени на переписывание и выкатывание новой версии.
Стоимость поддержки продукта состоит из таких составляющих: это время, которое тратится на разработку новых фич, багфиксы и тестирование, ввод человека в проект, деплоймент, а также минимально необходимый для поддержки и понимания уровень программистов (Senior vs Junior).
Если вы уменьшите эти показатели — значит, вы уменьшили стоимость поддержки. Также переписывание вполне окупает себя в том случае, когда ожидается рост количества пользователей, который старая система просто не выдержит.
Выберите подходящее время
Если вы привели хорошие аргументы и разработали детальный план — у Product Owner все еще может быть достойная причина для отказа: «Не сейчас».
Действительно, roadmap проекта разрабатывается на месяцы вперед, и внезапно добавить в план большие изменения не получится. Также вы можете быть не в курсе финансового состояния проекта — он «дышит на ладан и пытается выжить» или «получил третий раунд инвестиций, и планируется развитие»? Получить время и ресурсы на рефакторинг проще во втором случае.
Mожно обсудить с Product Owner, когда, по его мнению, все это можно будет реализовать, и запастись терпением. Подумайте: можно ли переписать не все, а часть? Определите самые проблемные части проекта, просмотрев историю баг-репортов или жалоб пользователей.
Спросим коллег
Oleksandr Brychuk, Head of IT Department at UniSender
В начале работы в UniSender мы столкнулись с тем, что продукт по своей природе был страшным легаси из практик начала 2000-х. Время на адаптацию нового человека в проект был немалым, некоторые части проекта вообще было трудно поддерживать, один баг нам обошелся в кругленькую сумму. Их было много, и уменьшить количество нам не удавалось.
Мы с коллегами взяли инициативу в свои руки. Сделали документ, в котором описали видение того, куда нужно двигаться с технической точки зрения. Примерно оценили сроки. Вариантов было два: переписывать с нуля или рефакторить. Пригласили внешних экспертов для аудита и проверки собственных гипотез. И представили это владельцам. В конце-концов решили рефакторить, и начали с наиболее важного для нашего бизнеса.
Мы рефакторили, а не переписывать с нуля, потому что часто останавливались в процессе и делали то, что было нужно в тот момент, а не в перспективе — это придало гибкости. Первые полгода ничего не делали, кроме рефакторинга, потом занимались им по-разному: от 30-40% до 60-70% времени. Все зависело от целей на тот момент.
Eugene Naumenko, Founder at Adferret.tech
Когда говорят «все переписать» — именно в такой абсолютной формулировке — это в большинстве случаев просто непрофессионально. Когда действительно есть потребность переписать, чаще всего это делается инкрементально — новые версии с новыми возможностями, эволюция, а не революция. В случаях, когда переписать все действительно нужно, программисты должны привести рациональные и логические обоснования для бизнес-людей и вместе сформировать бизнес-потребность, бизнес-ценность такого решения и его общую стратегию.
Eugene Bogatyriov, VP of Engineering, Waverley Software
Расчеты — это ключевой момент: нужно предоставить расчеты ROI проекта по переписыванию/рефакторингу. ROI — время, за которое окупится инвестиция в переписывание. Если переписывание окупается за год-полтора — эту идею примут и «спасибо» скажут. Расчеты желательно презентовать стейкхолдерам по одному, индивидуально, начиная снизу по иерархии. Есть неписаное правило о том, что продукт надо переписывать с нуля минимум каждые 10 лет в связи с устареванием технологий. У меня был пример как раз такого типа, где десктопный продукт переписывали с нуля под веб. Команда 80 человек, три года.
Summary
Итого, если вам кажется, что ваш проект нужно переписать:
Я слышал, что умение ответить на этот вопрос на собеседовании в некоторых компаниях является критерием прохождения собеседования на сеньорские позиции. Но чтобы лучше понять ответ на него, нужно разобраться, почему вообще плохо переписывание истории?
Для этого, в свою очередь, нам понадобится быстрый экскурс в физическую структуру git-репозитория. Если вы точно уверены, что знаете об устройстве репо всё, то можете пропустить эту часть, но даже я в процессе выяснения узнал для себя довольно много нового, а кое-что старое оказалось не вполне релевантным.
На самом низком уровне git-репо представляет собой набор объектов и указателей на них. Каждый объект имеет свой уникальный 40-значный хэш (20 байт, записанные в 16-ричной системе), который вычисляется на основе содержимого объекта.
Основные типы объектов — это blob (просто содержимое файла), tree (набор указателей на blobs и другие trees) и commit. Объект типа commit представляет собой только указатель на tree, на предыдущий коммит и служебную информацию: дата/время, автор и комментарий.
Где здесь ветки и тэги, которыми мы привыкли оперировать? А они не являются объектами, они являются просто указателями: ветка указывает на последний коммит в ней, тэг — на произвольный коммит в репо. То есть когда мы в IDE или GUI-клиенте видим красиво нарисованные веточки с кружочками-коммитами на них — они строятся на лету, пробегая по цепочкам коммитов от концов веток вниз к «корню». Самый первый коммит в репо не имеет предыдущего, вместо указателя там null.
Итак, почему же переписывание истории репозитория вредно?
Итак, развеяв опасения перед изменением истории репозитория, можно, наконец, перейти к главному вопросу: зачем оно нужно и когда оправдано?
Но это тривиальный случай. Давайте рассмотрим более интересные.
Допустим, вы сделали большую фичу, которую пилили несколько дней, отсылая ежедневно результаты работы в репозиторий на сервере (4-5 коммитов), и отправили свои изменения на ревью. Двое-трое неутомимых ревьюверов закидали вас крупными и мелкими рекомендациями правок, а то и вовсе нашли косяки (ещё 4-5 коммитов). Затем QA нашли несколько краевых случаев, тоже требующих исправлений (ещё 2-3 коммита). И наконец при интеграции выяснились какие-то несовместимости или попадали автотесты, которые тоже надо пофиксить.
Но бывает также, что к код-ревью вы уже подошли с историей репо, напоминающей салат «Оливье». Такое бывает, если фича пилилась несколько недель, ибо была плохо декомпозирована или, хотя за это в приличных коллективах бьют канделябром, требования изменились в процессе разработки. Вот, например, реальный merge request, который приехал ко мне на ревью две недели назад:
У меня рука машинально потянулась к кнопке «Report abuse», потому что как ещё можно охарактеризовать реквест из 50 коммитов с почти 2000 изменённых строк? И как его, спрашивается, ревьюить?
Честно говоря, у меня ушло два дня просто на то, чтобы заставить себя приступить к этому ревью. И это нормальная реакция для инженера; кто-то в подобной ситуации, просто не глядя, жмёт Approve, понимая, что за разумное время всё равно не сможет сделать работу по обзору этого изменения с достаточным качеством.
Но есть способ облегчить жизнь товарищу. Помимо предварительной работы по лучшей декомпозиции задачи, уже после завершения написания основного кода можно привести историю его написания в более логичный вид, разбив на атомарные коммиты с зелёными тестами в каждом: «создал новый сервис и транспортный уровень для него», «построил модели и написал проверку инвариантов», «добавил валидацию и обработку исключений», «написал тесты».
Каждый из таких коммитов можно ревьюить по отдельности (и GitHub, и GitLab это умеют) и делать это набегами в моменты переключения между своими задачами или в перерывах.
50 (подставьте вместо “50” вашу цифру).
Кстати, если вы в процессе работы над задачей подливали к себе ветку master, то сначала надо будет сделать rebase на эту ветку, чтобы merge-коммиты и коммиты из мастера не путались у вас под ногами.
Вооружившись знаниями о внутреннем устройстве git-репозитория, понять принцип действия rebase на master будет несложно. Эта команда берёт все коммиты в нашей ветке и меняет родителя первого из них на последний коммит в ветке master. См. схему:
Иллюстрации взяты из книги Pro Git
Если изменения в C4 и C3 конфликтуют, то после разрешения конфликтов коммит C4 изменит своё содержание, поэтому он переименован на второй схеме в C4’.
Да, и кстати, вы можете поменять коммиты местами. Возможно, это создаст конфликты, но в целом процесс rebase редко обходится совсем уж без конфликтов. Как говорится, снявши голову, по волосам не плачут.
Вы можете повторять rebase несколько раз, затрагивая только части истории, и оставляя остальные нетронутыми при помощи pick, придавая своей истории всё более и более законченный вид, как гончар кувшину. Хорошим тоном, как я уже написал выше, будет сделать так, что тесты в каждом коммите будут зелёными (для этого отлично помогает edit и на следующем проходе — squash).
Поначалу вы, скорее всего, будете тратить на этот процесс (включая первоначальный rebase на master) час, а то и два, если фича реально развесистая. Но даже это намного лучше, чем ждать два дня, когда ревьювер заставит себя наконец взяться за ваш реквест, и ещё пару дней, пока он сквозь него продерётся. В будущем же вы, скорее всего, будете укладываться в 30-40 минут. Особенно помогают в этом продукты линейки IntelliJ со встроенным инструментом разрешения конфликтов (full disclosure: компания FunCorp оплачивает эти продукты своим сотрудникам).
Последнее, от чего хочется предостеречь, — не переписывайте историю ветки в процессе код-ревью. Помните, что добросовестный ревьюер возможно клонирует ваш код к себе локально, чтобы иметь возможность смотреть на него через IDE и запускать тесты.
Спасибо за внимание всем, кто дочитал до конца! Надеюсь, что статья будет полезна не только вам, но и коллегам, которым ваш код попадает на ревью. Если у вас есть клёвые хаки для git — делитесь ими в комментариях!