22 августа 2012 в 15:25

Практика рефакторинга в больших проектах из песочницы

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

Итак, представьте себе следующую ситуацию. Так уж случилось, что вам надо отрефакторить очень большой кусок кода, целую подсистему. Строк, эдак, на 200К. Причем рефакторинг явно выглядит очень крупным, затрагивающим базовые концепции, по которым построена ваша подсистема. Фактически надо переписать всю архитектуру, сохранив бизнес логику. Такое бывает, если, например, вы сделали один проект и у вас впереди новый, и вы хотите в нём исправить все ошибки прошлого. Допустим, по первым прикидкам, на рефакторинг надо месяца 2, не меньше. В процессе рефакторинга всё должно работать, в том числе нельзя мешать другим программистам добавлять новые фичи и чинить баги в подсистеме. Часто такой рефакторинг бывает насколько сложен, что совершенно невозможно замерджить старый код в новый, а также невозможно выкатить результат по частям. Фактически вам надо заменить двигатель самолёта на лету.

Примеры из практики, как моей, так и моих коллег:
  • Переделать всю работу с базой данных с чистого JDBC на Hibernate.
  • Переделать архитектуру сервиса с отсылки-приёмки сообщений на удалённый вызов процедур (RPC).
  • Полностью переписать подсистему трансляции XML файлов в рантайм объекты.


Что делать? С какой стороны подойти к проблеме? Ниже представлен набор советов и практик, которые нам помогают справиться с этой проблемой. Сначала более общие слова, а потом конкретные методики. В общем-то ничего сверхъествественного, но кому-то может помочь.

Подготовка к рефакторингу

  • Разбить рефакторинг на части, если это возможно. Если возможно, то нет проблем. Все остальные советы про то, что надо делать, если это не удалось.
  • Постарайтесь выбрать период, когда активность добавления новых фичей в подсистему будет минимальна. Например, удобно переделывать бэкенд, в то время как все усилия команды сосредоточены на фронтенде.
  • Хорошо ознакомьтесь с кодом, зачем он вообще нужен. Какая архитектура заложена в основании подсистемы, какие стандартные подходы, которые там использовались. Чётко определить для себя, в чём заключается новая концепция, и что конкретно вы хотите поменять. Должна быть измеряемая финальная цель.
  • Определить область кода, которую захватит рефакторинг. По возможности изолируйте её в отдельный модуль/в отдельную директорию. Это в будущем будет очень полезно.
  • Рефакторинг без тестов делать очень опасно. У вас должны быть тесты. Как без них жить я не знаю.
  • Запустите тесты с подсчётом покрытия, это даст много информации для размышления.
  • Почините сломанные тесты, которые относятся к искомой подсистеме.
  • Анализируя информацию о покрытии методов можно найти и удалить неиспользуемый код. Как ни странно, такого часто бывает до 10-15%. Чем больше кода удастся удалить, тем меньше рефакторить. Профит!
  • По покрытию определите, какие части кода не покрыты. Надо дописать недостающие тесты. Если юнит тесты писать долго и утомительно, напишите, хотя бы, высокоуровневые smke тесты.
  • Постарайтесь довести покрытие до 80-90% осмысленного кода. Не надо стараться покрыть все. Убьёте много времени с малой пользой. Покройте хотя бы основной путь выполнения.


Рефакторинг

  • Оберните вашу подсистему интерфейсом. Переведите весь внешний код на использование этого интерфейса. Это не только форсирует применение хороших практик программирования, но и упростит рефакторинг.
  • Убедитесь, что ваши тесты тестируют интерфейс, а не реализацию.
  • Сделайте возможность при старте указывать, какую реализацию этого интерфейса использовать. Это возможность нужно поддержать и в тестах и в продакшне.
  • Записать ревизию системы контроля версий, на которой началось написание новой имплементации. С этой секунды каждый коммит в вашу старую подсистему – ваш враг.
  • Напишите новую реализацию интерфейса вашей подсистемы. Иногда можно начинать с нуля, иногда можно применить излюбленый метод — copy&paste. Писать её надо в отдельном модуле. Периодически гоняйте тесты на новой реализации. Ваша цель — сделать так, чтобы все тесты проходили успешно на новой и старой реализации.
  • Не надо ждать, когда вы всё полностью напишите. Заливайте код в репозиторий как можно чаще, оставляя включённой старую реализацию. Если долго держать код в загашнике, можно огрести проблем с тем, что другие люди рефакторят модули, которые вы используете. Простое переименование метода может доставить вам много проблем.
  • Не забудьте написать тесты, специфичные для новой имплементации, если такие имеются.
  • После того как всё напишите, посмотреть историю SVN в папке со старым кодом, чтобы найти, что там менялось за время вашего рефакторинга. Надо перенести эти изменения в новый код. По идее, если вы что-то забыли перенести, тесты должны это поймать.
  • После этого, все тесты должны проходить как со старой, так и с новой подсистемой.
  • Сразу после того, как вы убедились в стабильности новой версии вашего модуля, переключайте на него тесты и продакшн. Блокируйте коммиты в старую подсистему. Всеми силами старайтесь минимизировать время существования двух подсистем в параллель.
  • Подождите неделю или две, соберите всё багло, зачините его и смело удаляйте старую подсистему.

Дополнительные советы

  • Все новые фичи, создаваемые параллельно с рефакторингом, должны покрываться тестами на 100%. Это нужно для того, чтобы при переключении на новую имплементацию, упали тесты и сигнализировали о том, что в новой имплементации не хватает кода из старой.
  • Любой фикс бага надо делать по принципу — сначала пишем тест, который будет воспроизводить проблему и падать, потом его чиним. Причины те же самые.
  • Если вы используете систему а-ля TeamCity, на время рефакторинг сделайте отдельный билд, где будут гоняться все тесты на новой подсистеме. Автоматический билд делает ваш новый, ещё не используемый, код «официальным». К нему начинают применяться все те же политики и правила, что и ко всему остальному.
  • Часто бывает так, что вы не знаете, исправили ли вы всё, что хотели в старом коде на новую архитектуру. Например, вы не знаете, не использует ли ваш код где-то прямое JDBC подключение, вместо Hibernate. Или вдруг где-то проскользнуло сообщение, а не RPC вызов. Для обнаружения таких мест надо придумать способ сделать старую методу неработающей. Т.е. сломать её в тестах. Например, сломать систему доставки сообщений или подсунуть системе неработающий JDBC драйвер. Практика показывает, что таким образом обычно находится как минимум штук 5 забытых и не исправленных мест.
  • Поговорите с другими программистами, держите их в курсе вашего прогресса. Если они будут знать, что вам осталась неделя, они иногда могут подвинуть свои задачи до времени выхода новой версии вашей подсистемы. Не надо будет сливать изменения.


Опыт подсказывает, что даже страшные и большие подсистемы можно отрефакторить относительно малой кровью. Главные ваши помощники, это тесты и систематичность.
Андрей Фролов @Randll
карма
67,2
рейтинг 0,0
Самое читаемое Разработка

Комментарии (51)

  • +1
    Хочу добавить довольно полезный аддон Pex, который генерит тупые, но покрывающие 100% кода тесты — очень полезно именно при рефакторинге, когда код покрыт не полностью или вообще не покрыт. research.microsoft.com/en-us/projects/pex/
    • 0
      А для Java есть аналог?
  • +4
    Вообще-то мне кажется, что такой большой рефакторинг лучше делать на отдельной ветке. Сначала на отдельной ветке делается рефакторинг, а потом делается merge в основную ветку. И никакого существования параллельно двух систем.
    • +5
      «Было гладко на бумаге...». По опыту часто процесс вливания изменений сложно назвать обычным merge. Появляются, исчезают, переносятся не то что файлы, а целые каталоги и процесс объединения отрефакторенного кода и изменений старого кода очень трудоемок и проводится вручную.
    • +5
      Пробовал ветки, не прокатило. По двум основным причинам и несколльким дополнительным.

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

      — Ваш код живёт в отдельной ветке, а значит от не рефакторится другими программистами. В моём случае, переименование метода в какой-то внешней подсистеме, которую использует наша подсистема, будет подсуппорчено автоматически, средствами IDE. В случае с веткой, такой рефакторинг надо будет делать вручную. Если над проектом работает 10+ программистов, кто-то что-то да переименует раз в 3 дня стабильно.

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

      — Коллегам проще показывать что-то в существующем коде а не просить вычекать новую ветку.

      Справедливости ради… У ветки есть одно, но сомнительное преимущество — своим рефакторингом не сломаешь билд транка.
      • +1
        Мой опыт показывает что как раз для больших рефакторингов ветки — единственный возможный вариант. Потому что:
        .) когда есть мерж, на котором все застрелились, и после которого полезли неочевидные баги, все тупо откатывается и транк живой и невредимый.
        .) проще один раз настроить систему чтоб небыло проблем с переключениями между веток чем потом пытаться догадаться где что и почему отвалилось вместо того чтоб просто сравнить с предыдущей версией.
        .) если вы всей группой работаете в одной локалке то вычекать новую ветку — наименьшая проблема возникающая при рефакторинге, особенно при большом.
        .) сомнительное преимущество не сломать билд — это «на самом деле»(тм) самая главная фича. когда от билда зависят 100+ человек ломать его очень неохота, потому что из-за тебя они вынуждены тратить свое время на решение твоих глупостей, а это никто не любит.

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

        Ветки наше все, мержи — неизбежное зло, но с очень хорошим бонусом. Надо просто смириться с этим и жить дальше.
        • +7
          Чтобы не было потом весёлых мержей, в этом деле огромный вклад внесёт гит, если вы его конечно используете.
          После ответвления от основной ветки каждый день проводится ребейз(rebase) своей ветки на основную ветку разработки.
          Ребейз в гите — это перенос точки ответвления бранча. Работает это как буд-то заново происходит ответвление от ветки с последовательным накладыванием на неё всех ваших коммитов.
          При этом при возникновении конфликта вы будете разруливать не финальное состояние ваших файлов, когда хз что и как было изменено, а состояние файлов лишь на момент того вашего коммита, при котором и произошёл конфликт.
          • +3
            При этом при возникновении конфликта вы будете разруливать не финальное состояние ваших файлов, когда хз что и как было изменено, а состояние файлов лишь на момент того вашего коммита, при котором и произошёл конфликт.

            Тут немного поясню. Когда двумя коммитами A и B в одном и том же файле вы сделали конфликтующие с «транком» изменения, то сначала при ребейзе у вас будет возможность поправить конфликт из коммита A(процесс ребейза остановится), а затем продолжить ребейз, после чего он остановится уже на конфликте из коммита B.
          • +1
            Насколько я понимаю, гит это примерно тоже самое что и mercurial.
            У нас общий SVN + у программеров локальный mercurial с гейтом в SVN. У нас тоже есть rebase. Но rebase каждый день, это мердж каждый день. Пробовал и так. Выдержал неделю :) Слишком тяжело каждый день начинать с часового мерджа. С Mercurial это делать легче чем с SVN, но всё равно не намного.

            Mercurial, кстати, позволяет играться с локальными ветками и патчами, перекладывая их туда-сюда. Но не решает глобальной проблемы «Ваш код живёт в отдельной ветке, а значит он не рефакторится другими программистами».
            • 0
              Примерно тоже самое, хотя как там работает ребейз не знаю.
              Вот про мерж непонятно. Если он у вас по часу у вас каждый день занимает, то итоговый мерж потребует времени за все предыдущие дни разом, плюс большой оверхед из-за того, что массу изменений за долгий период скопом мержить труднее, чем по чуть-чуть.
              Или меркуриал не запоминает как конфликты при ребейзе разрешаются и каждый раз заново? С гитом при последующих ребейзах конфликты, возникшие при предыдущих, больше не появляются.
              «Подливание» произвольных коммитов в бранч тоже есть — cherry pick.
              • 0
                Дело в том, что если делается рефакторинг, то код очень живой и часто меняется. Поэтому действительно каждый раз мерджить надо как по новому. Ну в любом случае, если не было коммита, то не решается проблема «автоматического рефакторинга отрефактореного кода».
            • +1
              Вы так мучаетесь именно потому, что у вас SVN. Посмотрите эпичное выступление Линуса Торвальдса в Google, посвящённое Git, он там очень толково и понятно рассказывает, чем хороши распределённые VCS. Имейте ввиду, что эффективный стиль работы с ними сильно отличается от SVN и т.п. Почитайте Pro Git.
              • 0
                Ещё раз. У нас не просто SVN. У нас Mercurial из которого мы делаем push в SVN. SVN нужен для простых смертных, меркуриал для програмеров.
                • 0
                  Так в чём же сокрыт сакральный смысл держать SVN? Он настолько проще Mercurial в обращении? Я действительно хочу знать, это не «троллинг» и не фэнбоизм по части Git. Что такого эти смертные не могут осилить в Mercurial после SVN? У меня даже второй вопрос появился: что это за смертные такие, которым SVN дают? На одной предыдущей работе давали доступ гейм-дизайнеру и художникам — хорошего было мало.
                  • 0
                    Геймдизайнеры, художники, и все остальные. 200+ человек. Как им не давать svn? Как они работать тогда будут? Они производят 70% всех коммитов.

                    Изначально у нас был SVN. SVN прост и понятен всем. Ветки, rebase, патчи и т.п. это сложно и мало кому нужно. Народ будет путаться и чаще косячить. Осилить наверное смогут, но нафига? Этож надо переучить уйму народа, перенастроить софт, переписать коммит хуки (у нас их сотни)… Слишком много гемора.

                    Кроме того mercurial плохо справляется, если в него закачать весь наш проект, поэтому у нас в нём только код.
                    • 0
                      Понятно. Если так много завязано на SVN, то лучше ничего не трогать, конечно.
                    • 0
                      имхо все зависит от длительности проекта. если он на полгода то вобщем можно и потерпеть.
                      если же несколько лет то гемора с svn->mercurial на протяжении всего этого времени гораздо больше, чем если один раз всех перевести.
                      да, будет трясти месяца два, потом все научатся и все будет ок. двух месяцев вполне хватит — у вас же не олигофрены работают.
                      практика показывает что как раз легаси барахло причиняет большинство проблем, потому по возможности от него надо избавляться.
          • 0
            у нас базар. там вроде что-то такое было. надо посмотреть.
            но в целом все зависит от величины рефакторинга, т.к если он действительно большой и кто-то влил новую фичу то веселье один фиг гарантировано.
        • 0
          1 — в моём варианте всё тоже самое, только ещё проще. поменял настройку в конфиге и транк работает.
          2 — не понял аргумента :( Как сравнивать с другой версией, если классы переколбашены на 70-100%? Всё равно смотреть только глазами, а не диффом.
          3 — наш проект чекаутится 2 часа.
          4 — в моём случае сломать билд можно только сломал в компиляцию. И то, если сам дурак и не проверил её до коммита. Ничего страшного, быстрый фикс или реверт всё всегда чинит. От билда зависят 100+ человек, это да. Всё равно его будут ломать, и не важно как. Мой код ничем не отличается от любого другого кода. Практика показывает, что компиляция ломается примерно раз в два дня и чинится в течение 15 минут. При ответственном отношении и быстром фиксе это не проблема.

          Конечно, мерджа я же не избегаю, просто я делаю его вручную. Автоматика всё равно не справляется. И мой код в отдельной папочке это по сути и есть ветка, только не отдельной части репо, а прямо в коде.

          Чтоб было понятно, если мне не изменяет память, то описанную мной схему мы применяли как минимум раз 6. Плюси и минусы схемы изучены, и в технологии расставлены подпорки, чтобы уменьшить влияние минусов.
          • 0
            1 — это если у вас есть возможность сделать отдельный модуль, который можно подключать паралельно старому. далеко не всегда она есть. у нас вот нету.
            2 — логику сравнивать — смотреть глазами, думать головой. вы предлашаете покрывать только 80-90% кода тестами. это как раз те самые «простые 80%», со сложными 20% начнется ловля багов и выяснение почему не так. наличие рабочей старой ветки позволяет посмотреть как и почему и для чего оно там работало, и почему не работает здесь.
            3 — меняйте свн на что-то другое — гит, базар, меркуриал. у меня новая ветка делается минуту-две. проекта занимает 240M. в локалке все коммитится не мгновенно, но близко к этому.
            4 — хуже когда не компиляция ломается, это действительно чинится за 15 минут, а когда начинают валиться тесты на каких-то экзотических платформах, к которым доступ только через инет и там легко и незатейливо можно ухлопать несколько дней на выяснение причин. и если время критично, например готовится релиз или что-то еще, то проще откатить все назад и выяснять в чем проблема в отдельное время.

            я на этом месте работаю лет 6, сначала был биткипер, потом базар. имхо ветки — самое оно;)
            • 0
              Вы невнимательно прочитали концепцию. Против всех ваших аргументов в технологии есть подпорки.

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

              2 — смотреть глазами и думать головой в моём случае тоже надо. 80-90% кода покрывается просто. Оставшиеся 10% добить очень сложно, это обычно обработка ошибок. Отлично, если вы их покроете. Но реальность такова, что 100% -ого покрытия не бывает. :( Ещё раз повторюсь — в моём случае «старая ветка» сохраняется и переключается локально одной настройкой в конфиге.

              3 — а у меня проект занимает 34 ГБ. :) Новая ветка делается быстро, но чекаутится долго. Про SVN и меркуриал я уже писал в комментах.

              4 — Ещё раз повторюсь. Отрефактореный код просто лежит в репо и не включается до тех пор, пока не оттестирован на всех платформах и т.п. На случай фейла всегда есть флажок в конфиге.
      • 0
        Все апологеты рефакторинга в один голос заявляют об основном тормозе рефакторинга — страхе. Если боятся чтото улучшать, нафиг тогда вообще это писать?
        • 0
          Вы о чем?
          Страх он от отсутсвия тестов. Много тестов — have no fear!
          • 0
            Я о том что «рефакторинг может чтото сломать и всё будет плохо, на рабочем проекте нельзя ничего трогать аааа мы все ...»
    • 0
      Любые отдельные ветки — зло. Не только для задач рефакторинга.

      Допустим, у вас есть две ветки, которые долго не мержились и велась параллельная разработка. При этом, допустим, всё делалось «по уму» — хорошее покрытие тестами и т.д.

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

      Поэтому по возможности, нужно не плодить ветки. А если плодить, то на короткое время. Лучше одна ветка, но частые сливания кода.
      Иногда это невозможно, конечно. Но я встречал людей, которые на ровном месте выдумывали проблемы, разрабатывая «очень умные» схемы управления проектами внутри одного большого проекта.
      Умного в этом ничего нет. Как только мерж и выкат на продакт, так и полезли возрожденные баги, новые баги и хрен знает что.
      • 0
        >>Любые отдельные ветки — зло
        Слишком категорично. Ветки рулят, если надо делать стабильный релиз не останавливая работу в транке. Или надо суппортить несколько версий одного проекта одновременно.

        ИМХО ветки могут жить если мерди идут только в одну сторону — из транка в ветку.
        • 0
          А почему в одну сторону? Ну вот, например, стратегия, которую Вы упомянули: разработка в транке и ветки для релизов. Отвели ветку для релиза, а потом на ней нашли несколько небольших багов. Естественно их исправить на ветке, и потом мерджнуть исправления в транк.
          • 0
            Естественно делать роовно наоборот. Например потому что баг в транке может быть уже зачинен, надо просто замерджить нужный коммит в ветку.
            • 0
              В очень примитивных случаях — да, так можно делать. Но в общем случае это может быть проблематично: транк мог уже сильно измениться, отделять только нужные изменения муторно и небезопасно (можно случайно занести лишнее изменение и получить новый баг), да и, в конце концов, место, где был баг, может просто измениться до неузнаваемости. Поэтому в качестве общего правила гораздо надежнее изменять на ветке поддержки, и потом мерджить в транк, или в ветку разработки (в зависимости от выбранной стратегии веток).
        • 0
          Да, категорично. И программировать — это зло. Любая строчка кода — зло )))

          Это такой способ оценки, что лучше, а что хуже. Хороший по-моему критерий для оценки качества кода или качества работ.
          Когда такого критерия нет, то часто нет вообще никаких критериев. Новички в программировании «любят» свою работу, романтизируют ее. Это отражается на качестве. Например, пытаются заранее принимать решения об архитектуре. Если им предоставить два варианта архитектур, без такого критерия они впадают в восхищение или поиск красоты.

          А если понимать, что программирование — это увеличение энтропии, что это потенциальные баги, то сразу же возникают вопросы:
          1. Кода больше? Зло
          2. В коде учитывается больше, чем требуется на сейчас — зло.

          3. Лишние ветки — зло.

          Но и не кодить нельзя и без веток не всегда обходимся. Просто предлагаю рассматривать выбор решений не как выбор лучшего решения, а как выбор наименее вредного из всех. Тогда больше «лакмусовых бумажек», больше понимания, от чего нужно отказываться и куда двигаться. Когда человек выбирает лучшее из хороших, нет ни мотива развивать решение ни ощущения, что нужно делать для этого
          • 0
            Чувствуется взгляд опытного программера :) С бритвой Оккама в применении к программингу я пожалуй соглашусь.
          • 0
            Как говорится, самый лучший код — это тот, который не надо писать :)
      • +1
        >И всё «по уму» пошло коту под хвост. Сидит какой-то человек, разгребает кучу кода, субъективно решает, что важно и что правильно, включая тесты.
        Да, очень сильно обобщая так и есть. Но это просто одна из частей работы, за которую платят деньги и на которую ты согласился. Мерж по сути ничем не отличается от прибивания багов или написания фич. Относиться надо не менее ответственно. Если человек что-то не понимает — выясняет кто внес изменения и идет говорить с автором до просветления.
        ТДД будет сломано если мерж делать тяп-ляп. Мержи — это не халява от vcs, мозги надо держать включенными.
        Риски есть всегда. Чтоб небыло неожиданных багов тестировать надо перед выкатываением.
        • 0
          ТДД будет сломано если мерж делать тяп-ляп. Мержи — это не халява от vcs, мозги надо держать включенными.
          Риски есть всегда. Чтоб небыло неожиданных багов тестировать надо перед выкатываением.


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

          Но всё не только от мозгов. Есть и технологии.
          В ТДД человек пишет перед началом кодирования тест. Код покрывается тестами довольно неплохо. Эти все действия до полуавтомата доведены. Архитектура во многом строится сама, благодаря различным оценкам кода, а не планирования наперед.

          Что происходит при мерже? Правильно — появился бог, который СУБЪЕКТИВНО решает, что нужно, а что нет. Включая тесты. Тесты перестают быть критерием оценки кода, потому что один человек соединяет написанное разными людьми и теперь только от его мозгов зависит, как слепится продукт.

          А тестить перед выкатом, конечно, надо. Но при всем при этом, при частых сливаниях кода, проблем этих в РАЗЫ меньше. И потенциальных багов тоже. Чем большее расхождение по времени веток, тем сложнее их потом мержить и тем больше будут проявляться проблемы, описанные выше.
          • +1
            С ваших слов выходит что код сам себя пишет и все клево, а потом появляется несовершенный человек и всю малину портит. И код который надо мержить и тесты писали точно такие же люди и я не очень понимаю почему на мерже все резко и сразу должно сломаться если человек делает все ответственно. Сломаться может если делали все абы как, или человек — дурак. Оба события вероятны, но если комманда вменяемая то обычно они _не_ происходят.
            Мерж — не трагедия, а просто часть работы. А когда код еще и обвешан тестами на те же 80-90% то вообще не проблема — валящиеся тесты покажут где косо смержили.
            Когда вы пишете новую фичу, фиксите сложный баг то решаете кучу всяких проблем в коде — собственно главную задачу + совместимость с другими частями + стараетесь сделать «по-уму» + требования сторонние какие-нибудь. И все это вас не смущает. А как появляется мерж так все — проблема. Я не вижу логики.
            • 0
              Код почти сам себя пишет. Можно так сказать. Приблизительно

              Программирование относится к точным наукам. Не гуманитарным. И творчества там быть не должно, по хорошему. Чем более профессиональный человек, тем больше у него строгих выработанных правил, которым он следует. Жаль, что эти правила не описаны четко где-то в конкретном месте. По книгам разбросаны. И по головам, с опытом.
              Программирование — это перевод требований на язык программирования. И есть определенные правила, как к двигаться к цели. Код сам подсказывает, что в нем не так. Тест, рефакторинг, хотя и делает человек, но делает это вполне механически. Как водитель. Знает куда едет и знает, что надо в какой последовательности нажимать. Был бы автомобиль умнее, сам бы так ездил.

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

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

              В общем, это дело намного рискованнее, чем написание кода и ведение одной ветки. Мерж веток подобен рефакторингу, но без покрытия тестов. Поэтому лучше работать в одной ветке. Все будут синхронизировать тесты, будут в курсе происходящего.
              • +1
                Нет, это феерично:)
                Про программирование в целом:
                Код сам себя не пишет, ни почти, ни чуть-чуть, ни самую капельку. Так же код ничего не подсказывает. Код/тесты это просто текст. Все происходит в голове программиста, а вы его легко и незатейливо из процесса исключаете. Это смешно. В современном мире все и всегда зависит от человека, программирование не исключение.
                Если бы творчества, хоть на грамм, в программировании небыло бы, то можно было бы легко и незатейливо написать «универсальный писальщик программ» который сам бы все остальное и написал. Однако такого нет именно из-за неопределенности происходящего. И книг со сводом правил нет именно по этой же причине.
                Умных и Очень умных людей — вагон, в т.ч и в программировании. Если бы все было так просто как вы описываете то эти люди давным давно уже все какие надо правила придумали бы и описали, в каком-нибудь «Едином Своде Правил Программиста» и всеми ими бы пользовались, потому что так действительно было бы удобнее всем. Но ничего подобного нет. Задумайтесь почему так.

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

                • 0
                  Так же код ничего не подсказывает. Код/тесты это просто текст. Все происходит в голове программиста, а вы его легко и незатейливо из процесса исключаете. Это смешно. В современном мире все и всегда зависит от человека, программирование не исключение.


                  Вам может и ничего не подсказывает ))
                  Код, не просто текст. Он написан по правилам. Как минимум, языка программирования. И не всё зависит о человека, если уж…
                  Потом, чем более точные науки, тем менее субъективны. Основная черта, отличающая их от психологий, стихосложения и живописи — это упор на объективность. Это значит, что человек делает минимум предположений на счет реального мира и старается их исключить вообще. Относиться без предубеждений. «Истину» принимать, такую какая бы она не оказалась, а не руководствоваться выбором, что нравится, а что нет. Логично?

                  Теперь о том, что зависит от человека. Человек решает, допустим, задачу по физике. Так, оказывается, он действует по каким-то строгим правилам. И приходит к решению задачи. И правильных решений — одно или совсем немного. Задо неправильных — бесконечность. Вот эта бесконечность — это и есть «творчество». Физик не скажет: я захотел выбрать такое решение, потому что так хочу, я имею право, я ж креативный. Почему вы думаете, что в программировании иначе?

                  В программировании тоже есть правила. Например, не использовать магических чисел. Человек видит такой код, и код ему подсказывает, что он плохой, это надо «полечить». Второй случай: человек видит, что куски кода повторяются. Просто видит, что такие похожие конструкции видел несколько раз. Это он, напоминаю, на код смотрит. А значит, применяя это правило — код ему подсказывает, надо посмотреть в эту сторону, возможно лажа. Третий случай: человек видит, что данные дублируются. Понимает, что возможна рассинхронизация, значит где-то лажа. Четвертый случай. Человек видит, что глубина наследования больше двух-трех. Возможно лажа. Анализирует код на предмет упрощения.

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

                  Человек пишет тест на свое усмотрение, «на глаз», потом по нему, опять-таки на свое усмотрение, пишет код. И это все ок. А когда он начнает этот код мержить, на свое усмотрение же, это резко становится проблемой. Это несколько нелогично, не находите?


                  Логично. Человек тест пишет не на глаз. Не совсем на глаз. По крайней мере очень старается, чтобы этого глаза было как можно меньше. Он анализирует требования и тест пишет либо по ним, либо анализирует наиболее простое решение требования и пишет тест на одну из частей этой реализации. А код, так вообще старается не на глаз писать. Тесты уже мешают код писать «на глаз».
      • +2
        Ветки зло, когда их делают хаотично и неумело. При правильном подходе ветки — благо :)
        Долгоживущая параллельная ветка без мерджей — это, конечно, зло и типичный пример того, как ветками пользоваться не надо. С другой стороны, стратегия при которой, например, стабильный код живет в транке, а на каждый спринт (если мы используем SCRUM) создается своя ветка, которая живет несколько недель, а потом вливается в транк — хорошая и правильная стратегия.
        • 0
          Нехорошо и неправильно. Зачем в вашем случае отдельная ветка на спринт? Кому нужен стабильный код, в который нет ничего, что делалось последний месяц?
          • 0
            Стабильный код нужен для поддержки рабочей версии. Эта модель очень хороша для задач, в которых существует одна текущая версия. Например, для веб-аппликаций. Или для корпоративных систем. В этих ситуациях одновременно идет разработка ноё версии (или даже нескольких), и срочное исправление багов, а также внесение срочных изменений в рабочую версию. А как еще вы предлагаете организовывать разработку в таких случаях?
            • 0
              Если было бы хоть сто веток, но при любом изменении в какой-то из веток, это изменение появлялось во всех остальных, то у нас по сути одна ветка.

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

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

              Так можно.
              Это общий принцип: лучше мелкими шагами, более короткими спринтами, с частыми сливаниями кода работать. Как только ветки разошлись, они назад чудесным образом не сходятся. И со временем цена мержа будет только увеличиваться.
  • 0
    Часто поступаю так при работе с кодом не покрытым тестами: выделяю кусок кода в функцию/метод, пишу тест(ы) на основной путь выполнения, рефакторю (или пишу с ноля), чтобы эти тесты проходили, потом начинаю писать тесты для особых условий существующие в старом коде и доводить до ума новую реализацию.
  • +2
    Я посоветую всего одно:
    прочитать книгу Физерса «Эффективная работа с унаследованым кодом». Я жалею что прочитал её слишком позно. Там есть методики как относительно безопасно рефакторить и код с тестами и без тестов, как вычленять мега методы и т. п.
    • 0
      Причем лучше идти на amazon за оригиналом, потому как перевод — так себе.
    • 0
      Прочитаю, спасибо
  • 0
    > Любой фикс бага надо делать по принципу — сначала пишем тест, который будет воспроизводить проблему и падать, потом его чиним. Причины те же самые.
    Мне кажется это должно быть в топе по приоритету :)
  • +2
    Обратите также внимание на метод Микадо, предлагаемый в одноименной книге. Её авторы, казалось бы, не изобрели ничего нового, но зато довольно удачно сформулировали набор эвристических правил, позволяющих проводить сложные рефакторинги.

    Вкратце, вся суть метода заключается в последовательном применении следующих правил:

    1. Записываем нашу цель
    2. Пытаемся реализовать её наиболее наивным способом
    3. Находим ошибки и проблемы, мешающие реализации
    4. Придумываем решение для проблем, не углубляясь в анализ
    5. Записываем все решения в качестве новых целей
    6. Откатываем изменения, внесённые на шаге 2
    7. Повторяем процесс для каждой новой цели
    8. Если цель достигнута, и ошибок нет, то переходим к следующей свободной цели


    Что в итоге мы получаем:
    1. Код сохраняется в рабочем состоянии даже во время сложного и затянувшегося рефакторинга (т.к. мы отбрасываем сломанный код).
    2. Последовательность зависимых друг от друга мини-рефакторингов записывается, и её не надо хранить в голове. Кроме того, в записанном виде её удобнее и проще обсуждать с коллегами.
    3. Даже сложный рефакторинг мы сможем разбить на малые шаги, просто следуя алгоритму метода.


    Для ознакомления с подробностями рекомендую скачать и проглядеть первоисточник.

    Моё личное впечатление от метода: он требует определённой дисциплины и некоторых дополнительных затрат времени на ведение графа, но зато позволяет реально меньше ломать голову над всеми дополнительными проблемами, которые вылезают во время рефакторинга. Стоит его хотя бы разок попробовать при работе над чем-то сложным.
    • 0
      Интересный подход, порочитаю, спасибо.
  • 0
    Т.к. мержить другую ветку бывает затруднительно, я иногда использую такой подход: пишу новый код прямо в основную ветку но он отключен — завязан на константы. Когда функционал написан и протестирован, я переключаю константы — новый код начинает работать вместо старого, если что-то идет не так, то я переключаю константы обратно на старый код. Когда новый код показал стабильность, старый можно по тихонько вычищать. А иногда несколько версий продолжают работать параллельно (зависит от задачи), например старые документы одного типа работают по старому алгоритму, новые документы это-го же типа работают по новому алгоритму, что напоминает версионность.

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

    Ну просто не могу удержаться

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