Цена удобства
Разработка редактора кода, который мог бы использоваться в самых разных проектах - от небольших до крупных - является сложной задачей. Многие инструменты решают эту задачу путем наслоения своих решений и обеспечения расширяемости. Самый нижний слой очень низкоуровневый и близок к базовой системе сборки, а самый верхний слой - это высокоуровневая абстракция, удобная в использовании, но менее гибкая. Таким образом, они делают простые вещи простыми, а все остальное - возможным.
Однако Apple решила использовать другой подход в Xcode. Причина неизвестна, но, скорее всего, оптимизация под задачи масштабных проектов никогда не была их целью. Они сделали ставку на удобство для небольших проектов, не обеспечили достаточной гибкости и сильно связали инструменты с базовой системой сборки. Чтобы добиться удобства, они предоставили разумные настройки по умолчанию, которые вы можете легко заменить, и добавили множество неявных поведений, связанных со временем сборки, которые являются виновниками многих проблем при масштабировании.
Ясность и масштаб
При работе в масштабе явность имеет ключевое значение. Она позволяет системе сборки заранее проанализировать и понять структуру проекта и зависимости, а также выполнить оптимизацию, которая иначе была бы невозможна. Та же явность критически важна для обеспечения надежной и предсказуемой работы таких функций редактора, как SwiftUI previews или Swift Macros. Поскольку Xcode и проекты Xcode приняли неявность допустимым дизайнерским решением ради удобства – принцип, который унаследовал менеджер пакетов Swift, – сложности работы с Xcode проявляются и в Swift Package Manager.
THE ROLE OF TUIST
Роль Tuist можно описать как инструмент, который предотвращает неявно определенные проекты и использует явность для обеспечения лучшего опыта разработчиков (например, валидация, оптимизация). Такие инструменты, как Bazel, идут дальше, спускаясь на уровень системы сборки.
Этот вопрос почти не обсуждается в сообществе, но он очень важен. Работая над Tuist, мы заметили, что многие организации и разработчики думают, что текущие проблемы, с которыми они сталкиваются, будут решены с помощью Swift Package Manager, но они не понимают, что, поскольку он построен на тех же принципах, даже если он смягчает так хорошо известные конфликты Git, они ухудшают опыт разработчиков в других областях и продолжают делать проекты неоптимизируемыми.
В следующих разделах мы рассмотрим несколько реальных примеров того, как неявность влияет на работу разработчика и здоровье проекта. Список не является исчерпывающим, но он должен дать вам хорошее представление о проблемах, с которыми вы можете столкнуться при работе с проектами Xcode или пакетами Swift.
Когда удобство начинает мешать
Общий каталог созданных продуктов
Xcode использует каталог внутри Derived Data для каждого продукта. В нем хранятся артефакты сборки, такие как скомпилированные двоичные файлы, файлы dSYM и журналы. Поскольку все продукты проекта попадают в один и тот же каталог, который по умолчанию виден другим таргетам для компоновки, таргеты могут неявно зависеть друг от друга. Хотя это может не представлять проблемы при наличии всего нескольких таргетов, при росте проекта это может проявиться в виде неудачных сборок, которые трудно отладить.
Следствием этого проектного решения является то, что многие проекты случайно собираются с плохо определенным графом зависимостей.
TUIST DETECTION OF IMPLICIT DEPENDENCIES
Tuist предоставляет
командудля обнаружения неявных зависимостей. С её помощью можно проверять в CI, что все зависимости указаны явно.
Поиск неявных зависимостей в схемах
Определять и поддерживать граф зависимостей в Xcode становится все сложнее по мере роста проекта. Это сложно, потому что зависимости зашиты в файлах .pbxproj в виде фаз и настроек сборки, отсутствуют инструменты для визуализации и работы с графом, а изменения в нём (например, добавление нового динамического прекомпилированного фреймворка) могут потребовать изменений конфигурации выше по цепочке (например, добавлениt новой фазы сборки для копирования фреймворка в бандл).
В какой-то момент Apple решила, что вместо того, чтобы развивать графовую модель до чего-то более управляемого, разумнее добавить опцию разрешения неявных зависимостей во время сборки. Это опять же сомнительный выбор, поскольку в итоге вы можете получить замедленное время сборки или непредсказуемые сборки. Например, сборка может пройти локально из-за некоторого состояния в данных derive, которые действуют как singleton, но затем не скомпилироваться на CI из-за другого состояния.
TIP
Мы рекомендуем отключить эту функцию в схемах проекта и использовать, например, Tuist, который облегчает управление графом зависимостей.
SwiftUI Previews и статические библиотеки/фреймворки
Некоторые функции редактора, такие как SwiftUI Previews или Swift Macros, требуют компиляции графа зависимостей для редактируемого файла. Такая интеграция редактора с системой сборки требует, чтобы система сборки разрешала все неявные зависимости и формировала корректные артефакты, необходимые для работы этих функций. Как можно представить, чем более неявным является граф, тем сложнее эта задача для системы сборки, и поэтому неудивительно, что многие из этих функций работают ненадёжно. Мы часто слышим от разработчиков, что они давно перестали использовать SwiftUI Previews из-за их ненадёжности. Вместо этого они либо используют примеры приложений, либо избегают определённых решений, таких как статические библиотеки или скриптовые фазы сборки, поскольку они нарушают работу этих функций.
Сливаемые библиотеки
Динамические фреймворки, хотя и более гибкие и удобные в работе, негативно влияют на время запуска приложений. С другой стороны, статические библиотеки запускаются быстрее, но увеличивают время компиляции и с ними сложнее работать, особенно при сложных графах зависимостей. Разве не было бы здорово, если бы можно было переключаться между ними в зависимости от конфигурации? Вероятно, именно так подумала Apple, когда решила поработать над сливаемыми библиотеками. Однако и здесь они снова перенесли ещё больше логики разрешения зависимостей на этап сборки. Если вы рассуждаете о графе зависимостей, представьте, насколько труднее это делать, когда статическая или динамическая природа таргера определяется во время сборки на основе настроек в других таргетах. Удачи в попытках обеспечить надёжную работу и при этом не сломать такие фичи, как SwiftUI Previews.
Многие пользователи приходят в Tuist, желая использовать сливаемые библиотеки, и наш ответ всегда один и тот же: в этом нет необходимости. Вы можете управлять тем, будут ли ваши таргеты статическими или динамическими, на этапе генерации, в результате чего получается проект с графом, известным ещё до компиляции. При этом не требуется разрешать какие-либо переменные во время сборки.
bash
# 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 и принять связанный с этим технический долг или довериться нам в этом пути к лучшему опыту разработчика. Мы вас не разочаруем.
