Что такое антипаттерны? Разбираем примеры (часть 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);
   }
Удобно, просто и компактно, не правда ли? Показателем, хорошего разработчика является даже не частота использования паттернов, а скорее частота избеганий антипаттернов. Незнание — худший враг, ведь своих врагов нужно знать в лицо. Что ж, сегодня у меня на этом всё, всем спасибо))