Цена удобства
Создание редактора кода, который мог бы использоваться как для небольших, так и для крупных проектов, — сложная задача. Многие инструменты решают эту проблему, выстраивая решение слоями и обеспечивая расширяемость. Самый нижний слой очень низкоуровневый и близок к базовой системе сборки, а самый верхний слой представляет собой высокоуровневую абстракцию, удобную в использовании, но менее гибкую. Таким образом, они делают простые вещи лёгкими, а всё остальное — возможным.
Однако Apple выбрала другой подход с Xcode. Причина неизвестна, но, скорее всего, оптимизация под задачи крупных проектов никогда не была их целью. Они чрезмерно вложились в удобство для небольших проектов, предоставили мало гибкости и жёстко связали инструменты с базовой системой сборки. Чтобы обеспечить удобство, они предлагают разумные значения по умолчанию, которые можно легко заменить, и добавили множество неявных поведений, разрешаемых во время сборки, что становится причиной многих проблем при масштабировании.
Явность и масштаб#
При работе в масштабе явность имеет ключевое значение. Она позволяет системе сборки заранее анализировать и понимать структуру проекта и зависимости, а также выполнять оптимизации, которые иначе были бы невозможны. Та же явность критична для надёжной и предсказуемой работы функций редактора, таких как предпросмотры SwiftUI или макросы Swift. Поскольку Xcode и проекты Xcode приняли неявность как допустимый дизайнерский выбор ради удобства — принцип, унаследованный Swift Package Manager, — трудности использования Xcode присутствуют и в Swift Package Manager.
Роль Tuist можно описать как инструмент, который предотвращает создание проектов с неявно определённой структурой и использует явность для улучшения опыта разработчика (например, валидации, оптимизации). Такие инструменты, как Bazel, идут ещё дальше, перенося этот подход на уровень системы сборки.
Эта проблема редко обсуждается в сообществе, но она значима. Работая над Tuist, мы заметили, что многие организации и разработчики считают, что текущие вызовы будут решены Swift Package Manager, но они не осознают, что, поскольку он строится на тех же принципах, хотя и смягчает хорошо известные конфликты Git, он ухудшает опыт разработчика в других областях и продолжает делать проекты неоптимизируемыми.
В следующих разделах мы рассмотрим реальные примеры того, как неявность влияет на опыт разработчика и здоровье проекта. Список не исчерпывающий, но он даст вам хорошее представление о вызовах, с которыми вы можете столкнуться при работе с проектами Xcode или пакетами Swift.
Удобство, которое мешает#
Общая директория собранных продуктов#
Xcode использует отдельную директорию внутри папки Derived Data для каждого продукта. В ней хранятся артефакты сборки, такие как скомпилированные бинарные файлы, файлы dSYM и логи. Поскольку все продукты проекта попадают в одну директорию, которая по умолчанию видна другим целям для линковки, вы можете получить цели, которые неявно зависят друг от друга. Хотя это может не быть проблемой при небольшом количестве целей, с ростом проекта это может проявиться в виде сбоев сборки, которые трудно отлаживать.
Следствием этого дизайнерского решения является то, что многие проекты случайно компилируются с плохо определённым графом зависимостей.
Tuist предоставляет команду для обнаружения неявных зависимостей. Вы можете использовать эту команду в CI, чтобы убедиться, что все ваши зависимости являются явными.
Поиск неявных зависимостей в схемах#
Определение и поддержка графа зависимостей в Xcode усложняется по мере роста проекта. Это сложно, потому что они закодированы в файлах .pbxproj как фазы сборки и настройки сборки, нет инструментов для визуализации и работы с графом, а изменения в графе (например, добавление нового динамического предварительно скомпилированного фреймворка) могут потребовать изменений конфигурации выше по потоку (например, добавления новой фазы сборки для копирования фреймворка в бандл).
В какой-то момент Apple решила, что вместо развития модели графа во что-то более управляемое целесообразнее добавить опцию разрешения неявных зависимостей во время сборки. Это снова спорный дизайнерский выбор, потому что вы можете получить более медленное время сборки или непредсказуемые сборки. Например, сборка может успешно пройти локально из-за некоторого состояния в Derived Data, которое действует как синглтон, но затем не скомпилироваться в CI, потому что состояние отличается.
Мы рекомендуем отключить это в схемах вашего проекта и использовать такие инструменты, как Tuist, которые облегчают управление графом зависимостей.
Предпросмотры SwiftUI и статические библиотеки/фреймворки#
Некоторые функции редактора, такие как предпросмотры SwiftUI или макросы Swift, требуют компиляции графа зависимостей из редактируемого файла. Эта интеграция между редактором требует, чтобы система сборки разрешала любую неявность и выдавала правильные артефакты, необходимые для работы этих функций. Как вы можете догадаться, чем более неявен граф, тем сложнее задача для системы сборки, поэтому неудивительно, что многие из этих функций работают ненадёжно. Мы часто слышим от разработчиков, что они давно перестали использовать предпросмотры SwiftUI из-за их ненадёжности. Вместо этого они используют либо примерные приложения, либо избегают определённых вещей, таких как использование статических библиотек или скриптовых фаз сборки, потому что они приводят к сбоям этой функции.
Объединяемые библиотеки#
Динамические фреймворки, хотя и более гибкие и удобные в работе, негативно влияют на время запуска приложений. С другой стороны, статические библиотеки запускаются быстрее, но увеличивают время компиляции и сложнее в использовании, особенно в сложных сценариях с графом зависимостей. Разве не было бы здорово переключаться между ними в зависимости от конфигурации? Именно так, вероятно, рассуждала Apple, когда начала работу над объединяемыми библиотеками. Но снова они перенесли больше логического вывода на этап сборки. Если вам сложно анализировать граф зависимостей, представьте, каково это, когда статическая или динамическая природа цели определяется во время сборки на основе настроек сборки в некоторых целях. Удачи в попытках сделать это надёжным, не сломав при этом такие функции, как предпросмотры SwiftUI.
Многие пользователи приходят к Tuist с желанием использовать объединяемые библиотеки, и наш ответ всегда одинаков: вам это не нужно. Вы можете контролировать статическую или динамическую природу ваших целей на этапе генерации проекта, что приводит к проекту, граф которого известен до компиляции. Никакие переменные не нужно разрешать во время сборки.
# The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project}
# to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value}
TUIST_DYNAMIC=1 tuist generate
Явность, явность и ещё раз явность#
Если есть важный негласный принцип, который мы рекомендуем каждому разработчику или организации, желающей масштабировать разработку с Xcode, то это принятие явности. И если явностью трудно управлять в «сырых» проектах Xcode, стоит рассмотреть другие варианты, например Tuist или Bazel. Только тогда станут возможны надёжность, предсказуемость и оптимизации.
Будущее#
Неизвестно, предпримет ли Apple какие-либо шаги для предотвращения всех вышеупомянутых проблем. Их непрерывные решения, встроенные в Xcode и Swift Package Manager, не дают оснований полагать, что это произойдёт. Как только вы допускаете неявную конфигурацию как допустимое состояние, от него трудно отказаться без внесения критических изменений. Возврат к первопринципам и переосмысление дизайна инструментов может привести к поломке многих проектов Xcode, которые случайно компилировались годами. Представьте себе возмущение сообщества, если бы это произошло.
Apple оказалась в ситуации, напоминающей проблему курицы и яйца. Удобство помогает разработчикам быстро начать работу и создавать больше приложений для их экосистемы. Но их решения, направленные на обеспечение удобства в таком масштабе, затрудняют гарантию надёжной работы некоторых функций Xcode.
Поскольку будущее неизвестно, мы стараемся максимально соответствовать отраслевым стандартам и проектам Xcode. Мы предотвращаем вышеупомянутые проблемы и используем наши знания, чтобы обеспечить лучший опыт разработки. В идеале нам не пришлось бы прибегать для этого к генерации проектов, но отсутствие расширяемости у Xcode и Swift Package Manager делает её единственным жизнеспособным вариантом. Это также безопасный вариант, потому что для поломки проектов Tuist им придётся сломать проекты Xcode.
В идеале система сборки была бы более расширяемой, но разве не было бы плохой идеей иметь плагины/расширения, которые взаимодействуют с миром неявности? Это не кажется хорошей идеей. Поэтому, похоже, нам понадобятся внешние инструменты, такие как Tuist или Bazel, чтобы обеспечить лучший опыт разработки. Или, возможно, Apple удивит нас всех и сделает Xcode более расширяемым и явным...
Пока этого не произошло, вам предстоит выбрать: принять удобство Xcode и взять на себя технический долг, который с ним связан, или довериться нам в этом путешествии к лучшему опыту разработки. Мы вас не подведём.