Привет, дорогой читатель! Думаю, ты точно знаешь, что такое API и как сделать, чтобы твои изменения были API-совместимыми. На самом деле я сам никогда не задумывался о том, что существует ABI-совместимость, пока не столкнулся с разработкой библиотеки.  У нас в Альфе есть библиотеки, которые используются несколькими проектами. Про это у меня был доклад на Mobius. При разработке этих библиотек мы всегда думали об API-совместимости, и это логично, но не задумывались о вопросе ABI-совместимости, а это довольно важный вопрос. Эту тему я раскрывал в другом докладе на Mobius. Сейчас расскажу, почему этот вопрос стоит вашего внимания.  API — application programming interface, например, исходный код нашего приложения. ABI — application binary interface, например, класс-файлы, которые были сгенерированы на основе нашего исходного кода. Почти всегда ABI напрямую связан с API, так как ABI — это скомпилированная форма API. Но эта связь не всегда работает так, как мы ожидаем. Давай рассмотрим несколько практических примеров:  В данном простом примере ты можешь обратить внимание, что пропала информация о дженериках. Теперь мы с тобой поняли, что API не всегда равно ABI.  Давай рассмотрим простую схему, как библиотека может подключаться в проект, и будем эту схему расширять:  Пока диспозиция несложная, есть наш проект, он подключает в себя Library A версии 2.0.Ок, давай пойдем дальше:  Мы с тобой подключили в наш проект библиотеку B, которая в свою очередь подключает в себя библиотеку A с версией 1.0. Мы получаем конфликт версий у библиотеки A, который в дефолтной стратегии Gradle решается таким образом, что в Runtime classpath у нас остаётся библиотека A версии 2.0.  Ок, сейчас мы закрепили с тобой, что Gradle оставит библиотеку A более высокой версии. И именно в этот момент вопрос бинарной совместимости становится для нас максимально острым. А почему так ? Давай разбираться дальше.  Давай представим, что мы разработчики Library A и в нашей библиотеке в версии 1.0 есть вот такой класс:  Теперь нам понадобилось добавить в него новое поле и выпустить библиотеку версии 2.0:  А в Library B у нас есть вот такой код:  Давай остановимся и подумаем. Будут ли у нас проблемы, если мы запустим наше приложение и исполнение дойдёт до кода с инициализацией дата-класса? Сектор «Краш в рантайме» на барабане! Посмотрим на стектрейс этого краша:  Теперь попробуем разобраться, почему всё-таки мы поймали краш. Вернёмся на шаг назад. Если мы сделаем в API несовместимые изменения, клиент нашей библиотеки не сможет скомпилировать своё приложение с новой версией нашего кода, ему нужно будет внести правки. В примере выше с дата-классом мы сохранили API-совместимость, так как добавили дефолтное значение новому параметру. Так что с точки зрения компиляции у нас проблем не будет. Мы сделали API-совместимое изменение, которое ломает ABI-совместимость (бинарную совместимость). И вот эту проблему наш клиент уже не отловит в компайл-тайме. Она отстрелит у него в рантайме, как на скриншоте выше. А всё потому, что мы не перекомпилировали Library B, она всё ещё скомпилирована с Library A версии 1.0 и теми сигнатурами конструкторов и методов, которые были в этой версии. Сигнатура конструктора нашего дата-класса поменялась, и Library B крашнулась, потому что в рантайме не нашлось подходящего конструктора класса MarkdownModel.У меня даже есть быстрое решение, давай добавим @JvmOverloads в конструкторе класса:  Запускаем, ну теперь-то точно сработает:  Опять не сработало, но теперь мы получили новую ошибку. Что же тут не так ? Взглянем на сгенерированный метод copy до нашего изменения:  А теперь посмотрим на метод copy после нашего изменения:  Если ты обратишь внимание, сигнатура метода copy поменялась, и JvmOverloads аннотация нас не спасла. Library B просто не знает про такую сигнатуру и вполне закономерно выбросит нам exception NoSuchMethodError. Хуже всего, что в дата-классе добавление нового поля в праймари конструктор всегда равно бинарно несовместимым изменениям. Но когда только начинаешь делать библиотеку, это не совсем очевидная история. И хуже всего то, что такие проблемы не отлавливаются компилятором (они и не могут быть им отловлены). Клиенты нашей библиотеки получат краш в рантайме. Допустим, в Library A версии 1.0 есть вот такой интерфейс:  В Library A версии 2.0 в этот интерфейс добавилось новое поле LONG_TAP:  В Library B есть вот такой метод:  А внутри нашего My Awesome Project есть кусочек кода:  Фух, ну вот мы и собрали кейс. Будут ли у нас проблемы в такой схеме ?  Не буду тебя долго томить. Проблема будет. Это кажется странным, но добавление нового публичного поля может стать ломающим бинарную совместимость изменением ¯_(ツ)_/¯.  Думаю, нет смысла объяснять почему именно, схема примерно такая же: Давай подведём черту:  API и ABI — это не всегда одно и то же. Если мы с тобой пишем библиотеку, нужно всегда думать о бинарной совместимости. Бинарная совместимость может ломаться в самых неожиданных местах. Однако в своём докладе на Mobius я рассказывал, как детектить такие кейсы. Мы для этого пользуемся своей надстройкой над плагином от JetBrains.   The Binary Compatibility Challenge  Thiago Macieira: Binary compatibility for library developers Differences Between APIs and ABIs Public API challenges in Kotlin Абакар Магомедов — Артефакты, или Туда и обратно. Использование внутренних библиотек между проектами Абакар Магомедов — Ошибки при проектировании публичного API наших библиотек, и как мы их решали  Android TechLead