Дублювання коду без очевидної абстракції


14

Ви коли-небудь стикалися з випадком дублювання коду, коли, дивлячись на рядки коду, ви не могли прилаштувати до нього тематичну абстракцію, яка правдиво описує її роль у логіці? І що ви зробили, щоб вирішити це?

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

Як ви вважаєте, що найкращий спосіб вирішити щось подібне?

Відповіді:


18

Іноді дублювання коду є результатом "каламбура": дві речі виглядають однаково, але ні.

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

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

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

Як ви відповісте на це питання, ви можете краще зрозуміти, якою може бути хороша абстракція.

Шаблони дизайну також є корисними інструментами. Можливо, ваш дублюваний код виконує обхід певної форми, і слід застосувати шаблон ітератора.

Якщо у вашому дублюваному коді є кілька повернених значень (і тому ви не можете виконати простий метод вилучення), можливо, вам слід зробити клас, який містить значення, що повертаються. Клас може викликати абстрактний метод для кожної точки, який змінюється між двома фрагментами коду. Потім ви зробите дві конкретні реалізації класу: по одному для кожного фрагмента. [Це фактично шаблон дизайну Методу шаблонів, не плутати з поняттям шаблонів у C ++. Крім того, те, що ви дивитесь, може бути краще вирішено за допомогою стратегії.]

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


4

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

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


+1 для безлічі чудових технік, стислих у тісному просторі. (Ну, було б +1 для описаних методик.)
Макнейл

3

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


2

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

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

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

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

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