четверг, 15 апреля 2010 г.

[prog.flame] Попрограммировал на Java, делюсь впечатлениями. Часть II. Какую бы Java хотелось иметь (минимальный вариант).

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

Здесь описываются, на мой взгляд, минимальные изменения языка. Т.е., при желании, их можно было бы реализовать в препроцессоре (что-то вроде старого Cfront-а), который бы генерировал обычный Java-исходник. В следующей заметке я попробую описать более серьезные изменения.

Итак, в первую очередь в Java мне нужны typedef-ы. Чтобы вместо:

private Map<com.intervale.someprj.customproto.Uid, com.intervale.someprj.customproto.Pdu> parseStream(java.nio.channels.ReadableByteChannel from) {...}

public void handleStreamContent(java.nio.channels.ReadableByteChannel from) {
   Map<com.intervale.someprj.customproto.Uid, com.intervale.someprj.customproto.Pdu> pdus =
      parseStream(from);
   ...
}

можно было бы написать:

typedef Map<com.intervale.someprj.customproto.Uid,
            com.intervale.someprj.customproto.Pdu>
      PduByUidContainer;
typedef java.nio.channels.ReadableByteChannel ByteChannel;

private PduByUidContainer parseStream(ByteChannel from) {...}

public void handleStreamContent(ByteChannel from) {
   PduByUidContainer pdus = parseStream(from);
   ...
}

Использование typedef-ов не только уменьшает объем кода, но еще и скрывает часть деталей реализации. Так, если со временем заменить в определении ByteChannel тип ReadableByteChannel на что-нибудь другое, то изрядную часть кода даже не придется переписывать (про автоматические рефакторинги в Java я в курсе, но это несколько другое).

Во-вторых, в Java очень хотелось бы иметь свободные функции. Без них можно обходиться за счет public static методов классов и static import-а. Но все-таки проще было бы иметь возможность написать простую функцию, не привязанную ни к какому классу, чем придумывать вспомогательный класс, который бы содержал один-два public static метод.

В-третьих, в Java очень не хватает перегрузки операторов. Не знаю, возможно ли в Java повторить C++ный синтакис перегрузки операторов (т.е. запись вида operator+ или operator<<), но вполне подойдет и подход языка D: функции со специальными именами. Чтобы можно было написать:

class TrickyByteStream {
   ...
   public TrickyByteStream opLShift(byte b) {...}
   public TrickyByteStream opLShift(byte[] a) {...}
   public TrickyByteStream opLShift(short s) {...}
   ...
}

TrickyByteStream bs = new TrickyByteStream();
bs << byteValue << byteArray << shortValue << intValue;

В совокупности со свободными функциями перегрузка операторов может дать важный эффект – возможность расширять функциональность чужих классов. Например, я написал TrickyByteStream, а кто-то написал собственный класс UserInfo. И сделал оператор сдвига UserInfo в TrickyByteStream в виде свободной функции. В результате, объекты UserInfo смогут сохраняться в TrickyByteStream точно так же, как если бы я сам встроил в класс TrickyByteStream эту функциональность.

В-четвертых, очень бы хотелось видеть в Java автоматический вывод типов для переменных. То, что есть в C# 3.0, что добавили в C++0x с помощью ключевого слова auto и то, что планируется в Java 7 (если мне мой склероз не изменяет). Все-таки двадцать первый век на дворе. Хочется уже просто писать:

public void handleStreamContent(ByteChannel from) {
   auto pdus = parseStream(from);
   ...
}

В-пятых, в Java не хватает lambda-функций. Анонимные классы, по сути, тоже самое и есть, но уж очень большой у них синтаксический оверхед :) А еще было бы здорово, чтобы в качестве lambda-функций можно было передавать свободные функции. Вот это было бы действительно удобно – нужно отсортировать список – вызываешь java.util.Collections.sort и вместо объекта-компаратора передаешь свободную функцию. Эффект такой же, как от использования объектов-компараторов, но телодвижений и строчек кода в программе будет меньше.

В-шестых, в Java следует заменить checked exceptions на спецификатор nothrow. Т.е. вместо того, чтобы тупо перечислять исключения, который может выбросить метод (что, в конце-концов ведет к простым конструкциям вида throws Exception или throws MyLibraryBasicException), нужно просто явно говорить – вот здесь исключений не будет (зуб даю!), а здесь – сколько угодно. Поскольку тема эта большая и говорить о ней можно много, то не буду ее развивать. Скажу только, если если делать препроцессор в обычную Java, то спецификатор nothrow не должен вызвать затруднений. Те методы, которые не имеют nothrow транслируются в методы со спецификацией throws Exception. Методы с nothrow транслируются в методы без спецификации throws.

Хорошо было бы иметь еще и отдельный блок nothrow, который можно было бы использовать внутри методов/функций (т.е. сам метод может выбрасывать исключения, а вот какая-то его часть не должна это делать – такая часть помещается в блок nothrow). Но, боюсь, это уже слишком сильная модификация языка.

В-седьмых, в Java необходимы что-то вроде using из C# или scope(*) из D. Писать очистку ресурсов в случае исключения в блоках finally не правильно, поскольку она оказывается далеко от места захвата ресурсов. Раз уж C++ных деструкторов, которые гарантированно вызываются при выходе из области видимости, нет, то помогли бы специализированные средства. Вряд ли конструкцию using из C# удалось бы сделать через препроцессинг – ведь требуется еще и поддержка идиомы IDisposable в стандартной библиотеке. А вот конструкция scope(exit) из D, да еще в сочетании со свободными функциями и lambda-функциями, пришлась бы в тему:

auto cf = (JmsConnectionFactory) context.lookup(connectionFactoryFromJndi);

auto destination = (JmsDestination) context.lookup(destinationFromJndi);

auto connection = cf.createConnection();
scope(exit) { safeCloseConnection(connection); }

auto session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
scope(exit) { safeCloseSession(session); }

auto producer = session.createProducer(destination);
scope(exit) { safeCloseProducer(producer); }

Для сравнения, вот так бы этот код выглядел через finally:

JmsConnectionFactory cf = null;
JmsDestination destination = null;
Connection connection = null;
Session session = null;
MessageProducer producer = null;
try {
   cf = (JmsConnectionFactory) context.lookup(connectionFactoryFromJndi);

   destination = (JmsDestination) context.lookup(destinationFromJndi);

   connection = cf.createConnection(); 

   session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); 

   producer = session.createProducer(destination);
   ...
} catch( ... ) {
} finally {
   if(null != producer)
      safeCloseProducer(producer);
   if(null != session)
      safeCloseSession(session);
   if(null != connection)
      safeCloseConnection(connection);
}

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

Еще одна штука меня раздражала и которую, имхо, следует устранить. Допустим, есть пакет com.intervale.customproto. В котором есть класс Pdu. И в котором есть класс BasicPduFormatter:

class BasicPduFormatter {
   public String format(Pdu what) { ... }
}

Так вот, если я в другом пакете захочу унаследоваться от BasicPduFormatter и перекрыть его метод format, то я не смогу работать с классом Pdu, пока не сделаю его импорт к себе в пакет. Т.е. я наследуюсь от BasicPduFormatter (т.е. он импортируется в мой пакет), но необходимое ему определение Pdu автоматически не импортируется. Что не правильно, я считаю. Да и исправить это, имхо, не так сложно.

По-минимуму, пожалуй, и все. Существуй такой язык для JVM здесь и сейчас, я бы сильно задумался, а стоит ли дальше оставаться на C++ ;)

PS. Насколько мне позволяют судить мои скудные познания в C#, многое из упомянутого мной здесь в C# уже реализовано. Вот только C# и .Net, по большому счету, нифига не кроссплатформенные.

6 комментариев:

Alex комментирует...

>> Вот только C# и .Net, по большому счету, нифига не кроссплатформенные.

По большому счёту - нифига. С JVM сравнить нельзя.
Но если брать Windows/Linux, и Mono - и под Mono (а не .NET) вести разработку, то всё не так плохо. В качестве UI (если он нужен) - GTK (может быть есть wxWidgets порт, не знаю).
Хотя к Mono можно предъявить достаточно много претезний: и количество багов, и сроки их исправления. И отсутствие некоторых библиотек, которые входят в .NET и которые логично было бы использовать.
Для информации, в общем.

eao197 комментирует...

>Хотя к Mono можно предъявить достаточно много претезний: и количество багов, и сроки их исправления. И отсутствие некоторых библиотек, которые входят в .NET и которые логично было бы использовать.

А еще я слышал нарекания в отношении производительности генерированного Mono-вским компилятором кода и скоростью работы Mono-вского GC.

xonix комментирует...

Это все НЕ НУЖНО. Возможно, за исключением using.

>>Итак, в первую очередь в Java мне нужны typedef-ы.

Неоправданное усложнение языка. Замедлит компиляцию.
Кстати, писать полные имена классов - не круто. Надо импортировать необходимые классы и писать:

private Map parseStream(ReadableByteChannel from)

>>Во-вторых, в Java очень хотелось бы иметь свободные функции

Зачем? 1.Это усложнит стройную ООП-модель, добавляя лишние сущности 2. сделает код Java не таким однородным, давая разработчикам больше свободы самовыражения. А это плохо (вспомним перл).

>>В-третьих, в Java очень не хватает перегрузки операторов.

Это может быть удобным при разработке в блокноте (меньше писать) а при разработке в нормальной IDE - только мешает. В самом деле, сейчас пишешь имя объекта, ставишь точку и у тебя выпадает полный набор методов. По-Вашему же - некоторые "методы" окажутся операторами. Плохо. И опять же - лишний синтаксический шум.

>>В-четвертых, очень бы хотелось видеть в Java автоматический вывод типов для переменных.

Самое вредное хотение. Ибо исходник пишется прежде всего для повторного чтения. А когда я вижу код

auto pdus = parseStream(from);

я могу только догадываться о сигнатуре метода parseStream.
Во-вторых - это опять таки навредит хорошей IDE, ибо в ней написав
SomeCoolIface a = new{тут нажимаем некий шорткат}
я получу

SomeCoolIface a = new SomeCoolClass();

или же

SomeCoolIface a = {опять некий шорткат, получаем}getSomeCoolObj()

а в вашем случае мне это придется писать ручками.

>>В-пятых, в Java не хватает lambda-функций.

Вроде, планируются в новой версии.

>>В-восьмых, в Java хотелось бы иметь возможность помещать части одного класса в несколько исходных файлов.

Лишнее. Используйте наследование (от автосгенеренного класса).

>>Еще одна штука меня раздражала и которую, имхо, следует устранить.

В хорошей IDE такой проблемы просто не возникает. Т.к. все импортируется автоматически. У меня все импорты даже свернуты и я практически их даже никогда не вижу. =)

Одним словом, имхо, нет смысла превращать яву в C++ или D ) Ява это ява. Она потому и популярна, что многие её "недочеты" на самом деле - её сильные стороны.

eao197 комментирует...

2xonix:

>>typedef-ы.

>Неоправданное усложнение языка. Замедлит компиляцию.


Заблуждение. Чистый C-шный код с typedef компилируется на порядки быстрее С++ного кода с шаблонами.

>Кстати, писать полные имена классов - не круто.

Не круто, но от совпадения имен никто не защищен.

>>Во-вторых, в Java очень хотелось бы иметь свободные функции

>Зачем? 1.Это усложнит стройную ООП-модель, добавляя лишние сущности


Стройная ООП-модель хороша только в теории. На практике ООП всего лишь один из вариантов.

>2. сделает код Java не таким однородным, давая разработчикам больше свободы самовыражения.

В этом и смысл. Я знаю нескольких разработчков, которым не нравится Java из-за ограничения самовыражения.

>А это плохо (вспомним перл).

А еще вспомним Python и Ruby, где с этим проблем нет.

>>В-третьих, в Java очень не хватает перегрузки операторов.

>Это может быть удобным при разработке в блокноте (меньше писать) а при разработке в нормальной IDE - только мешает. В самом деле, сейчас пишешь имя объекта, ставишь точку и у тебя выпадает полный набор методов. По-Вашему же - некоторые "методы" окажутся операторами. Плохо. И опять же - лишний синтаксический шум.


Не поверю в то, что IDE для статически-типизированного языка будет не в состоянии сделать такой же продвинутый autocomplete для операторов точно так же, как он это делает для точки.

>>В-четвертых, очень бы хотелось видеть в Java автоматический вывод типов для переменных.

>Самое вредное хотение. Ибо исходник пишется прежде всего для повторного чтения. А когда я вижу код

auto pdus = parseStream(from);

я могу только догадываться о сигнатуре метода parseStream.


Ну вы уж определитесь, пишите ли вы код в блокноте или в IDE. Поскольку в IDE подсветка типа для переменной будет работать просто на "Ура!"

>Во-вторых - это опять таки навредит хорошей IDE, ибо в ней написав
SomeCoolIface a = new{тут нажимаем некий шорткат}
я получу


А имя SomeCoolIface откуда возьмется? Не святой же дух его напишет, а вы. Вот вы и будете писать auto a = new Some{тут вы получите некий шорткат по возможным конструкторам}.

Так что проблема высосана из пальца.

А вот объем кода auto сокращат изрядно.

>>В-восьмых, в Java хотелось бы иметь возможность помещать части одного класса в несколько исходных файлов.

>Лишнее. Используйте наследование (от автосгенеренного класса).


Как по мне, так это выполнение через задницу того, что можно сделать нормально. Тем более в языке с только одиночным наследованием.

>Ява это ява. Она потому и популярна, что многие её "недочеты" на самом деле - её сильные стороны.

Я описал то, чего мне в Яве не хватает. И, практика показала, что с тему, кому в Яве всего достаточно, мне не по пути.

SiGMan комментирует...

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

eao197 комментирует...

2SiGMan: вот и я под такими впечатлениями все это и написал.