31 October 2012

Про Android API

Благими намерениями что-то оптимизировать устлана дорога в Ад для программистов

Забудьте все, чему вас учили (c) армейское 

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



Жаба давит

Язык программирования Java. Наверное, какие-то теплые чувства эта штуковина еще могла вызывать в далеких 90-х. Сегодня одна радует только толстожопых enterprise программистов, которым платят за их работу на этом бейсике XXI века неприлично большие деньги. Люди же порядочные, а также те, у кого кругозор чуть пошире, понимают какое это безнадежно устаревшее и окаменевшее гавно мамонта, на котором даже очень простую идею меньше чем в три экрана и не выразишь. 

Сразу скажу, я не яростный противник виртуальных машин, managed кода и сборки мусора. Я говорю лишь о том, что с момента появления Java мы видели и стремительно развивающийся .NET, который очень быстро перегнал своего идейного вдохновителя, и множество языков под JVM, типа той же Scala. И в конечном счете, вся эта история, по-моему, доказала нехитрый тезис о том, что совершенно невозможно сделать удобный в использовании язык программирования со статической типизацией в рамках полной аскетичности и тотального минимализма. Чем больше фич -- тем лучше. Прости, дедушка Вирт. 



"Оптимизаторы"

С Java разобрались, едем дальше.

Целый ряд проблем связан с попытками типа оптимизировать некоторые вещи. 

Вообще большинство вещей в Android реализованы на C++ из вполне понятных соображений производительности. При этом API пользователя к этой функциональности сделан через Java классы, т.е. это некий промежуточный слой перехода из объектно-ориентированной среды и виртуальной машины в нативный код. Сам интерфейс взаимодействия виртуальной машины и нативного кода довольно ограниченный и, откровенно говоря, неудобный, ибо предоставление ABI для C++ это та еще история, и чтобы ее успешно разрешить, нужно серьезно над этой задачей работать (см. что сделала Microsoft в WinRT). В итоге получается, что со стороны пользователя вроде как есть современный интерфейс, основанный на языке высокого уровня и классах, но ниже, под капотом, вся эта красота вынуждена использовать крайне невыразительные средства для взаимодействия с нативным кодом, который, собственно говоря, всю функциональность и реализует. Возникает, так сказать, когнитивный диссонанс. Так вот, одна из проблем Android заключается в том, что множество ограничений взаимодействия VM и native серьезно сказались на дизайне высокоуровнего API, который, в итоге, предоставили бедному пользователю.

Возможно, что тут сказалась узость мышления людей, которые были ответственны за дизайн системы. Не исключено, что многие вещи делались из благих соображений оптимизации и слой абстракции специально делали максимально тонким... В любом случае, это привело к тому, что API на уровне пользователя выглядит крайне хреново, и напоминает не API, разработанный в XXI веке, а какой-то, простигосподи, примитивный Win32 API хрен знает какого года выпуска. Повсюду слабая типизация, куча волшебных флагов, для которых даже не используется enum -- все это производит крайне удручающее впечатление. 

Приведу пример. 
Класс Intent -- если говорить по сути, то эта такие сообщения, которыми могут обмениваться между собой процессы. Проблема заключается в том, что на самом деле "интенты" делятся на два вида: 1) запрос на выполнение действия 2) уведомление подписчиков о том, что в системе произошло некое событие. Очевидно, что на низком уровне "интенты" эта такая порция информации, которую можно передать из процесса в процесс, и при такой передаче сам характер информации мало кого парит, однако на пользовательском уровне эти два совершенно разных типа однозначно надо было бы разделить, ибо выполнить действие и уведомить о событии это совершенно разные вещи. 

В итоге, имеем то, что имеем -- читаем документации по классу и постоянно натыкаемся на оговорки типа "A string naming the action to be performed — or, in the case of broadcast intents, the action that took place and is being reported", т.е. кругом вот эти "or in case of", которые, очевидно, указывают на хреновый дизайн. Поле класса, которые называется "Action", должно содержать действие, и как-то глупо писать много-много слов, объясняющих, что иногда туда надо записывать событие. Очевидно, нужен тип BroadcastIntent и поле "Action" в нем должно называться "Event". Тогда, глядя на вещи типа ACTION_SCREEN_ON, ты не будешь задаваться вопросом -- а это вообще команда включить экран или это уведомление о том, что экран включился. 

Скажу больше -- товарищам рукоделам надо было пойти еще дальше, и спрятать эти ужасные детали класса Intent для самых ходовых случаев, ибо

startCall("1234")

читается сильно лучше, чем

startActivity( new Intent(Intent.ACTION_CALL, "tel:1234") ).

Тоже самое и с броадкастами -- их надо не валить в один несчастный метод с дурацким switch подобным кодом, а куда лучше подписываться на них точечно, предоставляя анонимный класс с обработчиком, который на вход, опять же, будет получать не абстрактный Intent, а типизированные вещи а-ля класс BatteryLowEvent или класс ScreenOnEvent с очевидным образом названными полями.

С Intent разобрались, посмотрим еще вот на какую штуку -- на использование перечислений (enum) в API. Как я уже говорил, использовать всякие флажки и режимы в Android API очень любят, а вот делать это с помощью enum -- нет.
Почему? Разработчики утверждают, что это чертовски накладно. Мол при использовании и лишний код генерируется, и лишняя reflection информация... Поэтому когда вы читаете документацию по Android или пишете код, вас повсюду преследует "int flags" или "int mode" о которых вы ровным счете ни хрена сказать не можете.

Что же касается попытки сэкономить на отказе от enum и типа так оптимизироваться...

Ну, во-первых, в народе это называют "экономить на спичках". Потому что сначала бы берете виртуальную машину для которой долго не делаете JIT, используете сборку мусора и предлагает далеко не самый удобный способ взаимодействия с нативным кодом. А потом пытаетесь что-то типа выиграть, отказавшись от enum в случаях, типа когда нужно указать как именно пользователю нужно показать диалог. Ну не мудаки ли вы, ребята?

А во-вторых, надо знать историю и учиться на ошибках других. Посмотреть, например, почему сдох Palm со своей убогой, зато очень "быстрой", однозадачной операционной системой, когда на рынок мобильных решений пришла Microsoft с Windows CE, которая унаследовала все основные черты своей старшей сестры. Вспомнить, наконец, что закон Мура пока еще, слава богу, работает, и даже ваши первые поделки на Android уже работали на железе, которое было в разы мощнее железа, на котором запускалась Windows 95. Вчера вы попытались сэкономить жалкие 300 байт, отказавшись от enum, а после этого много-много лет тысячи программистов будут использовать ваш API и думать о том, какие же вы близорукие мудаки.


// Кстати, справедливости ради, Microsoft, которая обычно довольно таки дальновидна, тоже шагала в мобильной области по всем этим граблям. Например, в мобильной версии .NET для enum не работал reflection. Экономия-с.



Индусы под руководством сержантов

С "оптимизациями" разобрались, теперь о проблемах иного рода.


Описание UI в XML. Я поставил IntelliJ IDEA, поэтому визуальный редактор я не щупал, но много раз слышал о том, что им мало кто реально пользуется... Проблема здесь даже не в том, что нужно учить отдельный синтаксис (с совершенно идиотскими приколами, типа setOnClickListener в коде, но onClick в XML), и писать руками много-много ненужных букв. Проблема в том, что все рассказы есть серии "такой подход это шикарный способ изоляции представления от вашего кода" есть самое настоящее вранье. Почему? Да потому, что эта изоляция вредна и надумана, и практически все описанные в XML элементы нужды для работы кода. Мы меняем их свойства, цепляем обработчики, получаем значения полей. При этом никакого вменяемого способа добраться до этих элементов у нас нет, а делать это таким вот образом

Button button = (Button) findViewById(R.id.button_id);

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

При этом перед глазами есть же масса примеров обхода этой проблемы! Начиная с доисторической Delphi. Или тот же .NET, в котором для WinForms все действия пользователя в UI редакторе генерировали абсолютно легитимный код, последовательно создающий объекты и выставляющий их свойства.
Но лучше всего этот вопрос закрыли в Qt. С файлом описанием UI можно поступать двояко. Можно как в Android загрузить его в runtime, а потом выуживать через Find() нужные тебе компоненты. А можно скормить специальному компилятору, и получить готовый класс, в котором будут полями лежать все созданные виджеты. В большинстве случаев люди пользуются вторым сценарием, ибо это более удобно и надежно.


Работа с Activities в рамках одного приложения. Простите, граждане, но это ад и пиздей.

Первое -- нет никакого вменяемого способа передать в конструктор формы (можно я так буду называть эти, блин, активити?) параметры. Более того, у вас вообще отобрали возможность вызывать конструктор формы (описанной в вашем же приложении!) напрямую. Только через вышеупомянутый механизм intent'ов со всем вытекающими последствиями, вроде криптографии для передачи каких бы то ни было входных параметров.
(Кстати, делегирование управления временем жизни форм от приложения к операционной системе -- отдельная большая беда, но я не буду ее сейчас трогать.)

Второе -- нет модального способа показа форм! Вы не можете, например, вызвать диалог выбора контакта и тут же линейно написать код, который что-то с этим контактом сделает. Вы вынуждены разрывать цепочки исполнения и писать порцию ужасного кода в onActivityResult(), который будет похож на гавнокод, который обычно пишется в методе onReceive() для BroadcastReceiver.
Почему не сделали модальный режим? Потому что кто-то (из благих побуждений) решил детектировать ситуацию блокировки выполнения UI thread пользовательскими обработчиками событий как аварийную, но при этом ему не хватило мозгов совместить и анализ этой блокировки, и модальные формы (это технически решаемая задача).


Еще из всяких мелочей, которые попались на глаза.

Нет простого и очевидного виджета ListView. Тот ListView, который предлагается к использованию, это ужасный монстр, который отлично подходит для сложных случаев, когда нужно реализовать virtual view или асинхронно добавлять новые элементы в фоне. Есть только одна проблема -- в подавляющем большинстве случаев такие навороты не нужны, так на хрена выносить людям мозг всеми этими адаптерами и прочей хренью?

Или вот локализация. Кто-то правда находит это удобным -- на каждый чих (добавление новой строки) лазить в отдельный файл (здравствуй, любимый XML), описывать там ресурс и придумывать id для него? Ведь есть же масса примеров сильно более удобного решения проблемы -- смотри тот же Qt.

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


Итого

Кода под Android я пока особо много не написал, но я нескромно исхожу из своего богатого опыта программирования, и этот опыт однозначно указывает мне на то, что Android проектировали те еще безрукие индусы без всякого царя в голове. Умудриться в XXI веке придумать объектно-ориентированный API на Java, который навевает не самые добрые ассоциации с Win32 API, который был сишный и зачат еще в 80-х -- ох, это надо было сильно постараться! 

Мое, опять же, нескромное мнение, такое: API в XXI веке должен быть удобным, очевидным, надежным. API Android не удовлетворяет ни одному из этих требований, и, наверное, единственное, чем могут оправдываться его создатели, что он типа быстрый. Ну, феноменальная скорость работы Android уже давно стала притчей во языцех; а когда я сегодня смотрю на свой Galaxy S3 и думаю о том, что пару лет назад кто-то додумался сэкономить 200 килобайт флеш память для того, чтобы программисты всюду использовали int, вместо enum, мне становиться понятно, что человеческой глупости просто нет предела.

Как-то так.
Наверное, стоило бы для справедливости выебать и SDK для iOS, но тут есть ряд проблем. Во-первых, пидарасы из Apple сделали так, что работает оно только под их кашерную OS X, а созданные приложения просто так на свой личный iPad или айфон не поставишь. А во-вторых, мое отвращение к Objective-C столь велико, что я не вижу смысла накручивать дальше степень своей ненависти.

В любом случае -- любите друг друга!

No comments:

Post a Comment