Это еще не мануал по языковым изменениям и пр., как прошлогодний с Тибуроном. Это просто мои первые впечатления после того, как я немножко поигрался с новой версией RAD Studio.Предисловие, или отвлечённые размышления про Embarcadero и Microsoft
У ребят из
CodeGear Embarcadero, как я понял, есть одна занятная привычка: они не заморачиваются.
Годовой релиз, по их мнению — это ровно два КРУПНЫХ нововведения в языке и компиляторе, пяток мелких, повысить версии поддерживаемых БД/протоколов (возможно, добавить пару новых — если настроение с утра будет подходящим), подогнать VCL под свежие возможности Винды и прикола ради выполнить десяток-другой запросов из QC по поводу IDE. А вместе, в общем и целом, набирается достаточная база для того, чтобы на протяжении одного-двух месяцев наполнять блоги delphifeeds.com демонстративными постами и разъезжать по миру с конференциями и демонстрациями. Ах да, еще есть CodeRage, на котором обязательно надо рассказать разработчикам, как теперь они могут быстро и просто по-новому делать то, что они делали вот уже много лет как собственными усилиями.
На самом деле я сейчас произнёс не более чем длинную скептическую тираду =) Потому что всё это перечисление не имеет никакого смысла, пока я не уточню, что же ИМЕННО выбрали в качестве "крупных нововведений" в этом году коджировцы. И вот тогда уже можно о чём-то говорить. Самое интересное вот в чём: релиз RAD Studio выходит раз в год, релиз Visual Studio вместе с новым .NET Framework — раз года в три. Каждый релиз последней, соответственно, приносит нам примерно в 3 раза больше фич, чем типичный релиз первой. Иногда и того меньше (фичи C# 4, к примеру, по сравнению с C# 3 тянут как раз на годик работы коджировцев). Но вот в чём загвоздка: эти фичи
доработаны. То есть ты смотришь на них, читаешь один-два поста в блогах MSDN и сразу думаешь: "Ага, отлично, вот это я могу приткнуть сюда, а вон то так и просится вон в тот мой класс месячной давности". И все рады и довольны. Коджировцы же, в свою очередь, работают несколько иначе. После того, как Хейлсберг, гениальный представитель стыка тысячелетий, ушёл в MS, последние версии Delphi исповедуют принцип "А теперь возьмём вон ту фичу C#, сделанного нашим же идеологом с учётом прошлых граблей, и реализуем её в нативном Делфи, как он и хотел бы". Но поскольку они не Хейлсберг, то фича реализовывается... ммм... не до конца. Их можно понять, у них меньше людей, времени ровно год, а не три, бюджет поджимает... но всё-таки каждый август я, читая delphifeeds, восклицаю "Wow!", а через некоторое время, немножко покодив на свежем релизе, чешу затылок и думаю "Твою мать..."
Хотите конкретики? OK, поясняю, что я имею в виду. Возьмём в качестве примера 2009 год и дженерики. Ясное дело, дженерики — это был гигантский прорыв в Делфи, их многие годы ждали, без них было дико неудобно программировать кучу специфических вещей, с этим никто и не спорит. Но вот что интересно... дженерики-то ввели, а обеспечить их должную поддержку на требуемом уровне так и не получилось (не успели?). К примеру, в IDE простейший рефакторинг "Rename" при попытке подсунуть ему обобщённый тип начинал заикаться и материть разработчика непечатными словами. IDE считала ошибками безобиднейшие конструкции DeCAL2009, из-за чего я практически не мог пользоваться рефакторингом во время кодинга на Делфи: ну не жует она код, в котором есть "красные подчеркивания"! Хотя все конструкции корректны, и компилятор спокойно обрабатывал весь код без единого варнинга даже. Дальше, если у нас уже есть модуль
Generics.Collections с обобщёнными классами и интерфейсами, то почему бы не переделать на него VCL? Почему-то до сих пор банальнейший
TListView своё свойство
Items представляет как специфический класс
TListItems, а не просто а-ля
TList<TListIem>, хотя функционал у них идентичен. Обратная совместимость — это замечательно, но в итоге библиотека хромает на обе ноги. В конце концов, чтобы решить проблемы обратной совместимости, можно вспомнить, как в незапамятные времена добрые люди придумали интерфейсы.
К чему я веду, собственно... Любая новая возможность в C# появляется
интегрированной: типичные сценарии её использования продуманы и встроены в FCL, IDE ведёт себя так, словно фича была здесь всегда, и дополнительно предлагается кучка новых классов/структур/интерфейсов/методов старых классов, которые удобно используют эти новые возможности. Delphi же исторически предназначен для сторонних разработчиков, традиция писать под него сотни новых компонентов и библиотек не переведётся, пока существует на свете сам Delphi. Поэтому любая новая возможность Делфи вводится в компилятор, обеспечивается простейшей базовой поддержкой, документируется с парой примеров — и гуляйте, ребята, пишите монструозные библиотеки с её юзаньем, улучшайте программистам жизнь, мы своё дело сделали.
И это удручает.
Языковые возможности
Декларативное программирование, в отличие от императивного, не так плотно вошло в нашу жизнь. На самом деле миллионы человек ежедневно пользуются его плодами, бродя по HTML-страничкам (да-да, несмотря на то, что HTML — язык разметки документа, его также смело можно назвать и попросту декларативным ЯП). Но вот активное использование этой концепции в других областях, кроме Веб-разметки, скриптинга и пр., а также с более продвинутыми возможностями, встречалось очень редко. До тех пор, пока команда разработчиков .NET не предложила всем
атрибуты.
Простая ведь штука, на самом-то деле. Просто определённый вид пометки любой сущности с возможностью конкретизации этой пометки несколькими параметрами. Самое интересное в том, что сам атрибут по факту
ничего не делает: для того, чтобы воспользоваться его возможностями, нужно написать где-нибудь код, который в run-time берет информацию о требуемом объекте, проверяет наличие у него некоторого атрибута, и в случае его присутствия уже делает своё чёрное дело, чего, собственно, и хотел человек, навесивший на объект этот атрибут. Часто это не слишком-то удобно на самом деле, но уж как есть. С помощью атрибутов отлично реализуется парадигма АОП (аспектно-ориентированного программирования), что, собственно, и сделали ребята из RemObjects, родив для Delphi Prism замечательный фреймворк под названием Cirrus. А когда у тебя есть АОП-фреймворк — у тебя есть краткий и не морозящий мозги способ реализовать логирование, многопоточность, запись сущностей в БД, контрактное программирование и кучу прочих прелестей, которые иначе вылились бы в гору лишних мусолящих взгляд строчек в
твоем исходнике. Шикарно, на самом-то деле. Главное, чтобы такой фреймворк кто-нибудь написал — или продемонстрировал общие принципы его создания.
Вот тут и кроется проблема, затронутая мною в предисловии. Да, в Делфи теперь есть шарповские атрибуты, они аналогичным образом наследуются от
TCustomAttribute, имеют всё тот же интуитивный синтаксис и подчиняются тем же законам использования. Вот только в среде они обозначены лишь четырьмя страничками справки. Я не нашёл в VCL и RTL
ни одного сделанного руками коджировцев атрибута. Ни тебе
Serializable, ни
Log, ни
Obsolete. Возможно, атрибуты были использованы, чтобы дать нам свежие возможности RTTI, не знаю; в справочной системе об этом нет ни слова, в модуле
Rtti мною не было найдено ни одного атрибута. В общем и целом, пока какая-то добрая душа не реализует полный фреймворк, аналогичный Cirrus`у, использовать атрибуты бесполезно. Потому что любой мой собственный исходник будет в итоге велосипедом.
Хочется большего и прямо сейчас — идём на delphifeeds, читаем один
практический пример. Нет, лучше
еще один.
Да, кстати. Атрибуты не полностью скопированы с C#. В справочной системе я, к примеру, не нашёл способа, как применить атрибут к
возвращаемому значению функции. То, для чего в Шарпе предназначены атрибутные префиксы, а-ля
[return: MinimumInteger(10)]. Про подобные префиксы устранения неоднозначности в Делфи (а их же тьма-тьмущая:
assembly, return, type, method, param...) в справке не сказано ни слова.
Теперь про RTTI. Здесь у меня нет ни единой претензии. Подобной универсальной системы ждали все, и ждали довольного долго, и теперь работа с RTTI в Делфи ничем не уступает дотнетовскому или джавовскому отражению. Просто скопирую сюда код из статьи, совмещающий демонстрацию использования RTTI и атрибутов:
var
LContext: TRttiContext;
LType: TRttiType;
LAttr: TCustomAttribute;
begin
{ Create a new Rtti context }
LContext := TRttiContext.Create
{ Extract type information for TSomeType type }
LType := LContext.GetType(TypeInfo(TSomeType));
{ Search for the custom attribute and do some custom processing }
for LAttr in LType.GetAttributes() do
if LAttr is TSpecialAttribute then
WriteLn(TSpecialAttribute(LAttr).FValue);
{ Destroy the context }
LContext.Free;
end;
Вот модулем
Rtti я теперь с удовольствием буду пользоваться на каждом шагу. И всем рекомендую.
Чтобы кастомизировать процесс создания метаинформации для RTTI, можно использовать новые директивы
{$RTTI} и
{$WEAKLINKRTTI}. Они дают всю необходимую гибкость.
Дальше идёт директива
delayed. Степень полезности — средняя, но применение найти можно. Короче говоря, это отложенный вызов функции из библиотеки. Если вы собираетесь экспортировать функцию из дллки, в которой по какой-то причине этой функции может и не быть (проблемы версий), пишете
delayed, а в рантайме уже проверяете, какую реализацию использовать: библиотечную, точно зная, что при данных условиях вызов функции не навернется, или собственную. Таким образом был реализован механизм естественного ввода (жестов и тачей) в VCL Weaver`а: на Windows 7 используем системные функции, а на предыдущих ОС — собственный движок. Исходник из delphifeeds:
function CloseDesktop; external user32 name 'CloseDesktop';
function CloseTouchInputHandle; external user32 name 'CloseTouchInputHandle' delayed;
function CloseWindow; external user32 name 'CloseWindow';
...
if OSVersion = WINDOWS_7 then
CloseTouchInputHandle
else
MyInternalSimplerClose;
Главная польза, наверное — отсутствие пяти строк дополнительного кода везде, где раньше с этой целью использовали динамическую загрузку DLL.
Последняя фича действительно очень и очень востребована — это кастинг интерфейсов обратно в объект, на который он ссылается. Если вы создали экземпляр некоторого объекта, реализующего некоторый интерфейс, а потом присвоили ссылку на этот объект переменной интерфейсного типа, вы можете воспользоваться старыми добрыми операторами
as и
is, чтобы привести эту переменную обратно к объектному типу. Да что там, даже старый паскалевский небезопасный кастинг (с использованием имени типа) здесь сработает. Все правила остаются в силе — если запрошенный класс не реализует данный интерфейс,
as выбросит исключение,
is вернёт
false, а небезопасный кастинг просто присвоит итоговой переменной
nil. Всё просто.
Работает такой кастинг только для дельфийских объектов. COM`у — нет =) Почему —
читайте здесьПродолжение следует...
Следующую часть рассказа, описывающую новенькие красоты VCL, RTL и IDE, я сегодня уже не смогу рассказать вам, любезные читатели. Оставайтесь на нашей волне денек-другой, и все будет, обещаю. Потому что впечатление от этих возможностей у меня сейчас на порядок выше, чем от тех же атрибутов.