Это перевод статьи Джо Армстронга Why OO Sucks, повествование ведётся от имени автора оригинальной публикации.
Когда я впервые познакомился с объектно-ориентированным программированием (ООП), мне не понравилась эта идея. Не знаю, почему именно — просто почувствовал, что здесь что-то не так. После появления ООП эта парадигма стала очень популярной, а её критика считалась дурным тоном. Объектная ориентированность стала чем-то, что должно обязательно быть в каждом «нормальном» языке программирования.
Когда Erlang стал популярным, у нас часто спрашивали, относится ли он к объектно-ориентированным языкам. Правдивый ответ на этот вопрос должен быть таким: «Конечно, нет, Erlang не объектно-ориентированный язык». Но мы не хотели акцентировать на этом внимание, поэтому придумали уловку. Мы говорили вслух, что Erlang в некотором роде является объектно-ориентированным языком. Такой ответ удовлетворял тех, кому везде нужна объектная ориентированность. Но вдумчивый собеседник слышал в этом ответе правильный посыл: «Erlang — не совсем объектно-ориентированный язык».
В такие моменты я вспоминал слова руководителя подразделения IBM во Франции, которые он сказал на открытии конференции IEEE Logic в Париже. В языке Prolog разработчики IBM добавили много объектно-ориентированных расширений. На вопрос «зачем это сделано» руководитель ответил: «Наши потребители хотели, чтобы Prolog был объектно-ориентированным, поэтому мы сделали его объектно-ориентированным».
Я тогда подумал: «Как всё просто объясняется, и никаких сомнений и колебаний, никаких вопросов насчёт того, действительно ли нужно делать Prolog объектно-ориентированным языком».
Так почему же объектно-ориентированное программирование — это плохо?
Моя оценка ООП касается основных принципов этой парадигмы. Мои возражения против некоторых из них приводятся ниже.
Возражение 1: структуры данных и функции не должны быть связанными друг с другом
Объекты связывают функции и структуры данных в неделимые единицы. Я думаю, что это фундаментальная ошибка, так как объекты и функции принадлежат разным мирам. Почему?
Функции что-то делают. Они что-то принимают и что-то возвращают. То, что они принимают и возвращают — структуры данных, которые меняются с помощью функций. В большинстве языков программирования функции состоят из последовательностей команд — сделай это, а затем сделай то. Чтобы понять функции, нужно понять, в какой последовательности выполняются команды. В функциональных языках программирования с ленивыми вычислениями и в логических языках программирования последовательность играет меньшее значение.
В свою очередь, структуры данных просто существуют. Они не выполняют действий и они изначально декларативны. Понять структуры данных намного проще, чем понять функции.
Функции можно представить в виде чёрных ящиков, которые принимают что-то на вход, выполняют действия и возвращают что-то. Если я понимаю, что функция принимает и возвращает, я понимаю функцию. Но это не значит, что я могу её написать.
Обычно «понимание» ограничивается наблюдением, что функция — это часть вычислительной системы, чья работа — изменять структуры данных типа Т1 в структуры данных типа Т2.
Так как функции и структуры данных имеют разную природу, не надо считать их одинаковыми и работать с ними как с одинаковыми сущностями.
Возражение 2: всё есть объект
Представьте себе time. В объектно-ориентированном языке time должно быть объектом. В Smalltalk даже число 3 — объект. Но в языках, которые не относятся к объектно-ориентированным, time — экземпляр типа данных. Например, в Erlang есть много разных вариантов времени, которые можно чётко и однозначно определить.
-deftype day() = 1..31.
-deftype month() = 1..12.
-deftype year() = int().
-deftype hour() = 1..24.
-deftype minute() = 1..60.
-deftype second() = 1..60.
-deftype abstime() = {abstime,year(),month(),day(),hour(),min(),sec()}.
-deftype hms() = {hms,hour(),min(),sec()}.
…
Эти определения не принадлежат какому-то объекту. Они универсальные, и их можно использовать без ограничений. Любая функция может управлять структурами данных, которые представляют время. Здесь нет связанных методов.
Возражение 3: в объектно-ориентированных языках определения типов данных распространяются везде
В объектно-ориентированных языках определения типов данных принадлежат объектам. Поэтому невозможно найти все определения типов данных в одном месте. В Erlang или C можно определить все типы в одном включаемом файле или словаре данных. В объектно-ориентированном языке такое невозможно — определения распространяются везде.
Вот пример. Допустим, я хочу определить структуру данных в глобальной области видимости. Глобальный тип данных — такой тип, который доступен из любой точки в системе.
Программисты Lisp давно знают, что лучше иметь меньше глобальных типов данных и больше функций, которые работают с ними, чем иметь много типов данных и мало функций для работы с ними.
Глобальный тип может быть связанным списком, массивом, хэш-таблицей или более сложным объектом.
В объектно-ориентированном языке мне придётся выбирать объект, в котором я определю глобальный тип. Все остальные объекты, которые захотят использовать эту структуру данных, должны наследовать этот объект. Допустим, я хочу создать объект time, в котором определяется эта структура данных, и в котором… Это очень сложно.
Возражение 4: у объектов есть приватное состояние
Состояние — корень всех зол. В частности, нужно избегать функций с побочными эффектами.
Если в языках программирования состояния нежелательны, то в реальном мире они распространены. Я сильно интересуюсь состоянием своего банковского счёта. Когда я пополняю его или трачу с него деньги, всегда рассчитываю, что состояние корректно обновится.
С учётом того, что в реальном мире состояния распространены, какие условия должен обеспечить язык программирования для работы с состояниями?
- Объектно-ориентированные языки говорят «прячьте состояние от программиста». Состояние скрывается, и доступ к нему можно получить с помощью функций.
- Обычные языки программирования, например, C или Pascal, говорят, что состояние переменных контролируется правилами области видимости языка.
- Чистые декларативные языки говорят, что состояния не существует. Глобальное состояние системы передаётся во все функции и выходит из всех функций. Механизмы типа монад в функциональных языках и DC-грамматики в логических языках программирования используются, чтобы спрятать состояние от программистов. Поэтому программисты могут работать так, как будто состояние не имеет значение, но при этом у них может быть доступ к состоянию системы, если это необходимо.
Объектно-ориентированные языки выбрали опцию «прятать состояние от программиста». Это худший вариант из возможных. Вместо того, чтобы работать с состоянием и минимизировать неудобства, они просто прячут его.
Почему же ООП популярно?
- Считалось, что объектно-ориентированное программирование проще изучать.
- Считалось, что в ООП проще переиспользовать код.
- ООП было на волне хайпа.
- ООП создало целую индустрию программного обеспечения.
Я не вижу доказательств первых двух пунктов. Похоже, движущей силой популярности ООП остаются пункты 3 и 4. Если технология или язык настолько плохи, что вокруг них вырастает целая индустрия, которая решает их же проблемы, то это хорошая возможность для людей, которые ориентируются в первую очередь на зарабатывание денег.