Як далеко я можу підштовхнути рефакторинг без зміни зовнішньої поведінки?


27

За словами Мартіна Фаулера , рефакторинг коду - це (моє наголос):

Рефакторинг - це дисциплінована методика перебудови існуючого коду, зміна його внутрішньої структури без зміни зовнішньої поведінки . Його серце - це низка невеликих поведінок, що зберігають трансформації. Кожна трансформація (її називають "рефакторинг") робить мало, але послідовність перетворень може призвести до значної перебудови. Оскільки кожен рефакторинг невеликий, менше шансів піти не так. Система також працює повністю після кожного невеликого рефакторингу, зменшуючи шанси на те, що система може серйозно зламатись під час реструктуризації.

Що таке "зовнішня поведінка" в цьому контексті? Наприклад, якщо я застосую метод перейменування пересування та переміщую якийсь метод до іншого класу, це виглядає так, що я змінюю зовнішню поведінку, чи не так?

Тож мені цікаво зрозуміти, в який момент перестань переставати бути рефактором і стає чимось більшим. Термін "рефакторинг" може бути використаний для більших змін: чи є для нього інше слово?

Оновлення. Дуже багато цікавих відповідей про інтерфейс, але чи не зрушив би метод рефакторингу змінити інтерфейс?


Якщо існуюча поведінка вичерпна або є неповною, то змініть її, видаліть / перепишіть. Тоді ви, можливо, не будете повторно факторизувати, але кого хвилює, як називається, якщо в результаті цього система стане кращою.
Робота

2
Ваш керівник може потурбуватися, якщо вам дали дозвіл на рефактор і ви зробили перезапис.
JeffO

2
Межею рефакторингу є одиничні випробування. Якщо у вас є специфікація, складена ними, все, що ви змінюєте, що не порушує тести, - це рефакторинг?
Джордж Сільва

1
Схожий питання на SO stackoverflow.com/questions/1025844 / ...
Сільванаар

Якщо ви хочете дізнатися більше, ця тема також є активним дослідницьким полем. На цю тему існує багато наукових публікацій, наприклад, 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
я б сказав, що "зміна дизайну" не перероблена. редизайн звучить занадто істотно.
користувач606723

4
цікаво - в вашому прикладі класу А «існуючий корпус коду», і якщо метод M є відкритим , то зовнішня поведінка є змінюється Таким чином , ви могли б , ймовірно , сказати , що клас А перероблений, в той час як система в цілому в даний час перероблений ..
saus

Мені подобається спостерігати за користувачами. Ось чому я б не сказав, що розрив одиничних тестів є знаком, а скоріше закінчення до кінця або тести інтеграції були б ознакою.
Енді Візендангер

8

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


1
Я не думаю, що це хороша відповідь. Рефакторинг може передбачати зміни API. Але це не повинно впливати на кінцевого користувача чи спосіб читання / генерування вводу / файлів.
rds

3

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

  • Ці межі є досить жорсткими, щоб змусити мене почувати себе в безпеці, знаючи, що якщо я періодично перетинаюсь, це буде виявлено досить скоро, так що мені не доведеться відмовляти багато змін, щоб відновитись. З іншого боку, вони дають достатню свободу для вдосконалення коду, не турбуючись про зміну неактуальної поведінки.

  • Те, що мені особливо подобається, - це те, що такі межі є адаптивним, так би мовити. Я маю на увазі, 1) Я роблю зміни та переконуюсь, що вони відповідають специфікаціям / тестам. Потім, 2) він передається до QA або користувальницького тестування - зверніть увагу, він може все-таки вийти з ладу, оскільки щось не вистачає в специфікаціях / тестах. Добре, якщо 3a) тестування пройде, я закінчив, добре. В іншому випадку, якщо 3b) тестування не вдасться, то я 4) відкату змін і 5) додавання тестів або уточнення специфікацій, щоб наступного разу ця помилка не повторилася. Зверніть увагу , що незалежно від того , якщо тестування пропусків або зазнає невдачі, я отримати що - то - або з коду / тестів / специфікації поліпшується - мої зусилля не перетворюються в загальний обсязі відходів.

Що стосується інших видів меж - поки що мені не пощастило.

"Спостережливість для користувачів" - це безпечна ставка, якщо людина має дисципліну, яку слід дотримуватися, - що мені якось завжди докладало великих зусиль для аналізу існуючих / створення нових тестів - можливо, занадто багато зусиль. Ще одна річ, яку мені не подобається в цьому підході, - це те, що сліпо слідування йому може виявитися занадто обмежуючим. - Ця зміна заборонена, оскільки завантаження даних займе 3 секунди замість 2. - Ум, а як щодо перевірки з користувачами / експертами UX, чи це актуально чи ні? - Ні в якому разі, будь-яка зміна поведінки, що спостерігається, заборонена, період. Сейф? Будьте впевнені! продуктивні? не зовсім.

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


3

Книга Refactoring досить сильна у своєму повідомленні, що ви можете виконувати рефакторинг лише тоді, коли будете охоплені тестовими блоками .

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

Але як щодо таких простих переробників, як зміна назви класів чи членів? Хіба вони не зламають тести?

Так, вони є, і в кожному конкретному випадку вам потрібно буде врахувати, чи важливий цей розрив. Якщо ваш SUT є загальнодоступним API / SDK, то перейменування насправді є надзвичайною зміною. Якщо ні, напевно, це нормально.

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


3

Найкращим способом визначення "зовнішньої поведінки" в цьому контексті можуть бути "тестові випадки".

Якщо ви перефактуруєте код і він продовжує передавати тестові випадки (визначені до рефакторингу), то рефакторинг не змінив зовнішньої поведінки. Якщо один або кілька тестів не вийде , то ви б змінили зовнішню поведінку.

Принаймні, це моє розуміння різних книг, опублікованих на цю тему (наприклад, Фоулера).


2

Межею буде лінія, яка розповідає про те, хто розробляє, підтримує, підтримує проект, та тими, хто є його користувачами, крім прихильників, підтримувачів та розробників. Отже, до зовнішнього світу поведінка виглядає однаково, тоді як внутрішні структури, що стоять за поведінкою, змінилися.

Тож має бути нормально переміщувати функції між класами до тих пір, поки вони не є тими, які бачать користувачі.

Поки переробка коду не змінює зовнішню поведінку, додавання нових функцій або видалення існуючих функцій, я вважаю, що добре називати переробку рефакторингом.


Не погоджуюсь. Якщо ви заміните весь код доступу до даних на nHibernate, він не змінює зовнішню поведінку, але не дотримується "дисциплінованих прийомів" Фоулера. Це буде реінжиніринг, а його називання рефакторингом приховує чинник ризику.
пдр

1

Маючи належну повагу, ми повинні пам’ятати, що користувачі класу - це не кінцеві користувачі програм, побудованих із класом, а скоріше класи, які реалізуються за допомогою використання класу, який відновлюється - або виклику або успадковування від нього. .

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

Отже, якщо в класі є метод під назвою "doSomethingAmazing", це не має значення для користувача, чи реалізується це клас, на який вони посилаються, або надклас, на якому цей клас побудований. Для користувача важливо лише те, що новий (відновлений) "doSomethingAmazing" має такий самий результат, як і старий (нефактований) "doSomethingAmazing.

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


Що робити, якщо у вікні використовується форма для появи діалогового вікна "Ви впевнені, що хочете натиснути кнопку ОК?" і я вирішив її видалити, оскільки це мало користі і дратує користувачів, то чи я повторно створив код, переробив його, доповнив його, відключив його, інше?
Робота

@job: ви змінили програму, щоб зустріти нові характеристики.
jmoreno

ІМХО, можливо, ви тут змішуєте різні критерії. Переписування коду з нуля насправді не рефакторинг, але це так незалежно від того, змінив він зовнішню поведінку чи ні. Крім того, якщо зміна інтерфейсу класу не рефакторинг, як з'являються Move Method та ін. існують у каталозі реконструкцій ?
Péter Török

@ Péter Török - Це повністю залежить від того, що ви маєте на увазі під "зміною інтерфейсу класу", оскільки мова OOP, яка реалізує успадкування, інтерфейс класу включає не лише те, що реалізується самим класом усіма його предками. Зміна інтерфейсу класу означає видалення / додавання методу до інтерфейсу (або зміну підпису методу - тобто числа типу переданих параметрів). Рефакторинг означає, хто відповідає методом, класом або суперкласом.
Zeke Hansell

ІМХО - це все питання може бути надто езотеричним, щоб мати корисне значення для програмістів.
Zeke Hansell

1

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

e: Я б вважав "метод переміщення" зміною зовнішньої поведінки. Майте на увазі, що Фаулер говорить про існуючі бази кодів, які були випущені в дику природу. Залежно від вашої ситуації, можливо, ви зможете переконатися, що ваша зміна не порушує жодних зовнішніх клієнтів і продовжуйте ваш веселий шлях.

e2: "Отже, яке правильне слово для змін, що змінюють зовнішню поведінку?" - API Refactoring, Breaking Change і т. Д ... його все ще рефакторинг, просто не дотримуючись кращих практик рефакторингу публічного інтерфейсу, який вже в природі з клієнтами.


Але якщо він говорить про загальнодоступний інтерфейс, а як щодо рефакторингу "методом Move"?
SiberianGuy

@Idsa Редагував мою відповідь на вирішення вашого питання.

@kekela, але мені досі незрозуміло, чим закінчується ця "рефакторинг"
SiberianGuy

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

0

"Метод переміщення" - це техніка рефакторингу, а не сама рефакторинг. Рефакторинг - це процес застосування кількох методів рефакторингу до класів. Там, коли ви говорите, я застосував "метод переміщення" до класу, ви насправді не маєте на увазі "я переробив (клас)", ви насправді маєте на увазі "я застосував техніку рефакторингу для цього класу". Рефакторинг, в цьому найбільш чистий сенс застосовується до дизайну, або більш конкретно, до певної частини дизайну програми, яку можна розглядати як чорну скриньку.

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

На закінчення "межу", згадану у питанні, перетинають, якщо ви плутаєте (змішуєте: D) рефакторинг-техніку з рефакторингом-процесом.

PS: Добре запитання, btw.


Прочитайте його кілька разів, але все одно не дістаньте
SiberianGuy

яку частину ви не зрозуміли? (є рефакторинг-процес, до якого застосовується ваше визначення, або техніка рефакторингу, у вашому прикладі метод переміщення, до якого визначення не застосовується; для цього метод перенесення не порушує визначення рефакторингу-the- обробляти або не перетинати її межі, якими б вони не були). Я кажу, що для вашого прикладу не повинно існувати занепокоєння. межа рефакторингу не є чимось нечітким. ви просто застосовуєте визначення чогось до чогось іншого.
Белун

0

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


0

Межі рефакторингу є специфічними для даного рефакторингу. Просто не може бути однієї відповіді, і вона охоплює все. Однією з причин є те, що сам термін рефакторинг не є специфічним.

Ви можете зробити рефактор:

  • Вихідний код
  • Архітектура програми
  • Дизайн / взаємодія користувача з інтерфейсом

і я впевнений у кількох інших речах.

Можливо, рефактор можна було б визначити як

"дія або сукупність дій, які зменшують ентропію в певній системі"


0

Напевно є певні межі щодо того, наскільки далеко ви можете рефактор. Дивіться: Коли два алгоритми однакові?

Люди зазвичай розглядають алгоритми як більш абстрактні, ніж програми, які їх реалізують. Природним способом формалізації цієї ідеї є те, що алгоритми - це класи еквівалентності програм відносно відповідного відношення еквівалентності. Ми стверджуємо, що такого відношення еквівалентності не існує.

Отже, не заходьте занадто далеко, інакше у вас не буде впевненості в результаті. З іншого боку, досвід наказує, що часто можна замінювати один алгоритм іншим і отримувати однакові відповіді, іноді швидше. У цьому краса, так?


0

Ви можете думати про «зовнішнє» значення

Зовнішній для щоденного стояння.

Отже, будь-яка зміна системи, яка не впливає на жодних свиней, може вважатися рефакторною.

Зміна інтерфейсу класу - це жодне питання, якщо клас використовується лише єдиною системою, яка будується та підтримується вашою командою. Однак якщо клас є публічним класом у рамках .net, який використовує кожен програміст .net, це вже зовсім інша справа.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.