Продолжение размышлизмов, навеянных недавним опытом программирования на 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, по большому счету, нифига не кроссплатформенные.
>> Вот только C# и .Net, по большому счету, нифига не кроссплатформенные.
ОтветитьУдалитьПо большому счёту - нифига. С JVM сравнить нельзя.
Но если брать Windows/Linux, и Mono - и под Mono (а не .NET) вести разработку, то всё не так плохо. В качестве UI (если он нужен) - GTK (может быть есть wxWidgets порт, не знаю).
Хотя к Mono можно предъявить достаточно много претезний: и количество багов, и сроки их исправления. И отсутствие некоторых библиотек, которые входят в .NET и которые логично было бы использовать.
Для информации, в общем.
>Хотя к Mono можно предъявить достаточно много претезний: и количество багов, и сроки их исправления. И отсутствие некоторых библиотек, которые входят в .NET и которые логично было бы использовать.
ОтветитьУдалитьА еще я слышал нарекания в отношении производительности генерированного Mono-вским компилятором кода и скоростью работы Mono-вского GC.
Это все НЕ НУЖНО. Возможно, за исключением 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 ) Ява это ява. Она потому и популярна, что многие её "недочеты" на самом деле - её сильные стороны.
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 хотелось бы иметь возможность помещать части одного класса в несколько исходных файлов.
>Лишнее. Используйте наследование (от автосгенеренного класса).
Как по мне, так это выполнение через задницу того, что можно сделать нормально. Тем более в языке с только одиночным наследованием.
>Ява это ява. Она потому и популярна, что многие её "недочеты" на самом деле - её сильные стороны.
Я описал то, чего мне в Яве не хватает. И, практика показала, что с тему, кому в Яве всего достаточно, мне не по пути.
В принципе, согласен - проблема явы в черезмерной многословности, что особенно ломает после всяких питонов и руби. Все этих конструкции с классами на каждый чих, без тайпдефов и других полезных мелочей воспитывают в программисте машинстку и ухудшают читаемость кода. Умные IDE с автогенерацией кода только усугубляют ситуацию.
ОтветитьУдалить2SiGMan: вот и я под такими впечатлениями все это и написал.
ОтветитьУдалить