06 May 2011

I Have An App For That! (Pt. IV)

(продолжение, предыдущая часть)


Язык до Киева доведет!


“Программное обеспечение может быть двигателем для постиндустриального мира, но его создание, увы, остается доиндустриальным ремеслом”

Начнем разбираться с языками программирования.

Что такое ЯП? ЯП это один из способов описания того, чего вы хотите получить от исполнительного устройства, в частном случае -- некоего процессора.

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

Почему в области ЯП мы имеет Вавилон -- зачем языков так много и почему все время придумывают новые? Если отвечать на этот вопрос в самом общем виде, то ответ будет простым -- языки имеют свои области специализации, на одном языке удобнее писать веб-сервисы, на другом -- математические расчеты. Это похоже на множество инструментов -- молоток служит для одних целей, микроскоп -- совсем для других, хотя, по слухам, им тоже можно забивать гвозди.
Если же попытаться ответить на вопрос более конкретно, в контексте обсуждаемой нами темы, то я бы сказал, что самое главное, что отличает разные языки, это степень их высокоуровневости. 

О чем идет речь? Объясню на простом примере. Давайте представим себе, что человечество каким-то чудом переживет апокалипсис 2012-го года -- перенесемся в XXIII век и вообразим, что у вас есть робот-домработнико (среднего рода, потому что это же робот). Задачи у этой штуки простые и строго утилитарные -- принеси-подай, найди пульт от телевизора, сбегай за пивом и все такое прочее. Если темпы прогресса в области искусственного интеллекта хоть как-то сравняются с темпам, с которыми правительство Штатов наращивает свой внешний государственный долг, то ваше желание выпить чашечку кофе вы озвучите роботу командой вроде “Старина, сделай мне чашечку кофе, а?”. Ну а если роботы будут тупые, как нынешние программы машинного перевода, то вы начнете длинную триаду вроде: “Внимание, начало команды!... Пройди три метра прямо, поверни налево, пройди пять метров прямо по коридору, поверни направо, в среднем ящике крайней тумбочки справа возьми турку, подойди к умывальнику, открой кран и набери в нее 100 мл воды. Закрой кран, поставь ее на правую ближнюю конфорку, зажги огонь (бла-бла-бла... еще много много скучных команд... и наконец) Поверни направо, пройди три метра и поставь чашку на стол. Исполняй!”... Уфф, по-моему, самому сварить себе кофе проще...
Так вот, в первом случае это был высокий уровень управления. Во втором -- низкий, что-то из области ассемблера.

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



Так вот, пожалуй, ключевое отличие между собой всех языков программирования, с помощью которых сегодня создаются мобильные приложения, это степень их высокоуровневости. Применительно к ЯП это означает, что программы на языке высокого уровня обычно более лаконичны, так как там используется более высокий уровень управления и нет ненужных деталей более низких уровней. А из этого следует, что на их написание уходит меньше времени. А еще это значит, что в них меньше ошибок, т.к. обычно принято считать, что количество ошибок пропорционально объему кода; причем высокоуровневые языки защищают программиста от массы глупых, механистических ошибок, решают рутинные вопросы, давая сосредоточиться на сути решаемой проблемы. В наш век индусов-программистов я часто говорю о том, что высокоуровневые языки обладают высокой дуракоустойчивостью (имея в ввиду минимальное число случаев undefined behavior). Если вернуться к нашему примеру с роботом и кофе, то это означает, что если вы забыли роботу дать команду “закрыть кран”, после того, как он набрал в турку воды, вы все равно НЕ затопите соседей -- робот сам проследит за этим.

Читающие эти строки, наверное, думают -- “О! Если все так здорово, зачем вообще что-то писать на низкоуровневых языках?”. Ответ на этот вопрос будет состоять из двух частей.

Во-первых, по причинам общей инертности индустрии. Смотрите: есть какой-то старый язык, для которого уже написаны десятки компиляторов, сотни библиотек, про него написано множество книг и есть десятки тысяч программистов, которые умеют на нем программировать. Я уже молчу о том, что уже существуют сотни тысяч строчек кода, написанного на этом языке, и многое из этого добра все еще актуально, т.е. его надо поддерживать, т.е. добавлять новые функции, исправлять старые ошибки. Все это удерживает от быстрого внедрения новых языков программирования, даже если они во всех смыслах лучше, чем языки старые. Отдельная история -- в некоторых случаях выбор языка носит почти религиозный характер. К примеру, многие вещи, относящиеся к Linux, часто пишутся на единственно истинном языке -- на С, потому что именно на С написано ядро этой операционной системы, и от этого языка в полном восторге главный бог этой религии, Линус Торвальдс. 


Линус определенно крут. Он мог бы за семь дней запрограммировать на си эту Вселенную

Причина вторая, почему старички так медленно сдают свои позиции -- низкоуровневые языки имеют и свои преимущества. Да, как правило код на них более многословен, его труднее писать, но в умелых руках этот инструмент позволяет получить значительно более производительный код. Объяснение этому простое -- языки высокого уровня предлагают вам одно решение, покрывающее огромное множество случаев. Тут приходится платить за универсальность и возможность использовать команды высокого уровня, ведь очевидно, что в некоторых частных случаях можно написать код, который работает значительно лучше и быстрее, т.к. этот код будет написан специально для этого конкретного случая. Не смотря на то, что мощность процессоров медленно, но верно, растет из года в год, в этом мире есть масса задач, где программисты по прежнему пытаются выжать из железа максимум, борясь буквально за каждый такт. А языки высокого уровня просто не позволяют работать на этом уровне.

Так-с, с причинами многоязычия разобрались, теперь хотелось бы освятить еще один важный вопрос. Как я уже говорил, программа, написанная на ЯП, обычно компилируется в команды процессора некой целевой архитектуры. Если мы говорим о ПК, то это x86. Или x86-64, сейчас это актуальная тема. Если же программа должна работать на чем-то мобильном, то это скорее всего ARM... Если же ваша программа должна работать на нескольких видах процессоров, то ее надо скомпилировать под каждый вид, и получить некое множество двоичных образов-файлов, каждый из которых способен исполняться процессором только одной архитектуры. Не очень удобно, согласитесь. Для решения этой проблемы была придумана концепция виртуальной машины (VM), которая стала очень активно использоваться с появлением Java платформы в середине 90-х. Эта придумка оказалась настолько удачной, что когда Microsoft начала создавать свое, конкурирующее с Java, решение под названием .NET, то концепция VM перекочевала туда, причем корпорация зла смогла сделать это все намного лучше, т.к. были учтены многие ошибки первопроходца в этой области, компании Sun.  

У многих людей, далеких от глубокого понимания темы, само словосочетание “виртуальная машина” сразу вызывает в голове мысли “о! это тормоза и это все очень медленно!”. На самом деле, это не совсем так. Дело в том, что программа, скомпилированная в код для виртуальной машины, обычно не исполняется в режиме интерпретации, т.е. когда шаг за шагов берутся последовательно инструкции виртуального процессора и исполняются на процессоре реальном. Это действительно относительно медленный способ, который, по причине неэффективности, сегодня используется редко (кстати, таким путем работали приложения в Android вплоть до версии 2.1 включительно). Вместо интерпретации используется технология JIT. Смысл этой штуки заключается в том, что перед исполнением какого-либо кода, написанного для виртуальной машины, он сразу весь транслируется в двоичный код целевой платформы (результат этой трансляции обычно сохраняется в каком-либо кэше, чтобы не заниматься этой относительно дорогой процедурой снова, при каждом запуске приложения), а потом исполняется фактически как обычный файл с командами реального процессора.

Уровень нынешних JIT технологий таков, что получаемый исполняемый код, в плане скорости, очень часто может тягаться с кодом, полученным классическим путем, т.е. компиляции из C/C++ исходника. И очень часто проигрыш в производительности кода на C# коду на C++ происходит не из-за того, что первый компилируется под виртуальную машину, а из-за того, что в C++ можно использовать всякие низкоуровневые штучки и хаки, которые запрещены в C# просто на уровне языка (то, о чем я говорил как о дуракоустойчивости).
Закрывая тему VM, добавлю, что перспективы оптимизации кода для виртуальных машин намного лучше, чем кода, полученного при классическом подходе. Все дело в том, что при исполнении виртуального кода есть возможность налету за ним наблюдать, находить узкие места, и прямо налету пытаться их оптимизировать, с целью получения более высокой производительности...

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

Но это все была вводная перед тем, чтобы перейти непосредственно к языкам, которые сегодня активно используются в мобильных разработках. Поехали!



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

Компилятор языка С, который сам по себе очень прост (если не брать в расчет оптимизацию кода), есть практически под все известные процессорные архитектуры. Скажу больше, для некоторого числа платформ -- си и ассемблер это два единственных поддерживаемых языка.
Синтаксис языка C в той или иной мере повлиял на синтаксис всех остальных языков, про которые я буду говорить ниже.
Язык си это своего рода стандарт де факто для большого числа вещей. Многие языки более высокого уровня имеют возможности стыковки с кодом написанным именно на си.
Практически все современные мобильные платформы позволяют разработчикам приложений для них писать код на С. Единственное исключение -- Windows Phone, но к обсуждению этого ограничения мы еще вернемся. 



Язык Objective-C. Это фактически язык С, к которому добавили поддержку объектно-ориентированного программирования плюс некоторые зачатки динамической типизации.

Язык получился несколько странным и довольно несбалансированным, поэтому распространение получил только для написания кода для устройств от Apple. Objective-C практически бессмысленно рассматривать в отрыве от богатых библиотек классов, которые создала для него Apple -- все это автоматически означает, что код, написанный на этом языке, практически никогда не работает на не яблочных платформах. И если разработчик думает о том, чтобы писать переносимый между разными платформами код, он никогда не выберет Objective-C.

Почему я назвал этот язык странным?

Ну, во-первых, не смотря на то, что он обладает си-подобным синтаксисом, сама объектно-ориентированная модель имеет абсолютно уникальную нотацию.

Смотрите сами -- код вызова метода foo у экземпляра a:

a.foo()  // C++
a.foo()  // Java
a.foo()  // C#

// ... и даже:
a.foo()  // Python
a.foo()  // Ruby

// ну вы поняли, и наконец!...
[a foo]  // Objective-C

Причем отмечу, что Python и Ruby, в отличии от Obj-C, не обладают си-подобным синтаксисом.

Вторая странность -- добавленная в язык динамическая типизация. В двух словах, эта когда ЯП дает возможность программисту писать в режиме “i don’t care” (перевел бы на русский, но это будет что-то непечатное). Мол пиши что хочешь и как хочешь, можешь писать очень быстро, левой задней, делать множество нелепых ошибок, но о том, что это за ошибки и где ты их наделал, ты узнаешь уже только тогда, когда твоя программа начнет исполняться. А если к процессу тестирования ты подошел спустя рукава, то обо всех твоих ошибках узнают уже счастливые пользователи твоей программы.
Эта парадигма очень активно используется в низкопроизводительных скриптовых языках, которые сегодня очень популярны в веб-разработке (PHP, Python, Ruby). Добавление этого свойства в Objective-C, который не очень далеко ушел от примитивного С, выглядит довольно сомнительным решением.

Третий момент -- управление памятью. Упрощенное (автоматическое) управление памятью это как раз тот ключевой момент, который отличает высокоуровневый язык от языка низкоуровневого. Все ниже описанные языки так или иначе решают вопрос с автоматизацией этого вопроса. С++ с помощью специального поведения классов (автоматический вызов деструктора, т.н. RAII парадигма). Java и C# с помощью сборщика мусора.
В Obj-C сборка мусора появилась очень поздно, спустя 20 (!) лет после его создания и до сих находится в положении бедного родственника. Потому что к тому моменту уже были написаны миллионы строчек кода без его поддержки. А главное, потому, что при программировании под iOS сборки мусора нет, а это значит, что любой мало-мальски серьезный разработчик не будет использовать это свойство языка, т.к. всегда есть вероятность, что написанный Obj-C код понадобиться компилировать под две платформы -- мобильную и десктопную.

Про Obj-C это, пожалуй все. Если кто-то считает, что я слишком строг к этому замечательному языку -- милости просим в эту запись, я всегда открыт для дискуссий по этому вопросу. 



Следующий язык -- C++. Как и Objective-C, это некоторое развитие С, в который точно так же добавили поддержку объектно-ориентированного программирования. Но кроме этого, было добавлено еще очень много различных свойств, которые, с одной стороны, не нарушили совместимость с оригинальным языком прародителем (т.е. С), а с другой, создали качественно новый, довольно высокоуровневый, язык.

Именно C++ был тем языком, с помощью которого шел процесс популяризации объектно-ориентированной парадигмы среди программистов. Однако в какой-то момент оказалось, что для многих областей этот язык слишком сложен и избыточен, и на многие ниши, изначально занятые “плюсами”, началась атака с разных сторон, в том числе и со стороны Java и того же .NET.

Но, не смотря на все это, C++ по прежнему более чем актуален, и держит за собой большое число областей применения. Уверен, что если вы пробежитесь глазами по ярлыкам приложений на вашем рабочем столе, то подавляющее большинство из них было написано именно на плюсах, а не на чем-то еще (статистика на моей стороне, в том смысле, я надеюсь вы используете Windows, а не Mac). За плюсами по-прежнему масса серьезных десктопных приложений (от браузера до видеоплеера), практически весь gamedev, разработка для встраиваемых систем, высоко нагруженные веб-сервисы и многое другое. 


Ядро практически любого современного 3D движка написано на C++

У C++ есть огромное число недостатков разной степени серьезности. Он очень сложен (а это значит, что очень сложен и C++ компилятор), он очень медленно компилируется, он почти полностью совместим с си (а это значит, что не уберегает пользователя от огромного числа примитивных и очень неприятных низкоуровневых ошибок). Плюсы дают программисту слишком много свободы, которой многие, к сожалению, не могут правильно распорядится. И, тем не менее, язык этот значительно более мощный, чем С, и после С это самый первый кандидат на написание высокопроизводительного кросс-платформенного кода.

Нет ничего удивительного в том, что вы можете писать на С++ практически под все мобильные ОС, причем как под старые, вроде Windows Mobile или Symbian, так и под новые -- bada, Android или iOS. Собственно, именно C++ чаще всего выступает тем самым общим знаменателем, который используют при написании игр, работающих и под iOS, и под Android.

В заключении отмечу, что сами по себе реализации C++ бывают очень разными. К примеру, в свое время, на этапе создания Symbian (а это было еще до принятия первого стандарта языка), очень умные дядьки решили по живому отрезать у “плюсов” его некоторые крайне полезные (а на их взгляд -- слишком расточительные) свойства, что самым плачевным образом сказалось на удобстве программирования под эту ОС. Самое удивительное, что не смотря на то, что история показала ошибочность этого решения, по этим же самым граблям решил прошагать еще и Samsung со своей bada (подробнее). 



Следующий язык -- Java. Тут у нас как раз тот самый случай, когда сам язык очень тяжело отделить от его библиотек, вернее даже сказать, от одной из его множества т.н. платформ.

В свое время Sun ввело понятие “Java платформы” -- это некий комплект из виртуальной машины, которая исполняет байт-код, полученный из кода на ЯП Java, плюс некий набор библиотек. Виртуальная машина и библиотеки, при этом, могут успешно работать на нескольких операционных системах, что позволяет говорить о некой кросс-платформености такого решения.

Если кто не помнит, изначально Java задумывалась для того, чтобы писать на ней веб-приложения, работающие на стороне клиента, в браузере. Именно поэтому была придумана виртуальная машина, которая обеспечивала безопасную концепцию исполнения кода sandbox, плюс решала вопрос исполнения этого кода силами процессоров разных архитектур. Время показало, что в этом направлении технология Java почти полностью провалилась, уступив место Adobe Flash (кстати, как вы думаете, используется ли в этом решении технология VM?). Ну а Java, совершенно неожиданно для себя, оказалась очень тепло принята в Enterprise решениях (т.н. Java EE платформа).

Еще одна платформа, напрямую относящаяся к теме нашей беседы -- Java ME, или, хорошо всем известная J2ME, с помощью который можно создавать приложения для простых телефонов. Малоактуальная сегодня тема.

Ну а вспомнил я в этом разделе про Java, конечно же, из-за Android. Так вот, Java в Android не имеет никакого отношения к Sun (или Oracle) и упомянутым выше Java платформам. Решив использовать в своей новой мобильной ОС прогрессивную концепцию виртуальной машины, создатели Android встали перед вопросом: “а на чем под нее программировать?”. Нет ничего удивительного, что выбор пал на язык Java, т.к. в силу популярности Java платформ это один из самых популярных и востребованных ЯП на рынке, а значит есть множество людей, которым не надо учить новый язык для создания Android приложений.

Собственно, в Google создали с нуля свою виртуальную машину, свой набор библиотек (который частично, только на уровне самых примитивных вещей, пересекается с набором классов в Java платформе от Sun) и свой компилятор из языка Java в байткод свеженаписанной виртуальной машины Dalvik


Вот он какой -- Dalvik!

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



И, наконец, последний участник нашего обзора языков -- C#, самый юный из всех наших участников.
Как и про Java, про C# невозможно говорить, вне его платформы -- .NET. В свое время, в конце 90-х, в Microsoft родилась идея этого решения, вдохновленного, в первую очередь, успехами Java. Как мне кажется, Microsoft не собиралась лезть с .NET в Enterprise (хотя в этой области у них традиционно очень сильные позиции), главная проблема которая на тот момент решалась с помощью создания .NET -- вопрос создания обычных десктопных приложений для Windows. В те годы для этих целей корпорация предлагала два решения. Первое, основанное на Visual Basic, с одной стороны, было очень простым и удобным для неискушенного разработчика, но с другой -- слишком примитивным и плохо масштабирующимся для мало-мальски серьезных задач. Второе решение, на основе C++ и библиотеки MFC, было наоборот, слишком громоздким, сложным в использовании и избыточным для большинства типовых проектов. Поэтому .NET задумывался как золотая середина между этими двумя подходами, он должен был полностью вытеснить Visual Basic, но оставить разработку на C++ для некоторых специфических задач, очень требовательных к производительности или использующих большую базу кросс-платформенного кода.

.NET направление оказалось сверх успешным. Помимо разработки классических дестопных приложений, эта технология пришла в веб-сервисы -- ASP.NET, заработала на стороне клиента в браузере -- Silverlight, была предложена разработчикам игр для Windows и X-Box 360 -- XNA, шагнула на мобильные платформы -- была и в старой Windows Mobile, и является единственным средством разработки в новой, Windows Phone 7.

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

Некоторые считают темпы обрастания языка новыми возможностями его недостатком, но мне кажется, такие обвинения есть следствие банальной зависти, и озвучиваются людьми, программирующие на Java. На мой взгляд, самый главный недостаток C# это его привязанность к закрытым платформам, которые целиком находится во власти Microsoft. Код, написанный под .NET, очень редко работает вне Windows экосистемы. В природе существует проект Mono -- открытая реализация .NET платформы, но очень многие специалисты довольно скептически относятся к использованию этого дела в мало-мальски серьезных проектах.

На этом краткий обзор языков программирования будем считать законченным, переходим непосредственно к платформам.

(окончание тут)

No comments:

Post a Comment