До недавнего времени у Dropbox была техническая стратегия использовать общий код C++ для мобильных приложений iOS и Android. Идея понятна: написать код один раз на C++ вместо его дублирования отдельно на Java и Objective C. Мы приняли эту стратегию ещё в 2013 году, когда группа инженеров мобильной разработки была относительно небольшой и приходилось быстро развивать продукт. Такое решение позволило выдавать большой объём кода как на Android, так и на iOS силами маленькой команды.

Теперь мы полностью отказались от этой стратегии в пользу родных языков каждой платформы (в первую очередь Swift и Kotlin, которые не существовали, когда мы начинали). Решение связано с (не очень) скрытыми издержками на совместное использование кода.

Все проблемы вытекают из главного: оверхед оказался больше, чем просто написать код два раза.

Прежде чем анализировать различные типы оверхеда, хотелось бы пояснить, что мы так никогда не дошли до момента, когда бoльшая часть кодовой базы велась на C++. Издержки фактически помешали двигаться в этом направлении.

Также стоит отметить, что гораздо более крупные компании, такие как Google и Facebook, уже несколько лет разрабатывают масштабируемые решения для совместного использования кода. Такие решения пока не очень распространены. Хотя сторонние системы вроде React Native или Flutter позволяют избежать части накладных расходов, некоторые издержки всё равно остаются (по крайней мере, до тех пор, пока одна из этих технологий не станет популярной и не созреет в достаточной степени). Например, Airbnb отказалась от использования React Native во многом по тем же причинам, что изложены в этой статье.

Все издержки можно сгруппировать по четырём основным категориям.

Оверхед пользовательских фреймворков и библиотек


Проще всего прогнозировать издержки на создание фреймворков и библиотек. Они примерно разбиваются на две подкатегории:

  • Фреймворки, которые позволяют взаимодействовать с хост-средой для создания полноценного мобильного приложения. Например:
    • Djinni, инструмент для создания межъязыковых объявлений типов и интерфейсов соединения
    • Фреймворк для выполнения задач в фоновом режиме против основного потока (тривиально на родных языках платформы)

  • Библиотеки на замену языковым стандартам или решениям open source, которые можно было использовать в родных языках, например:
    • json11 для (де)сериализации JSON
    • nn, ненулевые указатели для C++

Ничего из этого не нужно, если оставаться на родных языках платформы. И наше участие в проектах open source на родных языках, вероятно, принесло бы больше пользы разработчикам. В сообществе C++ культура open source была (и есть?) не так развита, как в сообществе мобильных разработчиков, тем более что мобильного сообщества C++ практически не существует.

Обратите внимание, что эти издержки особенно высоки для C++ (в отличие от других возможных неродных языков, таких как Python или C#), потому что здесь нет одной полнофункциональной стандартной библиотеки. При этом только у C/C++ есть компилятор, поддерживаемый как Google, так и Apple, поэтому переход на иной язык порождает целый ряд других проблем.

Оверхед нестандартной среды разработки


В мобильной экосистеме много инструментов для повышения эффективности разработки. Мобильные IDE очень функциональны, а Google и Apple вложили много ресурсов, чтобы сделать их идеальными на своих платформах. Отойдя от дефолтов, мы отказываемся от некоторых преимуществ. В первую очередь, отладка на родном языке обычно превосходит отладку C++ в IDE по умолчанию.

Мне особенно запомнилась одна ошибка, которая вызывала блокировку фоновой потоковой структуры, что приводило к случайным сбоям приложения. Такие ошибки трудно отследить даже с простым, стандартным стеком. Поскольку проблема включала отладку многопоточного кода, работающего между C++ и Java, на её отслеживание ушли недели!

В дополнение к потере стандартного инструментария, пришлось инвестировать время в создание своих инструментов для поддержки общего кода C++. Самое главное, требовалась кастомная система сборки для создания библиотек, которые содержат код C++, а также оболочки Java и Objective-C. Она должна генерировать цели, понятные и Xcodebuild, и Gradle. Создание такой системы отняло у нас много ресурсов, поскольку её приходилось постоянно обновлять для поддержки изменений в двух системах сборки.

Оверхед на устранение различий между платформами


Хотя iOS и Android являются «мобильными приложениями», которые обычно предоставляют одни и те же функции, в самих платформах есть определённые различия, которые влияют на реализацию. Например, как приложение выполняет фоновые задачи. Даже похожие вещи со временем могут начать сильно отличаться (например, взаимодействие с камерой).

В результате нельзя просто так написать код один раз и запустить его на другой платформе. Нужно потратить много времени на интеграцию и кодирование под конкретную платформу, и иногда этот код заканчивается прямо на уровне C++!

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

Оверхед на найм, обучение и удержание разработчиков


Последнее, но не менее важное, — это стоимость обучения и/или найма разработчиков для работы с нашим очень своеобразным стеком. Когда Dropbox начал использовать эту мобильную стратегию, у нас была основная группа опытных разработчиков на C++. Эта группа запустила проект C++ и обучила других мобильных разработчиков.

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

В результате мы столкнулись с реальной нехваткой критических знаний для поддержания кодовой базы C++. Оставалось только два варианта, и каждый требовал существенных усилий:

  1. Найти и нанять кандидатов с очень специфическим набором навыков (мы безуспешно пытались в течение года).
  2. Обучить собственных мобильных (или C++) разработчиков, что практически невозможно сделать в отсутствие сеньоров с нужным набором навыков для выполнения обучения. Даже когда основная группа ещё не разошлась, мобильные разработчики обычно не интересовались C++, поэтому поиск людей для обучения также представлял большую проблему.

Помимо найма, выпуск собственного технологического стека создал проблему удержания — мобильные разработчики просто не хотели работать над проектом C++. Это заставило многих талантливых инженеров покинуть проект вместо того, чтобы продолжать мучиться с плохо поддерживаемым кастомным стеком. В целом, сообщество мобильных разработчиков очень динамично — новые технологии и модели появляются часто и внедряются быстро. Лучшие инженеры любят поддерживать навыки в актуальном состоянии.

Зрелому продукту со стандартным стеком нелегко идти в ногу со временем. Вы жертвуете новизной ради стабильности. Эта проблема значительно увеличивается, если заблокировать себя в кастомном стеке за пределами более широкой мобильной экосистемы.

Вывод


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

Кроме того, мы хотим, чтобы наши инженеры хорошо себя чувствовали и могли вносить вклад в сообщество. Именно поэтому мы приняли решение привести нашу практику в соответствие с отраслевыми стандартами.

Комментарии (4)


  1. dvrsh
    16.08.2019 14:11

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


    1. KanuTaH
      16.08.2019 14:18

      Ну не знаю, а в чем идея? У всего есть границы применимости. Если автор признает, что даже в 2013 году, когда еще по сути не существовало инструментов для кроссплатформенной мобильной разработки, и по сути приходилось на все педалить свой велосипед,

      Такое решение позволило выдавать большой объём кода как на Android, так и на iOS силами маленькой команды

      то сейчас, когда есть Qt, Flutter, React Native, для разработки продукта на начальном этапе силами небольшой команды использовать что-нибудь из этого — самое то. Потом, когда продукт и его команда вырастут и начнут приносить деньги, тогда можно будет уже перейти на нативные технологии (если будет смысл в этом к тому времени, конечно).


  1. mOlind
    16.08.2019 15:41

    Расскажу свою историю: Мы делаем фреймворки для офлайн карт, навигации, поиска на iOS и Android. Код максимально шарится между платфомами. Приведу пример: Для того чтобы показать вьюху с картой на стороне ОС мы обрабатываем касания, реализуем API для взаимодействия с компонентом и создаем surface для рендера. Весь остальной код пишется на C++. И там его очень много. Парсинг данных, парсинг стиля, применение стиля к данным, подготовка данных для OpenGL/Metal, весь сетевой стек, очереди фоновых задач, обработка данных пользователя. API, которое видит разработчик — это просто верхушка айсберга. Это с одной стороны. Много сложного кода, его лучше писать один раз. И ошибки ловить один раз.
    При этом мы делаем приложения Guru Maps на своих же фреймворках. И тут ситуация обратная весь интерфейс и большинство кода этих приложений пишутся так, как это принято на конкретной архитектуре. Общий код на C++ тоже есть, но его мало. Это работа с данными и конверсия между бинарным форматом,GPX,KML в любую сторону.
    Категоричный вывод о том что общий код на C++ вреден — я бы не делал. Он сильно облегчает жизнь. Но это должно быть что-то такое, в чем нужна скорость и нет зависимости от специфичных для платформы плюшек.


  1. ababo
    16.08.2019 17:59

    Самое главное, требовалась кастомная система сборки для создания библиотек, которые содержат код C++, а также оболочки Java и Objective-C.
    Gradle отлично переваривает CMake-проекты.