Рефакторинг і принцип відкритого / закритого типу


12

Нещодавно я читав веб-сайт про розробку чистого коду (тут не посилаю посилання, оскільки це не англійською мовою).

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

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

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

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

Чи не існує суперечності / конфлікту між застосуванням відкритого / закритого принципу та пропонуванням використання складних рефактори в якості найкращої практики? Або ідея тут полягає в тому, що під час розробки класу можна використовувати складні рефактори A, але коли цей клас був успішно протестований, його слід заморозити?

Відповіді:


9

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

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

Можливо, ви робите рефакторинг, щоб дозволити реалізувати наступну функцію, не порушуючи OCP, коли ви це зробите.


Ви, звичайно, не повинні мислити як будь-який принцип як мету дизайну . Вони - це інструменти - ви не робите програмне забезпечення для того, щоб бути гарним і теоретично правильним зсередини, ви намагаєтеся отримати корисність для свого клієнта. Це настанова , не більше того.
Т. Сар

@ T.Sar Принцип - це керівний принцип, до чого ви прагнете, вони орієнтовані на ремонтопридатність та масштабованість. Це для мене схоже на мету дизайну. Я не можу бачити принцип як інструмент у тому, як я бачу дизайнерський шарф або рамку як інструмент.
Tulains Córdova

@ TulainsCórdova Ремонтопридатність, продуктивність, коректність, масштабованість - це цілі. Принцип відкритого закриття - це засіб до них - лише один із багатьох. Вам не потрібно щось підштовхувати до принципу відкритого закриття, якщо він не застосовується до нього або це зашкодить фактичним цілям проекту. Ви не продаєте клієнту "Відкритість". Як просте керівництво , це не краще, ніж правило, яке можна відмовитись, якщо ви нарешті знайдете спосіб зробити свою справу більш зрозумілим і зрозумілим способом. Настанови - це інструменти, зрештою, нічого більше.
Т. Сар

@ T.Sar Є так багато речей, що ви не можете продати клієнтові ... З іншого боку, я згоден з вами в тому, що ви не повинні робити речей, які заважають цілям проекту.
Tulains Córdova

9

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

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

OC - ​​фрактал . Це яблука на всіх глибинах вашого дизайну. Усі припускають, що він застосовується лише на рівні класу. Але вона однаково застосована на рівні методу або на рівні складання.

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

Дотримуватися Open-Closed буквально означає, що кількість класів вибухне. Ви будете створювати (капітал "Я") інтерфейси без потреби. У вас з’явиться біт функціональності, розподілений по класах, і тоді вам доведеться написати набагато більше коду, щоб передати все це разом. В якийсь момент вам прийде світанок, що змінити початковий клас було б краще.


2
"Злочинів у порушенні OC набагато менше, коли ви можете додавати нові методи до класу, а не змінювати існуючі.": Наскільки я розумію, додавання нових методів зовсім не порушує принцип OC (відкритий для розширення). . Проблема полягає в зміні існуючих методів, які реалізують чітко визначений інтерфейс і тому вже мають чітко визначену семантику (закрита для модифікації). В принципі, рефакторинг не змінює семантику, тому єдиний ризик, який я бачу, - це введення помилок у вже стабільний і добре перевірений код.
Джорджіо

1
Ось відповідь CodeReview, яка ілюструє відкриті для розширення . Цей дизайн класу можна розширити. На противагу цьому, додавання методу - це модифікація класу.
radarbob

Додавання нових методів порушує LSP, а не OCP.
Tulains Córdova

1
Додавання нових методів не порушує LSP. Якщо ви додасте метод, ви ввели новий інтерфейс @ TulainsCórdova
RubberDuck

6

Принцип відкритого закриття, здається, є принципом, який з'явився до того, як TDD було більш поширеним. Ідея полягає в тому, що рефакторний код ризиковано, оскільки ви можете щось зламати, тому безпечніше залишити існуючий код таким, який є, і просто додати його. За відсутності тестів це має сенс. Мінусом такого підходу є кодова атрофія. Кожен раз, коли ви розширюєте клас, а не рефакторинг його, ви отримуєте додатковий шар. Ви просто прикручуєте код зверху. Кожен раз, коли ви набираєте більше коду, ви збільшуєте ймовірність дублювання. Уявіть; У моїй кодовій базі є послуга, яку я хочу використовувати, я вважаю, що у неї немає того, що я хочу, тому я створюю новий клас, щоб розширити його і включити свій новий функціонал. Інший розробник з'явиться пізніше і також хоче скористатися тією ж послугою. На жаль, вони не ' не розумію, що моя розширена версія існує. Вони кодують проти оригінальної реалізації, але їм також потрібна одна з функцій, яку я кодував. Замість використання моєї версії вони тепер також розширюють реалізацію та додають нову функцію. Зараз у нас є 3 класи, оригінальна і дві нові версії, які мають деякий дублюючий функціонал. Дотримуйтесь відкритого / закритого принципу, і це дублювання продовжуватиме нарощуватися впродовж життя проекту, що призведе до зайвої складної бази коду.

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


1
Я не є прихильником принципу відкритого закриття, а також TDD (в тому сенсі, що я їх не вигадував). Мене здивувало те, що хтось запропонував принцип відкритого закритого І використання одночасно рефакторингу AND TDD. Це здалося мені суперечливим, і тому я намагався розібратися, як об'єднати всі ці вказівки у цілісний процес.
Джорджіо

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

Ви повинні думати, що клас буде використовуватися не лише у вашій кодовій базі. Бібліотеку, яку ви пишете, можна використовувати в інших проектах. Отже, OCP важливий. Крім того, новий програміст, який не знає розширеного класу з необхідними йому функціональними можливостями, - це проблема зв'язку та документації, а не проблема дизайну.
Tulains Córdova

@ TulainsCórdova в коді програми це не стосується. Щодо бібліотечного коду, я стверджую, що семантична версія була кращою придатністю для повідомлення про переломні зміни.
opsb

1
@ TulainsCórdova зі стабільністю API бібліотечного коду набагато важливіше, тому що неможливо протестувати код клієнта. За допомогою коду програми тестове покриття негайно повідомить вас про будь-які поломки. Інакше кажучи, код програми може вносити зміни до змін без ризику, тоді як код бібліотеки повинен керувати ризиком, підтримуючи стабільний API та
зриви

6

Словами мирянина:

A. Принцип O / C означає, що спеціалізацію потрібно проводити шляхом розширення, а не шляхом зміни класу для пристосування до спеціалізованих потреб.

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

C. Рефакторинг не порушує цей принцип.

Коли дизайн дозріває , скажімо, через деякий час у виробництві:

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

Принцип відкритого / закритого дуже не зрозуміло. Ваші бали A і B підходять саме так.
gnasher729

1

Для мене Принцип відкритого закриття - це правило, а не жорстке і швидке правило.

Що стосується відкритої частини принципу, підсумкові класи в Java та класи в C ++ з усіма конструкторами, оголошеними приватними, порушують відкриту частину принципу відкрито-закритого типу. Існують гарні випадки твердого використання (зверніть увагу: тверді, не тверді) для підсумкових занять. Проектування для розширюваності важливо. Однак для цього потрібно багато передбачень та зусиль, і ти завжди перебираєш лінію порушення ЯГНІ (тобі це не знадобиться) та вводиш кодовий запах умоглядної загальності. Чи повинні бути відкриті ключові компоненти програмного забезпечення для розширення? Так. Усі? Ні. Це саме по собі є умоглядною загальністю.

Що стосується закритої частини, то при переході від версії 2.0 до 2.1 до 2.2 до 2.3 деякого продукту не змінювати поведінку - дуже гарна ідея. Користувачам це дуже не подобається, коли кожен незначний випуск порушує їхній власний код. Однак попутно часто виявляється, що початкова реалізація у версії 2.0 була принципово порушена або що зовнішні обмеження, що обмежували початковий дизайн, більше не застосовуються. Ви посміхаєтесь і несете його та підтримуєте цей дизайн у версії 3.0, чи робите 3.0 невідповідним у чомусь сумісному? Відстала сумісність може бути величезним обмеженням. Основні межі випуску - це місце, де дозволено порушувати відсталу сумісність. Вам потрібно остерігатися, що це може засмутити ваших користувачів. Має бути хороший випадок, чому потрібен цей розрив із минулим.


0

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

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

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

Про принципи SOLID Вони справді хороші вказівки щодо розробки програмного забезпечення, але вони не мають релігійних правил, яких слід сліпо слідувати. Іноді, багато разів, додаючи другу та третю та n-ю особливості, ви розумієте, що ваш початковий дизайн, навіть якщо він поважає Open-Close, не дотримується інших принципів або вимог до програмного забезпечення. Є еволюція дизайну та програмного забезпечення, коли потрібно зробити більш складні зміни. Вся справа в тому, щоб якнайшвидше знайти та усвідомити ці проблеми та максимально застосовувати методи рефакторингу.

Не існує такого поняття, як ідеальний дизайн. Не існує такого дизайну, який би міг і повинен дотримуватися всіх існуючих принципів чи зразків. Це кодування утопії.

Я сподіваюся, що ця відповідь допомогла вам у вашій дилемі. Сміливо запитайте роз'яснення, якщо це потрібно.


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

"Іноді, багато разів, додаючи другу, третю та n-ту функцію, ви розумієте, що ваш початковий дизайн, навіть якщо він поважає Open-Close, не відповідає іншим принципам або вимогам до програмного забезпечення.": Тоді я б почніть писати нову реалізацію Bі, коли це буде готово, замініть стару реалізацію Aна нову реалізацію B(це одне використання інтерфейсів). Aкод може слугувати основою для Bкоду 's, і тоді я можу використовувати рефакторинг Bкоду під час його розробки, але я думаю, що вже перевірений Aкод повинен залишатися замороженим.
Джорджо

@Giorgio Під час рефактора ви можете вводити помилки, тому ви пишете тести (а ще краще - TDD). Найбезпечніший спосіб рефактора - це зміни коду, коли ви знаєте, що він працює. Ви знаєте це, маючи набір тестів, які проходять. Після зміни виробничого коду тести все-таки повинні пройти, тож ви знаєте, що ви не ввели помилку. І пам’ятайте, тести є настільки ж важливими, як і виробничий код, тому ви застосовуєте до них те саме правило, що і до виробничого коду, а також утримуйте їх в чистоті та рефакторируйте їх періодично та часто.
Patkos Csaba

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

Так, це я мав на увазі: не кидайте робочий код, поки у вас не буде дійсної (добре перевіреної) заміни.
Джорджіо

-1

Як я розумію - якщо ви додасте нові методи до існуючого класу, він не порушить OCP. однак їх біт переплутав із додаванням нових змінних у Клас. Але якщо змінити існуючий метод і параметри в існуючому методі, то він неодмінно порушить OCP, тому що код вже перевірений і переданий, якщо ми навмисно змінимо метод [Коли зміниться вимога], тоді це буде проблема.

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