четверг, 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, по большому счету, нифига не кроссплатформенные.

Отправить комментарий