2015-12-27

О времени

Скоро пройдет последняя, 31536001-я, секунда 2015 года. Такая некруглая цифра, на одну секунду больше обычного, из-за того, что 30 июня 2015 года была добавлена високосная секунда. Дело в том, что вращение Земли замедляется, и, чтобы как-то компенсировать это расхождение со временем, которое мы уже давно меряем атомными часами, и вводят дополнительную секунду.
А в 2016 году будет 31622400 секунд, на один день больше, потому что год будет високосный. Это уже из-за попытки согласовать период обращения Земли вокруг своей оси и вокруг Солнца, которые соотносятся как 365.256366004 к одному. Вспоминайте это число всякий раз, когда вас будут убеждать в креационизме.
Но это всё касается григорианского календаря. А если помните, до Революции в России был «старый стиль», благодаря которому мы теперь удачно отмечаем Рождество 7 января. Это — юлианский календарь. Ну а в исламском календаре нынче 1437 год со дня переселения пророка Мухаммеда из Мекки в Медину.
Leap Second
Не так-то просто всё это учесть в наших компьютерных системах. Так что сосредоточимся пока только на григорианском календаре, как наиболее распространённом международном стандарте.
Как хранить дату и время? Казалось бы, очевидно, — хранить числа: отдельно год, месяц, день, и еще часы, минуты, секунды, ну и миллисекунды, если надо. Так и делают. Правда, если хотят сэкономить, и, например, хранят только последние две цифры года, то получается проблема 2000 года. Зато можно, если не нужно время, а только конкретный день, хранить только год-месяц-день. Или наоборот, если интересно время безотносительно конкретного дня — хранить только часы-минуты-секунды.
Таким образом хранят дату-время чаще всего базы данных. В MySQL таковыми являются типы DATE, TIME, DATETIME, а также YEAR. Еще они могут хранить доли секунды с нужной точностью. В PostgreSQL есть DATE и TIME, но нет DATETIME. В Oracle тоже есть DATE, который по сути является DATETIME. В языках программирования тоже используется подобное представление. В Си есть стандартная структура tm, определённая в time.h. В Python имеются date, time и datetime. В C# тоже есть «структура» DateTime. А вот в Java такой структуры нет. Но об этом ниже.
Есть ещё обожаемый мною стандарт ISO 8601 о строковом представлении даты и времени. Вместо того, чтобы мучительно вспоминать, что американцы пишут сначала месяц, а потом день («12/31/2015»), мы говорим, что сначала надо писать год, потом месяц, потом день: «2015-12-31T23:59:59». Замечательной особенностью таких строк является то, что лексикографический порядок их сортировки совпадает с реальным порядком даты-времени. ISO 8601 поддерживается всеми современными СУБД и библиотеками работы с датой-временем для преобразование строки в дату и наоборот.
Gregorian Calendar
Всё хорошо, пока речь идёт о времени в одном месте. Но и тут могут быть тонкости. При переходе с летнего времени на зимнее, когда часы переводятся на час назад, показания часов с часу до двух ночи повторяются. Если мы храним лишь часы и минуты как два числа, возникает неоднозначность. Ну а летнее время еще далеко не везде отменили.
А ещё Земля у нас круглая. Поэтому, когда где-то день, то где-то в это же время — ночь. Где-то уже наступило завтра, а где-то ещё сегодня. Поэтому одних значений даты-времени недостаточно. Необходимо уточнить, в каком именно месте Земли это время. Это и есть часовые пояса или таймзоны.
Взяли Землю, и поделили на 24 дольки. За нуль взяли меридиан, проходящий через город Гринвич. И получилось 24 часовых пояса: от -12 до +12 часов от Гринвича. И стали записывать часовой пояс как смещение от времени по Гринвичу: в Москве (сейчас) — GMT+3, в Омске — GMT+6. Правда, сейчас чаще вместо времени по Гринвичу (GMT) используется всемирное координированное время — UTC. Разница в том, что GMT — это астрономическое понятие, а UTC — это то самое время по атомным часам. Сейчас в компьютерах уместнее говорить именно об UTC и смещении от него.
Итак, мы задаём год-месяц-день-часы-минуты-секунды и смещение от UTC. И однозначно получаем момент времени. И даже при переводе часов мы знаем, что сначала было 1:30 UTC+7, а потом случилось 1:30 UTC+6.
Time Zones
Но есть одна неприятная особенность. Смещение от UTC для данной местности есть величина непостоянная. Тут и летнее-зимнее время, и смены государственных границ, и попытки государств оптимизировать потребление электроэнергии, что выливается то в введение летнего времени, то в отмену летнего времени, то в смену часовых поясов. Чтобы как-то это учитывать, в каждой системе есть некая база данных, которая задаёт часовые пояса в некоторых географических терминах и хранит сведения об изменениях смещения от UTC в данной местности.
В подавляющем большинстве случаев используется база данных часовых поясов, ныне поддерживаемая IANA, также известная как Olson database, или timezone database, или tz database, или zoneinfo database. Это набор свободно доступных файликов, которые уже включены в состав операционной системы, среды выполнения (например, JRE) или библиотеки для работы с датой-временем. В файликах содержатся, например, сведения о том, что «Asia/Omsk» — это нынче UTC+6, до 26 октября 2014 это было UTC+7, а ещё раньше это было летом UTC+7, а зимой UTC+6. Хранятся все подобные исторические изменения в таймзонах.
Кроме длинных имён таймзон вроде «America/New_York» часто используются сокращения вроде «EST» (Eastern Standard Time — стандартное время на восточном побережье США, где Нью-Йорк как раз находится) или «EDT» (Eastern Daylight Time — летнее время на восточном побережье США). Как правило, инструменты работы с датой-временем прекрасно понимают подобные сокращения. Однако, я предпочитаю их не использовать, ибо понятны они в основном только местным жителям, неоднозначны, и, кроме того, сами местные жители часто употребляют их неправильно, например, летом говорят EST, хотя технически более верно говорить EDT.
End of Daylight Saving Time
Несмотря на то, что и смещение от UTC, и название географической местности — это таймзоны, надо эти два понятия различать. Смещение от UTC позволяет однозначно идентифицировать момент времени, заданный в виде года-месяца-дня-часа-минуты-секунды. Это очень хорошо для хранения момента времени, нет неоднозначностей. А имя часового пояса задаёт правила преобразования и отображения даты-времени в данном окружении. Часовой пояс может различаться для каждого пользователя системы, и надо корректно отобразить дату-время в часовом поясе именно данного пользователя, независимо от того, как мы эти дату-время сохранили ранее. И при этом будут корректно обработаны исторические перепетии местного законодательства.
Основная боль в работе с базой таймзон — её актуальное состояние. IANA-то отслеживает изменения в мире, и свежие версии появляются в худшем случае за пару месяцев до вступления изменений в силу. Но вот эти обновления очень долго проникают в реальные системы. Хорошо, если у вас какой-нибудь Linux с актуальной поддержкой. Тогда системные пакеты с базой таймзон сами прилетят с обновлениями. Плохо, что, например, Java поддерживает свою версию этой БД. И если вы не озаботились о ручном обновлении, вы можете получить изменения слишком поздно. Отдельную разновидность глюков порождают разные версии базы (читай, JRE) на разных окружениях.
Еще раз, не нужно использовать «географические» названия часовых поясов для хранения даты-времени в БД. Здесь возможны неоднозначности при переходе на летнее-зимнее время, полностью аналогичные описанным выше. «2015-11-01T01:30» в «America/New_York» — это UTC-4 или UTC-5? Поэтому в ISO 8601 можно задать только смещение, чтобы однозначно определить момент времени. «2015-11-01T01:30-05» и «2015-11-01T01:30-04». А для самого UTC (нулевое смещение) используется специальная буква «Z»: «2015-12-31T23:59:59Z».
Базы данных и библиотеки для работы с датой-временем умеют работать с часовыми поясами. В C# DateTime можно задать либо в UTC, либо в «локальной» (то бишь, установленной в данной ОС) таймзоне. В SQL есть типы данных с пометкой WITH TIME ZONE. Однако, это не означает, что в БД будет хранится смещение от UTC. Тут всё хитрее. И сильно зависит от БД.
Unix Timestamp
Есть ещё один способ задания даты-времени. Мы просто берём некий момент времени: эпоху или начало эпохи — и начинаем считать секунды или миллисекунды от этого момента. И получаем просто число, которое однозначно указывает на некий момент времени, с секундной или миллисекундной точностью. В Unix за начало эпохи взяли 1 января 1970 года UTC (полночь, начало этого дня). И секунды с этого момента принято называть Unix timestamp.
Замечательной особенностью такого указания даты-времени является то, что оно совсем никак не зависит ни от таймзон (начало эпохи определено в UTC), ни от календаря. Число секунд легко (компьютеру) перевести в часы-минуты-секунды любого часового пояса и в год-месяц-день любого календаря.
К недостаткам можно отнести проблему 2038 года. Если счётчик секунд у нас — 32-битное знаковое целое, то 19 января 2038 года этот счётчик переполнится. Но это не сильно большая беда, ибо всё чаще используется 64-битный счётчик миллисекунд, который переполнится в 292278994 году, на наш век хватит.
Именно таковым является класс Date в Java. Единственное, что он хранит, — это счётчик миллисекунд от начала Unix эпохи типа long. То, как Date отображается в IDE, зависит от окружения, в котором запущена IDE, включая часовой пояс и локаль операционной системы. То, как Date будет выглядеть где-нибудь на сервере (что вернёт его метод toString()), зависит от окружения сервера: его часового пояса и локали, а также актуальности его базы часовых поясов. Но это всегда будет конкретная миллисекунда от начала эпохи.
Аналогичные типы есть в базах данных. TIMESTAMP в MySQL. TIMESTAMP и TIMESTAMP WITH TIME ZONE в PostgreSQL, тут началом эпохи принято 1 января 2000 года, а суффикс WITH TIME ZONE влияет на преобразование дат при записи и чтении (учитывая часовой пояс клиента БД). TIMESTAMP в Oracle.
Real Timestamp
Так как метка времени — лишь число, возникает соблазн произвести арифметические действия над этим числом. Не надо так.
Вам может захотеться прибавить или отнять 3600 секунд, чтобы «подкорректировать» часовой пояс или летнее/зимнее время, которые у вас отображаются «неправильно». Это категорически неверно. Метка времени не содержит никакой информации о часовом поясе. Прибавление секунд переставляет эту метку на другой момент времени, совсем другой. А отображаться она может не так, как вы ожидаете, по двум причинам. Либо у вас уже есть неправильные данные, и тогда их надо поправить, а не подкручивать на лету. Либо у вас стоит неверный часовой пояс, либо база данных часовых поясов устарела. Для проверки правильности метки времени можно воспользоваться стандартными командами.
Отобразить метку времени в человекочитаемом формате:
 % date -d "@1451584799"              
Thu Dec 31 23:59:59 OMST 2015
 % date -d "@1451584799" -u
Thu Dec 31 17:59:59 UTC 2015
 % TZ="America/New_York" date -d "@1451584799"
Thu Dec 31 12:59:59 EST 2015
Преобразовать дату-время в метку:
 % date -d "2015-12-31T23:59:59" "+%s"
1451584799
 % date -d "2015-12-31T23:59:59Z" "+%s"
1451606399
 % date -d "2015-12-31T23:59:59-05" "+%s"
1451624399
А ещё вы можете захотеть перейти к следующему часу или дню. Запомните, добавлять 86400 секунд к метке времени, чтобы получить завтра, — это так же глупо, как добавлять 30 дней, чтобы получить то же число следующего месяца. Про то, что в месяце может быть совсем разное число дней, помнят все (а вы точно помните, какой год считается високосным в григорианском календаре?). А про переход на зимнее-летнее время все почему-то забывают. Ну и я вам еще рассказал про високосные секунды.
Чтобы правильно переходить от одной даты к другой применяются специальные методы, тесно связанные с используемым календарём. Часто это специальные типы данных, предназначенные для кодирования интервалов времени. Тогда к типу, представляющему дату-время, можно добавить или отнять тип, представляющий интервал, и получить новую дату-время. Таков класс TimeSpan в C#, timedelta в Python, INTERVAL в PostgreSQL и Oracle. Используйте их. Даже в ISO 8601 есть синтаксис для интервалов: например, «P3Y6M4DT12H30M17S».
А в стандартной библиотеке Java таких классов нет. Потому что нет переопределения операторов, наверное. Зато есть класс Calendar, который делает то, что нужно. Есть методы для установки года-месяца-дня-часов-минут-секунд. Есть методы для чтения их же. Есть методы для добавления годов-месяцев-дней-часов-минут-секунд. Ну а на входе или выходе можно получить стандартную метку времени — Date. В общем-то, всё что нужно, если достаточно григорианского календаря. Ведь что такое «последний день текущего месяца»? Это взять сегодня, добавить месяц, взять его первое число и отнять день.
Все эти метки времени, часовые пояса, базы данных таймзон и прочее, конечно, имеют смысл только если вы работаете с новейшим временем. Т.е. с тем временем, который используется в 99% софта. Но если вам нужно датировать исторические события до нашей эры, или наоборот, события, связанные с продолжительностью жизни звёзд, вероятно, вам понадобится и другой, вовсе не григорианский, календарь. Да и миллисекундная точность не нужна будет. Вероятно, тут нужны более специальные решения.
Creation of Light
Подведём итог.
  • Если вы храните дату в виде года-месяца-дня-часов-минут-секунд, вам просто обязательно явно указывать смещение этих значений от UTC.
  • Или же соблюдать соглашение о том, что все значения даты-времени будут в UTC.
  • Или же хранить стандартные метки времени, не привязанные к часовому поясу.
  • Внимательно читайте документацию к типам данных вашей БД, хранение даты-времени везде сделано по-разному.
  • При преобразовании входных значений в дату-время, при преобразовании даты-времени в человекопонятные значения обязательно явно указывайте часовой пояс из базы часовых поясов, не полагайтесь на умолчательные значения, они могут различаться в разных окружениях.
  • Используйте длинные имена из базы данных часовых поясов, не используйте сокращения.
  • Не используйте типы даты-времени, привязанные к окружению или не содержащие часовой пояс (типа LocalDateTime). Всегда считайте, что ваш код может быть запущен на сервере с совершенно левой таймзоной и должен обслуживать пользователей по всему миру.
  • Следите за актуальностью базы данных часовых поясов.
  • При преобразовании даты-времени в строку, кроме часового пояса вам понадобится еще и явно указать локаль.
  • Используйте ISO 8601 на входе и на выходе, если нет явных указаний на локализацию (например, в логах или для указания даты-времени в API).
  • Никогда не производите арифметических действий с метками времени, годами, месяцами, днями, часами, минутами и секундами. Никогда не пытайтесь сами высчитать високосный год или переход на летнее/зимнее время.
  • Используйте стандартные библиотеки для работы с датой-временем, и только их. Не пытайтесь изобрести велосипед. Следите за актуальным состоянием базы данных часовых поясов.
  • Могут быть библиотеки, более удобные, чем стандартные (например, JodaTime). Решайте сами, нужна ли вам дополнительная библиотека только ради удобства.
  • В редких экзотических случаях (работа с не григорианским календарём) вам могут понадобиться дополнительные библиотеки вроде ICU.
  • Всегда синхронизируйте время на серверах, NTP в помощь.
  • При отладке и сопровождении, а также по жизни, не запутаться в часовых поясах поможет timeanddate.com.

2015-12-13

О HappyDev'15

Подготовился, начался, случился и завершился очередной, пятнадца... ой, четвёртый HappyDev. Это такая омская сибирская айтишная конференция для омичей и их иногородних друзей. Которая по традиции проходит за городом, на местной базе отдыха имени Ивана Стрельникова.
HappyDev
По традиции был концерт. На этот раз очень шумной и энергичной омской группы Радости и Счастья Цемент-BanD. Ребята вполне дают жару, сильно не хуже ульяновских групп, что я слышал на UlCamp.
По традиции был открытый бассейн с неприлично мутной, но лечебной йодо-бромной водой. Правда, плавать в -3°C всё же не так весело, как в -30°C. Была хорошая пионерлагерная еда, с много каш и супов.
Ну и, в нарушение традиций, были собаченьки. Лайки появились на символике конференции. А хаски, самоеды и маламуты захватили половину официальных фоточек и почти весь Инстаграм, они фотографировались вместе с участниками.
Ой нет, в Инстаграме еще засветился лазертаг. Это когда две толпы народу, с пластмассовыми автоматиками и лампочками на голове, бегают по лесу под звуки «пиу-пиу» и «тра-та-та».
Ёжик в туманности
Ах да, конференция. Айтишная!
В этот раз идея была такая. Дать докладчикам кратко высказаться по теме для широкой аудитории. А для интересующихся техническими подробностями организовать мастер-класс. Длинный, на два-четыре часа. Похоже, получилось. Мастер-классы пользовались популярностью, и участникам нравилось.
Мой небольшой организаторский вклад в этом году заключался в насильственном вытягивании из докладчиков материалов к мастер-классам (старались, чтобы это были образы виртуальных машин, которые бы работали без интернетов) и добровольной раздаче их участникам. Это я решил раздавать файло через BitTorrent Sync. Прошу не бить.
Открывал конференцию матёрый омич-петербуржец Лёша Зиновьев. Рассказывал про большенькие данные. Интересующимся темой, но не желающим погружаться в айтишную тематику, могу порекомендовать соответствующую книгу. Таки Big Data уже здесь и сейчас, и всех победят. На мастер-класс, где, говорят, можно было воочию понаблюдать, как работают страшными хадупами и спарками, я, к сожалению, не попал.
Послушал, как Данил Никифоров рассказывал про решение для синхронизации данных мобильных приложений от Couchbase. Типа запускаем NoSQL базу Couchbase Lite на девайсе, запускаем Couchbase Sync Gateway где-то на облаке, и всё ваши милые данные синхронизируются в облачный же Couchbase Server. В общем, ничего нового, я об этом слышал ещё два года назад. Оно всё, может быть, и хорошо. Но меня напрягает обилие слова «Couchbase» во всём этом. Какой-то типичный vendor lock.
С удовольствием послушал доклад и посетил мастер-класс Вадима Литвинова из дружественного Новосибирска. Он рассказывал про MZBench — штуку, которую они написали для нагрузочного тестирования своей мобильной игрули Game of War, серверной её части. Тема распределённого нагрузочного тестирования близка моему сердцу. Было интересно увидеть и даже чуть-чуть пощупать, что получилось у Machine Zone.
Получилось неплохо. Произвольные сценарии тестирования, на Erlang или Python. Автоматизированное развертывание узлов для генерации нагрузки (в первую очередь, в Amazon). Автоматизированный деплой и запуск кода нагрузки. Автоматизированный сбор и отображение метрик. Оказывается, существует аддитивный способ сбора данных, чтобы потом получать аггрегированные персентили (это страшная математика, в которую мне самому еще предстоит погрузиться :) Очень красиво выглядит идея писать код нагрузки совершенно произвольно, но чтобы результатом его работы были определённые метрики. Только мне хочется полностью асинхронной нагрузки. И вообще, возникло желание написать свой универсальный сборщих системных метрик, а ля Diamond, но на Node.js (асинхронный), и с полноценной поддержкой тегов в стиле InfluxDB. Пока борюсь с собой.
Свой омский Коля Линкер устроил мастер-класс по монадкам. Что они такое, я в точности не понял. Ну вроде как интересный фокус в функциональном стиле. Но почему ему придают такое большое значение? А вот проникнуться Хаскелем (это не собаченьки!) вроде удалось. Понравилась мощь выведения типов. Понравилось, что типы и определения записываются отдельными строчками. Как-то понимаешь, что всякие споры о том, как кошернее записывать типы: в Си-стиле (до имени переменной) или в Паскаль-стиле (после имени переменной, через двоеточие), и в каком случае допустимо имя типа упускать, ибо выводится, — это всё ни о чём, на фоне Хаскеля.
Анатолий Орлов из бывшего Яндекса поведал об, в общем-то, известных тонкостях работы TCP. Речь о размере окна подтверждения. В TCP на каждый переданный пакет ожидается подтверждение его приёма. Если ожидать подтверждения прямо для каждого пакета, то скорость передачи будет ну оооочень низкой, ибо придётся ждать, пока пакет уйдёт туда, пока придёт подтверждение оттуда, и лишь затем посылать следующий пакет. Время пересылки пакета тут критично, а его уменьшить до нуля нельзя, таки скорость света. Поэтому TCP шлёт сразу несколько пакетов без подтверждения, а потом заполучает подтверждение для всех сразу. Вот количество этих самых пакетов, которые можно отослать без подтверждения, и есть размер окна.
Если размер окна маленький, то передача будет идти медленно. Но если размер окна большой, возникает другая проблема. Если при передаче возникли ошибки или подтверждение затерялось, то нам придётся слать все пакеты окна заново. Это снова снижает скорость передачи. По-хорошему, размер окна надо подстраивать под текущее качество (количество потерь) канала связи. Собственно, всё оборудование и софт, работающий с TCP, так и делают.
Но тут возникает другая тонкость. Качество канала от вас до сервера может сильно различаться. Типичный современный пример: хорошая оптика от вашего домашнего маршрутизатора до интернетов и плохонький вайфай до ноутбука. По идее, по оптике нужно большое окно, а через вайфай передавать с маленьким окном, в ожидании частых потерь и повторов передачи. Да вот только TCP определяет один единственный размер окна на всю TCP сессию, от ноутбука до сервера в интернетах.
Решение: разбить TCP соединение на вашем домашнем маршрутизаторе, запустив на нём, скажем, кэширующий HTTP прокси. Помнится, на заре интернетов по выделенке я примерно такое и делал. Была обратная ситуация: были потери у провайдера. Я поднял у себя прокси и кэширующий DNS (ибо потери DNS запросов оочень сильно тормозит пользование интернетами), и стало гораздо веселее.
Александр Рожнов рассказал про Docker и Kubernetes. Первое — это популярная система контейнеризации и распространения образов под Linux. Второе — среда распределённого разворачивания контейнеров. Я не запомнил, что было на докладе. Интересней были последующие мастер-классы. Кто был — были в восторге. Я — пропустил.
Под конец субботы в главном ангаре выступал известный джедай-практик Максим Дорофеев. Он рассказывал, в общем, всё о том же, рисовал неприличные надписи на сцене и проводил сеанс групповой депрокрастинации. Сеансу я предпочёл бассейн, ибо полгода назад уже участвовал в более серьёзном тренинге. Честно говоря, я удивлён, как ещё находятся люди, которые не только не начали приводить свои дела в порядок, но даже не слышали, кто такой Дорофеев. Видимо, в ближайшие годы стабильный заработок Максиму гарантирован.
Ass Raptor
В воскресенье утром сходил на доклад Алексея Букурова про генерацию интерфейсов. Извините, чуть не уснул. Не понял, зачем, о чём и с какой стати. Не зачем это было сделано, а зачем так скучно об этом докладывать.
Дальше в главном зале пошли подряд три доклада архитектурной направленности. Начал крутой чел из омского Люксофта — Максим Юнусов. Вроде, всё правильно говорил. Есть паттерны и антипаттерны... Нет абсолютно правильных архитектурных решений... Но что-то слишком часто звучало слово «антипаттерн». По мне, так паттерны существуют, наносят пользу или приносят вред, независимо от того, найдётся ли умный дядька, чтобы описать их в своей книжке, или найдётся ли другой умный дядька, чтобы в другой книжке написать, что этот паттерн — говно. А еще мне показалось, что докладчик явно не договаривает, возможно, в желании не нагружать аудиторию. Ведь, кроме процедурного да ООП, есть ещё функциональное программирование да те же акторы. Ведь, после джуниоров, сениоров и архитекторов-прагматиков, где последних заботит правильность реализации технического решения, есть еще две ступени заботы: забота о нуждах заказчика и забота о конечных пользователях.
Кстати, об акторах. Продолжил Антон Тарасенко. Они в Тамтэке, на основе практических нужд реальных проектов, родили маленький легковесный фреймворк для асинхронного программирования. Половина доклада была посвящена преимуществам асинхронности. Другая половина — конкретному решению для объединения CompletableFuture в пайплайны для обработки сетевых запросов. Стоп. А акторы ли это? Это больше похоже на LINQ и ленивые вычисления. Ну или на маленький Apache Spark, если хочется так думать. Вроде красиво, но хочется мастер-класса :)
Забегая вперёд, забавно видеть, как одни и те же вещи называются в разных решениях по-разному. Context — Message. Pipeline — MessageMap. Service — Actor.
Ну и Евгений Тюменцев. Широко известный в узких кругах не только Омска архитектор-учёный-революционер-преподаватель. Он рассказывал про свою реализацию акторной модели, которая была подтверждена реальными внедрениями, в разных инкарнациях существует уже лет шесть, и сейчас переписывается на Java.
Мне тяжело тут пересказывать. На самом деле, я знаю об этой реализации несколько больше участников конференции. Впервые о ней услышал года два с половиной назад, а последние полгода мимо меня постоянно ходили непосредственные разработчики этого чуда. Тем интереснее было наблюдать за реакцией зала. Кажется, кто-то был шокирован, а кто-то возмущён. Прекрасно!
Удивление
Главное тут — SOLIDность. Мы делаем систему согласно всем SOLID принципам. И получается конструктор — набор кубиков, из которых можно сделать всё что угодно. Мы не сверлим дырки в существующих кубиках и не работаем напильником. Если нам нужен новый функционал, мы делаем новый кубик и ставим его на нужное место. В серьёзных базах данных, от CRUD уже давно используется только C и R. Мы никогда не модицифируем и не удаляем данные, мы создаём новые версии или помечаем на удаление.
Так же должно быть и с кодом. Пишем новый код и подключаем его к системе. Но никогда не правим старый. Именно поэтому плохи ifы, switchи и enumы. Изменение требований (условий окружающей среды) требует изменения подобных конструкций. Их невозможно расширить, не переписав код, не потеряв старый код, который работал, блин, и был весь протестирован. А вот справочники, таблицы переходов, в конфиге или в базе, перегруженные методы, различные реализации интерфейсов — зер гут.
Вот SOLIDная реализация акторной модели и даёт инструмент для создания и склеивания кубиков. Кубики просты, понятны, могут быть независимо оттестированы. Старые кубики никуда не деваются и могут функционировать одновременно с новыми. Ну а ядро системы, собственно, система управления акторами, будучи раз правильно написана, не меняется. Пуля не серебряная. Но всё же?
Lego New Hope

2015-11-28

О человеческих часах

Надеюсь, все читали великолепного Брукса, ту его книжку с говорящим названием «Мифический человеко-месяц». Ну это как раз про то, что девять женщин могут родить ребёнка за один месяц. Или, цитируя Григория Остера: «Известно, чем больше денег вкладываешь в строительство, тем быстрее оно идёт. Родик вложил столько денег, что завод ремней построился буквально за один час». На самом деле нет.
Warren Buffett
Возможно. мне повезло. Так получилось, что я никогда не работал на Одеске и прочих хитрых фрилансерных платформах, где идёт почасовая оплата, а чтобы ты честно отпахал эти часы, ведётся тотальный контроль. Ну вы знаете. Жмёте кнопку, что начинаете работать над задачей. Начинает считаться время. Начинают считаться нажатия кнопочек клавиатуры, движения и клики мышки. Начинают делаться скриншоты вашего экрана, исподтишка. И так, пока вы не нажмёте кнопочку, что вы перестали работать над задачей.
У меня было не так. Я работал в офисе. Я знал, когда прихожу на работу. Я знал, когда ухожу с работы. Я знал, что между этими моментами времени, за вычетом получаса или часа на обед, проходит восемь часов. И, таким образом, я нахожусь на работе, а, значит, подразумевается, что я работаю, по сорок часов в неделю.
И я считал вполне нормальным работать именно по сорок часов в неделю. Неважно, рано утром или поздно вечером. Неважно, с понедельника по пятницу или иногда по воскресеньям. Главное, чтобы в сумме выходило сорок часов за неделю.
И я трекал время в таск-трекере. Но восемь часов в день. Или по шесть и десять часов за два дня. Но в сумме сорок часов в неделю. Исходя из собственных внутренних ощущений о том, сколько времени на какую задачу я потратил. Лишь бы сумма сходилась.
И тут обнаружилась странность. Чем меньше я непосредственно писал код, и чем больше я занимался менеджерскими обязанностями, тем сложнее было связать часы, проведённые на работе, с конкретными задачами в таск-трекере. Вот тут я вроде этим занимался, вот тут — этим. А куда делись еще два-три часа? Так появлялись бесконечные (не потому, что их много, а потому, что они никогда не заканчивались) задачи со словом «communication» в названии. Ну или «management».
Ещё был в истории моей трудовой деятельности непродолжительный период, когда я выторговал себе возможность не трекать часы. Совсем. Я продолжал находиться на работе по восемь часов в день. Я продолжал пользоваться таск-трекером, чтобы банально не забывать, что нужно сделать. Но вместо оценок в часах я выставлял дату, к которой задача должна быть сделана. И правильно и вовремя менял статусы задач.
И я был счастлив. Здорово, когда над тобой не висит обязанность отчитаться за каждый час, проведённый в офисе. Здорово, когда не нужно мучительно вспоминать, что ты сделал за день, или, ужас какой, за неделю. Все твои обязательства выражаются в том, что задачи должны быть сделаны к тому сроку, который ты сам и указываешь. Всё.
И вот теперь, ради успокоения заказчика, я оказался под гнётом той самой одескоподобной штуки, которая следит за тобой и делает скриншоты. Заказчик тешит себя надеждой, что, поразглядывав скриншоты, он убедится, что разработчики действительно работают, и что они действительно кодят то, что он хочет.
Боже, почему я должен постоянно думать, какой именно задачей я занимаюсь прямо сейчас? Если мне приходится болтать с коллегами по поводу двух проектов, мне приходится тыкать в этой штуковине правильный проект. Это очень тяжело, так жёстко контролировать контекст своей мозговой активности.
Почему я должен постоянно беспокоиться, чтобы на скриншоты не попал какой-нибудь не тот чатик? Нет, то, что посторонние чаты отвлекают от Работы — это другой вопрос. Нифига чаты не отвлекают. Уведомления отключены. К тому же, если я действительно застрял в решении какой-то сложной задачи, фиг меня отвлечешь, даже если встать рядом и орать на ухо. Но тут мне надо всегда помнить, что, когда я переключаюсь на чат с супругой, чтобы за две минуты обсудить планы на вечер, мне нужно остановить всебдящую тулзу.
Но самым удивительным оказалось другое открытие. За восемь часов нахождения в офисе у меня получается затрекать хорошо если шесть часов времени работы над задачами. Ветераны Одеска говорят, что это нормально. Но я всё никак не могу привыкнуть к тому, что часа два-три опять куда-то пропадают. Пропадают они, если честно трекать время. Встал сходить попить чаю — остановил трекинг. Вышел в туалет — остановил трекинг.
Оказалось, что два-три часа каждый день тратятся не на кодинг, не на митинги. А на... Ну на жизнь. «Если программист смотрит в окно, это не значит, что он ничего не делает». Если я пошёл пить чай, я действительно отлучился от рабочего места минут на пятнадцать-двадцать. Я действительно не занят в это время работой над конкретным проектом. Я общаюсь. Я обсуждаю проблемы, в том числе и на этом проекте. Я обсуждаю проблемы на других проектах. Я хвастаюсь достижениями. Я делюсь опытом. Я учусь. Я отдыхаю от предыдущей задачи, чтобы со свежими силами взяться за следующую. Я живу. Два-три часа каждый день в офисе я не работаю, чтобы эффективно работать.
А у вас какой расклад получается?
К чему это я? Я считаю, что повсеместное использование человеко-часов — абсолютное зло. Час работы конкретного человека над конкретной задачей не говорит ни о чём. Ни об эффективности работы этого человека. Ни о прогрессе выполнения задачи. Человеко-часы — это просто порочная привычка, к величайшему сожалению прочно засевшая в мозгах всех участников процесса разработки ПО.
Мы продолжаем использовать человеко-часы для оценки трудоёмкости задач. Но ведь это неверно. Разным людям нужно совершенно разное время для выполнения одной и той же задачи. Плюс погрешность оценки. Во всех этих наших прогрессивных методологиях оценивают совсем по-другому. В Скраме важным является лишь обязательство команды (всей команды) выполнить определённый набор задач за определённый срок итерации. Каким образом принято это решение, как при этом оценивались задачи: в поинтах, в попугаях, раскладыванием по корзинкам — не важно. Важно выполнить обязательство. В Канбане вообще не важна абсолютная трудоёмкость задачи, важно лишь знать, какая задача труднее, а какая проще, чтобы правильно выбрать, какую вытащить в разработку. А метрики, скорости и прочие показатели, которые могут указать, когда же действительно будут завершены нужные задачи, считаются вовсе не от первоначальных оценок, а от моментов времени, когда задача переходит из одного статуса в другой.
Мы продолжаем использовать человеко-часы для расчёта заработной платы. Может, это удобно в каком-нибудь автосервисе, где все работы заранее определены производителем авто, и их трудоёмкость раз и навсегда задана в нормо-часах, и вот эти нормо-часы и оплачиваются. Но разве в программировании определены все задачи, которые приходится решать? Почему за час митинга по одному и тому же проекту менеджеру Пете заплатят в два раза больше, чем джуниору Васе, если Петя лишь спросил, как дела на проекте, а Вася полчаса распинался, как крокодил не ловится? Кто из них потратил больше сил? Сколько заплатят еще пятерым разработчикам, которые присутствовали на том же митинге, но не проронили не слова? А можно ли мерять меру участия в словах?
Нормочасы
Опять таки, отмеряя время на задачи в точных часах, я обнаружил, что количество мыслетоплива, потраченное за час настенного времени, существенно зависит от характера задачи и от настроения. Задача на хитрый кодинг, когда впадаешь в поток, тихо и незаметно съедает кучу времени. И потом кажется, что ещё можешь горы свернуть. Лучший случай для жёсткого учёта времени: мыслетоплива субъективно потрачено мало, а времени прошло много. Мне, к сожалению, такие задачи попадаются редко.
Наборот, когда не знаешь, за что взяться, с какой стороны подойти, пробуешь то да другое, а ничего не выходит, время ползёт еле еле, и выдыхаешься махом. Вымотался уже весь, а смотришь, лишь полтора часа прошло. В работе, например, суппорта (не первой линии) таких задач много. Очень фигово при жёстком учёте времени.
Когда настроение хорошее, время летит незаметно. Когда хмур и печален, время ползёт слишком медленно. Лучше, когда время летит, тогда меньше мыслетоплива тратишь за час, проще честно зафигакать аж десять часов полноценной работы.
Нельзя раньше
Ну и наши заказчики продолжают использовать человеко-часы для расчета длительности и стоимости проекта. Наивнейшим образом перемножая рейты на человеко-часы и полтора землекопа, получают якобы правильные цифры. Нифига эти цифры не правильные. Просто под них потом всё подгоняется, дабы не переписывать контракты, не пересчитывать бюджеты, и не ударить в грязь лицом. А чтобы еще и не прогореть, хитрые менеджеры заранее перемножают всё на Пи. А, как известно, благодаря эффекту выпрямления сроков, нельзя просто так взять и завершить проект раньше. Поэтому все буферы будут съедены, все бюджеты исчерпаны, и цифры с довольно высокой вероятностью сойдутся. Это не заслуга наших точных оценок и правильного перемножения человеков на их часы, это — заслуга правильно заложенного (и съеденного) буфера.
Между прочим, практика бездумной продажи часов разработчика исключительно как часов работы конкретного человека не важно над чем, главное, тому, кто платит деньги, носит весёлое название «body shop». В некотором роде это аналогично «Flesh Fair» из «И.И.»
Flesh Fair
Разработчики, вы действительно хотите мучиться и подгонять часы, проведённые в офисе, под выполненные задачи? Вы действительно хотите, чтобы незнакомый дядя постоянно пялился бы в экран вашего компьютера, не доверяя вам? Вы действительно хотите, чтобы продавались часы вашего сидения перед монитором, а не реально нанесённая польза? Может, вы хотите просто получать зарплату и просто делать то, что вам нравится и что у вас хорошо получается? И иногда получать премию, если заказчику сильно понравится то, что вы сделали.
Менеджеры, вы действительно считаете, что человеко-часы можно умножать на людей, оценки разных задач суммировать, сумму делить снова на людей, и на восемь часов в день, и получать календарную продолжительность проекта? Вы действительно считаете, что, взяв красивый рейт, желательно побольше, и зависящий от должности сотрудника, а не от его пользы на проекте и реальных умений, и умножив его на часы, можно получить реальный бюджет проекта? Может, вы не хотите всего этого перемножать, а хотите определить лишь две цифры: деньги и время?
Заказчики, за что вы платите деньги? За то, что менеджер Петя и джуниор Вася будут пытаться вам не соврать, что посвятили согласованное количество часов именно вашей проблеме? Или за то, что менеджер Петя и джуниор Вася (и не важно, кто там еще будет им помогать) действительно решат вашу проблему (или не решат, тут уже риски и репутация Пети и Васи) за оговорённые сроки и деньги? Неужели вы думаете, что сможете проверить, что озвученные Петей и Васей часы являются адекватной оценкой, и сможете удостовериться, что они потратили именно столько часов именно на вас? Может, у вас просто есть проблема, которую нужно решить за адекватный срок и за разумные, имеющиеся у вас, деньги? И, может, вам всё равно, сколько народу будет над этим работать и будут ли они спать ночами, если работа в конце концов будет выполнена?
Project Triangle
Но как же без человеко-часов? Есть варианты. Например, то, что называется FFF (fixed price, fixed timing, flexible scope). Заказчик определяет лишь две цифры: стоимость и срок, и хочет, чтобы за указанную цену, в указанный срок, кто-нибудь решил его задачу наилучшим возможным способом. А сам обязуется постоянно следить за тем, что решение задачи действительно движется в благоприятном направлении. Исполнитель собирает команду разработчиков (а точнее — решателей проблем), которая с хорошей вероятностью сможет решить данную проблему в указанный срок. Берёт их зарплату за этот срок, добавляет ожидаемую прибыль (она же буфер на риски) и получает бюджет. Как только все цифры и желания у заказчика и исполнителя сойдутся, работа начинается. Не правда ли, две цифры — это проще, чем куча попугаев, помноженных на питонов?

2015-11-14

Об Интентах

Продолжаю хвастаться своими андроидными приложениями.
В своём сдкартопосылательном приложении я изрядно подружился с андроидными интентами. Intent — это, буквально, намерение. Интенты — это основа Андроида как операционной системы. Это способ межпроцессного взаимодействия. Это сообщения, которые приложения или система посылают другим приложениям, а те как-то реагируют.
Intent workflow
В Андроид ничего не может произойти без Интента. Система может сообщить всем заинтересованным приложениям, что, например, появилось подключение к Сети. Одно приложение, скажем, Ланчер, в котором мы видим значки всех установленных приложений, может послать Интент другому приложению, с указанием отобразить определённый стартовый экран (в терминологиях Андроида — Активити), что приводит к запуску этого приложения и отображению этого самого экрана. Собственно, только для ответа на некий Интент приложения и могут запускаться.
Интент может содержать конкретное имя конкретного компонента (Активности или других) конкретного приложения. Может содержать URI, указывающий на некие данные других приложений, ресурсов в Сети или даже локальные файлы, с указанием MIME типа этих данных. Может содержать произвольный набор (ассоциативный массив) других данных, так называемые Extra, включая сериализованные объекты. Ну и Интент обязательно содержит действие (Action) и его категорию, которое ожидается быть выполненным на стороне получателя Интента.
Intent class
Скажем, запуск программы из Ланчера — это ACTION_MAIN в CATEGORY_LAUNCHER. Открытие файла или URL — это ACTION_VIEW в CATEGORY_DEFAULT или CATEGORY_BROWSABLE, URI и MIME тип того, что нужно открыть, явно передаются в Интенте. Расшарить/поделиться/послать что-то — это ACTION_SEND в CATEGORY_DEFAULT, а что именно посылается добавляется к Интенту либо текстом как EXTRA_TEXT, либо как URI в EXTRA_STREAM.
Это всё было бы не так интересно, если бы приложения не сообщали бы, какие Интенты они умеют принимать. Каждое Андроид приложение объявляет в своём манифесте, из каких компонентов оно состоит, и какие Интенты эти компоненты готовы принимать. Например, если приложение объявляет, что у него есть Активность, которая принимает Интенты с ACTION_MAIN в CATEGORY_LAUNCHER, то значок именно этой Активности появится в Ланчере, и именно эта активность будет запущена при тыкании по значку. А если такой Активности в приложении нет, то оно не появится в Ланчере, и чтобы удалить его, вам придётся залезть в настройки :) Но сам Ланчер — это обычная программа, и он запрашивает у системы, какие именно Активности умеют принимать ACTION_MAIN, и, исходя из полученного списка, рисует свой экран доступных приложений.
Любая другая программа тоже может проделать такой фокус. Скажем, запросить, какие приложения умеют открывать URI, начинающиеся на http://. Это, в каком-то приближении, будет список всех установленных в системе браузеров.
Получается, что в Андроид, после установки приложения, всегда известно, какого рода Интенты этим приложением обрабатываются. И часто используются Интенты, не адресованные конкретному приложению, а, например, отсылающие http:// URI любым приложениям, готовым его принять. При этом отображается диалог выбора конкретного получателя, наверняка часто такой видели. Технически подобный диалог — просто некоторая Активность, принадлежащая некоемому системному приложению.
Intent filters
Итак, мои приложения.
Второе по популярности (двадцать четыре тысячи активных пользователей) — браузерооткрывательное приложение. Дело в том, что почти все браузеры под Андроид, естественно, умеют открывать http:// URL, а также, что не всем известно, открывать и file:// URL. Поэтому, если вы сохранили HTML версию какого-нибудь сайта локально, вы на самом деле можете её открыть в любом удобном браузере и читать. Но беда в том, что если открытие http:// прописано у браузеров в манифесте, то про file:// они типа не знают. Единственным исключением была Опера. Получается, что, чтобы открыть локальный файл в браузере, вам приходится вбивать полный file:// URL в адресную строку вручную. Жутко неудобно. Функционал вроде есть, но воспользоваться им нормально не получается. А ведь нынешние браузеры весьма неплохо отображают картинки и даже видео.
Вот моё приложение и добавляет возможность открывать локальные html и текстовые файлы, а также файлы изображений и видео в любом установленном браузере. Чтобы непосредственно добраться до файла, вам ещё будет необходим какой-нибудь файловый менеджер.
Приложение в своём манифесте заявляет, что умеет открывать файлы. Ещё оно составляет список браузеров в системе, тех, кто умеет открывать http://. Когда к нему приходит Интент, оно просто отображает диалог выбора браузера, либо молча выбирает заранее выбранный браузер, и перенаправляет Интент браузеру. Всё.
И пошла игра с Интентами.
Ненавижу сокращённые URL. По ним совершенно не ясно, куда они ведут. А ведь по доменному имени, по нормальному, согласно ЧПУ, пути, часто можно понять больше, чем даже из заголовка статьи. И вот, чтобы делиться из этих наших твиттеров или RSS лент нормальными читабельными URL я создал урлоудлинятельную программу. Между делом оказалось ещё очень удобным удалять всякие незначащие параметры из URL, которые используются исключительно для трекинга рекламных кампаний, но не несут смысла.
Программа получает Интент с ACTION_SEND. Извлекает из него расшаренный текст. Ищет в тексте URL. Резолвит все редиректы, пока не получит финальный адрес страницы, для этого нужен Интернет. Удаляет из URL параметры, согласно настройкам. Заменяет в тексте URL на результат преобразования. Расшаривает текст далее, где его можно послать другой программе. Просто вклиниваемся еще одним шагом в стандартный процесс расшаривания ссылок.
Похожим образом работает и единственная (ну почти) программа, написанная на заказ — ебаеторговательная программа. Ебай — это, как вы знаете, такой интернет-аукцион. И есть такой, не вполне честный, способ выигрывать торги. Вы натравливаете на лот аукциона, от своего имени, некоего бота, который, за секунды до окончания торгов, внезапно делает ставку чуть-чуть выше текущей максимальной. Это называется sniping.
Так вот, нашлись ребята, которым очень нравился один такой снайперный бот. Только у создателей этого бота не было (и сейчас нет) мобильного приложения. Но зато был мобильный сайт. И вот для этих ребят я написал приложение. Оно получает Интент, которым стандартное приложение Ебая расшаривает ссылку на лот. Ведь это же вполне удобно и безобидно, куда-то ссылку отправить. Приложение находит идентификатор лота в пересылаемом тексте и формирует URL на мобильный сайт снайперного бота с идентификатором лота, чтобы бот сразу начал спайпить данный лот. Ну и формирует новый Интент, на открытие этого сайта. И сайт открывается в браузере. Опять вклиниваемся с процедуру расшаривания ссылки, и получаем новый удобный функционал.
Забавно, чем закончилось текущее существование этого приложения. При очередном обновлении его забанил Гугль в Гуглоплее. Мотивируя это тем, что я использую название и логотип этого самого снайперного бота. Это правда, но у меня было неформальное разрешение на их использование. Формального разрешения авторы бота мне так и не дали, может, не хотят светиться перед Гуглом. А переделать название и лого у меня еще руки не дошли.
Sniping
Я обожаю PlantUML. Так, что даже захотел иметь возможность писать диаграммы на планшете. Делать полноценный текстовый редактор я не рискнул. Ведь уже есть достойные редакторы, нечего с ними бодаться. Поэтому я снова решил поиграть с Интентами.
Сценарий такой. Редактируем текст в редакторе. Когда нужно, посылаем текст в наше приложение посредством Интента с ACTION_SEND. Приложение формирует из текста диаграммы URL на картинку на plantuml.com. Скачивает эту картинку. Открывает картинку в Активности для предварительного просмотра, откуда её можно переслать или открыть в другом приложении. Используем Интенты, чтобы как-то сынтегрироваться с другими приложениями, которые даже не подозревают, что с ними интегрируются. Так получилось плантуэмэльное приложение.
PlantUML
А недавно я задумался о несправедливости. Почему Интенты для расшаривания (ACTION_SEND) и Интенты для открывания (ACTION_VIEW) так неравнозначны? Расшарить текущий документ (или что там сейчас открыто) можно почти из любого приложения. А почему нельзя переоткрыть это же в другом приложении? Почему нельзя открыть текущую страницу в другом браузере? А почему, наоборот, нельзя послать куда-нибудь ссылку из текущего документа? Это худо-бедно возможно в некоторых популярных почтовых клиентах и браузерах. Но обычно ссылка может быть открыта только в этом же приложении.
И я начал делать расшаривательнооткрывательную программу. Она позволяет открывать то, что расшаривается, и расшаривать то, что открывается. На практике, если вы часто кидаете ссылки туда-сюда, оказалось очень удобно. Пока готово на 80%, уже можно пробовать. Пишу на Котлине.

2015-11-01

О мобильном

А давайте немного похвастаюсь своими мобильными приложениями. Я ведь, помимо прочего, пописываю свободные программы под Android. И немного рассказывал об этом на прошлом Droidсon в Москве.
Всё началось во времена первого айфона. Упоротые гики привозили эти самые айфоны из Штатов. А я ждал. Я не хотел доверяться Эпл, мне не нравилось уже тогда заметное их стремление подсадить пользователей исключительно на их продукцию. И тут появился Андроид. В лице HTC Hero. Это был практически первый гуглофон, доступный в широкой продаже, и даже официально в России. И я понял, что это тот айфон, который мне нужен. А когда приобрёл его, внезапно понял, что под него можно писать программы. И начал писать программы.
Welcome to Omsk
Пытаюсь вспомнить, с какой программы всё началось, и не могу. Наверное, всё же с погоды. Ну не было в раннем Андроиде нормальных погодных приложений и виджетов. А захотелось видеть хотя бы температуру воздуха в родном городе. Тем более, что была, и до сих пор здравствует знаменитая Муха. Это датчик температуры в центре Омска, с 2003 года публикующий показания (и графики) в интернетах.
Так возникла омскопогодная программа. Началась она с виджета на рабочий стол. Но Боря вдруг спросил: "А можно эту температуру вывести в строке состояния сверху экрана?". Так и родилась гениальнейшая идея: показывать температуру в строке состояния. Ныне эта идея так или иначе реализована во всех приличных погодных программах.
Реализация идеи встретила серьезное сопротивление со стороны Android SDK. Дело в том, что Андроид не позволяет рисовать что попало в строке состояния. Там могут выводиться только картинки-ресурсы, уже вкомпилированные в приложение. Хорошо, впрочем, что саму картинку можно выбирать. Вот так и получилось решение: включить в приложение картинки с показаниями температуры от -50 до +100 градусов (для Цельсия и Фаренгейта).
Потом в омскопогодную программу добавились графики температуры с той же Мухи. А потом я решил, что Омском ограничиваться не стоит и начал делать всепогодную программу. Поддержку омскопогодной программы я забросил, но девятьсот шестьдесят шесть каких-то чудаков до сих пор ею пользуются.
OpenWeatherMap
Всепогодной программе нужен был источник погоды во всех городах, а не только в Омске. И у Гугла он был. Помните iGoogle? Такая кастомизируемая стартовая страница с разными виджетам. Был там и виджет погоды. И данные он запрашивал во вполне понятном XML. Хороший был источник данных, но, раз уж был неофициальным, тихо, но заметно, умер. Пришлось в срочном порядке искать альтернативу. И единственным открытым и бесплатным погодным API оказался OpenWeatherMap.
OpenWeatherMap в ту пору только зарождался. Не было рекламы на страницах. Не было платных тарифов и ограничений в количестве запросов. Не раздавали API key. Пожалуй, я даже немного приложил руку к их развитию. Как минимум одно минорное обновление API было выпущено для исправления багов, которые нашёл я.
В общем, за неделю по вечерам была создана новая реализация запрашивателя погоды, и всепогодное приложение продолжило работать.
Другой челлендж во всепогодной программе случился с теми самыми картинками-иконками для циферок температуры. Еще на заре Андроида оказалось, что у HTC в Sense строка состояния вверху экрана — чёрная, а у обычного Андроида — белая. Потом она и у Андроида стала чёрная. А с приходом Ice Cream Sandwich еще и поменялся масштаб отображения иконок.
Для разных случаев, в зависимости от модели устройства или хотелок юзера нужны были разные иконки. А набор из 150 иконок занимал порядочный мегабайт. Для устройств времен HTC Hero — весьма много.
Поэтому я решил делать всепогодное приложение расширяемым. И подключать различные шкурки с разными иконками. Родился целый API по взаимодействию основного кода, который отвечал за своевременное обновление погоды, и шкурок, которые отвечали за отображение.
Сам я написал и поддерживаю пять шкурок. И была надежда, что кто-то захочет написать ещё. С другими цветами цифр, с виджетом, а не уведомлением, ещё с чем-нибудь. Но никто так и не написал. А сейчас наличие этих пяти шкурок смотрится странным, весь этот пяток мегабайт уже не стыдно просто включить в одно приложение.
Android MicroSD
Пожалуй, исторически второй программкой, но самой популярной из моих программок стала сдкартопосылательная программа. С ума сойти, её скачали более трёх миллионов восьмисот тысяч раз, и активно пользуются более четырёхсот тысяч человек. Почуяв успех, я впилил туда донейт в один бакс. Просто возможность дать мне доллар, если программа понравится. Без рекламы, без смс, без ограничения функционала без оплаты. И платят, что-то около сотни в месяц выходит.
А идея была простая. В то время не было Гуглофоточек. И встроенная в телефон Галерея, для просмотра нафотканного, оставляла желать лучшего. Она не умела организовывать фотографии, но умела отображать фото из разных папок на файловой системе как альбомы. Приходилось раскидывать файлы по папочкам в неуклюжих на тот момент файловых менеджерах, где было неудобно фоточки смотреть. А потом смотреть в Галерее, где неудобно было их раскидывать. Захотелось упростить задачу.
После изучения предметной области оказалось, что штука «Послать на» (она же «Share to» или «Send to») в Андроид — достаточно универсальна. Это стандартный Интент, который используется везде и всюду, и может пересылать абсолютно любой файл, а не только картинки. Идея расширилась: позволять пересылать любые файлы из любых приложений в любую папку на файловой системе.
Конечно, это можно было сделать в файловом менеджере. Но — гораздо дольше. Смотрите: надо найти файл, надо его выделить, надо вырезать или скопировать его, выбрав пункт меню, надо найти папку, куда его положить, надо снова выбрать пункт меню для вставки. А я придумал, как короче: нужно файл «послать на» SD карту, выбрать папки (а так как папки с прошлого раза запоминаются, то в 95% случаев этот шаг можно пропустить), нажать «скопировать» или «переместить». В два раза меньше действий.
Идея простая. И мне непонятно, почему до сих пор, аж уже в шестом Андроиде, это не стало функцией системы. Сейчас частично этот функционал встраивают в некоторые файловые менеджеры, но универсальности моей программы ещё никто не переплюнул.
Впрочем, в систему это не встроили, потому что незачем простому юзеру знать про какие-то там файловые системы.
Раньше на Андроиде не было доступных пользователю файлов на самом телефоне. Надо было обязательно вставлять SD карту, куда и кидались файлы тех же фоточек. Потому приложение и называется «Отправить на SD карту». Потом от SD карты отказались, и эмулируемая файлопомойка перекочевала в сам девайс. Потом некоторые производители вернулись к SD карте. И в Андроиде оказалось две файлопомойки. Где-то в этот момент, в Kit Kat, Гугль сделали мне подлянку. Внезапно оказалось, что приложения, честно запросившие пермишены на полный доступ на запись к общей файлопомойке, не могут в эту файлопомойку писать. А могут писать только в тот специально отведенный уголочек, в подпапку Android\<имя пакета приложения>, который аккуратно подчищается после удаления приложения.
Вроде как производители неправильно поняли Гугль. Имелось в виду, что все приложения имеют доступ на запись в свой приватный уголочек, а те, которые запросят пермишен на запись в файлопомойку (т.е. и моя сдкартопосылательная программа), должны по-прежнему иметь возможность писать куда угодно. Не знаю, кто кого как понял, но до сих пор я вижу на устройствах то невозможность писать на SD карту, то невозможность писать во внутреннюю память. Что попало. Понятно, что невозможность писать в произвольный каталог файловой системы делает мою программу полностью бесполезной. Файловые менеджеры тоже от этой глупости страдают. Хотя предустановленные файловые менеджеры магическим образом могут писать куда угодно.
Hacking Android
Получается, у меня какой-то талант натыкаться на какие-то мелкие недочеты в системе и затыкать их своими программами. Чтобы сделать мелкие вещи более удобными. Таких программ у меня много.
Продолжение следует.

2015-10-18

Об Амстердаме

Так получилось, что я четыре полных дня провёл в Амстердаме. Изображал туриста, шлялся по музеям. Впечатлялся.
I'm Sterdam
Сразу предупреждаю, в кофешопы не ходил, в квартале красных фонарей не показывался. Мне, как человеку некурящему, был отвратителен запах, доносящийся из открытых дверей кофешопов, мимо которых я проходил. Так что ничего особенного со мной в Амстердаме не произошло.
Терминологические тонкости. Амстердам — столица государства под названием Нидерланды. Если хотите кумыса и степей, то можно с ударением на последний слог: НидерландЫ. Собственно, «de Nederlanden» означает «низинные земли». Но по-английски страна почему-то называется Netherlands, ну почти как Neverland, где буйствовал Питер Пэн. А по-русски чаще всего говорят о Голландии, хотя Северная и Южная Голландии — это лишь две (из двенадцати) провинций Нидерландов. Амстердам как раз находится в Северной Голландии. И так как, в конце XVII века, Пётр I как раз потусил в Амстердаме, с тех пор как-то и повелось под Голландией подразумевать все Нидерланды. Ну это как съездить в Омск и говорить, что побывал в Сибири, а не в России.
Язык тамошний — нидерландский. Который по-русски так же чаще называют голландским. А вот по-английски «нидерландский» (язык или местный житель) будет «Dutch». Да да, именно так называли брутального Шварца в красной футболке в «Хищнике». Не путать с «Danish» — «датским», т.е. имеющий отношение к Дании, как Гамлет. Поэтому «Royal Dutch Airlines» — это королевские нидерландские авиалинии, они же KLM. (В оригинале красивее: «Koninklijke Luchtvaart Maatschappij»)
KLM sells
Из Москвы рейс выполняли как раз KLM. Впервые летел в самолёте, где бортпроводницы ни слова не умели по-русски, или делали вид, что не умели. Мучительно вспоминал английские названия еды, чтобы выбрать что-нибудь съедобное на обед. Но выбора предоставлено не было. «Enjoy your meal.» И всё. Обычная самолётная еда, без обид. Еще эти блондинистые европейки дежурно улыбались, когда обращались к тебе. В отличие от суровых перекрашенных русских девушек Аэрофлота на обратном пути. Впрочем, смотрелись эти улыбки вполне дружелюбно и естественно.
Аэропорт Амстердама — Схипхол — находится в двадцати километрах от самого города. Поэтому добирались на поезде, который оказался удивительно похожим на московский Аэроэкспресс. Тут впервые столкнулся с европейскими ценами. В свете крайнего курса рубля — совсем грустно. Поезд от аэропорта до центрального вокзала — почти 5 €. Такси от гостиницы до аэропорта — 45-55 € (в зависимости от количества человек). Час катания на трамвае — 3 €.
Central Station
В общественном транспорте не оплачиваются поездки, оплачивается время. Есть абонементы на час, на день, на неделю... Час поездки означает, что можно кататься сколько угодно, но после первого входа в трамвай, повторно войти (в другой трамвай) можно будет только в течение часа. В трамвае есть турникеты на вход (передняя дверь) и на выход, к ним нужно прикладывать билет. А вот каких-либо турникетов на поезде обнаружить не удалось. Есть загадочный жёлтый столбик у эскалатора на платформу, который пикает и мигает иногда зелёным, если к нему приложить билет. И всё. Никаких контролёров. Есть, правда, турникеты на выход, но они, похоже, всегда выпускают.
Выпить кофе, закусить — 5 €. Впрочем, кофе почти везде настоящий и вкусный. Пообедать одним блюдом — 10 €. Сходить в музей — 15-17 €. Взять в аренду велосипед на сутки — 10-15 € плюс залог в 100 €. Кило картошки — 2 €. Кило бананов — в три раза дешевле.
Canal
Первое впечатление от Амстердама: да он же как Питер. На самом деле — наоборот. Сначала Пётр I погулял по Амстердаму, а потом вернулся домой, повоевал и основал город на Неве. Много каналов (больше, чем в Питере). Маленькие домики (меньше, чем в Питере). Невероятной красоты старые здания.
Уже через пару дней понимаешь, что вся эта красота — декорации. Центральный вокзал более функционирует на подземных платформах, чем в здании центрального вокзала. Пару раз видел, как делают капитальный ремонт в этих стареньких зданиях о трех окнах на фасаде в ширину и в три этажа в высоту. Домики лепятся вплотную друг к другу и всё это живо напоминает застройку Бруклина. А для ремонта, видимо, чтобы влепить на первый этаж другое кафе, выносят всё насвозь. И оказывается, что несущей конструкцией дома являются стальные балки, как в небоскрёбах, а всё, что между ними — красивые кирпичные фасады — лишь декорация. С тем же успехом фасады могли быть картонными, может, где-то оно так и есть. Полагаю, пару сотен лет назад балки были деревянными.
Rebuilding
А на каналах, и, в особенности, на реке Амстел (да, где-то тут делают одноимённое пиво) во множестве имеются домики на воде. Иногда это баржи размером с квартиру. Иногда настоящие судёнышки с якорем и штурвалом. Иногда к одному причалу пришвартованы даже два судёнышка: одно используется как дом, а на другом растёт целый садик в кадках. В половине кадок растёт конопля. В любом случае у дома на воде есть номер, калитка, почтовый ящик, место для стоянки велосипедов. И все дома подключены к электричеству, водопроводу и даже канализации.
Cannabis garden
Для туристов поблизости от кофешопов продаётся всё что угодно. Пиво, конфеты, печеньки, леденцы, энергетические напитки. С коноплёй.
Cannabis forever
Правят в этом городе велосипедисты. Утром все местные едут на велосипедах на работу. Вечером едут с работы. Создавая даже велосипедные пробки, когда перед светофором пятьдесят метров велосипедной дорожки забиты велосипедами в два ряда. Едут дамы в юбках и колготках. Едут серьезные мужчины в костюмах, с папками документов. Едут ремонтники в спецовках, побрякивая инструментами.
На каждой маломальской улочке есть велосипедные дорожки, как правило отдельные для движения в соответствующие стороны. Дорожки соседствуют с тротуарами, так что выходить из здания надо очень аккуратно. Тротуары довольно узкие и можно ненароком встать на велодорожку, что чревато. Сначала обругают, потом задавят, потом снова обругают. Иногда даже для тротуара места нет, если улочка идёт по берегу канала. Вплотную к воде находится автостоянка, с заметным наклоном к воде, ограждённая лишь несерьёзной трубой ниже бампера. Наверное, регулярно кто-нибудь роняет машину в канал. Потом идёт узенькая полоска деревьев, вдоль неё не пройти, если не умеете проходить сквозь стволы. Потом велодорожка. Потом одна полоса для автомобилей. Потом другая велодорожка. Потом лишь, прижимаясь к фасадам домов, идет тротуар.
2m width
Машины тоже притесняют. На маленьких улочках только один ряд, даже встречается знак ограничения ширины, в два метра ширины. На дорогах побольше место отдано трамваю. Бывает даже, что на улице есть только велодорожки и трамвайные пути. Иногда даже один трамвайный путь в обе стороны, местами раздваивающийся, чтобы встречные трамваи могли разъехаться. И автомобили ездят по трамвайным путям, не стесняются. Но никогда не заезжают на велодорожки. Велосипедисты этого не простят.
На машинах тут вообще не ездят. Ну разве что всякие мусоровозы и дорогомойки. Или грузовички в магазинчики. Но в более богатых жилых кварталах полно припаркованных авто. Часто встречаются парковки с электрозарядкой. Стоят всякие мерседесы да ауди, а спереди, под звездой или кольцами, у них — розетка. И они этой розеткой подключены к розетке на стоящем рядом светящемся столбике. Пару раз видел заряжающуюся Теслу. Электрический зарядный кабель никого не интересует.
Tesla
А вот велосипеды, похоже, интересуют. Велосипеды паркуют где попало и как попало. Часто поперек тротуаров. Хотя часто встречаются нормальные парковки. А возле станций метро — целые поля велосипедов. Паркуют серьёзно. Один замок, встроенный в конструкцию велосипеда, — на заднее колесо. Солидная цепь, с металлом в палец толщиной, в тряпошном чехле, чтобы не царапала, — на переднее колесо, велосипед и штуковину, к которой припарковался. Во время движения цепь наматывается под сидушку.
Велосипеды встречаются разные. Но не очень спортивного вида. Почти всегда с тройкой передач. Встречаются дребезжащие и разваливающиеся на ходу экземпляры. Для перевозки грузов и детей популярны модели с тачкой впереди. Реальная деревянная тачка с лавочкой. На лавочку тесно садится до двух детей. Иногда дети еще накрываются сверху тентом от дождя. А один раз видел меготачку, на которой вывезли сразу половину детсадовской группы в парк погулять. Тачка с велосипедным приводом. Другая половина группы ехала в подобной же тачке, но с электроприводом.
Children bike
Велодорожки есть везде, поэтому нелюбители крутить педали выбирают транспорт, который тоже может ехать по велодорожкам. Часто встречаются мотороллеры. Бывают и спортбайки, но они всё же носятся по автодорогам. Много удивительных малюсеньких авто на полтора места, тарахтящих двигателем, нагло сруливающих на велодорожки, если надо, и бодающих велосипедистов.
Три креста по-вертикали, которые встречаются здесь буквально на каждом столбе — с герба города. Так что Амстердам тоже можно назвать городом трёх шурупов.
XXX
В первый день нам достался квест, где надо было побегать по центру и пофотаться. Получилось слишком быстро и сумбурно. Поэтому самые туристические места как-то не отложились в памяти. Да и этих самых туристов там было больше, чем хотелось бы. Да, площадь Дам, где, оказывается, и королевский дворец, и музей мадам Тюссо, и национальный монумент. На половинке площади ближе к королевскому дворцу стоял маленький клон Дарта Вейдера и за два евро позволял с ним сфотографироваться. Да, площадь Рембрандта, где стоит памятник, оказывается, самому Рембранту. А эта куча бронзовых мужиков с мушкетами, оказывается, — персонажи «Ночного дозора» того самого Рембрандта.
Rembrandt
А потом мы пошли в музей Ван Гога. Ну это тот рыжий психованный художник, который с Доктором еще встречался. Стояние в очереди в музей заняло всего лишь полчаса. Хождение по музею, созерцание работ Винсента Ван Гога и Эдварда Мюнха (который «Крик» нарисовал) оставили удручающее впечатление. Впрочем, «Звездная ночь» — хороша. Но на чудесной зеленой лужайке перед музеями совершенно невозможно было грустить. Так что всё хорошо.
Green grass
В Европе, ну как минимум в Нидерландах, всё ближе к Родине, чем, например, в Нью-Йорке (который на заре освоения назывался Нью-Амстердам, кстати). Розетки такие же. Унитазы работают таким же привычным образом. Нигде так же не надо давать чаевых, за исключением ресторанов.
Зато они тут напрочь лишены стеснительности. Если верить Википедии, в современном нидерландском языке почти отсутствует разница между мужским и женским родами, т.е. есть человеки и оно. Видимо поэтому однополые браки здесь разрешены аж с 2001 года. На рынке среди шоколадок встречаются шоколадные члены. В музеях встречаются экспозиции, которые наглядно рассказывают детям, откуда они берутся (12+). Даже в зоопарке на табличке про зебр среди прочих аспектов их дикой жизни показано и размножение. Шторы тут отсутствуют как класс. Тут довольно пасмурно, и, видимо, от солнца никто не закрывается. А ночью чего стеснятся. Поэтому идешь по вечернему Амстердаму, и можешь пялиться во все окна, кто там как ужинает и прочее. Хорошо, что в номере гостиницы на первом этаже шторы всё же были.
Modern yard
На следующий день я рванул в другую сторону в другие музеи. По пути увидел более современную застройку: многоквартиные домики в три-четыре этажа с милыми балкончиками и каменными вымощенными двориками. Где-то по пути ко мне пристал чёрный, на прекраснейшем английском выклянчивал пару евро на метро, доехать до дома, мол, прогулял всё с друзьями и на обратный путь денег нет. Абсолютно так же в Омске клянчат десятку на автобус. Но у меня двух евро в кармане не было.
Монетки тут вообще в ходу. Автоматы по продаже всяких газировок да шоколадок принимают только монеты. Всякие камеры хранения принимают в залог только монетки в пятьдесят евроцентов. Пятьдесят евроцентов здесь вообще как четвертак в Америке — самая ходовая монета. А вот во всех остальных местах проще расплатиться картой, чем наличными. Cash only не встречал.
Музей Nemo. В странном модерновом здании. С «кривыми» окнами изнутри. Лучший образовательный музей для деток, который я видел. Дети тут повсюду, носятся, кричат. Чтобы самому добраться до экспонатов нужно либо долго ждать, либо отгонять малышню. Тут есть штуки, демонстрирующие законы физики, механики, химии, геометрии, биологии.
Nemo
Мне понравились некоторые штуки. Водоворот с ручным приводом в прозрачном цилиндре воды. Волшебный крутящийся столик с какой-то жидкостью под поверхностью, которая сворачивается в замечательные вихри. Громадная машина Голдберга, где разноцветные шарики бесконечно бегают, сортируются, пересыпаются. Некоторые этапы «переработки» шариков требуют ручного вмешательства, например, отсортировать шарики по цвету, размеру и весу. За правильно выполненное задание начисляют очки. Интерактивненько. Водные потоки в стальном русле, которые можно перегораживать разными мешочками и загородками, чтобы добиться более высокой скорости вращения турбинки, чем у соседней команды. Громадная мозаика на медленно вращающемся пятиметровом колесе, куда можно вкладывать оранжевые треугольники. Набегают пацаны, и выковыривают треугольники. Набегают девчонки, и вставляют треугольники назад. Много всяких механических передач, которые можно покрутить, и посмотреть, как они устроены и работают. «Каменная» арка-мостик, где можно убедиться, что такая простая конструкция способна выдержать твой вес. Будете с детьми в Амстердаме, зайдите в Nemo, там можно застрять на целый день.
Ship
Рядом с Nemo на причале стоят вполне функциональные на вид парусники начала XX века. А чуть дальше — музей судоходства (Scheepvaartmuseum). Внутри — куча моделей кораблей, картины морских сражений, мимимишные карты и атласы XVII века и интерактивные экспозиции, посвященные роли морского транспорта в современной экономике. Ну а самая прелесть этого музея — реплика VOC-ship. VOC — это Verenigde Oostindische Compagnie, Голландская Ост-Индская компания. Настоящий деревянный большущий парусный корабль. Почти такой же, какие в XVII-XVIII веках плавали из Нидерландов в Индию и Индонезию. Почти такой же, какой своими руками строил наш Пётр I, когда немножко работал, в образовательных целях, на верфях этой самой Ост-Индской компании.
Корабль хоть и реплика, но выглядит вполне достоверно. Всё сделано из деревянных балок, а мачты — самые настоящие деревянные стволы. И почти по всему этому великолепию можно самостоятельно полазать. Посмотреть, какие там трюмы, где хранился провиант и порох. Удивиться маленьким размерам тогдашних коек, и жутковатым пилам в каюте хирурга. Потыкать кнопочки — типа пострелять из пушки. Прикоснуться к штурвалу. Снисходительно помахать с верхней палубы ручкой туристам, проплывающим вплотную под бортами твоего корабля на недостойных прогулочных водных трамвайчиках. В дождь. В плаще с капюшоном. Им бы еще организовать лазание по вантам, со страховкой, ваще был бы огонь.
VOC captain
Следующий день был посвящен государственному музею. Музей большой. Музей хороший. Рембрандт мне нравится гораздо больше Ван Гога. Всё же ближе к реализму и гораздо оптимистичнее. «Ночной дозор»? Ну «Ночной дозор». Читать историю этой картины в Википедии было интереснее, чем созерцать её саму.
Holland
Мне больше всего понравился самолёт. На третьем этаже музея. Настоящий британский F.K.23 Первой Мировой войны. Биплан из досок и тряпок. С мотором-звездой. Красота.
BAT
А еще у музее есть потрясающая многоэтажная библиотека, похоже, функционирующая.
Library
После музея мы забурились в парк. Это оказался Vondelpark. Ивы, дубы, тропинки, озера, мостики. Всё как в приличном парке, над которым поработал ландшафтный дизайнер. Оказывается, та странная скульптура рыбочеловека — работы Пикассо. Конструкции для лазания на детских площадках в парке (да и везде в Амстердаме) потрясают своими размерами и высотой. Деткам, пожалуй, нравится, а родителям, наверное, боязно. По окраине парка обнаружились частные домики, в основном каких-то адвокатов. Внутренние дворики этих домиков отделены от парка лишь не сильно широким каналом.
Kinder place
Утро последнего дня я посвятил зоопарку. Natura Artis Magistra. Самый крутой зоопарк из тех, что я видел. Зоопарк маленький, почти в самом центре Амстердама. Но на такой площади они умудрились разместить животных так, что ограждений между ними и тобой почти не заметно. И вообще, у меня сложилось впечатление, что многие звери мне позировали :)
Wolf
Можно безо всяких заборов посмотреть на жирафов и зебр, с мостика. Почти нос к носу посмотреть в глаза белому волку. В душном и жутко влажном (так положено) павильоне можно пристально поразглядывать огромных бабочек. Есть маленький, но очень шумный и мокрый водопад. Есть прозрачный улей, где можно вживую видеть, как пчелы делают соты и наполняют их мёдом. Есть неплохой аквариум, где амстердамцы подшутили над собой, и поместили автомобильные капоты и ржавый велосипедный остов в аквариум с местной фауной. Где-то тут рядом был еще планетарий и выставка про микробов, но я на них не попал.
Lama
После обеда мы устроили финальную велопрогулку. На арендованных велосипедах. В настоящий средневековый замок Muiderslot, что в городке Muiden, километрах в десяти от Амстердама. Оказалось, что я совершенно не умею ездить на велосипеде. Еле-еле выдержал поездку. И никаких сил не было любоваться окрестностями.
А любоваться было чем. Ехать на велосипеде по городу действительно приятно. Лишь пара светофоров попалась, на которых пришлось подождать, а так можно ехать почти без остановки. Город плавно переходит в парк. А за парком начинается сельская местность. Вообще не ожидал, что в тесной Европе бывает сельская местность. Малюсенькие одинокие домики. Зеленые луга с коровками и овечками. Одинокие ветряки на горизонте. Здоровенный Rijnkanaal, по которому постоянно идут приземистые грузовые суда. На корме каждого, на крыше рубки, почему-то стоит автомобиль.
Muiden выглядит в точности как Амстердам, только маленький. А Muiderslot — это настоящая средневековая крепость. С башнями и узенькими лестницами. С бойницами и дырками, чтобы кидать на атакующих всякую гадость. С каменными клозетами с дыркой на улицу и деревянной крышкой. Внутри устроен милый музей со всякими интерактивными развлечениями вроде прыгания в седле или стреляния из лука.
Castle
Потом мы вернулись в Амстердам и рванули в Схипхол. На такси. Таксист совершенно очаровательно разговаривал по-английски так же плохо, как по-нидерландски. «Where are you from? Russia? Siberia? Is it cold there? Aeroflot? You need this terminal, my friends.»
Схипхол оказался первым аэропортом на моей памяти, где всё закрывается на ночь. Совсем нечего поесть. А чтобы пассажиры не заснули, тут меняется яркость освещения. Начинаешь клевать носом, и вдруг наступает «утро», лампы начинают гореть ярче. Может, глюк.
Удивительно, но за время этой поездки ни один таможенник не сказал мне ни слова. И туда, и обратно.
Два трехчасовых перелёта, во время которых даже как-то удалось немного поспать. И мы снова дома.
Sea