У меня есть проект. В этом проекте я хотел реорганизовать его, чтобы добавить функцию, и я рефакторинг проекта, чтобы добавить функцию.
Проблема в том, что когда я закончил, оказалось, что мне нужно было сделать небольшое изменение интерфейса, чтобы приспособиться к нему. Так что я сделал изменения. И тогда класс потребления не может быть реализован с его текущим интерфейсом в терминах нового, поэтому ему также нужен новый интерфейс. Сейчас три месяца спустя, и мне пришлось исправлять неисчислимые, практически не связанные с этим проблемы, и я смотрю на решение проблем, которые планировались в течение года или просто указаны в списке, которые не будут исправлены из-за трудностей, прежде чем объект будет скомпилирован очередной раз.
Как я могу избежать такого рода каскадных рефакторингов в будущем? Является ли это просто симптомом моих предыдущих занятий, которые слишком сильно зависят друг от друга?
Краткое редактирование: в этом случае рефактором была особенность, так как рефактор увеличил расширяемость определенного фрагмента кода и уменьшил некоторую связь. Это означало, что внешние разработчики могли делать больше, и именно эту функцию я хотел предоставить. Таким образом, сам по себе оригинальный рефакторинг не должен был быть функциональным изменением.
Большие изменения, которые я обещал пять дней назад:
До того, как я начал этот рефакторинг, у меня была система, в которой у меня был интерфейс, но в реализации я просто dynamic_cast
прошел через все возможные реализации, которые я поставил. Это, очевидно, означало, что вы, во-первых, не могли просто наследовать от интерфейса, а во-вторых, что никто не имел бы возможности без доступа к реализации реализовать этот интерфейс. Поэтому я решил, что хочу решить эту проблему и открыть интерфейс для общего пользования, чтобы любой мог его реализовать, и что реализация интерфейса - это весь контракт, который требуется - очевидно, улучшение.
Когда я находил и убивал огнем все места, где я это делал, я обнаружил одно место, которое оказалось особой проблемой. Это зависело от деталей реализации всех различных производных классов и дублированных функций, которые уже были реализованы, но лучше где-то еще. Вместо этого он мог бы быть реализован с точки зрения открытого интерфейса и повторно использовать существующую реализацию этой функциональности. Я обнаружил, что для правильной работы требуется определенный фрагмент контекста. Грубо говоря, вызов предыдущей реализации выглядел примерно так
for(auto&& a : as) {
f(a);
}
Однако, чтобы получить этот контекст, мне нужно было изменить его на что-то вроде
std::vector<Context> contexts;
for(auto&& a : as)
contexts.push_back(g(a));
do_thing_now_we_have_contexts();
for(auto&& con : contexts)
f(con);
Это означает, что для всех операций, которые раньше были частью f
, некоторые из них должны быть включены в новую функцию, g
которая работает без контекста, а некоторые из них должны быть сделаны из части теперь отложенной f
. Но не все методы f
вызывают или нуждаются в этом контексте - некоторые из них нуждаются в отдельном контексте, который они получают отдельными средствами. Таким образом, для всего, что f
заканчивается вызовом (а это, грубо говоря, почти все ), я должен был определить, какой контекст им нужен, если таковой имеется, откуда они должны его получить и как разделить их со старого f
на новое f
и новое. g
,
И вот как я оказался там, где я сейчас. Единственная причина, по которой я продолжал идти, - это необходимость рефакторинга по другим причинам.
Ответы:
В прошлый раз, когда я пытался начать рефакторинг с непредвиденными последствиями, и я не мог стабилизировать сборку и / или тесты через один день , я сдался и вернул базу кода до точки, предшествующей рефакторингу.
Затем я начал анализировать, что пошло не так, и разработал лучший план, как выполнить рефакторинг в более мелкие шаги. Поэтому мой совет избегать каскадного рефакторинга: знайте, когда остановиться , не позволяйте вещам выйти из-под вашего контроля!
Иногда приходится кусать пули и выбрасывать полный рабочий день - определенно проще, чем три месяца работы. День, который вы теряете, не совсем напрасен, по крайней мере, вы научились не подходить к проблеме. И, по моему опыту, всегда есть возможность сделать небольшие шаги в рефакторинге.
Примечание : вы, похоже, находитесь в ситуации, когда вам нужно решить, готовы ли вы пожертвовать полными тремя месяцами работы и начать все заново с новым (и, надеюсь, более успешным) планом рефакторинга. Я могу себе представить, что это нелегкое решение, но спросите себя, насколько высок риск, который вам нужен еще через три месяца, чтобы не только стабилизировать сборку, но и исправить все непредвиденные ошибки, которые вы, вероятно, обнаружили во время переписывания, которое вы делали последние три месяца? ? Я написал «переписать», потому что я думаю, это то, что вы действительно сделали, а не «рефакторинг». Маловероятно, что вы можете решить свою текущую проблему быстрее, вернувшись к последней ревизии, где ваш проект компилируется и снова начинается реальный рефакторинг (в отличие от «переписать»).
источник
Конечно. Одним из изменений, вызывающих множество других изменений, является определение сцепления.
В худшем виде кодовых баз, одно изменение будет продолжаться каскадно, что в конечном итоге заставит вас изменить (почти) все. Часть любого рефактора, где есть широко распространенная связь, состоит в том, чтобы изолировать часть, над которой вы работаете. Вам нужно провести рефакторинг не только там, где ваша новая функция касается этого кода, но и там, где все остальное касается этого кода.
Обычно это означает, что некоторые адаптеры должны помочь старому коду работать с чем-то, что выглядит и действует как старый код, но использует новую реализацию / интерфейс. В конце концов, если все, что вы делаете, это изменяете интерфейс / реализацию, но оставляете связь, вы ничего не получаете. Это помада на свинье.
источник
Похоже, ваш рефакторинг был слишком амбициозным. Рефакторинг должен применяться небольшими шагами, каждый из которых может быть выполнен, скажем, за 30 минут - или, в худшем случае, не более одного дня - и оставляет проект готовым к выполнению, и все тесты все еще проходят.
Если вы сохраняете каждое индивидуальное изменение минимальным, для рефакторинга действительно не будет возможности сломать вашу сборку надолго. В худшем случае, вероятно, изменение параметров метода в широко используемом интерфейсе, например, для добавления нового параметра. Но вытекающие из этого изменения являются механическими: добавление (и игнорирование) параметра в каждой реализации и добавление значения по умолчанию при каждом вызове. Даже если есть сотни ссылок, такой рефакторинг не должен занимать даже дня.
источник
Дизайн желаемого мышления
Цель - отличный дизайн и реализация ОО для новой функции. Избежание рефакторинга также является целью.
Начните с нуля и создайте дизайн для новой функции , которую вы хотели бы иметь. Потратьте время, чтобы сделать это хорошо.
Однако обратите внимание, что ключ здесь «добавить функцию». Новые вещи, как правило, позволяют нам в значительной степени игнорировать текущую структуру базы кода. Наш желаемый дизайн мышления является независимым. Но тогда нам нужно еще две вещи:
Эвристика, извлеченные уроки и т. Д.
Рефакторинг был так же прост, как добавление параметра по умолчанию к существующему вызову метода; или один вызов метода статического класса.
Методы расширения существующих классов могут помочь сохранить качество нового дизайна с абсолютным минимальным риском.
«Структура» - это все. Структура - это реализация принципа единой ответственности; дизайн, который облегчает функциональность. Код будет оставаться коротким и простым на протяжении всей иерархии классов. Время на новый дизайн уходит во время тестирования, переделки и предотвращения взлома устаревших джунглей кода.
Занятия с желаемым мышлением сосредоточены на поставленной задаче Как правило, забудьте о расширении существующего класса - вы просто снова запускаете каскад рефактора и сталкиваетесь с накладными расходами «более тяжелого» класса.
Удалите все остатки этой новой функциональности из существующего кода. Здесь полные и хорошо инкапсулированные новые функциональные возможности важнее, чем избегание рефакторинга.
источник
Из (замечательной) книги « Эффективная работа с устаревшим кодом» Майкла Фезерса :
источник
Это звучит так (особенно из обсуждений в комментариях), в которые вы сами включили навязанные им правила, которые означают, что это «незначительное» изменение - такой же объем работы, что и полная переработка программного обеспечения.
Решение должно быть «не делай этого» . Это то, что происходит в реальных проектах. Множество старых API имеют в результате уродливые интерфейсы или заброшенные (всегда нулевые) параметры, или функции с именем DoThisThing2 (), которые делают то же самое, что DoThisThing () с совершенно другим списком параметров. Другие распространенные уловки включают в себя сохранение информации в глобальных или теговых указателях для того, чтобы переправить ее через большой кусок фреймворка. (Например, у меня есть проект, в котором половина аудио-буферов содержит только 4-байтовое магическое значение, потому что это было намного проще, чем изменить способ, которым библиотека вызывала свои аудио-кодеки.)
Трудно дать конкретный совет без конкретного кода.
источник
Автоматизированные тесты. Вам не нужно быть фанатом TDD, и при этом вам не нужно 100% покрытие, но автоматизированные тесты позволяют вам уверенно вносить изменения. Кроме того, звучит так, будто у вас дизайн с очень высокой связью; Вы должны прочитать о принципах SOLID, которые сформулированы специально для решения такого рода проблем в разработке программного обеспечения.
Я также рекомендовал бы эти книги.
источник
Скорее всего, да. Хотя вы можете получить аналогичные эффекты с довольно хорошей и чистой базой кода, когда требования меняются достаточно
Боюсь, вы не можете остановиться на устаревшем коде. Но вы можете использовать метод, который позволяет избежать эффекта отсутствия рабочей базы кода в течение нескольких дней, недель или даже месяцев.
Этот метод называется «Метод Микадо», и он работает так:
запишите цель, которую вы хотите достичь, на листе бумаги
внесите самое простое изменение, которое приведет вас в этом направлении.
проверьте, работает ли он, используя компилятор и ваш набор тестов. Если это продолжается, перейдите к шагу 7. в противном случае перейдите к шагу 4.
на своей бумаге отметьте вещи, которые необходимо изменить, чтобы ваши текущие изменения работали. Нарисуйте стрелки от вашего текущего задания к новым.
Отменить ваши изменения Это важный шаг. Это противоречит интуиции и вначале причиняет физическую боль, но поскольку вы только что попробовали простую вещь, на самом деле это не так уж плохо.
выберите одну из задач, в которой нет исходящих ошибок (нет известных зависимостей) и вернитесь к 2.
зафиксируйте изменения, вычеркните задачу на бумаге, выберите задачу без исходящих ошибок (без известных зависимостей) и вернитесь к 2.
Таким образом, вы получите работающую кодовую базу в короткие промежутки времени. Где вы также можете объединить изменения с остальной частью команды. И у вас есть наглядное представление о том, что, как вы знаете, вам еще нужно сделать, это помогает решить, хотите ли вы продолжить работу или прекратить ее.
источник
Рефакторинг - это структурированная дисциплина, отличная от очистки кода по вашему усмотрению. Вам нужно написать модульные тесты перед запуском, и каждый шаг должен состоять из определенного преобразования, которое, как вы знаете, не должно вносить изменений в функциональность. Модульные тесты должны проходить после каждого изменения.
Конечно, в процессе рефакторинга вы, естественно, обнаружите изменения, которые должны быть применены и которые могут привести к поломке. В этом случае постарайтесь реализовать совместимость прокладки для старого интерфейса, который использует новую платформу. Теоретически система должна работать как прежде, и модульные тесты должны пройти. Вы можете пометить прокладку совместимости как устаревший интерфейс и очистить ее в более подходящее время.
источник
Как сказал @Jules, рефакторинг и добавление функций - это две разные вещи.
... но действительно, иногда вам нужно изменить внутреннюю работу, чтобы добавить свои вещи, но я бы скорее назвал это изменением, чем рефакторингом.
Вот где все становится грязно. Интерфейсы предназначены как границы, чтобы изолировать реализацию от того, как она используется. Как только вы коснетесь интерфейсов, все с обеих сторон (реализация или использование) также должно быть изменено. Это может распространиться так далеко, как вы это испытали.
То, что один интерфейс требует изменений, звучит хорошо ... то, что оно распространяется на другой, подразумевает, что изменения распространяются еще дальше. Звучит так, как будто какая-то форма ввода / данных требует потока по цепочке. Это тот случай?
Ваш разговор очень абстрактный, поэтому его сложно понять. Пример был бы очень полезен. Обычно интерфейсы должны быть достаточно стабильными и независимыми друг от друга, что позволяет модифицировать часть системы без ущерба для остальных ... благодаря интерфейсам.
... на самом деле, лучший способ избежать каскадных модификаций кода - это именно хорошие интерфейсы. ;)
источник
Я думаю, что вы обычно не можете, если вы не хотите, чтобы все было так, как есть. Тем не менее, в таких ситуациях, как ваша, я думаю, что лучше информировать команду и дать им понять, почему необходимо провести рефакторинг, чтобы продолжить более здоровое развитие. Я бы не пошла и сама все исправила. Я говорил об этом во время собраний Scrum (при условии, что они есть у вас, ребята) и систематически подходил к этому вопросу с другими разработчиками.
источник