Как за 7 дней превратиться из server-side-программиста в client-side
Фото: ivan dupont
Всем привет! Меня зовут Дмитрий Исайкин, и с недавних пор я занимаюсь мобильной разработкой. Почему с недавних? Еще три месяца назад я был руководителем группы C/C++-разработки Почты Mail.Ru. Но однажды я в очередной раз задумался о том, что мне всё меньше и меньше нравится то, чем я занимаюсь. Больше времени стали отнимать обязанности тимлида: мотивация, стимуляция, отчётность, административные заботы. И тогда я решил: пора что-то менять. Разместил резюме, сходил на несколько собеседований. Всё это время я размышлял о том, чего же я хочу. Мне предложили возглавить направление разработки в одной достаточно крупной фирме, и я практически согласился. Но, «переспав» с этим решением бессонную ночь, я понял, что такая работа будет мне не в радость. Ещё был вариант пойти старшим разработчиком в другую хорошую фирму. Но там пришлось бы заниматься примерно той же работой, что и в предыдущие годы.
То ли в шутку, то ли всерьёз я предложил перейти в другой отдел на позицию iOS-разработчика (это была совершенно незнакомая мне область). Я давно хотел заняться чем-то новым, но меня останавливало неизбежное в этом случае падение доходов. А при внутреннем переходе падения зарплаты можно было избежать. Произошло чудо — меня взяли. Без собеседования, без испытательного срока (на самом деле собеседование всё же состоялось через неделю, в течение которой я по 12 часов в сутки читал документацию, туториалы, Хабр и различные статьи о разработке под iOS). Таким вот образом я, пробыв почти десять лет разработчиком высоконагруженных серверных решений, открыл для себя совершенно новый, чудный и огромный мир клиент-сайда.
Сейчас, спустя три месяца, могу твёрдо заявить, что сделал правильный выбор. За это время я узнал много нового, начал постигать премудрости клиентской разработки. Взглянул на сервер «с другой стороны баррикад» (раньше я занимался серверной частью почтовых сервисов Mail.Ru и My.com, теперь же разрабатываю их мобильные версии).
Разработка сервера и клиента во многом похожи. Но есть и достаточно существенные различия, которые требуют совершенно разных подходов при решении одних и тех же задач. Я уверен, что ещё не скоро стану гуру client-side-разработки, но всё же решил поделиться своими размышлениями по этому поводу. Заметьте, целью данной статьи ни в коем случае не ставилась попытка выяснить, что круче — клиент или сервер. Напротив, я попробую показать, что каждый из этих миров своеобразен, и что разработчик может очень много выиграть от перехода из одной области в другую.
Любая более-менее сложная серверная система состоит из достаточно большого (несколько десятков) количества связанных между собой компонентов. Это базы данных (у нас это были в основном MySQL и Tarantool), очереди, веб-серверы, прокси-серверы, балансировщики, системы мониторинга, резервирования, куча самописных сервисов с нужной нам бизнес-логикой. При разработке нового функционала часто требуется поменять код сразу нескольких компонентов. Для проверки и отладки кода удобно иметь собственную инсталляцию всей системы. Её необходимо развернуть, настроить, наполнить тестовыми данными. А потом периодически обновлять и пересобирать, так как многие компоненты находятся в активной разработке. Кстати, из сложности и гетерогенности серверной системы следует необходимость как-то этой сложностью управлять. Это всевозможные системы развертывания, управления, конфигурирования, мониторинга, диагностики, сбора, анализа и хранения логов. Подчас на разработку этих инфраструктурных компонентов приходится тратить львиную долю сил и времени.
При разработке клиентского мобильного приложения основная часть этой сложности прячется за серверными API. В большинстве случаев в качестве сервера для тестовой версии приложения выступает production-кластер, поэтому от клиентского разработчика не требуется никаких усилий по его поддержке. Всё, что нужно для разработки, — это достаточно мощный ноутбук с установленными SDK и IDE. Ну и тестовый телефон.
Как часто это случается в серверной жизни: по какой-то причине (массовые почтовые рассылки на День святого Валентина, или оптимизаторы поискового трафика постарались, или менеджеры купили трафик, или начались крупные распродажи — конкретные сценарии зависят от специфики сервиса) происходит серьёзный наплыв пользователей. Нагрузка на серверную систему возрастает. Одна из подсистем (обычно это база данных) немного деградирует из-за увеличившейся нагрузки, начинает медленнее отвечать или вообще перестаёт укладываться в приемлемое время ответа. Вследствие этого возрастает общее время обработки одного запроса на сервере бизнес-логики, что, в свою очередь, приводит к увеличению числа одновременно работающих процессов-воркеров. На сервере заканчивается свободная память, операционная система, пытаясь обслужить возросшее количество процессов, теперь в основном занимается переключением контекстов и перетасовкой страниц в памяти. Это ещё больше ухудшает положение. Тут в дело включается балансировщик, выкидывая из нагрузки затупивший сервер и тем самым увеличивая нагрузку на оставшиеся в строю серверы. Поздравляем, наш сайт прилёг, и поднять его без снятия нагрузки будет очень проблематично.
Понятное дело, подобные ситуации возникают обычно только в самом начале развития нового сервиса. В дальнейшем либо разработчики исправляют недостаток системы, либо сервис постепенно загибается.
Чтобы успешно переживать такие эксцессы, серверное ПО должно быть достаточно «эластичным» и масштабируемым. Тогда рост нагрузки приведёт не к отказу сервиса целиком, а, например, лишь к незначительной деградации по скорости обработки запросов. В таком случае временное повышение нагрузки можно просто переждать, а если нагрузка возросла навсегда (то есть сервис стал популярнее), достаточно компенсировать её, доставив необходимое количество серверов. Иногда для этого приходится серьёзно перерабатывать архитектуру всего приложения, целенаправленно выискивая и устраняя самые узкие места системы.
В мире клиентского ПО такой проблемы попросту нет. Каждый пользователь приносит с собой устройство со своим процессором и памятью. И сколько бы ни было инсталляций приложения, каждая его копия работает на своём устройстве.
Серверное ПО должно уметь переживать отказ оборудования. Если один из серверов вышел из строя, система обязана продолжить работать как ни в чём не бывало. Мало того, что не должны потеряться пользовательские данные — в идеале пользователи вообще не должны ничего заметить. Для достижения высокой доступности применяются такие приёмы, как репликация данных, серверы горячей замены, резервирование, дублирование серверов, устранение единых точек отказа. Упавшие из-за отказа сервера запросы могут быть перенаправлены на другой сервер группы (зависит от бизнес-логики).
А если, например, телефон выйдет из строя, никто не будет требовать, чтобы приложение продолжало работать на этом устройстве. Пользователь погорюет, конечно, но затем пойдёт и купит себе новый телефон — скорее всего, более крутой и мощный. Снова установит нужные ему приложения и продолжит работать. Главное, чтобы наше приложение не было виновато в поломке телефона.
Пожалуй, одно из самых главных нефункциональных требований к клиентскому приложению — это отзывчивость интерфейса приложения на действия пользователя. Причём существенны именно субъективные ощущения скорости отклика, а не реальное быстродействие. Пользователь не любит ждать: если он обратился к приложению, значит, именно сейчас ему понадобилось сделать нечто важное. И когда приложение тормозит, это раздражает. Замечали, как медленно CI-сервер начинает работать, когда вы уже собираетесь идти домой и просто хотите убедиться, что в этот раз тесты уж точно пройдут успешно? Подчас приходится жертвовать реальной скоростью работы, лишь бы создать у пользователя впечатление максимального быстродействия приложения. Для этого используются, например, анимация и индикаторы прогресса.
Для серверного ПО, напротив, важно умение обрабатывать огромное количество одновременных запросов (пользователей много, серверов мало), желательно с минимальным временем обработки запроса (латентность). Но время генерации ответа — отнюдь не главный параметр при оптимизации. Иногда незначительное увеличение времени обработки позволяет заметно улучшить пропускную способность системы в целом. Например, когда-то для меня было большим откровением, что некоторые клиенты для мемкэша откладывают выполнение запросов на несколько десятков миллисекунд, чтобы сгруппировать их в один запрос.
Большим плюсом серверной разработки для меня лично является возможность чёткого контроля версий ПО, работающих в продакшене. Обычно в бою одновременно крутится не больше двух версий (я говорю про in-house-разработку, с коробочными продуктами всё намного хуже). Это позволяет держать в голове один актуальный вариант кодовой базы. После обнаружения и исправления ошибки про неё можно благополучно забыть, не нужно бэкпортировать правки на другие поддерживаемые версии программы, код которых был уже многократно чуть ли не полностью переписан. Не нужно разбираться, присутствует ли там тоже этот баг и как его чинить в этой конкретной вариации кода.
Контроль боевых версий также позволяет относительно легко и слаженно, в несколько этапов, проводить переезд на новую схему данных. Обычно переезд выполняется в три стадии. Сначала выкатывается версия, умеющая читать данные в обоих форматах — и в старом, и в новом. После того, как все удостоверятся, что система работает в штатном режиме, без багов, её сменяет версия программы, которая, как и предыдущая, читает данные в обоих форматах, а вот пишет уже только в новом формате (на самом деле выкатка может заключаться просто во включении опции в конфиге — зависит от конкретного случая). Затем, после окончания полной конвертации данных, код поддержки старого формата полностью удаляется. Переезд завершён.
Итак, на сервере необходимо обеспечить корректное функционирование системы в ситуации, когда часть серверов уже обновили, и они хотят данные в новом формате, а часть серверов — ещё нет, и им подавай данные в старом формате. А вот на клиенте подобной проблемы с миграцией схемы данных вообще не существует. При обновлении приложения в удобный для пользователя момент (когда он сам согласился установить обновление и готов немного потерпеть) можно спокойно сконвертировать данные, в монопольном режиме последовательно применив к ним серию необходимых преобразований. То есть даже нет необходимости в разработке промежуточной версии. Красота.
Но вот зоопарк версий на клиенте точно есть. И мне, как бывшему серверному разработчику, это не нравится. Как только представлю, что кто-то, пользуясь приложенькой, натыкается на уже неделю или месяц как исправленный мною баг… Может, со временем я и привыкну, но пока аж в дрожь кидает!
Например, если на сервере обнаруживается критичная ошибка, мы можем быстренько сделать фикс, разработать комплекс мер по починке поломанных данных, и в короткий срок выкатить этот фикс на production-серверы. В случае мобильного приложения, даже если исправленная версия оперативно попадёт в стор, нельзя гарантировать, что пользователь кинется его обновлять. Кто-то экономит интернет-трафик, кому-то некогда обновляться, у кого-то обновления вообще отключены. А кое-кто мог и вовсе удалить «глючную приложеньку».
Современные мобильные телефоны имеют достаточно серьёзные вычислительные возможности, что позволяет разработчику сконцентрироваться не только на эффективности, но и на красоте кода. Также этому способствуют и популярные в клиентской разработке языки. Я рад, что познакомился с Objective-C именно сейчас, когда в языке уже повсеместно применяется автоматический подсчёт ссылок. С/С++ позволяют очень эффективно использовать ресурсы, и эта возможность оптимизации нередко приводит к излишнему усложнению алгоритма — лишь бы выделить память на стеке, а не в куче, лишь бы уменьшить количество вызовов виртуальных функций. Динамическая природа Objective-C, в котором практически отсутствуют возможности для преждевременной оптимизации, поначалу вызывает внутреннее сопротивление, но через некоторое время напряжение спадает, и у тебя начинает получаться более гибкий, красивый, лаконичный код.
Как видите, у клиентского и серверного программиста совершенно разные задачи. Их программы функционируют в совершенно разных условиях, к ним выдвигаются чуть ли не диаметрально противоположные требования. Соответственно, и решают они задачи по-разному. Различаются применяемые подходы, архитектурные решения. Даже взгляды на жизнь у нас отличаются, наверное.
Так стоило ли переходить на другую сторону баррикад? В моём случае — стоило. Часто в нашей жизни высокий профессионализм означает узкую специализацию. Чем больше человек погружается в свою область, тем более сильно «затачивается» под неё. Это естественный процесс. Со временем это, правда, приводит к тому, что человек перестаёт развиваться, узнавать новое. Его знаний и опыта полностью хватает для эффективного решения встающих перед ним задач. Ему становится скучно. В таком случае самым правильным и удачным выбором может стать смена профессии или хотя бы направления.
Итак, вы тоже стали замечать за собой потерю интереса к своей работе? Не торопитесь размещать резюме, для начала я бы посоветовал разобраться в причине. Решится ли проблема сменой работы? Может быть, вы сможете достичь своих целей и выполнения желаний на этой работе, к обоюдной пользе для себя и компании?
Например, вы считаете, что ваши результаты никто не замечает, вы жаждете известности, но она никак не приходит? Тогда имеет смысл выступить со своим докладом на профильной конференции или модном митапе. А может быть, вам надоело постоянно «косячить» и наступать на одни и те же грабли? Извлеките уроки и составьте план, как не допускать подобных ошибок в будущем. Или, например, вы хотите больше полномочий? Большинство руководителей (правильных) с радостью делегируют часть своих обязанностей, если их готов взять способный, надёжный и ответственный человек. Достаточно подойти и попросить. Но учтите, что бóльшие полномочия обычно подразумевают и бóльшую ответственность.
Если вы недовольны качеством выпускаемого вашей командой продукта или вам не нравится подход к разработке или тестированию, планированию релизов (а может быть, у вас и планирования релизов как такового не существует), то вполне может быть, что именно вы сможете всё исправить. Ну или хотя бы что-нибудь усовершенствовать. Проанализируйте, что нуждается в улучшении. Может быть, пора начать писать модульные тесты? Начните, напишите парочку тестов для своего нового функционала. Видите, что менеджер плохо сформулировал вашу задачу? Встретьтесь с ним и попытайтесь прояснить детали, обсудите видимые вами проблемы и недочёты.
Но, предположим, вы всё-таки поняли, что вам не нравится ваша профессия. Давайте выбирать. Главное — не пойти на поводу у моды. Современное общество диктует: конечно же, нужно идти на повышение, становиться начальником — будете попивать кофеёк и ничегошеньки не делать. Во-первых, это неправда — работать придётся очень много, просто обязанности будут другими. А во-вторых, даже если и так, долго ли вы сможете ничего не делать? Есть большая разница — ничего не делать две недели во время отпуска или же всю жизнь. Так что сначала разберитесь, хотите ли вы становиться начальником, брать на себя ответственность за других людей, или это просто дань моде. Очень часто люди оказываются на нелюбимой работе как раз из-за моды. На мой взгляд, самый лучший выход — подыскать вариант, в котором ваш предыдущий опыт позволит получить синергический эффект. Например, если вы были отличным менеджером по закупкам, ваш опыт может помочь вам стать отличным менеджером по продажам.
Программистам могу посоветовать попробовать силы в тестировании. Ваши навыки и знание специфики разработки помогут вам при соответствующем желании стать очень хорошим тестировщиком. По сути, задача тестировщика — убедиться, что программа удовлетворяет заявленным характеристикам. Задача программиста — сделать, чтобы программа им удовлетворяла. Задачи похожие, но разные. И проблемы возникают разные. И решать их нужно по-разному. Опыт в смежной области может очень сильно помочь. Аналогично тестировщик может попробовать свои силы в разработке. Его навыки тестирования и взгляд на систему с точки зрения обеспечения и контроля качества помогут ему стать очень хорошим программистом. Опять же при наличии желания и способностей.
Если вы тоже решили заняться клиентской разработкой под iOS, не откладывайте в долгий ящик. Для получения начальных знаний достаточно прочитать несколько руководств на официальном сайте Apple. Кроме того, на GitHub полно готовых проектов практически по всем тематикам. Читайте их код — это позволит вам быстрее и полнее понять практические аспекты и секреты разработки под iOS.