JavaRush /Java блог /Java Developer /Что такое антипаттерны? Разбираем примеры (часть 2)
Константин
36 уровень

Что такое антипаттерны? Разбираем примеры (часть 2)

Статья из группы Java Developer
Что такое антипаттерны? Разбираем примеры (часть 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→VolkswagenAmarok.

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);
   }
Удобно, просто и компактно, не правда ли? Показателем, хорошего разработчика является даже не частота использования паттернов, а скорее частота избеганий антипаттернов. Незнание — худший враг, ведь своих врагов нужно знать в лицо. Что ж, сегодня у меня на этом всё, всем спасибо))
Комментарии (3)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Майя Уровень 35
24 ноября 2023
а где часть 1?
Nick Уровень 20
14 июля 2021
Во классные вещи описаны, мы почти все их используем у себя на работе, в большом количестве.😁
Vlad Kravchuk Уровень 8
17 апреля 2020
Поэтому и лучше работать в команде и для решения запускать процесс обсуждения - а не изобретать свой велосипед