Как далеко я могу продвигать рефакторинг без изменения внешнего поведения?

27

По словам Мартина Фаулера , рефакторинг кода (акцент мой):

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

Что такое «внешнее поведение» в этом контексте? Например, если я применяю рефакторинг метода move и перемещаю какой-либо метод в другой класс, похоже, я меняю внешнее поведение, не так ли?

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

Обновить. Много интересных ответов об интерфейсе, но разве метод рефакторинга не изменит интерфейс?

SiberianGuy
источник
Если существующее поведение является отстойным или неполным, исправьте его, удалите / переписайте. Вы, возможно, не будете перефакторинг тогда, но кого волнует, как зовут, если система станет (правильно?) В результате этого лучше.
Работа
2
Ваш руководитель может позаботиться о том, чтобы вам дали разрешение на рефакторинг, и вы сделали переписывание.
JeffO
2
Границей рефакторинга являются юнит-тесты. Если у вас есть нарисованная ими спецификация, то что вы изменяете, не нарушая тесты, это рефакторинг?
Джордж Сильва
1
Аналогичный вопрос на SO stackoverflow.com/questions/1025844/…
sylvanaar
Если вы хотите узнать больше, эта тема также является активной областью исследований. Есть много научных публикаций на эту тему, например, informatik.uni-trier.de/~ley/db/indices/a-tree/s/…
Skarab

Ответы:

25

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

Таким образом, если вы переместите метод M из класса A в класс B, и оба класса находятся глубоко внутри приложения, и ни один пользователь не сможет наблюдать каких-либо изменений в поведении приложения из-за этого изменения, то вы можете по праву назвать его рефакторингом.

Если OTOH, какая-то другая подсистема / компонент более высокого уровня изменяет свое поведение или выходит из строя из-за изменения, это действительно (обычно) можно наблюдать пользователям (или, по крайней мере, системным администраторам, проверяющим журналы). Или, если ваши классы были частью общедоступного API, может существовать сторонний код, который зависит от того, является ли M частью класса A, а не B. Так что ни один из этих случаев не является рефакторингом в строгом смысле.

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

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

Так что же является правильным словом для переделок, которые изменяют внешнее поведение?

Я бы назвал это редизайном .

Обновить

Много интересных ответов об интерфейсе, но разве метод рефакторинга не изменит интерфейс?

Которого? Конкретные классы, да. Но являются ли эти классы непосредственно видимыми для внешнего мира? Если нет - потому что они находятся внутри вашей программы, а не являются частью внешнего интерфейса (API / GUI) программы - никакие сделанные изменения не наблюдаются внешними сторонами (если, конечно, изменение не нарушает что-то, конечно).

Я чувствую, что за этим стоит более глубокий вопрос: существует ли конкретный класс как самостоятельная сущность? В большинстве случаев ответ отрицательный : класс существует только как часть более крупного компонента, экосистемы классов и объектов, без которой он не может быть создан и / или непригоден для использования. Эта экосистема включает не только свои (прямые / косвенные) зависимости, но и другие классы / объекты, которые зависят от нее. Это потому, что без этих классов более высокого уровня ответственность, связанная с нашим классом, может быть бессмысленной / бесполезной для пользователей системы.

Например, в нашем проекте, который занимается прокатом автомобилей, есть Chargeучебный класс. Этот класс сам по себе бесполезен для пользователей системы, потому что агенты и клиенты пунктов проката не могут ничего сделать с индивидуальной платой: они имеют дело с договорами аренды в целом (которые включают в себя кучу разных видов сборов) , Пользователей больше всего интересует общая сумма этих сборов, которую они должны заплатить в конце концов; агент интересуется различными вариантами контракта, длительностью аренды, выбранной группой транспортных средств, страховым пакетом, дополнительными предметами и т. д., которые (с помощью сложных бизнес-правил) определяют, какие сборы присутствуют и как рассчитывается окончательный платеж. из них. А представители стран / бизнес-аналитики заботятся о конкретных бизнес-правилах, их синергии и влиянии (на доходы компании и т. Д.).

Недавно я провел рефакторинг этого класса, переименовав большинство его полей и методов (чтобы следовать стандартному соглашению об именах Java, которое полностью игнорировалось нашими предшественниками). Я также планирую дальнейший рефакторинг для замены Stringи charполей на более подходящие enumи booleanтипы. Все это, безусловно, изменит интерфейс класса, но (если я правильно сделаю свою работу), ничего из этого не будет видно пользователям нашего приложения. Никто из них не заботится о том, как представлены отдельные обвинения, хотя они наверняка знают понятие обвинения, Я мог бы выбрать в качестве примера сто других классов, не представляющих какой-либо концепции предметной области, так что я был бы даже концептуально невидимым для конечных пользователей, но я подумал, что было бы более интересно выбрать пример, в котором есть хотя бы некоторая видимость на уровне концепта. Это хорошо показывает, что интерфейсы классов являются только представлениями концепций предметной области (в лучшем случае), а не реальными вещами *. Представление может быть изменено, не затрагивая концепцию. И пользователи имеют и понимают только концепцию; Нашей задачей является сопоставление понятия и представления.

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

Петер Тёрёк
источник
3
Я бы сказал, «изменение дизайна», а не редизайн. Редизайн звучит слишком существенно.
user606723
4
интересно - в вашем примере класса А «существующий корпус кода», и если метод M является открытым , то внешнее поведение является изменяется Таким образом , вы могли бы , вероятно , сказать , что класс А переработанный, в то время как система в целом в настоящее время переработан..
колбаса
Мне нравится наблюдать за пользователями. Вот почему я бы не сказал, что поломка модульных тестов является признаком, а скорее сквозным, либо интеграционным тестом.
Энди Визендангер
8

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

Саид Нямати
источник
1
Я не думаю, что это хороший ответ. Рефакторинг может подразумевать изменения API. Но это не должно влиять на конечного пользователя или способ чтения / создания входных / файлов.
Rds
3

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

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

  • Что мне особенно нравится, так это то, что такого рода границы являются, так сказать, адаптивными . Я имею в виду, 1) Я делаю изменения и проверяю, что они соответствуют спецификации / тестам. Затем, 2) он передается в QA или пользовательское тестирование - обратите внимание, что здесь все равно может произойти сбой, потому что чего-то не хватает в spec / tests. Хорошо, если 3а) тестирование прошло, я в порядке, хорошо. В противном случае, если 3b) тестирование не пройдено, тогда я 4) откат изменения и 5) добавление тестов или уточнение спецификации, чтобы в следующий раз эта ошибка не повторилась. Обратите внимание , что независимо от того , если тестирование пропусков или терпит неудачу, я получить что - то - либо из кода / тестов / спецификации улучшается - мои усилия не превращаются в общий объеме отходов.

Что касается других видов границ - мне пока не повезло.

«Наблюдение за пользователями» - это безопасная ставка, если кто-то должен соблюдать дисциплину, что, по-моему, всегда связано с большими усилиями при анализе существующих / созданием новых тестов - возможно, слишком много усилий. Еще одна вещь, которая мне не нравится в этом подходе, заключается в том, что слепое следование ему может оказаться слишком ограничительным. - Это изменение запрещено, потому что с ним загрузка данных займет 3 секунды вместо 2. - Ну, как насчет проверки с пользователями / экспертом по UX, актуально это или нет? - Ни в коем случае, любые изменения в наблюдаемом поведении запрещены, точка. Сейф? Вы держите пари! продуктивный? на самом деле, нет.

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

комар
источник
3

Книга « Рефакторинг» довольно убедительна в своем сообщении о том, что рефакторинг можно выполнять только при наличии покрытия модульным тестом .

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

Но как насчет таких простых рефакторингов, как изменение имен классов или членов? Разве они не нарушают тесты?

Да, это так, и в каждом случае вам нужно будет подумать, значим ли этот разрыв. Если ваша SUT является общедоступным API / SDK, то переименование действительно является серьезным изменением. Если нет, то, вероятно, все в порядке.

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

Марк Симанн
источник
3

Лучшим способом определения «внешнего поведения» в этом контексте могут быть «контрольные примеры».

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

По крайней мере, так я понимаю различные книги, опубликованные по этой теме (например, книги Фаулера).

Цезарь Провиденти
источник
2

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

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

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

vpit3833
источник
Не согласен. Если вы замените весь свой код доступа к данным на nHibernate, это не изменит внешнего поведения, но не будет следовать «дисциплинированным методикам Фаулера». Это было бы реинжинирингом, а рефакторинг скрывает фактор риска.
фунтовые
1

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

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

Поэтому, если у класса есть метод с именем «doSomethingAmazing», для пользователя не имеет значения, реализован ли он классом, на который он ссылается, или суперклассом, на котором построен этот класс. Все, что имеет значение для пользователя, заключается в том, что новый (рефакторированный) «doSomethingAmazing» имеет тот же результат, что и старый (нерефакторированный) «doSomethingAmazing».

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

Зик Ханселл
источник
Что если в форме окна используется для всплывающего диалогового окна «Вы уверены, что хотите нажать кнопку ОК?» и я решил удалить его, потому что он приносит мало пользы и раздражает пользователей, а затем я повторно проанализировал код, перепроектировал его, исправил его, удалил ошибку, другое?
Работа
@job: вы изменили программу, чтобы соответствовать новым спецификациям.
Jmoreno
ИМХО, вы можете смешивать разные критерии здесь. Переписывание кода с нуля действительно не является рефакторингом, но это происходит независимо от того, изменило ли оно внешнее поведение или нет. Кроме того, если изменение интерфейса класса не было рефакторингом, почему бы Move Method et al. существуют в каталоге рефакторингов ?
Петер Тёрёк
@ Péter Török - Это полностью зависит от того, что вы подразумеваете под «изменением интерфейса класса», потому что в языке ООП, который реализует наследование, интерфейс класса включает в себя не только то, что реализовано самим классом всеми его предками. Изменение интерфейса класса означает удаление / добавление метода к интерфейсу (или изменение сигнатуры метода - то есть числа переданного типа параметров). Рефакторинг означает, кто отвечает на метод, класс или суперкласс.
Зик Ханселл
ИМХО - весь этот вопрос может быть слишком эзотерическим, чтобы быть полезным для программистов.
Зик Ханселл
1

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

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

e2: «Так какое слово подходит для переделок, которые изменяют внешнее поведение?» - Рефакторинг API, прерывание изменений и т. Д., Он по-прежнему рефакторинг, просто он не следует лучшим рекомендациям по рефакторингу общедоступного интерфейса, который уже широко используется клиентами.


источник
Но если он говорит об общедоступном интерфейсе, как насчет рефакторинга "Move методом"?
SiberianGuy
@Idsa Отредактировал мой ответ на ваш вопрос.
@kekela, но мне все еще неясно, где заканчивается эта «рефакторинг»
SiberianGuy
@idsa Согласно опубликованному вами определению, он перестает быть рефакторингом, как только вы меняете публичный интерфейс. (перемещение публичного метода из одного класса в другой было бы примером этого)
0

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

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

В заключение, «граница», упомянутая в вопросе, пересекается, если вы путаете (смесь: D) рефакторинг-технику с рефакторингом-процесс.

PS: Хороший вопрос, кстати.

Belun
источник
Прочитайте это несколько раз, но все еще не получите это
SiberianGuy
какую часть ты не понял? (есть рефакторинг-процесс, к которому применяется ваше определение, или рефакторинг-методика, в вашем примере, метод move, к которому определение не относится; поэтому метод move не нарушает определение рефакторинга-the- процесс, или не пересекает его границы, какими бы они ни были). Я говорю, что беспокойство, которое у вас есть, не должно существовать для вашего примера. граница рефакторинга не является чем-то размытым. вы просто применяете определение чего-то к чему-то другому.
Белун
0

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

user34691
источник
0

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

Вы можете рефакторинг:

  • Исходный код
  • Архитектура программы
  • Дизайн пользовательского интерфейса / взаимодействие с пользователем

и я уверен, что несколько других вещей.

Возможно, рефакторинг можно определить как

«действие или набор действий, которые уменьшают энтропию в конкретной системе»

Сильванаар
источник
0

Определенно есть границы того, как далеко вы можете провести рефакторинг. Смотрите: когда два алгоритма одинаковы?

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

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

Брюс Эдигер
источник
0

Вы можете думать о «внешнем» значении

Внешне к ежедневному вставанию.

Таким образом, любое изменение в системе, которое не затрагивает свиней, может рассматриваться как рефакторинг.

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

Ян
источник