Главная
Серии Обо мне Подписка
Паттерны проектирования — отстой

Паттерны проектирования — отстой

В software engineering термин «паттерн проектирования» — который, если честно, слегка избыточен: разве не все паттерны и есть дизайн? — упоминают на каждом шагу. Изначально он пришёл из архитектуры — настоящей архитектуры, со зданиями и чертежами, а не из софта.

Но в 1994 году у мира софта появилась своя версия — вышла теперь уже печально известная книга «Design Patterns» Гаммы, Хелма, Джонсона и Влиссидеса, известных как «Банда четырёх» (Gang of Four, GoF). Они представили 23 канонических паттерна как абстракции более высокого уровня, чем вызовы функций. Эти паттерны задумывались как language-agnostic, переиспользуемые шаблоны для решения повторяющихся проблем проектирования софта.

Звучит неплохо, да? Почти слишком неплохо...

Проблема в том, что паттерны проектирования выросли из полезного словаря почти до догмы. Их преподают как универсальные решения, применяют там, где они не нужны, и считают признаком хорошей инженерии.

Реальность такова: Паттерны проектирования — отстой.

Да, я это сказал.

Паттерны проектирования переоценены, используются чрезмерно и часто попросту не нужны.

Звучит резко, особенно для тех, кто держит книгу Банды четырёх у кровати, но выслушай меня. В большинстве случаев паттерны проектирования — это просто уродливые костыли вокруг того факта, что наши языки программирования недостаточно мощные или гибкие, чтобы выразить то, что мы на самом деле хотим.

Java и её роман с бойлерплейтом

Возьмём, к примеру, Java (сегодня я буду знатно её пинать, так что устраивайся поудобнее). С чего вообще начать? Java — образцовый пример «паттерн-мании». Каждый проект выглядит как копия предыдущего, потому что Java-разработчиков учат просто пихать как можно больше паттернов.

Это язык, который практически силой затащил паттерны проектирования в мейнстрим. Почему? Потому что Java многословна, негибкая, и ей не хватает выразительности. Это как писать роман, в котором разрешено использовать только трёхсложные слова — любая проблема ощущается в десять раз сложнее, чем должна быть.

О, ты пишешь простое CRUD-приложение? Лучше добавь Factory, может, Observer для уведомления об изменениях в базе, и не забудь Singleton — ну ты понимаешь, всем же нужен глобальный логгер, да?

Паттерн против возможности языка

Суть того, почему паттерны проектирования часто мусор, в том, что чаще всего они существуют только потому, что самому языку не хватает какой-то возможности. По сути, ты просто обходишь ограничения языка.

Возьми паттерн Singleton как пример. Ты видел это тысячу раз:

public class Logger {

    private static Logger instance;

    private Logger() {}

    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }
}

Это примерно 15 строк кода, чтобы просто сказать: «я хочу только один экземпляр этой штуки». Мы что, пещерные люди? Нам не должно приходиться писать такое руками. Современный язык должен предоставлять конструкции, чтобы решать это элегантно, но нет, не Java. Вместо этого мы получаем тонны бойлерплейта и сложности, замаскированной под «best practices».

Хочешь увидеть, как это делает Scala?

object Logger

Всё. Это весь паттерн Singleton. Готово.

Не надо возиться со статическими блоками, потокобезопасностью или прочей ерундой с ленивой инициализацией. Scala делает то, что Java должна была сделать с самого начала: относиться к этому как к базовой возможности языка, а не как к паттерну, который нужно вручную реализовывать. Java загоняет тебя в это болото, потому что в ней нет функций первого класса. Это вина языка, не твоя, что ты пишешь паттерн Strategy.

Паттерны проектирования не решают проблемы твоего кода — они решают проблемы твоего языка.

Паттерн Factory? Это просто отказ Java дать тебе нормальные конструкторы или нативный способ обрабатывать создание сложных объектов. В таких языках, как Python, Ruby или Scala, Factory нужен редко. На самом деле, большинство паттернов, о которых так любят говорить Java-разработчики, просто исчезают в более гибких, выразительных языках.

Возьмём для примера Python:

  • Нужен Factory? Просто передай функцию или умно используй __init__.
  • Хочешь Singleton? Возьми модуль или класс, который инициализируешь один раз, и на этом всё.
  • Observer? Ну, функции — граждане первого класса. Просто передавай их куда нужно.

Факт в том, что в достаточно выразительном языке паттерны либо становятся тривиальными, либо, что ещё лучше, их просто не существует. Ты решаешь проблемы напрямую, а не заворачиваешь их в абстракции, которые кто-то придумал 20 лет назад в языке, который плохо состарился.

Культ переусложнения

Культ переусложнения

Паттерны проектирования также питают более глубокую проблему в мире софта: переусложнение (overengineering). Я видел junior-инженеров (и, к сожалению, некоторых senior) с широко раскрытыми глазами при словах «паттерны проектирования», которые сразу же пытаются впихнуть в проект как можно больше паттернов.

Они решают воображаемые проблемы.

Когда это происходит, кодовая база превращается из понятной в нечитаемую, хрупкую и чрезмерно абстрагированную. Она становится лабиринтом интерфейсов, фабричных методов и странных соглашений об именовании, из-за которых кажется, будто ты пытаешься расшифровать древние тексты.

«Хорошие» паттерны проектирования обычно переусложнены, потому что должны охватывать как можно больше сценариев использования. Но у переусложнённого кода есть цена: его надо прочитать, понять, что он делает, и понять, как он работает в контексте всей системы. Соответственно, как и с любой другой техникой разработки, нужно взвешивать технику против цены её использования и решать, перевешивает ли выгода эту цену.

Не каждой проблеме нужен паттерн проектирования. На самом деле большинству не нужен. Часто самое простое и прямолинейное решение — правильное. Эмпирическое правило: если твоё решение нуждается в диаграмме, чтобы его объяснить, — ты зашёл слишком далеко.

Где паттерны реально полезны

Самая честная польза паттернов: дать короткое имя большой идее. В команде проще сказать «это фасад», чем каждый раз объяснять, почему мы прячем три класса за одной обёрткой.

Вот и всё. Вот реальный случай применения.

Паттерны не учат тебя писать хороший код. Они нужны, чтобы говорить о коде, который уже написан. Чтобы можно было спросить «что это за класс?» и услышать «а, это часть медиатора». А не для того, чтобы судорожно сверять свой код с UML-диаграммами, вопрошая: «какой паттерн тут применить?!»

Разработчик, который понимает базовые концепции — разделение ответственности, инкапсуляцию, композицию вместо наследования, — естественным образом напишет чистый код, даже если никогда не слышал слова «Singleton». Паттерны — это просто ярлыки для того, что хорошие разработчики и так умеют делать.

Я работал в нескольких крупнейших техкомпаниях. Никто не использует эти термины день за днём, и меня ни разу не спрашивали о них на собеседовании (для меня это было бы red flag).

Философия Гослинга против философии Гвидо

Есть отличный момент от DHH, который точно объясняет, почему Java стала такой.

У Джеймса Гослинга, создателя Java, был мрачный взгляд на программистов. Его посыл: средний разработчик выстрелит себе в ногу, если дать ему слишком много власти. Поэтому Java построили как песочницу — держи её жёсткой, держи безопасной, не дай им навредить самим себе. Отлично для страховой компании среднего звена, пишущей код, который должен прожить 20 лет. Не очень отлично для выразительности.

Гвидо ван Россум пошёл в противоположном направлении с Python. Его посыл: код читают больше, чем пишут, поэтому оптимизируй под ясность и дай программистам настоящие инструменты. Функции первого класса, декораторы, контекстные менеджеры, duck typing — Python даёт тебе строительные блоки, чтобы решать проблемы напрямую, а не прогонять всё через иерархии классов и церемонии интерфейсов.

Паттерны проектирования — это решение из мира Гослинга. Они существуют, потому что язык не доверяет тебе решить проблему напрямую. В языке из мира Гвидо ты просто... решаешь её.

Паттерны проектирования — это реликты времени, когда языки были недостаточно выразительными. Тот факт, что половина паттернов GoF исчезает в Python, Ruby или Scala, говорит всё сам за себя — дело никогда не было в дизайне. Дело было в том, чтобы язык не стоял у тебя на пути.

Liked this? I publish one deep-dive every other Tuesday.

Join 4,000+ engineers. No sponsors.

Get the newsletter

Понравилось? Вот что ещё стоит почитать:

Опциональные аргументы ДОЛЖНЫ быть ключевыми (Python 3)

Schema-on-Read vs Schema-on-Write

Как комьюнити превратилось в рекламу SaaS