Java не только классы
[EN]

Java — не только классы

Проповедники Java частенько грешат утверждениями вроде того, что все в Java является классом, и это единообразие — великое достижение и преимущество Java перед иными языками программирования. Всякая реклама, по определению, есть ложь — и в данном случае реклама врет трижды: во-первых, есть и другие языки, способные потягаться с Java по части единообразия (например, в Ruby все подряд обзывают объектом); во-вторых, далеко не все в Java — классы; а в-третьих, сведение всего к классам (или иной произвольно выбранной сущности) вовсе не дает никаких преимуществ.

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

Класс Java — лишь одна из возможных моделей типовых логических состояний, связанных с обычными способами употребления компьютеров; если мы не собираемся работать с большим количеством объектов данного класса, никакой практической пользы от этой конструкции нет — формальные свойства и методы удобнее будет заменить более непосредственными средствами управления. По сути дела, все классы являются абстрактными, поскольку любой класс — это лишь возможность определения, а завершенная спецификация нуждается в привязке к определенному объекту, к логическому состоянию компьютерной системы. Такая дополняющая спецификация отложена до момента создания экземпляра класса — и часто требует нетривиальных процедур инициализации. Некоторые объектно-ориентированные языки (вроде Python или Ruby) идут еще дальше и позволяют добавлять свойства и методы произвольным объектам, а не их классам (с образованием так называемых синглетонов). С другой стороны — чистоту классовой идеи портят статические свойства и методы, фактически сводящие класс к его единственному экземпляру, объекту-синглетону. Очевидно, статическая компонента любого класса может выделена в отдельный класс (объект); нет никаких оснований для упаковки таких статических определений внутрь обычного, абстрактного класса — кроме, может быть, некоторого минимального удобства в задании областей видимости по умолчанию. Можно заметить, что большинство классов в пакете java.lang.* на самом деле — одна видимость; они существенно статичны, и все, что от них требуется, — это задать условное пространство имен для зарезервированных слов языка, функционально не отличающихся от if, for или switch. использование вперемешку служебных слов, базовых типов и ссылок из статического пространства имен — это все дурная эклектика, ничего общего не имеющая с собственно классами.

Как ни крути, в языках со строгой и статической типизацией, вроде Java, идеологическую чистоту никогда невозможно соблюсти в полной мере — хотя бы потому, что представители класса вовсе не обязательно будут классами. Более того, на практике таких абсолютное большинство, поскольку лишь экземпляры класса java.lang.Class и его наследников могут быть названы классами — но как раз эта ветвь корневого класса java.lang.Object совершенно бесполезна, ибо не позволяет конструировать и модифицировать классы динамически; разумеется, всегда есть возможность напрямую манипулировать байт-кодом — но такой метод вряд ли окажется сколько-нибудь переносимым. Таким образом, "формальные" классы java.lang.Class, java.lang.reflect.Method или java.lang.reflect.Member — это всего лишь извращенный способ документирования, определяющий основные конструкции Java сугубо метафорически.

Хорошо, допустим, что классы и их экземпляры — это два изначальных примитива языка. Снимаются ли дальнейшие вопросы? Никоим образом. Существует первичная эклектичность, не устранимая ни при каких обстоятельствах. Программы на Java предварительно компилируются в байт-код, который впоследствии может быть выполнен на виртуальной машине Java. Но транслятор Java и JVM — примеры принципиально внеклассовых сущностей, не вписывающихся в общую иерархию, даже если начать с корневого класса, java.lang.Object. Различные реализации компилятора Java и JVM влияют на фактическую производительность и привносят разного рода несовместимости, преодолевать которые можно либо объявляя одну из имеющихся реализации стандартом для данной технологии — либо приняв некоторый протокол для указания на тип компилятора и целевой JVM в самом байт-коде, что впоследствии более продвинутые JVM могли адаптировать свое поведение в зависимости от этой информации и добиться таким способом совместимости со всеми предыдущими версиями.

Никто не сомневается, что возможно было бы устранить эклектику в данном частном случае путем определения специальных "формальных" классов для компилятора и JVM, и вполне возможно написать соответствующий код на Java. Однако, как показывает опыт некоторых других языков, это тупиковый путь. В наши дни преобладает мода на модульность и технологическое разнообразие, и большинство современных систем включают различные средства интеграции для объединения модулей, написанных на разных языках. В частности, это означает отказ от древоподобной модели как стандарта языковой организации. Вместо жесткой иерархической структуры — мы переходим к иерархической гибкости, допускающей рекурсивное наследование и неразрешимое взаимоопределение. В таких иерархиях целое может быть развернуто, начиная с любого произвольно выбранного элемента, порождая одну из допустимых иерархических структур; более того, параллельные потоки обработки могут оперировать с разными обращениями той же иерархии. Зародыш такого подхода обнаруживается и в Java в виде изначально заложенного в язык логического круга: объекты понимаются как экземпляры класса, а классы — частный случай объектов.

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

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

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

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

Сколь угодно полезный и эффективный подход можно использовать не по делу, вне области его реальной применимости, — и тогда его достоинства превратятся в недостатки. Чрезмерная последовательность — синоним неадекватности. Все знают, как записать любой текст на русском языке, используя алфавит из двух символов, — но будет ли такая запись удобочитаемой? Большинство людей предпочтет обычную латиницу. Более того, на письме большинство людей прибегает к индивидуальной системе сокращений, в дополнение к развернутой записи; это эффективно расширяет алфавит. Подобные составные символы во многом родственны арабским лигатурам — или китайским (японским) иероглифам, которые, как правило, состоят из более простых компонент (хотя и не обязательно фонетических). Очевидно, использование таких сокращений (идеограмм) становится неудобным, когда их слишком много, так что выбор подходящего символа в расширенной кодовой таблице требует больших усилий, чем полная фонетическая запись. Иначе говоря, в каждой прикладной области есть некоторый оптимальный алфавит (или класс эквивалентных кодовых таблиц), обеспечивающий максимальную эффективность. Длина такого алфавита зависит от свойств соответствующих деятельностей. Так, при наличии фиксированного набора операций алфавит стремится включить символы для каждой из них (независимо от колмогоровской сложности); напротив, в быстро развивающихся средах предпочтительней более компактный и универсальный набор символов.

Точно так же и в программировании не следует стремиться свести все живое к единственной платформе, или единому стандарту разработки. Слепая приверженность к одной излюбленной среде сродни страстной любви к написанию программ непосредственно в машинных кодах, игнорируя языки высокого уровня. В прикладном программировании и компьютерной науке идеология иерархического подхода понемногу набирает вес. Однако до окончания "языковых войн" еще далеко — упорное стремление изобрести единственно правильную технологию (во многом напоминающее бесчисленные попытки создания вечного двигателя — даже сейчас, когда все знают, что подобные устройства невозможны в силу наиболее фундаментальных законов термодинамики) все еще распространено среди компьютерщиков.


[Компьютеры] [Наука] [Унизм]