Когда я первый раз услышал об объектно-ориентированном программировании — сразу отнёсся к нему скептически. Честно говоря, даже не знаю, почему. Просто оно показалось мне каким-то неправильным. Но ООП очень быстро стало популярным (почему — я объясню ниже) и критика в его адрес превратилась в этакую «ругань в церкви». А объектно-ориентированность стала обязательной составляющей любого уважаемого языка программирования.
С ростом популярности Erlang часто стали задавать вопрос «— А Erlang — объектно-ориентированный?». Правильный ответ был бы «— Да что вы, нет!». Но мы не могли так заявлять в полный голос, поэтому пришлось выкручиваться. Мы придумали несколько достаточно нетривиальных ответов, которые бы представляли Erlang типа-объектно-ориентированным языком (для тех, кто больше всего тянет руку с этим вопросом), но при этом и не объектно-ориентированным для тех, кто на самом деле в теме.
Тут мне хотелось бы вспомнить тезисное выступление директора французского IBM на 7 конференции IEEE по логическому программированию. Он рассказал о том, что IBM добавило в Prolog достаточно много объектно-ориентированных расширений. На вопрос о причинах этого он ответил:
Помню, я думал «Как хорошо! Просто, никаких угрызений совести, никакого самоанализа, никаких метаний по поводу того, правильно ли так делать.»
Мои ЛИЧНЫЕ возражения по теме ООП — это претензии к его основным положениям. Я выделю некоторые из этих положений и суть моих претензий.
Объекты связывают функции и структуры данных вместе в неделимых сущностях. Я думаю, что это фундаментальная ошибка, потому что функции и структуры данных — понятия, относящиеся к абсолютно разным категориям. Почему это так?
Потому что функции выполняют действия. У них есть входные и выходные значения. Входные и выходные значения — это структуры данных, которые изменяются функциями. Во многих языках функции — это последовательности императивов: «сделай то, потом сделай это». Для понимания сути функции необходимо понять порядок выполнения этих императивов (в ленивых функциональных и в логических языках порядок может значения не иметь).
Структуры данных же просто существуют. Они не выполняют никаких действий. Они исключительно декларативны. «Понимание» структуры данных в разы проще «понимания» функции.
Функции представляются чёрными ящиками, трансформирующими входы в выходы. Если я понимаю вход и выход, то я могу понять и функцию (хотя это вовсе не означает то, что я смог бы эту функцию написать).
Функции обычно «понимаются» как части вычислительной системы, передающие данные из структуры данных типа T1 в структуру данных типа T2.
Поскольку функции и структуры данных — это совершенно разные виды животных, то решение содержать их в одной «клетке» фундаментально неверное.
Рассмотрим «время». В объектно-ориентированном языке программирования «время» должно быть объектом. Но в необъектно-ориентированном языке «время» — это экземпляр типа данных. Для примера, в Erlang есть много разных видов «времени», каждый из которых может быть строго и однозначно определён. Например, с помощью таких определений типов:
Заметьте, что эти определения не принадлежат ни одному конкретному объекту. Они общеиспользуемы без каких-либо ограничений, поэтому такие структуры данных, представлющие «время» могут быть использованы любой функцией.
Кроме того, у них нет никаких связанных методов.
В объектно-ориентированных языках программирования определения типов данных принадлежат объектам. Поэтому я не могу найти все определения типов данных в одном месте. В Erlang или в C я могу определить все мои типы данных в одном-единственном включаемом файле или в словаре данных. В объектно-ориентированных языках программирования я этого сделать не могу — определения типов данных размещаются повсеместно.
Я приведу пример. Предположим, я хочу определить глобально видимый тип данных.
Как показали практика и лисп-программисты, лучше иметь мало общеиспользуемых типов данных и много небольших функций, работающих с этими данными, нежели много типов данных и немного функций, работающих с ними.
При этом общеиспользуемый тип может быть связанным списком, массивом, хэш-таблицей или болеее сложной сущностью, например временем, датой или именем файла.
В объектно-ориентированном языке программирования я должен выбрать некоторый базовый класс, в котором я определю общеиспользуемую структуру данных. Все остальные классы, которые хотели бы использовать эту структуру данных, должны наследовать этот класс. Предположим, теперь я хочу создать объект типа «время», которому принадлежит эта структура, и в котором объект типа «время», которому… ну, вы поняли.
Состояние — это краеугольный камень всех проблем. В частности — необходимо избегать функций с сайд-эффектами.
В то время, когда состояние в языках программирования нежелательно, в реальном мире состояние доминирует. Мне интересно состояние моего банковского счёта, поэтому когда я кладу или снимаю с него деньги — я жду, что состояние счёта корректно обновится.
Что могут предложить языки программирования для работы с этим состоянием из реального мира?
Глобальное состояние системы хранится во всех функциях и вытекает изо всех функций. Используются механизмы навроде монад (для функциональных языков) и DC-грамматик (для логических языков). Эти механизмы прячут состояние от программиста, позволяя ему программировать не принимая состояние во внимание, но при этом позволяя ему и получать доступ к состоянию, если в этом возникнет необходимость.
Подход «спрятать состояние от программиста», выбранный для объектно-ориентированных языков программирования — это наихудший подход из возможных. Вместо того, чтобы приоткрыть состояние и минимизировать его влияние на код, оно скрывается так, как будто оно несущественно.
Лично я не вижу свидетельств первой и второй причин. Причины — это то, что находится за технологией. Если технология языка программирования настолько плоха, что создаётся новая индустрия для решения проблем создания этой новой индустрии — то это золотое дно для людей, которые хотят делать деньги, а не работу.
Вот это — то, что реально движет объектно-ориентированным программированием.
От переводчика.
Оригинал: Joe Armstrong, взято отсюда.
Нашумевший в определённых археологическо-программистских кругах текст, написанный Joe Armstrong, автором Erlang. Дату написания текста достоверно установить не удалось, но, судя по косвенным данным, это было в начале 90х.
С ростом популярности Erlang часто стали задавать вопрос «— А Erlang — объектно-ориентированный?». Правильный ответ был бы «— Да что вы, нет!». Но мы не могли так заявлять в полный голос, поэтому пришлось выкручиваться. Мы придумали несколько достаточно нетривиальных ответов, которые бы представляли Erlang типа-объектно-ориентированным языком (для тех, кто больше всего тянет руку с этим вопросом), но при этом и не объектно-ориентированным для тех, кто на самом деле в теме.
Тут мне хотелось бы вспомнить тезисное выступление директора французского IBM на 7 конференции IEEE по логическому программированию. Он рассказал о том, что IBM добавило в Prolog достаточно много объектно-ориентированных расширений. На вопрос о причинах этого он ответил:
«— Наши покупатели хотели объектно-ориентированный Prolog, поэтому мы сделали объектно-ориентированный Prolog»
Помню, я думал «Как хорошо! Просто, никаких угрызений совести, никакого самоанализа, никаких метаний по поводу того, правильно ли так делать.»
Почему объектно-ориентированное программирование — это отстой
Мои ЛИЧНЫЕ возражения по теме ООП — это претензии к его основным положениям. Я выделю некоторые из этих положений и суть моих претензий.
Возражение №1. Структуры данных и функции не должны быть привязаны друг к другу
Объекты связывают функции и структуры данных вместе в неделимых сущностях. Я думаю, что это фундаментальная ошибка, потому что функции и структуры данных — понятия, относящиеся к абсолютно разным категориям. Почему это так?
Потому что функции выполняют действия. У них есть входные и выходные значения. Входные и выходные значения — это структуры данных, которые изменяются функциями. Во многих языках функции — это последовательности императивов: «сделай то, потом сделай это». Для понимания сути функции необходимо понять порядок выполнения этих императивов (в ленивых функциональных и в логических языках порядок может значения не иметь).
Структуры данных же просто существуют. Они не выполняют никаких действий. Они исключительно декларативны. «Понимание» структуры данных в разы проще «понимания» функции.
Функции представляются чёрными ящиками, трансформирующими входы в выходы. Если я понимаю вход и выход, то я могу понять и функцию (хотя это вовсе не означает то, что я смог бы эту функцию написать).
Функции обычно «понимаются» как части вычислительной системы, передающие данные из структуры данных типа T1 в структуру данных типа T2.
Поскольку функции и структуры данных — это совершенно разные виды животных, то решение содержать их в одной «клетке» фундаментально неверное.
Возражение №2. Всё должно быть объектом
Рассмотрим «время». В объектно-ориентированном языке программирования «время» должно быть объектом. Но в необъектно-ориентированном языке «время» — это экземпляр типа данных. Для примера, в 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 я могу определить все мои типы данных в одном-единственном включаемом файле или в словаре данных. В объектно-ориентированных языках программирования я этого сделать не могу — определения типов данных размещаются повсеместно.
Я приведу пример. Предположим, я хочу определить глобально видимый тип данных.
Как показали практика и лисп-программисты, лучше иметь мало общеиспользуемых типов данных и много небольших функций, работающих с этими данными, нежели много типов данных и немного функций, работающих с ними.
При этом общеиспользуемый тип может быть связанным списком, массивом, хэш-таблицей или болеее сложной сущностью, например временем, датой или именем файла.
В объектно-ориентированном языке программирования я должен выбрать некоторый базовый класс, в котором я определю общеиспользуемую структуру данных. Все остальные классы, которые хотели бы использовать эту структуру данных, должны наследовать этот класс. Предположим, теперь я хочу создать объект типа «время», которому принадлежит эта структура, и в котором объект типа «время», которому… ну, вы поняли.
Возражение №4. У объектов есть скрытое состояние.
Состояние — это краеугольный камень всех проблем. В частности — необходимо избегать функций с сайд-эффектами.
В то время, когда состояние в языках программирования нежелательно, в реальном мире состояние доминирует. Мне интересно состояние моего банковского счёта, поэтому когда я кладу или снимаю с него деньги — я жду, что состояние счёта корректно обновится.
Что могут предложить языки программирования для работы с этим состоянием из реального мира?
- Объектно-ориентированные языки программирования скажут «— Надо спрятать состояние от программиста». Состояния скрываются, доступ к ним можно получить только с помощью специальных функций;
- Обычные языки программирования (C, Pascal) скажут, что видимость переменных состояния должна регулироваться правилами областей видимости языка;
- Декларативные языки скажут, что состояния нет.
Глобальное состояние системы хранится во всех функциях и вытекает изо всех функций. Используются механизмы навроде монад (для функциональных языков) и DC-грамматик (для логических языков). Эти механизмы прячут состояние от программиста, позволяя ему программировать не принимая состояние во внимание, но при этом позволяя ему и получать доступ к состоянию, если в этом возникнет необходимость.
Подход «спрятать состояние от программиста», выбранный для объектно-ориентированных языков программирования — это наихудший подход из возможных. Вместо того, чтобы приоткрыть состояние и минимизировать его влияние на код, оно скрывается так, как будто оно несущественно.
Почему ООП так популярно?
- Причина 1 — все думают, что его легко изучить;
- Причина 2 — все думают, что с его помощью можно повысить степень повторного использования кода;
- Причина 3 — хайп вокруг ООП;
- Причина 4 — оно создаёт Новую Индустрию Программирования.
Лично я не вижу свидетельств первой и второй причин. Причины — это то, что находится за технологией. Если технология языка программирования настолько плоха, что создаётся новая индустрия для решения проблем создания этой новой индустрии — то это золотое дно для людей, которые хотят делать деньги, а не работу.
Вот это — то, что реально движет объектно-ориентированным программированием.
От переводчика.
Оригинал: Joe Armstrong, взято отсюда.
Нашумевший в определённых археологическо-программистских кругах текст, написанный Joe Armstrong, автором Erlang. Дату написания текста достоверно установить не удалось, но, судя по косвенным данным, это было в начале 90х.