?

Log in

Решил тут поразмышлять вслух. Получилось многабукаф. Кому интересно.… - отражение жизни в экране монитора [entries|archive|friends|userinfo]
отражение жизни в экране монитора

[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

[May. 28th, 2008|01:48 am]
отражение жизни в экране монитора
[Tags|]

Решил тут поразмышлять вслух. Получилось многабукаф. Кому интересно.

Блеск и нищета объектов,

или о чём неубедительно пытался сказать Александр Горный на РИТ-2008.

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

Оная судьба коснулась и ООП. Нет, на самом деле нападки на объекты начались уже весьма давно, но, поскольку веб-индустрия расцвёла буйным цветом и вышла на первый план совсем недавно (а также в силу игнорирования ООП ранними версиями распространённых опенсорсных платформ веб-разработки) - до неё «классовая борьба» докатилась только сейчас.

Всем, вообще-то, понятно, что в современной вебне творится что-то не то – вот только взгляд на то, что именно «не то», у каждого разный. В пламенной речи gornal на орехи досталось всему ООП сразу, но поскольку аргументы были приведены достаточно сомнительные – попробуем разобраться, что же на самом деле гложет Гилберта Грейпа.

Компиляторы с Марса, интерпретаторы с Венеры


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

Что объединяет все языки программирования, хоть сколько-нибудь поднявшиеся над унылой равниной машинного кода? Все они являются инструментами, созданными человеком для своего удобства, и отражают в себе древнейшее и любимейшее занятие человека разумного: придумывание имён для вещей. Практически во всех средах программирования человек предпочитает работать с удобными ему текстуальными обозначениями, оперировать которыми не в пример легче, чем мельтешащими перед глазами адресами памяти и номерами регистров. А поскольку в любом мало-мальски серьёзном проекте имён накапливается целая куча, приходится их как-то организовывать. И мы неизбежно приходим к, в том или ином виде, нэймспэйсам.

Дадим простое и ёмкое определение: пространство имён - это отображение набора алфавитно-цифровых идентификаторов на множество сущностей (единиц данных и кода) различного типа. Структура (struct), её поля - это нэймспэйс. Объект, его свойства и методы - это нэймспэйс. Локальные переменные в функции - нэймспейс. Глобальные переменные модуля - ну вы поняли, да?

Обратим внимание на последнее свойство в нашем определении нэймспейса: различного типа. Оно важно. Обращаясь к сущности по её идентификатору, мы должны получить не просто доступ (указатель) к содержащейся в сущности информации, но и определить, к какому типу эта сущность принадлежит. Именно это свойство делает пространства имён столь гибким и распространённым элементом любого ЯП. Здесь и далее под "сущностью" мы будем понимать либо неструктурированные данные (фундаментальные типы языка), либо структурированные данные (массивы и объекты), либо адресуемые блоки кода (подпрограммы, функции, методы).

Постойте, но а как же происходит это самое получение типа? Не говоря уже о получении самой информации? Ха! Вот мы и подобрались к главному. "Крепитесь" (c) Фёдор Двинятин, сейчас я расскажу вам чем отличается динамическое ООП (и реализующие его ТруЪ Динамические Языки) от известного большинству нас с пелёнок ООП статического.

Итак,

1. В статических языках разрешение имён из нэймспейсов в указатели и типы происходит на этапе компиляции; сами имена существуют только на этапе компиляции; адресуемые ими данные во время выполнения хранятся в компактных бинарных "структурах" (C struct, Pascal record) в памяти или стеке.

2. В ТруЪ Динамических Языках разрешение имён в динамические типизированные сущности происходит на этапе выполнения; нэймспэйсы во время выполнения хранятся в ассоциативных массивах. Кроме того, обратив внимание на наше определение "сущности", придём к выводу, что язык должен непременно обладать функциями первого класса.

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

Всё правильно сделал: JavaScript, Lua, Python


Итак, поскольку в основном речь пойдёт о динамических языках - ибо именно они ныне правят вебом - упомянем о классической реализации ООП в языках статических (которые, по большому счёту, представляют C++, Object Pascal, Java и C#). Договоримся, что выкинем из рассмотрения средства рефлексии и будем говорить о минимальной работоспособной реализации ООП в компилируемом нативном коде.

Во время выполнения, статический объект - это по сути своей "структура" (struct) из полей, плюс ссылка на таблицу виртуальных методов для класса. Всё. На этапе выполнения никаких "классов" уже не существует - ссылки на все невиртуальные и статические методы уже разрешились на этапе компиляции с учётом всех хитромудрых отношений наследования. Стройная классовая иерархия превратилась в однообразные железные данные и код; всё уже намертво сбито гвоздями и склёпано заклёпками. Ибо - статика.

Совсем другую картину наблюдаем в языках ТруЪ-Динамических. Во время выполнения объект представляет собой совокупность: ассоциативный массив своих полей ПЛЮС ссылка на класс. А класс, в свою очередь, есть совокупность: ассоцмассив методов и статических полей ПЛЮС ссылка на предка(ов). Ко всему этому добавим реализованный на уровне языка (или средств метапрограммирования) прозрачные механизмы проброски обращений к атрибутам[*] объекта на иерархию классов (прототипов) и привязку методов[*] и ХОБА! динамическая ООП-система готова.

Почему я называю её ТруЪ? Потому что она простая. Потому что, согласно заветам товарища Оккама, она не вводит лишних сущностей, пользуясь тем, что и так есть в любом приличном динамическом языке: ассоцмассивами и функциями первого класса. Потому что скриптовые языки были созданы с целью максимально упростить и облегчить процесс разработки, именно поэтому они должны быть максимально гибкими, логично устроенными, прозрачными и простыми для понимания.

Как видим, в этой системе грань между объектом/классом и ассоцмассивом вообще исчезающе мала: объект суть ассоцмассив со строковыми ключами, плюс ссылка на предка. Собственно, некоторые языки эту грань создавать и не стали, удовлетворившись единственным типом на все случаи жизни . Рассмотрим ряд ТруЪ языков:

JavaScript (здесь подразумеваем ECMA-262 Edition 3): весьма оригинальная концепция. Объекты инстанцируются на базе функции-конструктора, с использованием оператора new. Проброска обращений прозрачная. Методы и статические атрибуты класса определяются как атрибуты свойства prototype в конструкторе - таким образом, не конструктор является членом класса, а класс является членом конструктора :]
Привязка методов полностью автоматическая, объект прозрачно передаётся в магической переменной this.

Lua: единый тип "таблица" (фактически, ассоцмассив), плюс средства для имитации ООП. Доступ к атрибутам через атрибут-синтаксис и индекс-синтаксис.
В качестве класса можно использовать таблицу-литерал. Конструктор - просто метод-фабрика (создаёт таблицу-литерал и накачивает атрибутами). Проброска обращений к атрибутам на предков - ручная, для создаваемого объекта перегружается оператор разындексации __index.
Привязка методов - автоматическая, при использовании специального оператора ":" вместо "." для определения или вызова метода. Объект передаётся в магическом аргументе self.

Python: здесь уже имеется полный набор синтаскического сахара для ООП, вполне похожего на свои аналоги в статических языках. "Объект" есть совокупность ассоцмассива атрибутов и ссылки на "тип", где "тип" (он же "класс") есть совокупность названия, ссылок на предков и ассоцмассива методов и статических свойств.
Инстанциация автоматическая, магический метод-конструктор называется __init__.
Доступ к атрибутам объекта через атрибут-синтаксис. Ассоцмассив атрибутов может быть получен для объекта через магический атрибут __dict __. Доступ к элементам ассоцмассива - через индекс-синтаксис.
Проброска обращений - очевидно, автоматическая.
Привязка методов - автоматическая при вызове (объект подставляется в виде первого аргумента, по соглашению он обычно именуется self). Статические методы определяются особо, при помощи спецфункций staticmethod и classmethod.

Однако не все языки в полной мере грокнули необходимость сращения объектов и ассоцмассивов. Упомянем их.

Ублюдки: Perl, Ruby, PHP


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

Жуткий синтаксис доступа к атрибутам, отсутствие нормального наследования, комическая функция bless - всё это делает объектный код в перле болезненным для восприятия. Впрочем, практически любой код на перле и так выглядит ужасно - зато упор на строковое представление данных даёт некоторые интересные средства для метапрограммирования.

Однако, даже попытки привести перл в порядок без отказа от магических замашек обладают заметными недостатками. Коли уж рассмотрели Python и Perl подряд, обратимся к их прямому потомку - Ruby. Гибрид решил соединить изящество Python с краткостью и гибкостью Perl, и в результате упростил некоторые вещи, усложнив при этом другие. Начнём с самого вопиющего. По умолчанию все поля объекта - приватные.

Ась?!

Какого хрена? Зачем вообще нужны приватные поля? От кого вы их собрались прятать? Тем более в открытом, интерпретируемом коде? Вы просто документируйте публичный интерфейс своих классов. А не публичный - не документируйте. Не надо относиться к программистам, которые будут использовать ваш код, как к идиотам - они сами разберутся, что им надо, а что нет. Так что приватные поля - зло. А приватные по умолчанию - полный бред. Инкапсуляция ради инкапсуляции не несёт смысловой нагрузки.

Вторая существенная проблема - отсутствие простой связи между объектами и ассоцмассивами. У объектов специальные магические методы для рефлексии, что немедленно делает механизм работы ООП менее понятным, лишает метапрограммирование лёгкости и изящества, и переводит Руби в разряд не-ТруЪ динамических языков.

Третья проблема - изобилие синтаксисов, идущее в ущерб простоте и стройности языка. К теме это, впрочем мало относится, поэтому останавливаться не будем. У нас впереди средоточие Мирового Зла, монстр веба - PHP.

Главной проблемой PHP является то, что его начали разрабатывать (и разрабатывают до сих пор) явные посредственности. Узнать посредственность легко - она всегда норовит скопировать всё "популярное", даже не попытавшись его толком переосмыслить и привести в гармонию. Так и PHP, начавши обезъянничать с Перла, закончил сдиранием с Явы.

Более глупой идеи придумать было нельзя. Java бесконечно далека от скриптовых языков. Где скрипты своей виртуозной работой со строками и динамическими типами проявляют нечеловеческую гибкость, неповоротливая скрипучая жаба вынуждена плодить и состыковывать вместе десятки статичных объектов-суставов.

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

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

PHP так и не понял, кем он хочет быть - набором библиотек для перла, легковесным скриптовым клеем или солидной Ынтерпрайз-платформой. Пока он всё ещё думает - смотреть без смеха не получается.

Как видим, реализации ООП у наиболее широко применяемых в веб-разработке языков страдают либо от дефицита лёгкости, либо от дефицита изящества. Чем это мешает нам? В той или иной степени, мы получаем:
- сложность расширения (тяжело "поапгрейдить" код, превратив ассоцмассив в объект)
- плохую читабельность (вредит поддерживаемости)
- неудобную рефлексию (тяжело отлаживать, тяжело расширять язык через метасредства)
- слабую гибкость (принуждает строить многоуровневые городушки)
- загадочность внутреннего устройства языка (без полного понимания принципов работы системы трудно использовать её продвинутые фичи).

Теперь ясно, что у нас есть плохое и хорошее ООП. А насколько полезно само ООП?

Границы применимости


Как известно, даже на C можно писать в ООП-стиле. Только надо вручную указывать ссылку на структуру при вызове её методов. И ещё помнить, к какой структуре её методы относятся. То же самое наблюдаем у скриптовых языков, только вместо структур тут применяются ассоцмассивы (или просто списки/кортежи). А поскольку структуры данных и операции над ними используются повсеместно, можно сказать что практически любая достаточно сложная программа использует ООП, пусть и в неявном виде.

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

С другой стороны, любые вспомогательные средства вносят свои ограничения и свои особые принципы работы. Разберёмся, приносят ли эти принципы пользу или вред в контексте веб-разработки.

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

Наследование:
В статических языках наследование используется по двум причинам: использование общей логики и задание общего абстрактного типа для схожих по сущности классов. В динамических языках, очевидно, второй аргумент теряет силу. А первый зачастую решается при помощи незатейливых глобальных функций. Поэтому до применения наследования дело доходит нечасто.

Полиморфизм:
Динамическая "утиная" типизация по определению представляет собой полиморфизм. О чём тут ещё говорить.

Перегрузка операторов:
Штука очень полезная для организации DSL[*], которые сильно повышают наглядность разработки. Однако, следует не забывать, что этому DSL'ю придется тщательно обучить остальных разработчиков, работающих над проектом.

Таким образом, приходим к выводу, что концепции ООП не противоречат напрямую основных принципам разработки в скриптовых языках, но и не вполне соотвтествуют им, вследствие чего применять их рекомендуется с умом и в ограниченном объёме.

Итоги дня

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

- любая система программирования должна быть организована просто и доступно для понимания

- динамические системы программирования созданы для иных целей, нежли статические, и не должны пытаться копировать подходы статических систем

- реализация ООП в динамических ЯП должна естественным образом опираться на ассоциативные массивы

- Ruby устроен иначе, поэтому он не совсем хороший язык

- PHP и Perl устроены иначе, поэтому (и не только поэтому) они - плохие языки

- метасредства в динамических языках есть благо, и ими следует активно, но с умом, пользоваться

- многослойные городушки из классов - строго противопоказаны динамическим языкам

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

Глоссарий

(я тут немножко ещё своей терминологии навертел...)

^ 1. Проброска обращений - автоматическая подстановка атрибутов класса (или предка класса) при обращении к атрибуту объекта. Во время проброски обращения к методу обычно производится его привязка.

^ 2. Привязка метода - объединение несвязанного нестатического метода (функции - атрибута класса) и экземпляра объекта. В результате получается "связанный метод" или делегат, который затем уже может быть выполнен.

^ 3. DSL - domain-specific language, предметно-ориентированный язык. Язык программирования, средства которого оптимизированы для описания и решения задач из определённой предметной области. Обычно строится на базе какого-нибудь универсального ЯП с мощными средствами метапрограммирования.
linkReply

Comments:
[User Picture]From: suvlehim_takac
2008-05-27 10:18 pm (UTC)
Вот в этом куске не понятно о каком языке речь:
Однако, даже попытки привести перл в порядок без отказа от магических замашек обладают заметными недостатками. Коли уж рассмотрели Python и Perl подряд, обратимся к их прямому потомку. Гибрид решил соединить изящество Python с краткостью и гибкостью Perl, и в результате упростил некоторые вещи, усложнив при этом другие. Начнём с самого вопиющего. По умолчанию все поля объекта - приватные.
(Reply) (Thread)
[User Picture]From: rainman_rocks
2008-05-27 10:26 pm (UTC)
Fixed.

Вообще, тяжело в ЖЖ такие опусы вычитывать/корректировать. Надо что-то другое измыслить.
(Reply) (Parent) (Thread)
[User Picture]From: zhectjahsik
2008-05-31 10:28 am (UTC)
Сразу прошу прощения за off-top, но надеюсь на понимание: Я так понимаю ,что мы с Вами ровесники. Однако, я так же понимаю, Вы не изменили своим юношеским-школьным пристрастиям к программированию и получили соответствующее образование, подкрепленное самообразованием. Я же высшее образование получил чисто математико-механическое, а работать решил все же в программировании. И очень остро ощущаю отсутствие у себя базы, идеологии, философии этого ремесла-исуксства-науки.
Могли бы Вы дать ссылки на "нужные" источники, которые как Вы считаете сформировали Вас, как человека разбирающегося в предмете? Ибо, к своему стыду, я даже не могу себе представить, для чего созданы динамические системы программирования и что они из себя представляют.
Надеюсь, на понимание, и заранее спасибо Вам.
(Reply) (Thread)
[User Picture]From: rainman_rocks
2008-06-01 05:23 pm (UTC)
Итить-кирбиздить. Вы так витиевато выражаетесь, что в ваших словах мне чувствуется издёвка. Ну да ладно, отвечу.

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

Определённых, мощных источников назвать не могу. Нахватал то тут, то там. Много интересного почерпнул с блоггеров-эссеистов, в основном Спольского и Грэма, щас вот Егге почитываю. Как ни странно, хороший источник - мануалы по языкам программирования. Почитаешь их, обобщишь в голове как Антонио Бандерас в х/ф "Тринадцатый воин" - гляди-ка, и кой-какое теоретическое понимание пришло.

Вследствие хаотичности моего метода получения знаний и ориентации на иностранные материалы, русскоязычная терминология в основном получается самопальная. Под "системой программирования" (хотя можно подставить и "среда", и "платформа") я подразумеваю совокупность языка программирования, его компилятора/интерпретатора, виртуальной машины (if any) и стандартной библиотеки. Динамическая система программирования - та, в которой используется динамическое определение типов и динамическая же подстановка методов. Проще говоря, это примерно то же, что называется "скриптовыми языками".

Кстати, если не секрет, а как вы нашли этот пост?..
(Reply) (Parent) (Thread)
[User Picture]From: zhectjahsik
2008-06-03 12:04 pm (UTC)
Еще раз спасибо.
Начну с последнего: пост нашел, набрав запрос Lua, ибо хотел хоть что-то узнать о том, как народ встраивает скриптовые языки в другие (мне бы для начала консольку для 3д-демки хотелось бы реализовать, а то перекомпиляция постоянная достала, а файлы ресурсов сперва надо разобрать, а перед этим их еще и подредактировать, но это тоже требует перезапуска приложения). Слышал, что так например ту же Lua вставили в Сталкер и на ней много чего понаписали.

оффтоп:
Вообще после мехмата народ, у которого нет четкой карьерной установки (своей или родительской), мечится как корова на минном поле, пока голова еще свежа и не закостенела. Раньше создание мехматов стимулировала оборонка, а сейчас ну кто пойдет на 12 т.р. за 40 часов в неделю применять свои знания на благо отечества (да еще и в факинговой столице)? Увы, таких мало и назвать их счастливыми людьми язык не поворачивается. Вот я сейчас и мечусь между уходом в "аналитики" или "программисты". Ни то, ни то сердце особо не греет, ни там, ни тут опыта и базы как таковой нет, но программированием я хоть в школе увлекался. А с другой стороны, вот почитаешь пост типа Вашего или еще каких-нибудь гуру и руки опускаются - как мало я всего знаю пока. А вообще программирование - ремесло суровое ,а потолок в зарплате через 10 лет служения в отрасли проигрывает финансово-аналитическим структурам.
(Reply) (Parent) (Thread)
[User Picture]From: rainman_rocks
2008-06-03 01:06 pm (UTC)
Бяяя. Аналитика - это, конечно, мило, но экономика мне претит по морально-идеологическим соображением. Свою карьеру вижу только в программизме (с возрастом уход в руководителя или в консалтинг), либо лингвистика / журналистика, ежли конкретно моча в голову стукнет.
(Reply) (Parent) (Thread)
[User Picture]From: zhectjahsik
2008-06-03 03:50 pm (UTC)
Консалтинг - это идея, почему-то совсем о ней не задумывался. С экономикой все тоже сложно, но уж коли за это деньги платят и не боги горшки обжигают...
(Reply) (Parent) (Thread)
[User Picture]From: yurvor
2008-07-10 04:00 am (UTC)
Грамотно.
(Reply) (Thread)