Русский
Русский
English
Статистика
Реклама

Зависимости

Перевод Путаница зависимостей. Как я взломал Apple, Microsoft и десятки других компаний

02.04.2021 20:22:21 | Автор: admin

С тех пор как я начал учиться программировать, я восхищаюсь уровнем доверия, который мы вкладываем в простую команду, подобную этой:

pip install package_name

В некоторых языках программирования, таких как Python, это простой, более или менее официальный способ установки зависимостей для ваших проектов. Такие установщики обычно привязаны к публичным репозиториям кода, куда каждый может свободно загружать пакеты кода для использования другими пользователями.

Вы, наверное, уже слышали о таких инструментах у Node есть менеджер npm и реестр npm, система управления пакетами pip языка Python использует PyPI (Python Package Index), а систему gems для языка Ruby можно найти на сайте RubyGems.

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


Конечно, может.

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

Другие хорошо известные пути атаки цепочек зависимостей включают использование различных методов компрометации существующих пакетов или загрузку вредоносного кода под именами зависимостей, которых больше не существует.

Идея

Пытаясь взломать PayPal вместе со мной летом 2020 года, Джастин Гарднер поделился интересным фрагментом исходного кода Node.js, найденного в GitHub.

Код предназначался для внутреннего использования в PayPal, и в его файле package.json, по-видимому, содержалась смесь публичных и частных зависимостей публичные пакеты от npm, а также имена непубличных пакетов, скорее всего, размещённых внутри PayPal. В то время этих имён не было в публичном реестре npm.

С логикой, диктующей, какой пакет должен быть получен из неясного здесь источника, возникло несколько вопросов:

  • Что произойдёт при загрузке в npm вредоносного кода с этими именами? Возможно ли, что некоторые внутренние проекты PayPal начнут по умолчанию переходить на новые публичные пакеты вместо частных?

  • Начнут ли разработчики или даже автоматизированные системы выполнять код внутри библиотек?

  • Если это сработает, сможем ли мы получить за это вознаграждение?

  • Сработает ли такая атака и против других компаний?

Без лишних церемоний я стал разрабатывать план, чтобы ответить на эти вопросы.

Идея заключалась в загрузке собственных вредоносных пакетов Node в реестр npm под всеми невостребованными именами, которые будут звонить домой с каждого компьютера, на котором они были установлены. Если какой-либо из пакетов в итоге устанавливается на серверах, принадлежащих PayPal, или в любом другом месте, то содержащийся в них код немедленно уведомляет меня.

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

Это всегда DNS

К счастью, npm позволяет при установке пакета автоматически выполнять произвольный код, что облегчает создание пакета Node, который собирает некоторую базовую информацию о каждом компьютере, на котором он установлен, с помощью своего сценария preinstall.

Чтобы найти баланс между возможностью идентифицировать организацию на основе данных и необходимостью избегать сбора слишком большого количества конфиденциальной информации, я остановился только на регистрации имени пользователя, имени хоста и текущего пути каждой уникальной установки. Наряду с внешними IP-адресами этого было достаточно, чтобы помочь специалистам по безопасности выявить потенциально уязвимые системы на основе моих отчётов, избегая при этом того, чтобы моё тестирование было ошибочно принято за реальную атаку.

Остался только вопрос: как мне вернуть эти данные?

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

Отправка информации моему серверу по протоколу DNS не была важна для работы самого теста, но она указывала на меньшую вероятность блокировки трафика и его обнаружения на выходе.Данные в шестнадцатеричной кодировке использовались как часть DNS-запроса, который достигал моего собственного полномочного сервера доменных имён напрямую или через промежуточные сопоставители. Сервер был настроен для регистрации каждого полученного запроса, по сути, сохраняя запись каждого компьютера, в который были загружены пакеты.

Чем больше, тем лучше

Теперь, после завершения подготовки плана атаки, пришло время обнаружить больше возможных целей.

Первая стратегия заключалась в поиске альтернативных экосистем для атаки. Поэтому я перенёс код как на Python, так и на Ruby, чтобы можно было загружать аналогичные пакеты в PyPI (Python Package Index) и RubyGems соответственно.

Однако, возможно, самой важной частью этого теста был поиск максимально возможного количества значимых имён зависимостей.

Несколько полных дней поиска имён частных пакетов, принадлежащих некоторым целевым компаниям, показали, что многие другие имена можно найти в GitHub, а также в основных службах размещения пакетов во внутренних пакетах, которые были случайно опубликованы, и даже в сообщениях на различных интернет-форумах.

Однако, оказалось, что лучше всего искать имена частных пакетов... в файлах JavaScript.

Видимо, довольно часто внутренние файлы package.json с именами зависимостей проектов JavaScript, встраиваются в общедоступные файлы сценариев во время их сборки, раскрывая имена внутренних пакетов. Точно так же получаемые по каналам утечки внутренние пути или вызовы require() в таких файлах также могут содержать имена зависимостей. Apple, Yelp и Tesla это лишь несколько примеров компаний, у которых внутренние имена были раскрыты таким образом.

Во второй половине 2020 года, благодаря помощи пользователя @streaak и его замечательным навыкам реконструкции, мы смогли автоматически просканировать миллионы доменов, принадлежащих целевым компаниям, и извлечь сотни дополнительных имён пакетов JavaScript, которые ещё не были заявлены в реестре npm.Затем я загрузил свой код в службу размещения пакетов под всеми найденными именами и стал ждать обратных вызовов.

Результаты

Успех был просто поразительным.

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

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

Поскольку имена зависимостей JavaScript легче найти, почти 75% всех зарегистрированных обратных вызовов исходили из пакетов npm, но это не обязательно означает, что Python и Ruby менее восприимчивы к такой атаке. На самом деле, несмотря на то что во время моих поисков мне удалось идентифицировать лишь внутренние имена gem для Ruby, принадлежащие восьми организациям, четыре из этих компаний оказались уязвимыми для путаницы зависимостей посредством RubyGems.

Одна такая компания канадский гигант электронной коммерции Giant Shopify, система сборки которой автоматически устанавливала пакет gem для Ruby с именем shopify-cloud всего лишь через несколько часов после того, как я его загрузил, а затем попытался выполнить код внутри него. Специалисты Shopify подготовили исправление в течение дня, а за обнаружение ошибки была присуждена награда в размере 30000 долларов.

Ещё одна награда в размере 30000 долларов была получена от Apple после того, как код в пакете Node, который я загрузил в npm в августе 2020 года, был выполнен на нескольких компьютерах внутри сети этой компании. Затронутые проекты оказались связаны с системой аутентификации Apple, известной за пределами компании как Apple ID.

Когда я подал идею о том, что эта ошибка, возможно, позволяла злоумышленникам внедрять бэкдоры в Apple ID, в компании Apple не посчитали, что этот уровень воздействия точно отражает проблему, и заявили:

Реализация бэкдора в оперативной службе требует более сложной последовательности событий, и это очень специфический термин, который несёт в себе дополнительные коннотации.

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

Такое же использование пакетов npm, устанавливаемых как на внутренние серверы, так и на ПК отдельных разработчиков, наблюдалось и в нескольких других успешных атаках на другие компании, причём некоторые установки часто выполнялись через часы или даже минуты после загрузки пакетов.

О, а имена PayPal, с которых всё началось? Они тоже сработали, что привело к ещё одной награде в размере 30 000 долларов. Фактически размер большинства присужденных премий за обнаружение ошибки был максимально разрешённым в рамках политики каждой программы, а иногда и превышал максимум, подтверждая в целом высокую серьезность уязвимости путаница зависимостей.

К затронутым компаниям также относятся Netflix, Yelp и Uber.

Это не баг, это фича

Несмотря на получение большого количества результатов по уязвимости путаница зависимости, одна деталь была и в определённой степени остаётся до сих пор неясной: почему это происходит? Каковы основные первопричины уязвимости такого рода?

Большинство затронутых организаций неохотно делились дальнейшими техническими подробностями о своих первопричинах и стратегиях смягчения последствий, но несколько интересных деталей всё же появилось во время моих исследований и общения со специалистами по безопасности.

Например, главный виновник путаницы зависимостей в Python, это, по-видимому, неправильное использование небезопасного по своей конструкции аргумента командной строки --extra-index-url. Если, используя этот аргумент с командой pip install library, указать собственный индекс пакета, можно обнаружить, что он работает ожидаемым образом, но то, что pip на самом деле делает за кулисами, выглядит примерно так:

  • проверяет существование library в указанном (внутреннем) индексе пакетов;

  • проверяет существование library в публичном индексе пакетов (PyPI);

  • устанавливает любую найденную версию. Если пакет существует в обоих индексах, то по умолчанию он устанавливается из источника с более высоким номером версии.

Поэтому загрузка пакета с именем library 9000.0.0 в PyPI приведёт к захвату зависимости в приведённом выше примере.

Хотя такое поведение уже было широко известно, простого поиска в GitHub выражения --extra-index-url было достаточно, чтобы найти несколько уязвимых сценариев, принадлежащих крупным организациям, включая ошибку, влияющую на компонент Microsoft .NET Core. Данная уязвимость, которая, возможно, позволила бы добавлять бэкдоры в .NET Core, к сожалению, была обнаружена вне рамок программы вознаграждения за нахождение ошибок в .NET.

В Ruby команда gem install --source работает аналогичным образом, но мне не удалось подтвердить, было ли её использование основной причиной каких-либо моих находок.

Конечно, замена --extra-index-url на --index-url быстро и прямо исправляет уязвимость, но снизить риск некоторых других вариантов уязвимости путаница зависимостей оказалось гораздо труднее.

JFrog Artifactory, часть программного обеспечения, широко используемая для размещения внутренних пакетов всех типов, предлагает возможность смешивать внутренние и публичные библиотеки в одном виртуальном репозитории, значительно упрощая управление зависимостями. Однако несколько клиентов заявили, что в Artifactory используется точно такой же уязвимый алгоритм, что был описан выше, при выборе между обслуживанием внутреннего и внешнего пакета с одним именем. На момент написания статьи не было никакого способа изменить это поведение по умолчанию.

В JFrog, как сообщается, знают об этой проблеме, но рассматривает её возможное исправление как запрос функции без расчётного времени его реализации, в то время как некоторые клиенты компании прибегли к изменению системной политики управления зависимостями, чтобы уменьшить риск путаницы зависимостей.

Компания Microsoft также предлагает аналогичную службу размещения пакетов под названием Azure Artifacts. По результатам одного из моих отчётов в эту службу были внесены некоторые незначительные улучшения, чтобы она могла гарантированно предоставить надёжный путь обхода уязвимостей путаница зависимостей. Как ни странно, эта проблема была обнаружена не в результате тестирования самой службы Azure Artifacts, а, скорее, путём успешной атаки на облачную систему Microsoft Office 365, в результате чего за этот отчёт была получена самая высокая награда Azure в размере 40 000 долларов.

Более подробную информацию о первопричинах и рекомендациях по профилактике можно найти в техническом документе Microsoft 3 Ways to Mitigate Risk When Using Private Package Feeds (Три способа снижения риска при использовании каналов частных пакетов).

Будущие исследования?

Хотя многие крупные технологические компании уже осведомлены об уязвимости этого типа и либо исправили её в своей инфраструктуре, либо работают над внедрением мер по смягчению последствий, у меня сохраняется ощущение, что есть ещё что-то, что нужно обнаружить.

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

Тем не менее я искренне призываю вас, независимо от вашего уровня опыта, потратить некоторое время и проработать эту идею в глубине своего сознания, независимо от того, связана она с безопасностью управления зависимостями или нет.

Даже крупные компании совершают порой ошибки и допускают уязвимости. На нашем курсе Этичный хакер мы учим студентов искать эти уязвимости и зарабатывать на этом. Если устали чинить баги в коде и постоянно надстраивать что-то новое приходите к нам, будем учиться ломать, находя бреши в софте даже у крутых корпораций.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Из песочницы Избегайте внедрения внешних библиотек в свой проект

14.10.2020 12:12:47 | Автор: admin
Часто можно услышать фразу: Зачем писать свой велосипед? Возьми готовую либу и пользуйся! За тебя уже все написали. Особенно часто подобные выражения слышат начинающие разработчики. При решении любой задачи они начинают смотреть готовые либы и бездумно тянуть их в свой проект. В этой статье Вы узнаете к каким последствиям может привести бездумное внедрение сторонних библиотек.

Потенциальные проблемы


Я всячески рекомендую избегать добавления внешних библиотек в свой проект. Однако это не значит, что использовать библиотеки зло, а Ваш Gradle файл вовсе должен быть очищен от любых внешних зависимостей! Я хочу донести мысль, что перед добавлением новой библиотеки нужно провести очень серьёзный анализ. Чтобы понять, как анализировать библиотеки, давайте рассмотрим с какими потенциальными проблемами Вы можете столкнуться при добавлении новой библиотеки в Ваш проект.

Размер приложения


Большинство библиотек не сильно увеличивают размер Вашего приложения. Но существуют такие либы, после добавления которых Ваше приложение увеличится в разы! Например, библиотека Realm может увеличить размер APK с 4MB до 12MB. В среднем приложение весит 100MB-200MB и лишние 8MB могут быть не заметны. Но не забывайте, что Realm не единственная библиотека, которая негативно влияет на размер APK. А можно ли уменьшить размер APK, используя эту зависимость? Да, можно сделать split apk по архитектурам процессора. Но это приводит к следующей проблеме (пункт 2).

Поддерживаемость кода


Проекты разрастаются, покрываются все большей логикой и разобраться порой в них становится все сложнее. Чтобы спустя нескольких лет развития проекта любой новый разработчик мог легко освоиться в проекте нужна четкая архитектура проекта. Однако некоторые библиотеки могут свести поддерживаемость проекта к нулю. Например, некорректная работа с EventBus может сильно запутать логику Вашего приложения. Тут важно четко разграничить: где и в каких кейсах Вы используете эту либу. А также быть уверенным, что никто и никогда не будет отклоняться от этих правил. Но что происходит на практике? Почти каждый разработчик, начиная работать с EventBus, использует его повсюду. В итоге проект становится абсолютно нечитаемым.

Время на изучение библиотеки


При добавлении новой библиотеки необходимо изучить как с ней взаимодействовать. Существуют библиотеки, которые могут очень негативно повлиять на скорость разработки в будущем.
Например, PagingLibrary от Google требует немалых усилий, чтобы понять как с ней взаимодействовать. Чтобы разобраться с этой библиотекой каждому новому разработчику потребуется от 12 до 20 часов. Вы теряете почти 3 дня! Как раз за эти 3 дня Вы можете запилить свою пагинацию и быть независимым от сторонних решений.

Скорость сборки


Скорость сборки современных приложений оставляет желать лучшего. Однако, если Вы хотите ещё больше увеличить время сборки используйте Dagger! Не знаю почему библиотекой до сих пор активно пользуются после появления Kotlin. В большинстве случаев Dagger содержит все 4 проблемы, которые были описаны выше.

Баги, баги, баги


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

  • AndroidPdfViewer оставляет утечки памяти, некорректно обрабатывает некоторые кейсы с null, из-за чего вылетает NullPointerException
  • Android Navigation Component некорректно работает с анимациями Shared Elemant'ов в некоторых кейсах
  • Cicerone иногда крашит приложение из-за executePendingTransactions()

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

Наличие уязвимостей в библиотеке


Знаете как проще всего взломать сразу несколько приложений, изучив лишь одни исходники? Нужно найти баг в крупной библиотеке, которая используется многими разработчиками. И через уязвимости этой либы получить доступ к данным вашего приложения. Если Вы не разрабатываете приложение, требующее повышенное внимание к безопасности пользователя, то можно на этот пункт закрыть глаза. В противном случае поищете issue, направленные на потенциальные уязвимости.

Поддержка библиотеки


Если библиотека не обновлялась уже год задайте вопрос, стоит ли её использовать. Дело в том, что в библиотеках регулярно находятся баги. Если эти баги не исправляются, то стоит ли использовать эту библиотеку? Вполне возможно, что в ближайшем будущем вы наткнетесь на баг библиотеки и вам придется искать альтернативные пути реализации фичи. Есть некоторые библиотеки, которые должны адаптироваться под возможности текущей версии Android API. Например, если появился новый элемент в Android, то нужно добавить его поддержку. К таким библиотекам можно отнести Anko, который больше не поддерживается. Теперь использовать эту библиотеку в крупных проектах нет смысла.

Библиотека присутствует во всех слоях проекта


Такие библиотеки как RxJava или PagingLibrary заставляют разработчика использовать их API на каждом слое приложения. Если вы уверены, что библиотека всегда будет в проекте, то проблем нет. Но если вам по каким-то причинам придется выпиливать библиотеку, то вы затратите колоссальные усилия! Вам придется переписывать полпроекта.

Ограничения библиотеки


Каждая либа предоставляет API, который ограничен как наличием открытыми методами, так и внутренней реализацией. Убедитесь, что возможностей библиотеки вам полностью хватает. Например, популярная библиотека Android Navigation Component очень сильно связывает руки разработчика. Она не предоставляет методы show, hide, add (которые есть в FragmentTransaction). Помимо этого библиотека усложняет работу с BottomNavigationView при необходимости хранить историю табов.

Огромный Gradle файл с зависимостями


Когда я прихожу на новый проект, я первым делом смотрю зависимости в Gradle файл. Он даёт понять, что умеет делать приложение и как решаются те или иные задачи. Но вот было моё удивление, когда увидел, что для работы с сетью используются и OkHttp, и Retrofit, и Volley (форк). И это только работа с сетью. Сам Gradle файл состоит из огромного числа библиотек, поддержка которых уже давно закончилась. Когда разработчик один, он может держать весь проект в голове, но когда приходят новые разработчики разобраться в таком проекте становится крайне сложно.

Чек-лист вопросов перед внедрением библиотеки


  1. Какие issue есть у данной библиотеки? Критичны ли они для моего проекта?
  2. На сколько эта библиотека/технология протестирована сообществом разработчиков? Сколько у нее звездочек на GitHub?
  3. Как часто разработчик отвечает на issue?
  4. Как часто разработчик обновляет библиотеку?
  5. Сколько времени потратят новые разработчики на изучение используемой технологии?
  6. Как библиотека повлияет на размеры приложения?
  7. Как библиотека повлияет скорость работы приложения?
  8. Как библиотека повлияет на скорость сборки? Помогает ли она сэкономить время разработки?
  9. Есть ли у библиотеки уязвимости?
  10. Будет ли библиотека присутствовать на каждом слое проекта? На сколько это критично?
  11. Каким образом библиотека ограничивает возможности разработчика (она почти всегда ограничивает). Хорошо это или плохо?
  12. Смогу ли я сам написать решение, которое будет заточено под мой проект за допустимые сроки?

Очень интересно услышать, на что ещё можно обратить при выборе библиотеки. Жду комментарии, отзывы и вопросы!
Подробнее..

Человечная декомпозиция работы

22.10.2020 22:11:19 | Автор: admin

img


За 15 лет работы разработчиком я обнаружил, что ложные убеждения о человеческой природе основные враги хорошей декомпозиции. Если знать о них и стремиться не угодить к ним в ловушку, со временем можно сформулировать советы по созданию качественной декомпозиции. Так произошло со мной, и я спешу поделиться этим знанием.


Ключевые понятия

Фича (feature) бизнес-возможность, автоматизируемая в программном продукте. Это англицизм, который широко применяется в ИТ-индустрии. Я не нашёл для него хорошего русского аналога.


Декомпозиция процесс разделения работы над фичей на отдельные задачи, часто включающий выбор исполнителя. Также декомпозицией называют результат процесса декомпозиции, то есть совокупность получившихся задач.


Смётка способность человека строить в уме модель функционирования механизма или природного явления, предсказывать поведение и находить причины неполадок посредством сосредоточенного усилия и интуиции. Это определение соответствует пониманию из книги Дзен и искусство ухода за мотоциклом и уточняет более общее значение из словаря: способность быстро соображать, рассчитывать.


Более 5 лет я работаю бэкенд-разработчиком в FunBox. В компании мы считаем, что хорошие продукты результат развитой инженерной культуры и стремимся практиковать этот принцип в разработке решений для мобильных операторов. Тут есть свои особенности стека и процессов, но есть и универсальные аспекты, применимые в любой предметной области. Один из них подход к декомпозиции. Благодаря продуктовому характеру работы и инженерной культуре мне удалось получить опыт и озарения, которыми я не могу не поделиться.


Навигация по статье:




Ложные убеждения о человеческой природе


Следующие ложные убеждения о человеческой природе могут приводить к неудачным решениям при декомпозиции:


  1. Человек от природы порочен, и только давление общества заставляет его сдерживать свои порывы.
  2. Человек ленив, и его нужно заставлять работать, иначе он будет прокрастинировать.
  3. Человек склонен до бесконечности улучшать любой достаточно хороший результат, даже если это объективно не несёт никакой ценности (перфекционизм).
  4. Человеком движет жажда материальных ценностей для себя (эгоизм и алчность).
  5. Человек нуждается в подчинении и в том, чтобы ему подчинялись (авторитаризм).
  6. Человек не любит людей и стремится избегать взаимодействия с ними в процессе решения рабочих и жизненных задач (мизантропия).

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


Опровержение этих убеждений не является предметом данной статьи. Над этим бьются философы и духовные лидеры на протяжении всей истории, и для каждого человека поиск опровержений будет индивидуальным. Я лишь вскользь коснусь возможных направлений поиска, до которых сам дошёл на данный момент.


Отступление. Направления поиска более реалистичных взглядов на человека и мир

Агностикам в вопросе существования высших сил может быть ближе миросозерцание гуманистов, например Эриха Фромма. Я попытался кратко сформулировать своё понимание миросозерцания гуманистов в докладе Гуманистическая декомпозиция работы.


Спустя полгода после доклада я познал более глубокое христологическое, мистическое, персоналистическое миросозерцание Николая Бердяева. Попробую кратко суммировать по памяти своими словами. Следуя своей интуиции, Бердяев верит в существование меона, испускающего свободу и породившего Бога. Бог же создал сей мир и человека по своему образу и подобию. Свобода человека не от Бога, а от меона. И Бог желает, чтобы из этой свободы человек творил новый мир.


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


Человек же может распорядиться меонической свободой регрессивно в силу неблагоприятных обстоятельств. Будучи не в силах созидать новый мир, он в отместку может разрушать мир сей. На протяжении истории для большинства людей преобладали неблагоприятные обстоятельства, и от недостатка интуиции формировалось перекошенное, иллюзорное миросозерцание, основанное на статистической видимости.


Миросозерцание Бердяева более глубокое и радостное, чем секуляризованное миросозерцание гуманистов, но оба они закладывают фундамент для преодоления ложных убеждений о человеческой природе.


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



Закон Паркинсона


Вера в первые 4 ложных убеждения может заставить человека согласиться со следующим утверждением:


Работа заполняет время, отпущенное на неё.

Это Первый закон Паркинсона, который в 1955 году сформулировал историк Сирил Норткот Паркинсон в сатирической статье на основе наблюдений за британской бюрократией. К сожалению, эта сатира очень быстро распространилась и стала универсальным законом. Даже менеджмент в индустрии программного обеспечения взял его на вооружение, стал тиражировать и бороться с его проявлениями.


В книге Человеческий фактор. Успешные проекты и команды Том ДеМарко и Тимоти Листер обосновывают неприменимость этого закона к людям, занимающимся креативной деятельностью, в противоположность серийному промышленному производству и бюрократии. Здесь и далее я ставлю название закона в кавычки, чтобы подчеркнуть отношение к нему как к ложному утверждению в контексте, отличном от описания бюрократии.



Континуум стилей декомпозиции


Стили декомпозиции работы лежат в континууме:


  • от микротаскинга, то есть дробления на неделимые атомарные задачи;
  • до #NoEstimates, то есть полного отсутствия формального дробления и оценки трудозатрат.

Микротаскинг


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


  • Если человек ленив (ложное убеждение 2), то задания нужно делать максимально мелкими, чтобы не было возможности ни минуты сфилонить и получить оплату за прокрастинацию.


  • Если человек не любит людей и общение (ложное убеждение 6), то всех участников производства нужно обезличить и организовать процесс так, чтобы не требовалось никакого взаимодействия, кроме того, что выражается в коде, документации к нему и тикетах.


  • Если человеком движет эгоизм и жажда материальных ценностей, то и работодатель и работник должны максимизировать свою выгоду и минимизировать потери. Нужно платить только за сделанные атомарные задания, устанавливая такую цену, чтобы она достаточно удовлетворяла потребности работника и стимулировала его больше работать без прокрастинации, но и без достаточного погружения в смысл. Чтобы делить работу над фичей на мелкие задания по 1015 минут, нужны некие архитекторы. Они должны обладать авторитетом, и исполнители будут им с радостью подчиняться, ибо испытывают в этом потребность (ложное убеждение 5).



Это устройство производственного процесса очень напоминает конвейер, на котором рабочие могут выполнять незатейливые, изолированные операции, не требующие ни большой квалификации, ни взаимодействия с другими людьми. Ещё Карл Маркс в 1840-х и Эрих Фромм в 1960-х годах описали такое устройство производства и экономические причины его существования. Также они описали, насколько губительно и невыносимо это для человека. Участвуя в производстве такого характера, человек теряет связь с результатами своего труда, отчуждается от них. Для человека исчезает смысл в его деятельности, он ощущает подавленность и тревогу. Всё это ведёт к раннему профессиональному выгоранию и более тяжёлым последствиям.


Когда все задачи мелкие, то постоянно приходится переключать контекст внимания. Переключение контекста требует от мозга больших энергозатрат (об этом хорошо написано в книге Thinking Fast And Slow). Это огромный источник потерь для смётки. Ослабление смётки приводит к ошибкам. При микротаскинге нет места для восстановления мыслительных ресурсов, или, как это ёмко называет Максим Дорофеев, мыслетоплива. Забота об этом ложится на плечи работника.


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


Я не могу поверить, что такой подход может приводить к качественным результатам в долгосрочной перспективе. Технический долг и неконсистентность должны неизбежно накапливаться, так что дальнейшее развитие становится тяжким делом. Даже если предположить, что продукты, разработанные по такой системе атомарных задач, получаются приемлемого для заказчиков качества, я не могу не сочувствовать людям, вовлечённым в этот тип производства.


NoEstimates


#NoEstimates (термин возник из хештега в Твиттере) предполагает, что любая фича в продукте разрабатывается быстрее, чем заинтересованные в ней лица успевают спросить про оценку трудозатрат.


Я узнал об этой концепции из доклада Асхата Уразбаева на AgileDays'14. Основными условиями для достижения ситуации, когда не возникает необходимости в оценках, Асхат называет следующие:


  1. Непрерывная доставка ценности заказчику.
  2. Отсутствие зависимостей, которые могут что-то сломать. Для обеспечения этого свойства нужен налаженный процесс непрерывной интеграции (CI) с междукомандными интеграционными тестами.
  3. Уравновешивание возможностей, то есть пропускной способности команды, и потребностей заказчика.

Будучи под влиянием доклада и своего опыта, я пришёл к более строгим критериям, необходимым для работы без оценок:


  1. Должны использоваться выдающиеся, не мейнстримовые технологии, дающие радикальный прирост продуктивности.
  2. Команды должны состоять из небольшого числа самомотивированных и высокопродуктивных профессионалов.
  3. Продукт должен сильно вовлекать своих воплотителей, чтобы они сами предугадывали нужные фичи или реализовывали запрошенную стейкхолдерами функциональность за меньшее время, чем отнял бы процесс оценки и планирования.

Возможно, условий, названных Асхатом, будет достаточно и я слишком требователен.


Ситуация #NoEstimates максимально благоприятна для людей. К сожалению, она встречается редко и может длиться недолго. Например, до тех пор, пока крупный игрок на рынке не купит бизнес и не заменит эту культуру своей.


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



Человечная декомпозиция


В континууме между микротаскингом и #NoEstimates лежит бесконечное множество стилей декомпозиции.
Кажется, мне удалось нащупать золотую середину и очертить стиль декомпозиции, подходящий людям творческим, неравнодушным к своему делу и своим коллегам, занятым в производственном процессе. Я назвал это человечной декомпозицией.


Свойства человечной декомпозиции


При человечной декомпозиции каждая задача удовлетворяет следующим критериям:


  1. Задача самодостаточна и целостна. Не должно быть аспектов в других задачах, которые могли бы ключевым образом повлиять на создаваемый образ решения данной задачи в голове.
  2. Задача не превышает 35 дней в предварительной оценке трудозатрат. Это ограничение позволит придать задаче обозримые границы и сделает её управляемой, помещающейся в голове.

Вся совокупность задач должна соответствовать архитектурному принципу Loose Coupling / High Cohesion (Слабая зависимость / Сильная сплочённость), а именно:


  • Loose Coupling: Зависимости между задачами должны быть минимальными.


  • High Cohesion: каждая задача должна содержать сильно сплочённые функциональные возможности, чтобы ничего нельзя было выбросить без потери целостности размышлений о задаче.



Замечание о характере зависимостей

Я заметил, что зависимости через модели и поля в БД гораздо лучше, чем зависимости по API сервисных объектов. Объясняю это тем, что проектирование изменений в БД производится тщательнее, поэтому, когда принято решение, вероятность его изменения низка.


Зависеть же от ещё не разработанных сервисных объектов крайне дискомфортно. Я бы советовал рассматривать зависимости между задачами по API сервисных объектов как decomposition smell (термин аналогичен code smell), то есть маркер низкого качества декомпозиции.


Верификация декомпозиции


Попытаться прийти к декомпозиции с такими свойствами можно итеративно, верифицируя получившийся набор задач. С проверкой помогут контрольные вопросы к отдельным задачам и набору в целом.


Контрольные вопросы к каждой задаче:


  1. Можно ли целостно думать о задаче в изоляции от других задач?
  2. Можно ли вынести из задачи что-то лишнее так, чтобы при этом не нарушилась целостность?
  3. Не слишком ли мала задача? Не должна ли она быть частью какой-то большей задачи, чтобы та была целостной?

Контрольные вопросы к совокупности задач:


  1. Нет ли между задачами слишком сильных зависимостей, возможно, неявных, в особенности если они даются разным исполнителям?
  2. Являются ли все задачи управляемыми по объёму (оценка не превышает 35 дней)?
  3. Не слишком ли мелко разбиты задачи и не нарушена ли их целостность?

Вопросы кажутся похожими, некоторые отличаются только объектом направленности. Так и есть. Но в процессе декомпозиции удобно переключаться между режимами анализа каждой задачи в отдельности и всей совокупности в целом. Найденные возможности улучшения при ответе на каждый вопрос порождают новую итерацию. На определённой итерации идеи улучшений заканчиваются и полученная совокупность задач с большой вероятностью будет обладать свойствами человечной декомпозиции.


Обоснование выбранной цифры для границы размеров задач


35 дней цифра условная. Граница размера задачи, которая помещается в голове, будет зависеть от сложности технологии. Данную цифру я привожу для проекта на Ruby on Rails.


Как ни парадоксально, но какое-либо ограничение по трудозатратам не только давит на исполнителя, но и помогает направлять мыслительный процесс при декомпозиции, помогает фокусироваться.


Многих такая большая цифра может тревожить, если они склонны соглашаться c законом Паркинсона. Я встречал два вида опасений, связанных с этим законом:


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

При склонности опасаться первого пункта, руководитель может начать следить за скоростью приращения программного кода. Почти всегда чем ближе к окончанию срока задачи, тем быстрее прирастает код, тем больше вносится изменений. Это можно ошибочно принять за доказательство справедливости утверждения о лени человека. На самом же деле это объясняется тем, что чем ближе к окончанию срока, тем ниже неопределённость, тем больше готового кода и понимания, как его улучшить, чтобы довести до образцового состояния. В начале работы из-за большой неопределённости нужно много размышлять, писать в блокноте, проектировать. В том числе нужно прокрастинировать, то есть заниматься отвлечённой деятельностью вдали от компьютера, чтобы мозг в фоновом режиме мог сформировать образ решения.


Большие сроки на выполнение одной задачи требуют доверия к исполнителю. Важно понимать, что люди хиреют при отсутствии доверия к ним, а при наличии расцветают. Могут быть и исключения, когда люди садятся на шею, но это патология, и будет ошибкой принимать её за правило. Нужно верить, что человек имеет потребность создавать ценности, быть полезным и в нормальном своём состоянии не будет вредить. Человек чувствует себя подавленным, если ему не удаётся уложиться в сроки или когда у него что-то не получается. В этой ситуации важно иметь регулярную обратную связь, чтобы вовремя обнаружить затруднения и поддержать.


При склонности опасаться второго пункта, руководитель может начать пытаться защититься от перфекционизма с помощью искусственно заниженных сроков, таких, чтобы вздохнуть было некогда, не то что сделать что-то избыточное по задаче.


Необоснованный перфекционизм чаще встречается у сотрудников с небольшим опытом. Они ещё не научились мыслить категориями ценности для бизнеса, но имеют потребность показать себя. Здесь продуктивным решением будет частая синхронизация с командой или наставником.


Примеры нарушения целостности задач и ощущения исполнителей


Приведу наиболее яркие и распространенные примеры нарушения целостности задач из своей практики.


Разделение работы над моделью и кодом, её использующим


Предположим, что реализуемая фича требует наличия сущности с персистентным хранением (модель в терминах Ruby on Rails) и CRUD пользовательского интерфейса для неё.
Напрашивающимся решением по декомпозиции будет завести задачу на реализацию модели и на реализацию пользовательского интерфейса, её использующего.


Но думать о модели без учёта использующей её части это нарушение целостности. Могут получиться неудачные названия полей, типы данных, валидации. Можно не выполнить какое-нибудь требование технологии пользовательского интерфейса, накладывающее ограничения на модель. Или можно сделать модель такой, что код пользовательского интерфейса будет сложнее, чем мог бы, если бы его брали в расчёт сразу.


Пример ошибки раздельного проектирования модели и пользовательского интерфейса в проекте на Ruby on Rails

В Ruby on Rails удобно делать группу чекбоксов для множественного выбора через связь многие-ко-многим. Это так называемая has_and_belongs_to_many HABTM-связь между двумя моделями без отдельной модели для связующей таблицы.
Если рассматривать модель без учёта пользовательского интерфейса, то все знают, что HABTM-связь использовать не рекомендуется.


Она выбрасывает модель связи, у которой могут появиться свои поля и поведение. Считается, что проектировщик может срезать углы, используя HABTM-связь, потому что не хочет задумываться о связующей модели как о сущности, имеющей собственную идентичность и состояние. Никто не хочет использовать нерекомендуемые приёмы. Но ситуация, когда связь нужна исключительно для выражения множественного выбора в веб UI, является исключением из правила. В этом случае искусственное введение связующей модели будет перебором и нарушением принципа Бритва Оккама, то есть внесением сущностей сверх необходимого для объяснения явления или его моделирования.


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


На ревью кода коллегам сразу будут видны и модель, и код пользовательского интерфейса, её использующий, будет очевидно большинство принятых решений.


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


Разделение внутри алгоритма


Рассмотрим пример фичи:


  • Есть два множества строк.
  • Первое множество влияет на состояние элементов второго множества, если элементы второго множества включают или не включают хотя бы один элемент из первого множества как подстроку.
  • При любом изменении в первом множестве строк должно пересчитываться состояние элементов второго множества.
  • При любом изменении в элементах второго множества, которые являются мутабельными, тоже должно пересчитываться состояние этих элементов.
  • Всё усложняется тем фактом, что первое множество строится из нескольких источников и зависит от изменений в этих источниках.
  • После пересчёта должна произойти выгрузка обновлённых состояний для другой системы.

Если для такой задачи делать человечную декомпозицию, то можно выделить основную задачу одному человеку на 5 дней, а вспомогательные задачи небольшого объёма дать коллегам, чтобы разгрузить голову основного исполнителя. Если же исходить из того, что люди ленивые и дата релиза уже назначена, то нужно подстраховаться: разбить весь объём работ на небольшие задачи менее двух дней в объёме трудозатрат и раздать двум доступным разработчикам.


Между кусками работы этих разработчиков будет много зависимостей. Один разработчик будет создавать узкоспециализированный сервисный объект, который должен будет использовать другой. До начала разработки они должны предугадать подходящий API и потом рассчитывать, что он не изменится, чтобы не подкинуть друг другу лишней работы. Принятый ими API не может быть достаточно хорошо продуман, ибо в этой ситуации некому думать об алгоритме целостно. В нём гарантированно будут ошибки, и он будет оказывать давление на психику исполнителей.


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


И тут выяснится, что только 10% сценариев использования работают корректно, а именно: при добавлении строки в первое множество происходит изменение состояния во втором множестве, при удалении же ничего не происходит.


Винить в этом разработчиков было бы несправедливо. Ни у одного из них не могло быть в голове целостной картины, ибо поставленные им задачи имели узкие границы, конкретные постановки и сроки без запаса по времени, а сроки срывать никто не любит. У них не было и достаточно политического веса, чтобы сказать, что по такой декомпозиции невозможно сделать работу качественно. Заранее можно иметь лишь интуитивное и смутное ощущение неправильности декомпозиции. Важным маркером здесь является наличие зависимостей между разработчиками по API сервисных объектов, но как это доказать в моменте, совершенно неочевидно.


Эта история из жизни позволила ретроспективно выявить проблемы и формализовать интуитивные действия при декомпозиции, которые мне удалось сформировать за годы работы.



Стратегии декомпозиции


Рассмотрим, какие стратегии помогут с небольшим количеством итераций приходить к человечной декомпозиции.


1. Отказ от декомпозиции


Если слово декомпозиция воспринимать буквально как разделение на части, то может показаться, что на выходе этого процесса должно обязательно получаться больше одной задачи.
Одна задача, даже большая, вполне может быть результатом процесса декомпозиции.
Если фича недостаточно велика и верхнеуровнево оценена в 35 дней, то может так случаться, что дальнейшее деление не даст преимуществ, но может создать проблемы, если ошибиться в границах задач. В особенности, если дать их разным исполнителям в надежде выполнить работу быстрее.


2. Делегирование исполнителю


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


Тимлиду может даже казаться, что декомпозиция работы это его основная обязанность, за которую его ценят. Или может казаться, что проще самому декомпозировать и требовать выполнения получившихся задач, чем помогать делать это другим. Эта иллюзия подкрепляется ложным убеждением 6, что человек стремится избегать взаимодействия с людьми из-за мизантропии. Кажется, что проще спустить сверху готовое задание исполнителю, а не помочь ему сформировать это задание самостоятельно, исходя из понимания требований самим исполнителем.


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


Если же декомпозицию делегировать, то у тимлида освобождается время для более творческой деятельности по стратегическому развитию продукта.


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


Таким образом, лучшим решением будет выбор главного исполнителя для реализации фичи и делегирование ему процесса декомпозиции. У главного исполнителя будет максимальная мотивация разобраться в требованиях и обеспечить себе и коллегам задачи с комфортными формулировками и оценками трудозатрат.


Лично я не могу приступить к написанию кода, пока у меня в голове не сложится приблизительный образ решения. Чтобы он сложился, необходимо точно знать, какую проблему и для кого нужно решить. Без этого мозг отказывается работать над задачей.


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


Можно возразить, что есть много разработчиков, предпочитающих работать по чётко сформулированным детальным требованиям, когда им говорят, что конкретно делать. Так и есть. Важно знать своих коллег и их предрасположенности. Таким сотрудникам я бы рекомендовал поручать второстепенные механизмы, требования к которым, как правило, более понятны и могут быть сформулированы заранее с достаточной точностью. Об этом далее в пункте 6 про выделение смыслового ядра.


3. Отказ от детального проектирования


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


Удобно представлять себе весь объём функциональности как кусок мрамора. От него нужно отделять крупные части, границы которых очевидны уже во время предварительного проектирования.
Может помочь и такой приём: в воображении посмотреть на решение через прищуренные глаза. При этом картинка расплывчата, но различаются силуэты и границы подсистем.


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


4. Группировка функциональности по сходному уровню сложности, неопределённости или риска


При планировании и оценке трудозатрат удобно разделять задачи по уровню сложности, неопределённости и риска. В зависимости от характера продукта может подойти разное количество баллов для оценки.


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


Если внутри задачи есть пункты с разными уровнями по этим свойствам, то с такой задачей можно застрять, при том что некоторые части уже готовы и их можно было бы продвинуть по конвейеру создания ценности. Это увеличивает объем незавершённой работы W.I.P. (work in progress). Эмоциональное самочувствие исполнителя ухудшается, потому что на него давит этот незавершённый гештальт.


5. Поэтапная декомпозиция


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


В этом случае нужно разделить работу над этим большим набором фич по нескольким этапам, релизам, спринтам. Частично готовую функциональность можно выкатывать на продакшн, но скрывать за фича-флагами или за авторизацией, чтобы бизнес-пользователи их не видели.
Не нужно обеспечивать декомпозицию сразу по всем этапам. Нужно откладывать процесс декомпозиции до последнего момента, когда её уже необходимо сделать, чтобы коллектив не простаивал.
К этому моменту будет собрана максимально возможная информация для принятия решений и распределения работы по исполнителям. Бережливое производство учит откладывать момент принятия важных решений до последнего момента.


6. Выделение смыслового ядра


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


Эти концепты я почерпнул из главы 15 Дистилляция книги Эрика Эванса Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем. Для простоты рассуждений я объединил неспециализированные подобласти (Generic Subdomains) и связные механизмы (Cohesive Mechanisms), от которых очищается смысловое ядро (Core Domain), в категорию второстепенных механизмов.


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


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


Эта стратегия очень похожа на метафору хирургической бригады Фреда Брукса из книги Мифический человеко-месяц.
Автор рекомендует назначать хирургом самого продуктивного программиста, а ассистентами менее продуктивных коллег с меньшим опытом.


Во времена, которые описывает Фред Брукс, системы были больше и обладали огромной случайной сложностью (accidental complexity) из-за неразвитости технологий и железа. Фичи были огромными, а итерации очень длинными.


Но стоит пойти дальше, ведь сейчас типичные бизнес-приложения разрабатываются небольшими приращениями, укладывающимися в итерации длиной от двух до четырёх недель. Технологии скрывают значительную часть сложности за абстракциями. Особенно в этом преуспевают omakase-фреймворки, в частности Ruby On Rails и подобные ему.


При этом есть огромный дефицит разработчиков на рынке, который только возрастает. Для компаний становится стратегически важно ускоренно выращивать младших сотрудников в средних и старших. Исходя из этой стратегии развития людей, стоит стараться чаще назначать хирургом не самого старшего, а самого младшего разработчика в команде и оказывать ему усиленную поддержку. Он будет вникать в фичу от уровня требований через декомпозицию с поддержкой наставника. Будучи максимально вовлечённым, младший разработчик будет ощущать драйв, осмысленность своей деятельности и быстрее расти.


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


В команде может оказаться добротный разработчик, отлично справляющийся с работой по чётким требованиям, но не желающий расти подобным образом. Ему можно поручать задачи по второстепенным механизмам, когда их декомпозирует хирург данной фичи. Других же разработчиков нужно назначать хирургами как можно чаще.


7. Выделение прототипа


Выделенное смысловое ядро может оказаться достаточно большим и неуправляемым по трудозатратам. Может быть не очевидным, как его разделить на подзадачи управляемого размера. В этом случае может выручить прототипирование. Стоит взять верхнеуровневую оценку трудозатрат на фичу и часть этого времени выделить на создание прототипа, а остальное время на продуктовую версию.


Можно поделить в пропорции золотого сечения, когда меньшая часть времени отдаётся на прототип, можно поделить и в отношении 1:3. Но важно выделять на прототип достаточно времени, чтобы не ощущалось чрезмерное давление. Лучше по факту закончить прототипирование чуть раньше тогда больше времени останется на продуктовую версию.


Как результат создания прототипа появится достаточно информации для разделения работы на задачи, выявления смыслового ядра и второстепенных механизмов, которые можно делегировать другим людям.


Прототип это тренировочная версия фичи, целью которой является обкатка решений проектирования.
При разработке прототипа допускается снижение стандартов качества, чтобы уменьшить давление на разработчиков и немного сэкономить время. Например, можно не покрывать тестами побочные сценарии. Можно даже не разрабатывать через тесты, если это не оправдано в рамках проверки идеи. Можно не заботиться о логировании, обработке ошибок и надёжности.


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


Продуктовую версию следует разрабатывать с нуля в соответствии со всеми принятыми практиками и дисциплинами, на прототип же лишь посматривать для следования очертаниям решения. Главный эффект от прототипа это формирование образа решения в голове и отбрасывание ошибочных предположений об алгоритме.


Когда нужно разработать какую-то фичу, обладающую достаточной новизной в системе, ощущается огромное давление. Нужно разработать всё сразу и не ниже уровня своего внутреннего стандарта. Это чувство парализует мозг и вгоняет в жёсткую прокрастинацию. Нужно дать возможность мозгу поэкспериментировать в более лёгкой, игровой форме, не требуя образцовых результатов.


Практика создания прототипов подробно описана в книге Дейва Томаса и Энди Ханта Программист прагматик. Путь от подмастерья к мастеру в 1999 году. Ещё раньше, в 1975 году, Фред Брукс описал подобную практику создания опытных систем в книге Мифический человеко-месяц.


Приведу яркую цитату из Главы 11 Планируйте на выброс для тех, кто скептически относится к прототипам:


Поэтому планируйте выбросить первую версию вам всё равно придётся это сделать.


От прецедента к должностной инструкции


У меня есть убеждение, что вместо стремления к недостижимому идеалу необходимо бороться за уменьшение степени несовершенства и боли. Для меня самым масштабным достижением на этом пути стало формулирование идей человечной декомпозиции.


Уже полтора года мы в команде осознанно применяем обозначенные в статье приёмы. Это дало тимлиду возможность не заниматься постоянно декомпозицией работы для других, а производить инновации: существенно улучшить архитектуру работы с логами, мониторингом и многими другими общими инфраструктурными механизмами. Тимлид смог поучаствовать в качестве основного разработчика в создании нескольких бизнес-фич. Младший разработчик смог сделать то же самое. Он получил драйв, самостоятельность и в результате вырос до мидла. Я сам стал работать ещё комфортнее, чем до изменений. Другие команды стали применять человечную декомпозицию и рассказали мне о положительном опыте.


Моя инструкция по человечной декомпозиции стала в FunBox частью должностных инструкций и отстаивается техническим руководством перед менеджментом в случае недопонимания.


Надеюсь, стремление к человечной декомпозиции поможет всем нам попасть в мир, в котором больше компаний с развитой инженерной культурой и продуктами, обладающими большей концептуальной целостностью.



Источники


Прийти к идеям человечной декомпозиции мне помогли следующие книги:


  1. Дейв Томас и Энди Хант Программист прагматик. Путь от подмастерья к мастеру.
  2. Фред Брукс Мифический человеко-месяц.
  3. Эрик Эванс Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем.
  4. Том ДеМарко и Тимоти Листер Человеческий фактор. Успешные проекты и команды.
  5. Мери и Том Поппендик Бережливое производство программного обеспечения. От идеи до прибыли.

Найти более глубокое обоснование идеям человечной декомпозиции и опровергнуть заблуждения о человеческой природе мне помогли эти книги:


  1. Все книги Эриха Фромма.
  2. Николай Бердяев Смысл творчества. Опыт оправдания человека и О назначении человека. Опыт парадоксальной этики.
  3. Кен Уилбер Благодать и стойкость. Духовность и исцеление в истории жизни и смерти Трейи Киллам Уилбер.
  4. Даниэль Каннеман Thinking Fast And Slow.
  5. Роберт Пёрсиг Дзен и искусство ухода за мотоциклом, в особенности рассуждения автора о смётке и вещах, которые её истощают. Я и само это слово узнал из книги.

Направления дальнейшего моего поиска:


  1. Клер Уильям Грейвз Спиральная динамика. Первый раз услышал о спиральной динамике от Максима Цепкова на конференции AgileDays'14, кстати, тогда же, когда и про #NoEstimates. Это знание сильно повлияло на меня.
  2. Кен Уилбер Интегральный подход. У Уилбера много книг. Я только начал их изучение но кажется, его миросозерцание созвучно Николаю Бердяеву и потому интересно, до каких озарений дошёл современный нам человек, интересующийся широким спектром вопросов философии, психологии, духовности и науки.
Подробнее..

Встречи планирования разработки в пандемию, или Как устроить электро PIP

15.04.2021 14:13:15 | Автор: admin
Сегодня мне хотелось бы с помощью моих коллег Agile-коучей Ани Родионовой, Макса Зотова и владельца продукта в Трайбе Розничное взыскание и урегулирование Свята Божухина рассказать о практике применения интересного инструмента. Итак, речь пойдёт о Program Increment Planning Meeting aka PI Planning.

Это метод планирования из SAFe (Scaled Agile Framework) гибкого фреймворка для крупных компаний. Ну, знаете, это когда люди стоят у стены, оклеенной стикерами, лепят всякие ниточки от одного стикера к другому, но при этом в городе не орудует маньяк.

Ниже пример места встречи одной из команд для PI в Сбере (обратите внимание на ту самую стену на заднем плане):

image

Все мы помним, что в марте 2020 года всё стало иначе: пандемия и режим самоизоляции внесли свои коррективы. Мы переобувались на лету за неделю, а то, что ранее, как нам казалось, было возможно только оффлайн, переместилось в онлайн.

С чего началось


В марте 2020 года бОльшая часть команд перешла на дистанционный формат работы. Со встречами по планированию нужно было что-то делать. С технической точки зрения у нас были Zoom и Confluence, но организационно нужно было немного докрутить.

Вот так выглядит SAFe, и скромную часть в нём занимает PI:

image

Вот что должны сделать разные участники, чтобы планирование прошло успешно:

image

На планировании руководителям нужно было рассказать о цели продукта за квартал так, чтобы у мини-команд, которые оказались на самоизоляции, было целостное понимание картины.

Scrum-мастерам поручили подготовить все шаблоны флипов (флипчартов). В онлайне они трансформировались в таблички на Confluence в специальном пространстве для совместной работы.

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

Группа фасилитаторов следила за тем, чтобы все шаблоны в Confluence были подготовлены и всё логистически готово.

Мы фокусировались на основных задачах и по возможности распределяли их. Must have-задачи это задачи, например, по изменению законодательства, которое в пандемию достаточно часто менялось, и к этому никак нельзя быть готовыми, но их точно надо сделать. У нас как у банка нет ни малейшей возможности не сделать этого в принципе.

Второй тип необычных задач сиротки. Представьте, что вы владелец продукта, у вас есть команда, вы понимаете, как развиваете свой продукт, но видите что-то со стороны, что тоже нужно сделать. И это важно. А вы уже не можете, ваш бэклог переполнен, и ваши задачи важнее. Тогда вы берёте эту задачу и помещаете её в специальное пространство, где живут все задачи, которые нужно сделать. Ну и, конечно, прояснить и посмотреть по эффектам, зачем они в принципе нам нужны.

Сама история почти не поменялась: если раньше были физические флипчарты, то теперь они стали виртуальными.

Первое пространство про цели, которые будет достигать команда в течение квартала. Команда показывает, какую метрику она обрабатывает, показывает её текущее состояние, будущее состояние, когда её можно померить, финансовый эффект и собственно какими задачами будут покрываться эта метрика, достигаться изменения.

Общий флип метрики это чтобы любой мимо неё проходящий мог сразу понять, в каком состоянии находится продукт на данный момент, в каком будет через квартал.

image

Следующий это флип зависимостей: наша огромная доска зависимостей мигрировала в маленькую табличку, которая просто указывает, какая непосредственная связь у команды с другой командой.

Потом риски. Если команда видит, что у неё, возможно, будут проблемы при реализации какой-то задачи, то она его обозначает, и потом уже мы решали, кто с этим работает и как.

image

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

И ещё один флип, на котором участники указывали контакты, по которым всегда можно было выйти на команду и обсудить бэклог на квартал.

Процесс


Мы бронируем два дня на эту работу, чтобы все всё успели. Начало в первый день в 10 утра, рассказ бизнеса, куда мы идём, каких основных ключевых целей ждёт от нас бизнес, какие основные изменения в архитектуре могут быть.

Дальше идёт работа команд:

  • Первая часть, когда команды как раз обсуждают цели и внутреннюю кухню, описания спринтов, говорят со смежниками, со стейкхолдерами.
  • Синхронизация команд, когда вы можете найти команду в Zoom, поговорить с ними. Под это полдня, важно, что все они на месте и ждут вас.

Во второй день команды рассказывают о своих целях, своих метриках, куда они идут, и уже верифицируют со всеми стейкхолдерами. Потом обязательно идёт обсуждение рисков. Следом голосование за доверие, насколько команда вообще верит в то, что запланировала. Допустим, пять это точно, прям точно-точно. В конце сбор обратной связи и ретроспектива.

В электронном виде стало удобнее: появилась чёткая структура, добавилась лучшая сохранность артефактов. Каждая задача ставится в Confluence.

В среднем из запланированного делается по факту 7080 %. Это очень качественный показатель.

Инвестиция в будущее


PIPы нужны потому, что планирование это инвестиция в будущее. После планирования, когда все команды так два дня плотно поработали с бэклогами, с зависимостями, обсудили всё на берегу, на последующие события, скрамы и планирование в команде уходит сильно меньше времени.

Вот так, если вкратце про PI Planning. Если хотите больше подробностей, то можно посмотреть выступление коллег тут.
Подробнее..

7 Кругов SPM или как сделать модульное приложение на Swift Package Manager

28.03.2021 20:06:28 | Автор: admin
Спасибо Jackie Zhao @jiaweizhao за фото на Unsplash Спасибо Jackie Zhao @jiaweizhao за фото на Unsplash

Думаю, многие разработчики сталкивались с задачей, когда нужно разбить проект на модули. В этой статье нет информации о том, как решать циклические зависимости или выделять по слоям функционал, подразумевается, что это уже реализовано. Цель статьи описать проблемы с которыми можно столкнуться при разбиении проекта на сабмодули с инструментом SPM, и помочь вам определиться стоит ли переходить на него.

Возможно, у многих возникнет вопрос: Зачем разбивать с помощью SPM?. Ведь можно просто создавать подпроекты. Можно, но в использовании SPM для разбиения есть несколько преимуществ:

  • С помощью SPM мы избавляемся от .xcodeproj файлов (забываем про конфликты в них);

  • Добавляем нативную возможность в будущем распространять наши модули. Допустим, вашему новому приложению понадобится использовать уже существующую авторизацию, тогда вам практически ничего не будет стоить распространение этого функционального модуля;

  • Нет альтернатив для проектов под разные операционные системы Linux/Windows;

  • Пакеты не требуют xcode для разработки.

Как выглядит сам процесс?

Для начала создаём Package.swift файл, в котором будут храниться все необходимые зависимости и информация о том, что из себя этот модуль представляет.

Пример файла с параметрами, с которыми вы скорей всего столкнётесь при работе:

import PackageDescription let package = Package(    // 1. Название нашего пакета    name: "Resources",    // 2. Платформы, которые поддерживаются нашим пакетом    platforms: [        .iOS(.v11),    ],    // 3. То, что другие программы будут брать в себя    // Продуктов может быть огромное колличество, хороший пример для этого Firebase SPM пакет    products: [        .library(            name: "Resources",            // Динамический или статический продукт          // по дефолту значение nil - SPM сам будет понимать что лучше подходит            //  преференция скорей всего будет отдаваться .static            type: .dynamic,            targets: ["Resources"]),    ],    // 4. Зависимости необходимые для работы нашего пакета,  // здесь они просто загружаются, добавляются они в targets    dependencies: [        // name - это название пакета(пункт 1), здесь нельзя указать кастомное название, необязательный параметр        .package(name: "R.swift.Library", url: "https://github.com/mac-cain13/R.swift.Library", .branch("master")),        // Пример подключения локального пакета        .package(path: "../Core")    ],    targets: [        // Это то из чего мы будем складывать наш продукт        // Для таргета обязательно нужно создать папку       // в Sources/имя_таргета для его работы      // либо если мы не хотим размещать его в Sources, можем указать "path:"        .target(            name: "Resources",            dependencies: [                // Здесь мы указываем зависимости которые мы хотим использовать в таргете                // name(пункт 3), package(пункт 1)                .product(name: "RswiftDynamic", package: "R.swift.Library")            ],            resources: [                // Все ресурсы которые мы хотим использовать нужно явно указать                // Путь к ним относительный от Sources/имя_пакета/то_что_мы_указали                // Если указываем папку, поиск идет рекурсивно                .process("Resources")            ])    ])

После создания модуля, подключаем его к проекту (либо к другому модулю, если модуль который вы делали лишь вспомогательный для других). Для удобства можно добавить пакет в workspace, просто drag and drop корневую папку с пакетом в Project navigator.

Введение на этом можно закончить и перейти к основной части статьи, где мы рассмотрим проблемы, с которыми вы можете столкнуться. На момент написания статьи мною использовались swift-tools-version:5.3, Xcode Version 12.2

Круг 1. Небольшое комьюнити.

Часто сложно найти информацию о проблемах с которыми можно столкнуться при работе с SPM. Из-за этого время затраченное на решение проблемы может значительно увеличиваться. Именно так и родилась идея для этой статьи: хочется поделится решениями, которые помогут сэкономить время.

Круг 2. Отсутствует фаза скриптов SPM пакета.

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

Круг 3. R.swift и SPM.

Так как у нас нет .xcodeproject файла, вызвать R.swift скрипт для генерации не получится, для этого нам нужно этот файл создать.

Для создания можно использовать XcodeGen и его аналоги. Либо swift package generate-xcodeproj, стандартного скрипта для генерации .xcodeproj файла в SPM.

Думаю, что лучше использовать стандартное средство и не добавлять новых зависимостей. Но, к сожалению, такая генерация не совсем подходит для R.swift. Если попробовать вызвать скрипт на сгенеренном файле, то получим:

error: [R.swift] Project file at 'file:///Users/.../Resources.xcodeproj/' could not be parsed, is this a valid Xcode project file ending in *.xcodeproj?

Чтобы локализовать проблему и найти почему файл проекта не может распарситься нужен XcodeEdit фреймворк который R.swift использует для парсинга. Билдим его и получаем exec файл, которому нужно передать .xcodeproj. Вызываем его:

pathtoexec/XcodeEdit-Example Resources.xcodeproj

и видим

Fatal error: 'try!' expression unexpectedly raised an error: XcodeEdit_Example.AllObjectsError.fieldMissing(key: "buildRules"): file XcodeEdit_Example/main.swift, line 21

Вот как можно это решить:

sed -i '' -e 's/isa = "PBXNativeTarget";/isa = "PBXNativeTarget";buildRules = ();/' Resources.xcodeproj/project.pbxproj

К сожалению, на этом проблемы не заканчиваются. generate-xcodeproj не добавляет файлы ресурсов в проект. То есть R.swift будет парсить .xcodeproj/.pbproject, но нужных файлов ресурсов там нет.

Это можно решить подключением ruby gem xcodeproj, с помощью которого можно добавить необходимые файлы. Но всё же такой подход более трудозатратный, и лучше вернуться к варианту с .xcodeproj генераторам по типу XcodeGen.

Пример минимального конфиг файла для XcodeGen:

# Название генерируемого xcodeprojname: Resourcestargets: Resources:   # Не важно что вы тут укажите, но это обязательные параметры   type: framework   platform: iOS   # Root folder с которого начинать генерировать   sources:     - Sources

Команда которая создаст .xcodeproj файл:

xcodegen generate --spec Resources.yml

Проблема с .xcodeproj решена, вот пример полного скрипта для генерации R.swift:

# Создание xcodeprojgenerateXcodeProject() { xcodegen generate --spec Resources.yml} # Получаем buildSettings с помощью логов xcodebuild команды, лучше проверять, создан ли этот файл# Чтобы сократить время скрипта и не билдить каждый разgetBuildSettings() { xcodebuild -project "Resources.xcodeproj" -target "Resources" -showBuildSettings > buildSettings.txt} # Создание enviroment переменных без которых R.swift скрипт работать не будетparseEnvironmentVariables() { export SRCROOT="$(cat buildSettings.txt | grep -m1 "SRCROOT" | sed 's/^.*= //' )" export TARGET_NAME="$(cat buildSettings.txt | grep -m1 "TARGET_NAME" | sed 's/^.*= //' )" export PROJECT_FILE_PATH="$(cat buildSettings.txt | grep -m1 "PROJECT_FILE_PATH" | sed 's/^.*= //' )" export TARGET_NAME="$(cat buildSettings.txt | grep -m1 "TARGET_NAME" | sed 's/^.*= //' )" export PRODUCT_BUNDLE_IDENTIFIER="$(cat buildSettings.txt | grep -m1 "PRODUCT_BUNDLE_IDENTIFIER" | sed 's/^.*= //' )" export PRODUCT_MODULE_NAME="$(cat buildSettings.txt | grep -m1 "PRODUCT_MODULE_NAME" | sed 's/^.*= //' )" export TEMP_DIR="$(cat buildSettings.txt | grep -m1 "TEMP_DIR" | sed 's/^.*= //' )" export BUILT_PRODUCTS_DIR="$(cat buildSettings.txt | grep -m1 "BUILT_PRODUCTS_DIR" | sed 's/^.*= //' )" export DEVELOPER_DIR="$(cat buildSettings.txt | grep -m1 "DEVELOPER_DIR" | sed 's/^.*= //' )" export SOURCE_ROOT="$(cat buildSettings.txt | grep -m1 "SOURCE_ROOT" | sed 's/^.*= //' )" export SDKROOT="$(cat buildSettings.txt | grep -m1 "SDKROOT" | sed 's/^.*= //' )" export PLATFORM_DIR="$(cat buildSettings.txt | grep -m1 "PLATFORM_DIR" | sed 's/^.*= //' )" export INFOPLIST_FILE="$(cat buildSettings.txt | grep -m1 "INFOPLIST_FILE" | sed 's/^.*= //' )" export SCRIPT_INPUT_FILE_COUNT=1 export SCRIPT_INPUT_FILE_0="$TEMP_DIR/rswift-lastrun" export SCRIPT_OUTPUT_FILE_COUNT=1 export SCRIPT_OUTPUT_FILE_0="$SRCROOT/Sources/Resources/Generated/R.generated.swift"} # Вызов скрипта для генерацииrswift() { R.swift generate --accessLevel public "$SCRIPT_OUTPUT_FILE_0"} # Заменяем бандл в котором R.swift пытается найти ресурсы по дефолту# Bundle.module - расширение генерируется SPM, если вы в пакете указываете для таргета ресурсные зависимостиreplaceRSwiftHostingBundle() { sed -i '' -e 's/Bundle(for: R.Class.self)/Bundle.module/' ./Sources/Resources/Generated/R.generated.swift} mkdir Sources/Resources/GeneratedgenerateXcodeProjectgetBuildSettingsparseEnvironmentVariablesrswiftreplaceRSwiftHostingBundle

Круг 4. Cocoapods зависимость в SPM пакете.

Основная сложность в том, что нет нативного способа подключения к пакету Pod или fat фреймворков, а некоторые разработчики свои библиотеки на SPM переводить не торопятся. Здесь нам поможет proxy пакет-обертка с XCFramework. Если Pod, который нужно подключить к SPM опенсорсный, то можно скачать исходный код библиотеки и собрать XCFramework по этому гайду.

Если Pod распространяется как собранный бинарник через .framework, то придется действовать немного по-другому.

Как и в предыдущем случае, нужно собрать XCFramework. Для начала из fat фреймворка, который поставляется Pod-ом необходимо собрать 2 фреймворка для симулятора и для девайса. После этого объединим их в универсальный фреймворк. Вот скрипт, который делает XCFramework из фремворка с архитектурами arm64 и x86_64. Узнать архитектуры, которые поддерживает фреймворк можно с помощью команды lipo -info pathtoframework.

# Делаем 2 копии фреймворка для симулятора и устройстваcp -a YandexMapsMobile YandexMapsMobile_simcp -a YandexMapsMobile YandexMapsMobile_device cd YandexMapsMobile_sim/YandexMapsMobile.framework/Versions/A# Здесь мы выбираем какая архитектура нам нужна из fat фреймворка и выносим ее в отдельный фреймворкlipo -thin x86_64 YandexMapsMobile -output YandexMapsMobile_x86_64# Создание universal framework-a для симулятора# Если вы хотите собрать не под одну архитектуру симулятора# а под несколько (i386), для этого нужно будет сделать два раза thin# и соединить их в createlipo -create YandexMapsMobile_x86_64 -output YandexMapsMobile_simrm -rf YandexMapsMobile YandexMapsMobile_x86_64mv YandexMapsMobile_sim YandexMapsMobilecd ../../../.. # Просто дублирование кода, но сборка под девайсcd YandexMapsMobile_device/YandexMapsMobile.framework/Versions/Alipo -thin arm64 YandexMapsMobile -output YandexMapsMobile_arm64lipo -create YandexMapsMobile_arm64 -output YandexMapsMobile_devicerm -rf YandexMapsMobile YandexMapsMobile_arm64mv YandexMapsMobile_device YandexMapsMobilecd ../../../.. # Объединяем в xcframeworkxcodebuild -create-xcframework -framework YandexMapsMobile_sim/YandexMapsMobile.framework -framework YandexMapsMobile_device/YandexMapsMobile.framework -output YandexMapsMobile.xcframework

И после того как был сделан XCFramework, необходимо обернуть его в пакет с помощью binaryTarget. Также можно дополнительно обернуть его необходимыми зависимостями, подключив его в другой таргет:

let package = Package(    name: "YandexMapsMobileWrapper",    platforms: [        .iOS(.v11),    ],    products: [        .library(            name: "YandexMapsMobileWrapper",            type: .static,            // Используем таргет обертку над XCFramework и его зависимостями            targets: ["YandexMapsMobileWrapper"]),    ],    dependencies: [    ],    targets: [        // Подключаем наш фреймворк локально, также его можно добавлять и через url        .binaryTarget(name: "YandexMapsMobileBinary", path: "YandexMapsMobile.xcframework"),        // обертываем наш фреймворк зависимостями        .target(            name: "YandexMapsMobileWrapper",            dependencies: [                .target(name: "YandexMapsMobileBinary"),            ],            linkerSettings: [                .linkedFramework("CoreLocation"),                .linkedFramework("CoreTelephony"),                .linkedFramework("SystemConfiguration"),                .linkedLibrary("c++"),                .unsafeFlags(["-ObjC"]),            ]),    ])

Круг 5. Краш при попытке получить бандл SPM пакета (Bundle.module).

К сожалению, мне не удалось понять причину почему так происходит, но бывают случаи когда бандл с ресурсами, который на текущий момент называется yourpackagename_yourpackagename, не попадает в директорию, где его ждет код авто-сгенерированный пакетом. Как вариант, можно самостоятельно написать код, который делает автогенератор и добавить туда ещё один возможный путь.

private class BundleFinder {}  // Это копия автосгенерированого SPM расширения, есть 2 отличияpublic extension Bundle {        // Отличие 1, другое название, чтобы не было конфликтов    static var resourceBundle: Bundle = {         let bundleName = "Resources_Resources"        let candidates = [            Bundle.main.resourceURL,            Bundle(for: BundleFinder.self).resourceURL,            Bundle.main.bundleURL,            // Отличие 2, еще один путь где может лежать бандл с ресурсами            Bundle(for: BundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent(),        ]        for candidate in candidates {            let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")            if let bundle = bundlePath.flatMap(Bundle.init(url:)) {                return bundle            }        }        fatalError("unable to find bundle named \(bundleName)")    }()    }

Круг 6. Блокирующая Resolve Swift Packages стадия.

Проблема проявляется когда все ваши SPM фреймворки подкачены проектом, и вы открываете ваш проект после его закрытия. Если SPM зависимостей много, то Xcode может резолвить их больше минуты. В течение этого времени Xcode может зависнуть и не отвечать на интеракции.

Круг 7. Other Linker Flags и SPM.

Может возникнуть ситуация, когда пакет должен линковаться со специальными флагами. Частый пример это ObjC флаг. Эти флаги для линковщика можно указать с помощью likerSettings: [.unsafeFlags([ObjC])] в таргете вашего пакета. К сожалению, если ваш пакет использует unsafeFlags, то его нельзя подключить к проекту напрямую, только через прокси-пакет, и только с указанием его как локальной или branch зависимости.

Вывод

Swift Package Manager удобный и интуитивно понятный нативный менеджер зависимостей. Однако, если вы планируете использовать его для разбивки приложения на подпроекты-пакеты, то нужно быть готовым к временным рискам и проблемам. К счастью, почти любая проблема связанная с SPM имеет свой workaround, но готовы ли вы идти на них и искать их?

Надеюсь решения проблем, с которыми я столкнулся помогут вам!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru