Антипаттерны в программировании и проектировании архитектуры
АНТИПАТТЕРНЫ В ПРОГРАММИРОВАНИИ:
Copy and Paste программирование
Когда проявляется?
Когда разработчику требуется реализовать две схожих задачи, самым простым решением может показатся следующее: скопировать уже готовый код в другое место, внесение некие изменения, чтобы решить вторую задачу.
Рассмотрим следующий пример:
public class Program
{
public static void Main()
{
}
public void SetAudiType(Car car)
{
car.Type = CarType.Audi;
}
public void SetBMwType(Car car)
{
car.Type = CarType.Audi;
}
}
public class Car
{
public CarType Type { get; set; }
}
public enum CarType
{
BMW,
Audi
}
}
Создав метод SetAudiType
, когда потребовалось создать еще 1 метод, SetBMWType
, разработчик не создал обобщенный метод SetType
который бы устанавливаем нужный тип, вместо этого он скопировал код и создал идентичный метод.
К каким проблемам приводит copy paste программирование:
- Ухудшается переиспользование кода — если потребуется использовать подобную функциональность в новом месте, то нужно будет выдергивать код и переносить его.
- Страдает качество кода — часто найденные недочёты в коде правятся только в одном месте, в остальных недостатки остаются.
- Усложняется поддержка кода — в случае, если в изначальном коде была ошибка, которую в будущем нужно исправить, то эта означает, что ошибка попала во все места, куда копипастился код. Это приводит к необходимости искать и исправлять код разных местах.
- Code Review значительно усложняется, так как приходится делать ревью одного и того же кода в разных местах, что замедлит производительность ревьювера и снизится читаемость кода.
Причины возникновения:
- Некомпетентность или наплевательское отношение к коду у программиста.
- Недостаток опыта в разработке, часто встречается подобный антипаттерн у джунов и даже мидлов.
- Ограничение времени на разработку. Спешка на проекте частая причина почему разработчику приходится прибегать к антипаттерну.
Решение:
- Правильный Code review. Даже тогда, когда заказчик просит быстро выкатить фичу, нужно проводить ревью и исправлять те ошибки, которые мог допустить разработчик
- Технический долг и рефакторинг. Даже если все же такой код проскочил на продакшн. В следующих итерациях нужно в обязательном порядке отрефакторить такой код.
- Разделяй и властвуй. Одним из выходов будет создание отдельных репозиториев с библиотеками или вынос дублирующих методов в отдельные классы и проекты по принципам которые более подробно описаны в SOLID и GoF паттернах.
Spaghetti code
Спагетти-код —Ход выполнения программы похож на спагетти, т.е извилистый и очень запутанныйзапутанный код. Иногда еще называют "кенгуру-код" (kangaroo code) из-за множества инструкций go to.
Когда проявляется?
Когда алгоритм реализуется в рамках одного метода (функции) и содержит очень большой длинный и запутанный код.
К каким проблемам приводит Spaghetti code:
- Подобный код очень плохо читаем даже для того, кто его написал.
- Очень часто содержит в себе множество других антипаттернов, в том числе Copy & Paste.
- Не очень эффективный Code Review.
- Переиспользовать такой код практически невозможно.
Причины возникновения:
В основном такой код проявляется из-за недостатка опыта в разработке и непонимания принципов программирования. "работает - не трогай" во время рефакторинга так же приводит к спагетти-коду.
Решение:
Рефакторинг или полное выкашивание таких участков кода и переписывание их заново.
Magic numbers
Магическое число — константы, используемые в коде, но которые не несут никакого смысла без соответствующего комментария.
Когда проявляется?
Это происходит тогда, когда в вашем коде начинают появляется константы и числа, значение которых не является очевидным.
К каким проблемам приводит Magic numbers антипаттерн:
- Разработчик, который не писал данный код, с большим трудом может понять как это работает.
- Со временем, даже тот кто писал этот код не сможет объяснить что-либо в этом коде.
- Числа и константы затрудняют понимание кода и его рефакторинг.
Причины возникновения:
- Спешка при разработке.
- Отсутствие практики разработки кода в команде или сопровождения проекта.
- Нету Code Review
Решение:
Проведение Code Review более опытными коллегами.
Hard code
Очень известный всем разработчикам антипаттерн. Но далеко не все понимают, что это антипаттерн разработки.
Когда проявляется?
Он часто связан с "Magic numbers" и они часто идут в паре.
К каким проблемам приводит хардкод:
- Код будет корректно работать только в том окружении, под который сделан хардкод.
- Может проявляется непредсказуемые дефекты во время переноса, переименования файлов, и их поведение может меняться при изменении конфигурации устройств.
- Невозможность гибкой настройки под нужный нам environment.
- Усложняет Unit и Integration testing.
Причины возникновения:
- Лень. Захардкодить значение всегда проще, чем думать о том как лучше гибко настроить и вынести конфигурацию этих значений.
- Малый код переростает в большой проект. Разработчик мог следовать KISS принципу, но далее, когда код разрастался, он не отрефакторил это решение.
- Разработчик во время написания алгоритма хардкодит какие-то значения, после реализации, забывает удалить или вынести в конфиг этот хардкод.
- Малый опыт разработки и недостаток опыта.
Решение:
- Запрет на хардкод в правилах программирования на проекте
- Code Review
- Просто не хардкодь, серьезно
Soft code
Обратная сторона Hard cod'a. Когда вы боясь хардкода, выносите в конфиг все подряд.
Когда проявляется?
В проекте настраивается и выносится в конфиг абсолютно всё, что можно. Это делает конфигурацию проекта невероятно сложной для понимания.
К каким проблемам приводит soft code:
- При разработке много сил и времени уходит на реализацию возможности настроек абсолютно всего чего возможно.
- Развёртывание такой системы влечет также дополнительные затраты (DevOps'ов и разработчиков)
Причины возникновения:
- Низкая квалификация разработчика и страх допустить анти-паттерн Hard Code.
- Недостаток опыта работы с разными окружениями.
Решение:
Перед началом разработки, на этапе проектирования и архитектуры следует определить, что должно быть конфигурируемым, а что является постоянным независимо от окружения или может быть настроено автоматически.
Использование принципов KISS, YAGNI поможет решить проблему.
Accidental complexity
Простыми словами Accidental complexity можно описать так: "слишком заумное решение".
Когда проявляется?
В коде есть избыточные проверки, часть кода, реализовано с использованием анти-паттерна Soft Code, что позволяет конфигурировать поведение нашего кода Также проявляется, когда технический долг не рефакторится.
К каким проблемам приводит Accidental complexity:
- Усложнение понимания кода.
- Снижение скорости разработки и рефакторинга такого решения.
Причины возникновения:
- Отсутствие рефакторинга.
- Некомпетентность программиста.
- "Выпендреж" или over engineering программиста, который пытается придумать более сложное решение простой задачи.
Решение:
- Code review
- Следования Принципам KISS, YAGNI, DRY помогает избежать проблем.
Boat anchor
Сохранение неиспользуемых частей кода, которые остались после оптимизации или рефакторинга "на будущее, а вдруг понадобится?".
Когда проявляется?
- После рефакторинга, некоторые куски кода остаются в солюшине, хотя вы их больше не юзаете.
- При сохранении части кода "на будущее", ну а вдруг понадобится в будущем?
К каким проблемам приводит Boat anchor:
Читаемость кода. Увеличение размера проекта без нужной на то причины.
Причины возникновения:
Разработчики не используют Системы контроля версий, а хранят "нужные" (но нигде не используемые старые куски кода) "на будущее".
Решение:
- Написание продуманного кода
- При рефакторинге и оптимизации кода принудительно удалять куски кода, который более использоваться не будет или создавать отдельную бранчу для этого когда, если вы предполагаете, что вернетесь к этому решению в будущем.
Reinventing the wheel (Изобретение колеса/велосипеда)
Смысл этого анти-паттерна в том, что разработчик реализует собственное решение для задачи с нуля (придумывает свой велосипед), для которой уже существуют решения которые могут быть лучше его и более протестированными и отлажеными.
Когда проявляется?
Когда разработчик считает свои знания уникальными, поэтому для каждой задачи пытается написать собственное решение с нуля, не смотря возможные готовые решения.
К каким проблемам приводит Reinventing the wheel:
- Потеря времени на написание "своего велосипеда" и понижение эффективности работы программиста.
- Снижает эффективность или оптимальность конечного продукта.
Причины возникновения:
- Повышенная самооценка или пониженная самокритичность.
- Нехватка времени на изучение готовых решений в интернете.
Решение:
Разработчик должен хорошо ориентироваться в задачах которые нужно решать, в том числе и грамотно использовать готовые решения.
Разумеется полностью отбрасывать возможность самостоятельного написания решений - нельзя, так как это прямая дорога к Copy and Paste антипаттерну.
Reinventing the square wheel (Изобретение квадратного колеса)
Создание плохого решения, когда существует хорошее известное решение.
Когда проявляется?
Как и в случае с Reinventing the wheel может проявлятся, когда разработчик считает свои знания уникальными, поэтому для каждой задачи пытается придумать свое "квадратное колесо", не имеея при этом достаточного опыта.
К каким проблемам приводит Reinventing the wheel:
- Время тратится на изобретение и реализацию собственного решения.
- Затраты на рефакторинг и тестирование.
Причины возникновения:
Разработчик не изучает (или не хочет изучать) решения которые уще существуют, а пишет такие с нуля.
Решение:
Разработчик должен хорошо ориентироваться в задачах которые нужно решать, в тч и грамотно использовать готовые решения, не изобретая свои решения где это не требуется.
Lava flow
Сохранение нежелательного (излишнего или низкокачественного) кода по причине того, что его удаление слишком дорого или будет иметь непредсказуемые последствия. Со временем приводит к тому, что проект начинает состоять из неподдерживаемых кусков кода, а доработка проекты вынуждает писать новые слабо поддерживаемые куски кода.
Когда проявляется?
При систематических нарушениях таких принципов разработки и проектирования как: DRY, KISS, SOLID, YAGNI, DDD, TDD, BDD.
К каким проблемам приводит Lava flow:
- Растет сложность проекта.
- Замедляется скорость разработки.
- Невозможность рефакторинга или разработки нового функционала.
Причины возникновения:
- Когда разработчик пишет код и после оставляет комментарии на подобии: "работает, не трогай"
- Разработка большого проекта ондим разработчиком.
- Отсутствие Code Review.
- Отсутствие проектирования архитектуры проекта перед началом разработки.
Решение:
- Code review
- Рефакторинг
- Проектирование архитектуры перед началом проекта и перепроектирование во время внедрения больших фич в существующий код.
Programming by permutation (Программирование методом подбора)
Многие неопытные разработчики пытаются решать некоторые задачи методом перебора, подбором параметров, порядка вызова функций, вызов всех методов подряд у third-party библиотек и т.д. Все попытки выглядят как "палец в небо" и подобные манипуляции устраняют только симптомы и не дают понимания сути того что вы делаете и с чем работаете.
Когда проявляется?
Если программист не понимает происходящего, не разбирается с библиотекой или тем алгоритмом который ему нужно реализовать, как следствие не сможет предусмотреть все варианты развития событий.
К каким проблемам приводит Программирование методом подбора:
- Будет потрачено время на решение задачи перебором, а после повторно потратится время изучение проблемы и на переделку нормального решения.
- Приучает разработчика к тому, что написание кода — это магия, а не инженерная работа.
Причины возникновения:
Всё сводится к низкой компетенции разработчика: если программист не может решить задачу несколькими путями — это скорее всего приведёт к появлению этого анти-паттерна.
Решение:
- Не браться за разработку задачи, в которой не хватает понимания и компетенции до тех пор, пока пробелы не будут закрыты.
- Пообщатся с архитекторами или более опытными разработчиками для прояснения ситуации или совместного поиска решений
Blind faith (Слепая вера)
Когда проявляется?
Когда программист думает, что его код всегда будет работать в идеальных условиях, поэтому никогда не выдаст ошибок, или никогда не получит неверные входные данные или данные неверного типа.
К каким проблемам приводит Blind faith:
- Код делает неожидаемые действия при определенным условиях.
- Приводит потенциально опасным последствиям.
- Приводит к каскаду ошибок, что значительно усложняет процесс поиска ошибки и устранения последствий.
Причины возникновения:
- Избыточное доверие к юзеру.
- Недостаточное количество Unit и Integration тестов
- Отсутствие валидации на стороне бек-енда (валидация на фронт-енде ничего не стоит)
Решение:
- Нужно помнить, что валидация на бекенде — это обязательное условие для любого BE девелопера
- Не доверять пользователю и делать защиту "от дурака"
- Не переходить грань, иначе пытаясь вырулить с этого антипаттерна вы попадете в воронку другого - Accidental complexity.
God Object (Божественный объект)
Божественный объект — анти-паттерн, который довольно часто встречается в языках программирования которые базируются на ООП.
Когда проявляется?
Когда разработчик нарушает принцип "разделяй и властвуй" или Single Responsibility с SOLID принципов.
К каким проблемам приводит God Object:
- Объект берет на себя слишком много обязанностей.
- Непереносимость кода.
- Сложно поддерживаемый код (Спагетти-код)
Причины возникновения:
- Плохие знания шаблонов проектирования и SOLID, GRASP принципов.
- Недостаточный опыт разработчика
- Написние сложного кода
Решение:
Использовать принципы разработки: DDD, TDD, DRY, KISS, SOLID, YAGNI, GRASP.
Dependency hell (ад зависимостей)
Разрастание программных продуктов и библиотек, приводящее к сложности установки новых и удаления старых.
Когда проявляется?
Когда у вас достаточно много модулей сложной структуры и вы пытаетесь еще вклинить несколько в разные проекты.
К каким проблемам приводит Dependency hell:
Есть несколько типов проблем которые выплывают с "ада зависимостей":
- Множество зависимостей. Приложение зависит от большого числа объёмных библиотек, которые требуют длительных установок и подключений, так же занимают много дискового пространства. Возможна ситуация, когда приложение построено на определённой платформе (например Java) и требует установки этой платформы, в то время, как 99% остальных ваших приложений поддержки этой платформы не требуют. Это частный случай проблемы когда-либо приложение использует маленькую часть большой платформы и в конечном итоге требует установки всей платформы (что может быть решено только с помощью рефакторинга приложения), либо маленькое приложение опирается на большое число различных библиотек одновременно.
Длинные цепочки зависимостей. Приложение зависит от библиотеки "A", которая зависит от библиотеки "B", ... , которая в свою очередь зависит от библиотеки "Z". Иногда наличие такой длинной цепочки зависимостей может приводить к возникновению конфликтов, когда разными компонентами цепочки требуются разные версии одного и того же пакета или библиотеки. (см. конфликтующие зависимости) Такие длинные цепочки зависимостей должны решаться менеджерами пакетов (такими как npm, nuget, и тд), которые делают это в автоматическом режиме, вместо того чтобы заставлять пользователя решать их самому.
Конфликтующие зависимости. Если "Приложение 1" зависит от библиотеки "А" версии 1.2, а "Приложение 2" зависит от той же библиотеки "А", но уже версии 1.3, и различные версии библиотеки "А" не могут быть одновременно установлены, то "Приложение 1" и "Приложение 2" нельзя одновременно использовать (или даже установить, если установщики проверяют зависимости).
Циклические зависимости Наверняка у вас были случаи, когда вам нужно слинковать код с разных проектов и вы получаете проблему circular referenc'ов.
Причины возникновения:
- Множество проектов
- Большая запутанная структура решения
- Отсутствие менеджера пакетов
Решение:
- Нумерация версий
- Параллельная установка различных версий ПО
- Менеджер пакетов
- Портативные приложения
Object cesspool (Объектная клоака)
Переиспользование объектов, находящихся в непригодном для переиспользования состоянии.
Когда проявляется?
Когда вы пытаетесь использовать объекты с пула, которые не переведены в нужное состояние.
К каким проблемам приводит Object cesspool:
Использование таких объектов с пула объектов может привести к разным негативным последствиям. Самое очевидное — код работает не так как вы ожидали.
Причины возникновения:
Object Pool достаточно распространенная практика в программировании. Зачастую языки как .NET, Java и тд реализуют в различных местах такие пулы объектов. Если же вы сами решили реализовать такой пул объектов, это может стать проблемой, если вы не учли того что состояние таких объектов нужно "обнулять".
Решение:
Стараться не использовать "свои велосипеды" где это не нужно, а там где без них не обойтись покрывать все узкие места тестами.
Другие анти-паттерны в программировании:
- Interface soup (суп из интерфейсов) - объединение нескольких интерфейсов, разделенных в один, по принципу изоляции интерфейсов (Interface segregation).
- Висящие концы - интерфейс, методы которого бессмысленны и реализуются «пустышками».
- Stub (Заглушка) - использование в объекте уже имеющийся малоподходящий по смыслу интерфейс, вместо создания нового.
- Cryptic code (Таинственный код) - использование аббревиатур вместо понятных имен.
- Lasagnia code- использование большого количества уровней абстракции.
- Ravioli code - объекты склеены между собой как пельмени, что не позволяет делать рефакторинг.
- Soap bubble (Мыльный пузырь) - объект, инициализированый мусором, и притворяющийся, что содержит данные.
- Mutex hell - слишком большое количество объектов синхронизации между потоками.
- Template cancer - использование шаблонов там, где их использование не оправдано.
- Два тоннеля (Two tunnel) - добавление новой функциональности в отдельное приложение вместо расширения имеющегося.
- Creeping featurism - добавление новых улучшений в ущерб суммарному качеству системы.
АНТИПАТТЕРНЫ АРХИТЕКТУРЫ И ПРОЕКТИРОВАНИЯ:
Golden hammer
Golden hammer — антипаттерн проектирования, проявляется в использовании одного и того же "решения" везде где только можно, в том числе путём искусственной "подгонки" условий, требований, ограничений задачи под данный код (Также известен под названиями: закон инструмента (The law of the instrument), молоток Маслоу (Maslows hammer), «молоточек» (Gavel).
Когда проявляется?
Применение одного решения (чаще всего какого-либо одного шаблона проектирования) для всех типов задач.
К каким проблемам приводит Golden hammer:
Многие разработчики используют данный антипаттерн не подозревая о собственной неосведомленности или некомпетентности, что приводит к значительным проблемам в проекте в архитектуре и поддержке кода.
Причины возникновения:
- У начинающих — нежелание к изучению нового и увеличению знаний в шаблонах проектирования и знаниях для начинающих; как следствие, новичок пытается решить все задачи единственным известным способом, который он освоил.
- У профессионалов — профессиональная деформация, что приводит к выработке предпочтений в шаблонах проектирования и архитектур, а не использования того шаблона и типа архитектуры, который нужен для решения конкретной задачи.
Решение:
При решении задачи продумывать не одно решение, а несколько, определить достоинства и недостатки, и делать взвешенный выбор в пользу самого удачного решения — именно к поиску таких решений и сводится эффективная разработка.
Analysis paralysis (Аналитический паралич)
Неоправданно большие затраты на анализ и проектирование. Часто приводит к закрытию проекта до начала его реализации;
Когда проявляется?
Очень часто встречается при waterfall подходе к разработке ПО
К каким проблемам приводит Аналитический паралич:
- Очень много затрат на анализ, проектирование, обсуждение
- Недостаток гибкости
- Недостаток опыта аналитиков и архитекторов
Причины возникновения:
- Недостаток опыта аналитиков и архитекторов
- Большой скоуп работ который пытаются покрыть сразу (waterfall)
Решение:
- Если это возможно переход c waterfall на Agile модель
- Декомпозиция итераций по проектированию и анализу для более эффективного внедрения фич.
Race condition
Состояние гонки — "плавающая" ошибка (гейзенбаг), проявляющаяся в случайные моменты времени и «пропадающая» при попытке её локализовать.
Когда проявляется?
Непредвиденные возможности наступления событий в неожидаемом порядке
К каким проблемам приводит Race condition:
- Потеря или порча данных.
- Взаимные блокировки.
- Уязвимости.
- Плавающие баги.
Причины возникновения:
Состояние гонки возникает, когда два или более потоков могут получить доступ к общим данным, и они пытаются изменить их одновременно (так называеммый параллелизм). Поскольку алгоритм планирования потоков может переключаться между потоками в любое время, вы не знаете порядок, в котором потоки будут пытаться получить доступ к общим данным. Следовательно, результат изменения данных зависит от алгоритма планирования потоков, то есть оба потока «участвуют в гонке» за доступ / изменение данных.
Проблемы часто возникают, когда один поток выполняет «check-then-act» (например, «check», если значение равно X, затем «act», чтобы сделать что-то, зависящее от значения, являющегося X), а другой поток делает что-то со значением в между «чеком» и «актом». Например:
if (x == 5) // The "Check"
{
y = x * 2; // The "Act"
// If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
// y will not be equal to 10.
}
Решение:
В примере который приведен выше, решение лежит на поверхности - применить lock:
// Obtain lock for x
if (x == 5)
{
y = x * 2; // Now, nothing can change x until the lock is released.
// Therefore y = 10
}
// release lock for x
Т.е синхронизация асинхронных потоков решает проблему описанную выше.
Другие паттерны архитектуры:
- Инверсия абстракции (Abstraction inversion) - сокрытие части функциональности от внешнего использования, с расчетом на то, что никто не будет его использовать
- Неопределённая точка зрения (Ambiguous viewpoint) - представление модели без спецификации её точки рассмотрения
- Большой комок грязи (Big ball of mud) - программа с нераспознаваемой структурой
- Бензиновая фабрика (Gas factory) - необязательная сложность дизайна
- Затычка на ввод данных (Input kludge) - забывчивость в спецификации и выполнении поддержки неверного ввода
- Раздувание интерфейса (Interface bloat) - разработка интерфейса очень мощным и очень сложным для реализации
- Волшебная кнопка (Magic pushbutton) - выполнение результатов действий пользователя в виде неподходящего интерфейса
- Перестыковка (Re-Coupling) - процесс внедрения ненужной зависимости
- Дымоход (Stovepipe System) - редко поддерживаемая сборка плохо связанных компонентов
- Мышиная возня - создание множества мелких и абстрактных классов для решения одной конкретной задачи более высокого уровня
- Членовредительство (Mutilation) - излишнее «затачивание» объекта под определенную очень узкую задачу так, что объект не способен будет работать с другими схожими задачами
- Сохранение или смерть (Save or die) - сохранение изменений в конфигурации на жесткий диск только при завершении приложения
Итоги:
Все антипаттерны разные и разные их причины возникновкения. Но можно выделить несколько 'golden bullets' как можно предотвратить каждый из них:
- Командная работа
- Изучение паттернов и архитектурных решений
- Code Review