JavaRush /Java блог /Random UA /Що таке антипатерни? Розбираємо приклади (частина 2)
Константин
36 рівень

Що таке антипатерни? Розбираємо приклади (частина 2)

Стаття з групи Random UA
Що таке антипатерни? Розбираємо приклади (частина 1) Що таке антипатерни?  Розбираємо приклади (частина 2) - 1 Сьогодні ми продовжимо огляд найпопулярніших антипатернів. Якщо пропустабо першу частину - вона тут . Отже, патерни проектування – це best practices, тобто приклади практик хорошого вирішення певних завдань, перевірені часом. У свою чергу, антипатерни — їх повна протилежність, адже це — шаблони пасток і помилок, які відбуваються при вирішенні різних завдань (шаблони зла). Що таке антипатерни?  Розбираємо приклади (частина 2) - 2Переходимо до наступного антипатерну розробки.

8.Golden hammer

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

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

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

    Підсумок помилки може бути дуже сумним: від поганої, нестабільної, важкої підтримки реалізації до повного провалу проекту. Адже немає жодної таблетки від усіх хвороб, як і немає одного шаблону на всі випадки життя.

9. Premature optimization

Передчасна оптимізація - це антипаттерн, назва якого каже за себе.
«Програмісти витрачають величезну кількість часу, розмірковуючи та турбуючись про некритичні місця коду, і намагаються оптимізувати їх, що виключно негативно позначається на подальшому налагодженні та підтримці. Ми повинні взагалі забути про оптимізацію у, скажімо, 97% випадків; більше, поспішна оптимізація є коренем всіх зол. І навпаки, ми повинні приділити всю увагу 3%, що залишабося,» — Дональд Кнут
Що таке антипатерни?  Розбираємо приклади (частина 2) – 3Як приклад – передчасне додавання індексів до бази даних. Чим це погано? А тим, індекси зберігаються у вигляді бінарного дерева, і відповідно, при кожному додаванні та видаленні нового значення дерево перераховуватиметься заново, а це все ресурси та час. Тому індекси потрібно додавати лише при гострій необхідності (при великій кількості даних і недостатній швидкості пошуку по них) і на найзначніші поля (за якими найчастіше шукають).

10. Spaghetti code

Що таке антипатерни?  Розбираємо приклади (частина 2) – 4Спагетті-код - це антипаттерн, що описує частину коду, яка є погано структурованою, заплутаною і важкою для розуміння, що містить багато всяких переходів, як-от: обертання винятків, умов, циклів. Раніше головним союзником цього антипаттерну був оператор goto, зараз його фактично не використовують, що прибирає низку складнощів та проблем, пов'язаних з ним.
public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
Жахливо виглядає, чи не так? На жаль, це найпоширеніший антипаттерн(( Подібний код у майбутньому не може розібрати навіть автор, інші ж розробники бачачи це думають, якщо воно працює, то й добре, краще не чіпати. Часто буває, що спочатку це був простий і дуже прозорий метод) , але з додаванням нових вимог на нього поступово навішувалися нові та нові умови, що й перетворило його на такий монстра.Якщо з'являється такий метод, потрібно відрефакторити або його повністю або деякі найбільш заплутані частини.Як правило при розробці проекту виділяють час на рефакторинг: наприклад , 30% часу спринту на рефакторинг і тести.Ну це якщо без поспіху (хоча куди без неї).Ось тут є непоганий приклад спагетті коду та його рефакторингу.

11. Magic numbers

Магічне числа — це антипаттерн, який торкається різнорідних константів у програмі без пояснення їхньої мети, сенсу. Тобто зазвичай немає адекватного імені або на крайній випадок коментаря, який пояснює, що й навіщо. Також як і спагетті код, є одним із найпоширеніших антипаттернів. Людина, яка не є автором даного коду, важко може або зовсім не може пояснити, що це і як воно працює (та й сам автор згодом не зможе). У результаті зміні цього чи його видаленні код магічно перестає працювати зовсім. Як приклад, 36 та 73. Як боротьба з цим антипаттерном раджу review of code. Потрібно, щоб ваш код переглядали розробники, які не задіяні в даній ділянці коду, у яких не замилене око і виникатимуть питання — а що це і навіщо? Ну і звичайно, потрібно писати більш інформативні імена чи залишати коментарі.

12. Copy and paste programming

Програмування шляхом копіювання та вставки - це антипаттерн, що передбачає бездумне копіювання чужого коду (copy and paste), внаслідок чого можуть виникати побічні ефекти, які ми не доглянули. Що таке антипатерни?  Розбираємо приклади (частина 2) – 5Як, наприклад, копіювання та впровадження методів з математичними обчисленнями чи складними алгоритмами, які ми до кінця не розуміємо, все це може працювати в нашому випадку, але за будь-яких інших обставин може призвести до біди. Припустимо, мені потрібний був метод для обчислення максимального числа з масиву. Покопавшись в інтернеті, я знаходжу це рішення:
public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
До нас приходить масив чисел 3,6,1,4,2, і як результат нам приходить - 6. Добре, залишаємо! Але минає час, і до нас приходить масив 2,5-7,2,3, і результат у нас буде -7. А це вже не гуд. А вся справа в тому, щоMath.abs()повертає максимальне значення абсолютної величини, і незнання цього призводить до краху, але у певній ситуації. Без розуміння рішення углиб, ви не зможете перевірити кілька випадків. А ще таке кодування може виходити за рамки внутрішньої побудови, як стилістично, так і на фундаментальнішому, архітектурному шарі. Такий код буде важче вичитувати та підтримувати. Крім того, звичайно, не забуваємо: стовідсоткове копіювання чужого коду – це окремий випадок плагіату. У тих випадках, коли програміст не до кінця розуміє те, що він робить, і вирішує взяти чуже нібито робоче рішення - це не тільки мінус до посидючості, а й дії на шкоду команди, проекту та й іноді всієї компанії (так що копіпастим обережно) .

13. Reinventing the wheel

Винахід колеса - це антипаттерн, більш відомий у нас як винахід велосипеда . По суті, цей шаблон є протилежністю розглянутому вище антипаттерну — копіпаст. Суть його полягає в тому, що розробник реалізує власне рішення для завдання, для якого вже існують рішення, причому у рази краще, ніж придумане програмістом. Що таке антипатерни?  Розбираємо приклади (частина 2) – 6Найчастіше це призводить лише до втрати часу та зниження ефективності роботи програміста: рішення може бути знайдено далеко не найкраще чи взагалі не знайдено. При цьому відкидати можливість самостійного рішення не можна, тому що це пряма дорога призведе до програмування копіпастом. Програміст повинен орієнтуватися у завданнях, які можуть постати перед ним, щоб грамотно їх вирішити, використовуючи готові рішення чи винаходячи власні. Дуже часто причиною використання цього антипаттерну є банальний поспіх і як результат недостатньо глибокий аналіз (пошук) готових рішень. Винахід одноколісного велосипеда- Це випадок аналізованого антипаттерну з негативним результатом. Тобто для проекту необхідне певне рішення і розробник створює його, але погано. У цей час хороший варіант вже існує і успішно використовується. Підсумок: втрата величезної кількості часу. Спершу ми створюємо щось неробоче, а потім докладаємо зусиль для рефакторингу та заміни на щось уже існуюче. Як приклад — реалізація власного кешу, коли вже багато існуючих. Яким би талановитим ви були програмістом, слід пам'ятати, що винахід велосипеда — це як мінімум витрата часу, а час, як відомо, — найцінніший ресурс.

14. Yo-yo problem

Проблема йо йо — антипаттерн, при якому структура застосування надмірно розмита у зв'язку з надмірною фрагментацією (наприклад, надмірно розбитий ланцюжок успадкування). "Проблема Йо-Йо" виникає, коли необхідно розібратися в програмі, ієрархія успадкування та вкладеність викликів методів якої дуже довга і складна. Програмісту внаслідок цього необхідно лавірувати між безліччю різних класів та методів, щоб контролювати поведінку програми. Термін походить від назви іграшки йо-йо. Як приклад, давайте розглянемо такий ланцюжок: У нас є інтерфейс технологій:
public interface Technology {
   void turnOn();
}
Від нього успадковується інтерфейс транспорту:
public interface Transport extends Technology {
   boolean fillUp();
}
А далі ще інтерфейс наземного транспорту:
public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
А від нього йде абстрактний клас машин:
public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /*some realization*/
       return true;
   }
   @Override
   public void turnOn() {
       /*some realization*/
   }
   public boolean openTheDoor() {
       /*some realization*/
       return true;
   }
   public abstract void fixCar();
}
Далі - абстрактний клас фольксвагена:
public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /*some realization*/
   }
   @Override
   public void brake() {
       /*some realization*/
   }
}
І нарешті, конкретна модель:
public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /*some realization*/
   }
}
Ось такий ланцюжок і змушує шукати відповіді на запитання на кшталт:
  1. Скільки методів у VolkswagenAmarok?

  2. Який тип потрібно вставити замість знака питання максимальної абстракції:

    ? someObj = new VolkswagenAmarok();
           someObj.brake();
На такі питання складно дати швидку відповідь: треба дивитися і розбиратися, і легко заплутатися. А що якщо ієрархія набагато більша, довша і заплутаніша, має всякі навантаження, перевизначення? А те, що ми матимемо структуру, яка малозрозуміла внаслідок надмірної фрагментації. Найкращим рішенням буде скоротити надмірний поділ. У нашому випадку – залишити Technology → Car → Volkswagen Amarok.

15. Accidental complexity

Непотрібна складність - це антипаттерн внесення непотрібної складності у рішення.
"Будь-який дурень може написати код, який зрозумілий комп'ютеру. Хороші програмісти пишуть код, який зрозумілий людині", - Мартін Фаулер.
Отже, що таке складність? Її можна визначити як ступінь труднощі, з якою дається виконання кожної операції у програмі. Як правило, складність можна розділити на два види. Перший вид складності - це кількість функцій, які є у системи. Його можна зменшити лише одним способом – прибрати якусь функцію. Потрібно стежити за написаними методами: якщо якийсь із них уже не використовується або використовується і не приносить цінності, його потрібно прибрати. Більше того, потрібно заміряти, як використовуються всі методи у додатку, для розуміння, у що варто вкладати зусилля (багато місць перевикористання), а від чого можна відмовитись. Другий вид складності – непотрібна складність, лікується лише професійним підходом. Замість того, щоб зробити щось «круто» (ця хвороба є не тільки у молодих розробників), потрібно думати про те, як зробити це максимально просто, адже найкраще рішення завжди просте. Наприклад, ми маємо невеликі суміжні таблички з описами до деяких сутностей, як до сутності користувача: Що таке антипатерни?  Розбираємо приклади (частина 2) – 8Тобто ми маємо id користувача, id мови, якою ведеться опис і саме опис. І такого ж типу ми маємо допоміжні таблички для таблиць cars, files, plans, customers. Тоді як у нас буде виглядати вставка нових значень у такі таблички?
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR,languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER,languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE,languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN,languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER,languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
І відповідно enum:
public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Начебто все просто і добре ... Але що буде з іншими методами? Адже вони теж будуть з купою switchі купою майже однакових запитів до бази даних, що в свою чергу сильно заплутає і роздмухує наш клас. Як це можна було б зробити простіше? Давайте трохи модернізуємо наш enum:
@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
Тепер кожен тип має назви оригінальних полів його таблиці. У результаті метод створення опису перетворюється на:
private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()),languageId, serviceId, description);
   }
Зручно, просто і компактно, чи не так? Показником хорошого розробника є навіть не частота використання патернів, а скоріше частота уникнень антипатернів. Незнання - найгірший ворог, адже своїх ворогів треба знати в обличчя. Що ж, сьогодні у мене на цьому все, дякую всім)))
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ