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

Блог компании dodo engineering

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

25.08.2020 18:19:47 | Автор: admin
Для меня всегда было загадкой, как люди делают хардверные стартапы. С программированием понятно, жмешь одни кнопки, рисуешь другие. А как там с реальным продуктом? Как находят форму? Как подбирают технологию? Как делают устройства удобным? Где искать производителей?

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



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

О чем ваш продукт?


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

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

В беруши встроен регулятор громкости, звук можно менять от 5 до 40 дБА. В отличие от обычных пенных или силиконовых беруш, Veer не нужно доставать, чтобы лучше слышать, достаточно повернуть внешнюю часть корпуса.

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



Как появилась идея?


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

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

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

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

Чем занимались до этого?


Ривер это инкубатор гаджет-стартапов. Мы развиваем b2c гаджеты, которые решают старые проблемы новыми, лучшими способами, от идеи до серийного производства.

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

Глава 1: разработка корпуса


Что решали в первую очередь?


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

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

Сначала получилось заглушать всего 25 дБ


Корпус первого прототипа сделали из металлического цилиндра диаметром 1 см. Внутри использовали фильтр: на 3d принтере напечатали регулятор и 4 круглые пластины с отверстиями в разных местах. Пластины располагались друг над другом и одна за другой закрывали отверстия.

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

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



Как измеряли шумоподавление?


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



Как увеличили заглушение?


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



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

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



Как работает плавная регулировка?


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



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


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

Глава 2: разработка амбушюры и главный квест


Поговорим про насадку. Какие этапы разработки вы прошли?


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

Гидравлическая? Ненадёжно


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



С перепонками? Некомфортно


Пока мы изучали мир ушных затычек, наткнулись на амбюшуру в форме конуса с перепонками, которая используется в военной авиации. Мы отпрототипировали подобную с 15 перепонками. Вышло очень не комфортно тонкий кончик упирался в ушную перепонку. От конусной формы отказались, но перепонки решили сохранить и сделали форму цилиндра.

Сначала разработали 3D модель. На основе модели напечатали на 3D принтере литьевую форму. Чтобы быстро получить прототип амбушюры вручную заливали силикон в форму шприцом.

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

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

Классические вакуумные + спираль? Снова некомфортно


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

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

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



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

Двухкупольная с двумя бомбошками? Не держится в ухе


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

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

За 3 дня нарисовали беруши и форму, напечатали форму, отлили амбушюру. Раздали тестерам предыдущей версии, результаты опять вышли спорные. 2 сферы неудобно засовывались и не очень хорошо заглушали, зато легко доставались и выпрыгивали из ушей сами собой. Из-за того, что ушной канал изогнутый, а амбушюра прямая, нарушался вакуум и форма сферы нарушалась. Мы планировали, что амбушюра будет подстраиваться под изгиб, но так не случилось.



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

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

Амбушюра с анатомическим изгибом. Финал.


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

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



Пожертвовали удобством первого надевания ради уровня заглушения


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

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


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

Глава 3: производство


Где искать поставщиков?


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

Что заказываете в Китае?


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

Еще в Китае заказывали кейсы для переноски берушей. Маленькие кейсы для амбушюр делаем уже в России, оказалось быстрее и не дороже.

Что делаете в России?


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

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

Новая оснастка устанавливается на фрезерный стол. Оператор помещает 12 заготовок в ложемент и зажимает их. Инструмент, который вытачивает деталь, спроектировали и изготовили специально для нашего корпуса. Это фреза, ее профиль повторяет контур носика корпуса.

А что производите сами?


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

Глава 4: тест драйв


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

Примерка


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

Если вставить неправильно, то будет несколько минусов.

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

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

Шумоизоляция


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

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



Внешний вид


На ухе смотрится хорошо. Мне сетка нравится, регулятор громкости металлически блестит, в итоге гармонично. Из-за сетки выглядят будто в них встроен ретро микрофон.



Комфорт


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

Просидеть в них весь день можно, ощущения нормальные, но я планирую надевать их временами:

  • Когда еду на работу в метро. Шум вагона тише, но людей слышно, если кто-то хочет выйти.
  • Под вечер в офисе, когда все начинают чуть меньше работать и чуть больше говорить.
  • Когда ребенок начал активно играть дома, а я работаю на удаленке.

Форма и гигиена


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

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

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

Итог


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

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

Тонкости авторизации обзор технологии OAuth 2.0

21.09.2020 18:14:10 | Автор: admin
Информационная система Dodo IS состоит из 44 различных сервисов, таких как Трекер, Кассы ресторана или Базы знаний и многих других. 3 года назад мы написали сервис Auth для реализации сквозной аутентификации, а сейчас пишем уже вторую версию. В основе сервиса лежит стандарт авторизации OAuth 2.0. Он довольно сложный, но если будете работать над аналогичным сервисом, стандарт вам пригодится. В этой статье я постарался рассказать о стандарте максимально просто и понятно, чтобы вы сэкономили время на его изучение.



Задача Auth


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

У сервиса Auth есть три основные задачи:

  • Единая точка аутентификации (SSO) для всех сервисов системы. Сервисы не хранят учётные данные, а доверяют это одному выделенному сервису.
  • Безопасный и гранулированный доступ к ресурсам. Безопасный, потому что пароли хранятся в одном месте и максимально защищены. Гранулированный, так как владельцы сервисов могут настраивать доступ к ресурсам как они захотят, опираясь на данные, пришедшие из сервиса аутентификации.
  • Централизованное управление пользователями и доступом. Благодаря тому, что вся информация о пользователе хранится в сервисе аутентификации, мы можем управлять пользователями централизованно.

Проблемы


Первая версия Auth часть монолита. Он использует свой собственный протокол общения с сервисами. Такая схема была необходима в тот момент, но за несколько лет работы проявились проблемы.

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

Dodo IS зависит от Auth. В старой реализации внешние сервисы обращаются к Auth при каждом действии пользователя, чтобы валидировать данные о нём. Настолько сильная привязка может привести к остановке работы всей Dodo IS, если Auth приляжет по какой-то причине.

Auth зависит от Redis. Притом достаточно сильно неисправность работы Redisа приведёт к падению Authа. Мы используем Azure Redis, для которого заявленный SLA 99,9%. Это значит, что сервис может быть недоступен до 44 минут в месяц. Такие простои не позволительны.

Текущая реализация Auth использует свой протокол аутентификации, не опираясь на стандарты. В большинстве своих сервисов мы используем C# (если говорим о backend) и у нас нет проблем с поддержкой библиотеки для нашего протокола. Но если вдруг появятся сервисы на Python, Go или Rust, разработка и поддержка библиотек под эти языки потребует дополнительных затрат времени и принесет дополнительные сложности.

Текущий Auth использует схему Roles Based Access Control, которая базируется на ролях. Обычно с ролью выдаётся полный доступ к определённому сервису, вместо привязки к конкретному функционалу. Например, в пиццериях есть заместители управляющего, которые могут вести определенные проекты: составлять графики или учитывать сырьё. Но у нас нет выдачи прав на конкретные компоненты системы. Приходится выдавать полный доступ к сервису, чтобы сотрудники могли получить доступ к составлению графиков или настройкам какого-либо компонента учёта.

Проблемы подтолкнули к тому, чтобы спроектировать и написать новую версию Auth. На старте проекта мы потратили 3 недели только на изучение стандартов авторизации и аутентификации OAuth 2.0 и OpenID Connect 1.0.

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

Что такое ОAuth2.0?


Разработку нового Auth мы решили начать с изучения доступных протоколов и технологий. Самый распространённый стандарт авторизации фреймворк авторизации OAuth2.0.

Стандарт был принят в 2012 году, и за 8 лет протокол меняли и дополняли. RFC стало настолько много, что авторы оригинального протокола решили написать OAuth 2.1, который объединит все текущие изменения по OAuth 2.0 в одном документе. Пока он на стадии черновика.

Актуальная версия OAuth описанна в RFC 6749. Именно его мы и разберем.

OAuth 2.0 это фреймворк авторизации.

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

Особенности:

  • Разделение сущности пользователя и приложения, запрашивающего доступ. Благодаря этому разделению мы можем управлять правами приложения отдельно от прав пользователя.

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

Разберёмся подробнее в особенностях.

Роли


В OAuth 2.0 определены четыре роли:

  • Resource owner сущность, которая имеет права доступа на защищённый ресурс. Сущность может быть конечным пользователем или какой-либо системой. Защищённый ресурс это HTTP endpoint, которым может быть что угодно: API endpoint, файл на CDN, web-сервис.
  • Resource server сервер, на котором хранится защищённый ресурс, к которому имеет доступ resource owner.
  • Client. Это приложение, которое запрашивает доступ к защищённому ресурсу от имени resource owner и с его разрешения с авторизацией.
  • Authorization server сервер, который выдаёт клиенту токен для доступа к защищённому ресурсу, после успешной авторизации resource owner.

Каждый участник взаимодействия может совмещать в себе несколько ролей. Например, клиент может быть одновременно resource owner, и запрашивать доступ к своим же ресурсам. Схему взаимодействия рассмотрим дальше.

Важно: клиент должен быть заранее зарегистрирован в сервисе. Как это сделать?

Регистрация клиента


Способ регистрации клиента, например, ручной или service discovery, вы выбираете сами, в зависимости от фантазии конкретной реализации. Но при любом способе при регистрации, кроме ID клиента, должны быть обязательно указаны 2 параметра: redirection URI и client type.

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

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

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

Токены


Токен в OAuth 2.0 это строка, непрозрачная для клиента. Обычно строка выглядит как случайно сгенерированная её формат не имеет значения для клиента. Токен это ключ доступа к чему-либо, например, к защищённому ресурсу (access token) или к новому токену (refresh Token).

У каждого токена своё время жизни. Но у refresh token оно должно быть больше, т.к. он используется для получения access token. Например, если срок жизни access token около часа, то refresh token можно оставить жить на целую неделю.

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

За access token закреплён определённый набор прав доступа, который выдаётся клиенту во время авторизации. Давайте разберёмся, как выглядят права доступа в OAuth 2.0.

Права доступа


Права доступа выдаются клиенту в виде scope. Scope это параметр, который состоит из разделённых пробелами строк scope-token.

Каждый из scope-token представляет определённые права, выдающиеся клиенту. Например, scope-token doc_read может предоставлять доступ на чтение к какому-то документу на resource server, а employee доступ к функционалу приложения только для работников фирмы. Итоговый scope может выглядеть так: email doc_read employee.

В OAuth 2.0 мы сами создаём scope-token, настраивая их под свои нужды. Имена scope-token ограничиваются только фантазией и двумя символами таблицы ASCII " и \.

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

Абстрактный OAuth 2.0. Flow c применением Access token


Мы рассмотрели роли, рассмотрели виды токенов, а также как выглядят scope. Посмотрим на flow предоставления доступа к сервису.

Ниже представлена абстрактная схема (или flow) взаимодействия между участниками. Все шаги на данной схеме выполняются строго сверху вниз. Разберём детальнее.



  • Client отправляет запрос на доступ к требуемому ресурсу resource owner.
  • Resource owner передаёт обратно клиенту authorization grant, который подтверждает личность resource owner и его права на ресурс, доступ к которому запрашивает client. В зависимости от flow это может быть токен или учётные данные.
  • Client отправляет authorization grant, полученный в предыдущем шаге authorization server, ожидая от него Access token для доступа к защищённому ресурсу.
  • authorization server убеждается в валидности authorization grant, после чего отсылает access token клиенту в ответ.
  • Получив access token, клиент запрашивает защищённый ресурс у resource server.
  • Resource server убеждается в корректности access token, после чего предоставляет доступ к защищённому ресурсу.

Клиент получает одобрение от resource owner, на основе которого ему выдаётся доступ к ресурсу. Всё просто. А будет ли так же просто, если мы добавим в эту схему работу с refresh token?

Абстрактный OAuth 2.0. Flow c применением Refresh token


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



Схема подробнее:

  • Client приходит c authorization grant к authorization server и просит предоставить ему access token и refresh token.
  • Authorization server убеждается, что с authorization grant всё нормально и возвращает клиенту запрошенные access token и refresh token.
  • Client с access token запрашивает защищённый ресурс, пока не получит первую ошибку доступа к ресурсу invalid token error.
  • После получения ошибки доступа, клиент идет к authorization server с refresh token и просит заменить просроченный access token на новый.
  • В ответ клиент получает новый access token, а также новый refresh token, либо продлевается время жизни старого refresh token.

Что такое grant?


Grant это данные, которые представляют из себя успешную авторизацию клиента владельцем ресурса, используемые клиентом для полученияaccess token.

Например, когда мы где-либо аутентифицируемся с помощью Google, перед глазами всплывает уведомление. В нём говорится, что такой-то сервис хочет получить доступ к данным о вас или к вашим ресурсам (выводятся запрашиваемые scope-token). Это уведомление называется Consent Screen.

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

Существует 4 + 1 способа получения grant grant type:

  • Authorization code используется для confedencial клиентов web-сервисов.
  • Client credentials используется для confedential клиентов, которые запрашивают доступ к своим ресурсам или ресурсам, заранее согласованным с сервером авторизации.
  • Implicit использовался public-клиентами, которые умеют работать с redirection URI (например, для браузерных и мобильных приложений), но был вытеснен authorization code grant с PKCE (Proof Key for Code Exchange дополнительная проверка, позволяющая убедиться, что token получит тот же сервис, что его и запрашивал. Прочитать подробнее RFC 7636).
  • Resource owner password credentials. В RFC 6819, посвящённому безопасности в OAuth 2.0, данный тип grant считается ненадёжным. Если раньше его разрешалось использовать только для миграции сервисов на OAuth 2.0, то в данный момент его не разрешено использовать совсем.
  • Device authorization (добавлен в RFC 8628) используется для авторизации устройств, которые могут не иметь веб-браузеров, но могут работать через интернет. Например, это консольные приложения, умные устройства или Smart TV.

Актуальными можно считать только authorization code (с PKCE), client credentials и device authorization grant, но мы рассмотрим все. Рассматривать grant будем в порядке возрастания сложности понимания.

Client credentials grant flow


Имеет самый простой flow, напоминающий обычную авторизацию на любом сервисе. Она выполняется с помощью учётных данных клиента, которые представляют собой client id и client secret аналог логина и пароля для пользователя. Так как для аутентификации требуется client secret, который должен соответствующе храниться, данный flow могут использовать только confedential клиенты.



Схема проста: клиент аутентифицируется на сервере авторизации передавая client id и client secret. В ответ получает access token, с которым уже может получить доступ к нужному сервису.

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

Resource owner password credentials flow


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



Resource owner передаёт свой логин и пароль клиенту, например, через формы на клиенте. Клиент, в свою очередь, с помощью него получает access token (и, опционально, refresh token).

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

Authorization code


Самый распространённый flow на данный момент. В основном используется для confidential клиентов, но с появлением дополнительной проверки с помощью PKCE, может применяться и для public-клиентов.

В данном flow взаимодействие client с resource owner проходит через user-agent (браузер). К user-agent есть одно требование: он должен уметь работать с HTTP-редиректами. Без этого resource owner не сможет попасть к серверу авторизации и вернуться обратно с grant.



Данный flow сложнее, чем предыдущие, поэтому будем разбирать по шагам. Для начала представим, что мы resource owner и перешли на страницу сервиса онлайн-обучения, который хочет сохранять результаты обучения к нам в облако. Ему требуется получить доступ к нашему ресурсу, например, определённой директории в облаке. Мы нажимаем на Авторизоваться и начинается путешествие по Authorization code grant flow:

  • На первом шаге клиент перенаправляет resource owner с помощью user-agent на страницу аутентификации Authorization server. В URI он указывает client ID и redirection URI. Redirection URI используется для понимания, куда вернуть resource owner после того, как авторизация пройдёт успешно (resource owner выдаст разрешение на scope, запрашиваемый клиентом).
  • Взаимодействуя с сервером авторизации через user-agent, resource owner проходит аутентификацию на сервере авторизации.
  • Resource owner проверяет права, которые запрашивает клиент на consent screen и разрешает их выдачу.
  • Resource owner возвращается клиенту с помощью user-agent обратно на URI, который был указан как redirection URI. В качестве query-параметра будет добавлен authorization code строка, подтверждающая то, что resource owner выдал необходимые права сервису.
  • С этим authorization code клиент отправляется на сервер авторизации, чтобы получить в ответ access token (ну и refresh token, если требуется).
  • Сервер авторизации валидирует authorization code, убеждаясь, что токен корректный и выдаёт клиенту access token (и опционально refresh token). С его помощью клиент сможет получить доступ к заветному ресурсу.

Если представить нас на месте resource owner, то мы видим просто перенаправление на сервер авторизации, аутентифицируемся, подтверждаем доступ на Consent screen и нас отправляет на уже работающий сервис. Например, мы проходим это много раз, когда заходим на сервис под учётной записью Google, Facebook или Apple.

Следующий flow построен на основе этого.

Implicit grant


Это оптимизация Authorization code grant flow для public-клиентов, которые умеют работать с redirection URI. Например, для браузерных приложений на JavaScript, или мобильных приложений. Требование к user-agent, с помощью которого взаимодействуют клиент и resource owner, сохраняется: он должен уметь работать с HTTP-редиректами.

Между authorization code и implicit есть основное отличие: вместо получения authorization code и access token по нему, мы сразу получаем access token после успешной авторизации resource owner. Кроме того, здесь не используется client secret из соображений безопасности приложение можно дизассемблировать и получить его. Подлинность проверяется только по redirection URI.



Многие шаги из данной схемы похожи на шаги из authorization code, но предлагаю их разобрать также подробно. Представим, что некое браузерное приложение хочет сохранять свои настройки в нашем Git-репозитории. Мы нажимаете Войти в GitHub и на этом этапе начинается работа Implicit flow:

  • Клиент с помощью user-agent и HTTP-редиректа перенаправляет resource owner на сервер авторизации. В параметрах запроса передает client ID и redirection URI, которые нужны для аутентификации клиента и последующего возврата resource owner обратно.
  • Resourse owner аутентифицируется, взаимодействуя через user-agent с сервером авторизации. Заодно подтверждает выдачу grant клиенту, с client ID которого он пришёл.
  • После подтверждения выдачи grant (нажатия allow на consent screen), user-agent возвращает resource owner на redirection URI. Кроме того, в URI fragment передаётся access token (URI fragment это то, что обычно идёт в URI после символа #).
  • Сам фрагмент сохраняется локально в user-agent. User-agent двигается дальше по redirection URI за web-страницей, которая нужна для получения access token и других необходимых данных из фрагмента. Она может находиться как на самом клиенте, так и на удалённом ресурсе, например, на CDN.
  • Web-ресурс возвращает web-страницу (может содержать в себе скрипт), которая может прочитать полностью redirection URI, в том числе и значение, указанное в фрагменте.
  • User-agent отрисовывает локально полученную страницу, включая исполнение скриптов, которые он получил от web-hosted client resource, которые получают access token.
  • Полученный access token user-agent просто передаёт клиенту.

Это сложный flow. Он мало используется в реальных сценариях. Но его всё ещё можно встретить в legacy-проектах.

Device authorization (RFC 8628)


С 2012 до 2019 появилось много умных устройств, на которых неудобно авторизоваться. Например, неудобно вводить сложный логин и пароль на телевизоре каждый раз при открытии ресурса. На некоторых устройствах это невозможно, например на серверных ОС без графического интерфейса. В августе 2019 этот flow появился как раз для таких сценариев.

Есть, как минимум, 3 требования к устройствам, чтобы работа с помощью Device authoraztion grant flow была возможна:

  • Устройство должно иметь возможность совершать исходящие HTTPS-запросы.
  • Устройство должно иметь возможность отображать URI и код пользователю.
  • Каждое авторизуемое устройство принадлежит resource owner, который для успешной авторизации должен иметь другое устройство с браузером, чтобы перейти по указанному URI и ввести указанный код.



Возможно, схема кажется сложной из-за обилия стрелок. Разберём её также пошагово, как и разбирали сложные flow до него.

Представим, что мы пытаемся авторизоваться на web-сервисе с помощью телевизора. Мы видим кнопку Авторизоваться как устройство и нажимаем. В этот момент начинается наш Device flow:

  • Телевизор делает запрос на сервер авторизации, передавая ему свой client ID.
  • Сервер авторизации убеждается, что такой клиент зарегистрирован и имеет соответствующий тип grant.
  • Если всё хорошо, то Authorization server возвращает device code, user code и verification URI. Device code это уникальный идентификатор устройства, которое авторизуется в системе.
  • Устройство отображает user code и verification URI владельцу этого устройства resource owner. Redirection URI может быть передан как строкой, так и с помощью QR-кода ограничений нет.
  • После того, как устройство отобразило user code и verification URI, оно начинает раз в некоторое время опрашивать сервер авторизации о её успешности.
  • Дальше в бой вступает resource owner. Он переходит по указанному verification URI, аутентифицируется и вводит user code, который он получил от устройства, подтверждая выдачу необходимых scope устройству. На этом действия от имени resource owner закончены.
  • Всё это время устройство (пункт 3) опрашивало сервер авторизации о её успешности. Устройство в очередной раз идёт к серверу авторизации со своим device code и client ID в надежде, что авторизация на этот раз прошла.
  • В этот раз, когда resource owner подтвердил передачу необходимых прав устройству, сервер авторизации возвращает в ответе на запрос access token (если предусмотрено настройками сервера и refresh token). И с помощью токена устройство уже может продолжать работу с ресурсом.

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

Вместо вывода


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

Если хотите погрузиться в тематику детальнее, то рекомендую в RFC 6749 (для OAuth 2.0) и RFC 8628 (для Device Flow). Кроме того, следить за актуальными версиями RFC можно на ресурсе, посвящённому OAuth.

Если статья была полезна и захотите подробностей пишите в комментариях, и в следующих статьях расскажу о PKCE, о протоколе аутентификации OpenID Connect 1.0, о нашей реализации сервера аутентификации и многом другом.

Полезные ссылки:

Подробнее..

Зачем мне психотерапевт?

10.12.2020 20:17:14 | Автор: admin
После терапии несколько лет мне захотелось пробовать то, что я раньше боялся. Например, в 2014 году я решил начать выступать. Но так как мне было страшно даже разговаривать с людьми, каждый раз перед выступлением всю ночь не спал мозг взрывался. Со временем мозг переобучился и привык, и теперь страх для меня вызов. Это воспоминания Ивана Замесина о последствиях курса психотерапии. Иван предприниматель и основатель сервиса подбора психотерапевтов Мета. Недавно он приходил к нам на подкаст Ничего такого, где рассказал зачем нужен психотерапевт, какие установки мешают начать терапию и что можно от неё ожидать. Мы под впечатлением написали статью на основе разговора.




О нашем собеседнике: Иван Замесин (zamesin) отвечал за продукт в Яндекс.Картинках, работал в chatfuel.com как продакт-менеджер. Сейчас Иван предприниматель: проводит курсы по продуктовому мышлению в IT-компаниях, например, Skyeng, Cian, HH.ru, Mail.ru, и развивает сервис по подбору психотерапевтов Мета, как его создатель. Мета это сервис, который помогает сделать первый шаг к психотерапии. Ведёт блог, где делится результатами работы сервиса.



Зачем нужна психотерапия?


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

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

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

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

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

Утрированно эти три способа можно сравнить с ремонтом в квартире. Иногда достаточно поклеить новые обои и поменять диван, а иногда приходится восстанавливать всё после сильного пожара. Зависит от состояния.

Кто такой психотерапевт?


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

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

  • Высшее психологическое или медицинское образование по психиатрии.
  • 30 и больше часов супервизии (один из методов повышения квалификации) и не меньше 12 часов супервизии в год.
  • Больше 100 часов личной терапии.
  • Не меньше 500 часов дополнительного долгосрочного обучения в одном из направлений психотерапии, например, гештальт-терапии или когнитивно-поведенческой. При этом психотерапевт может стать психоаналитиком, если прошел подготовку в методе психоанализа.

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

Зачем ходить к психотерапевту?


Например, в 2010 году у меня отжали бизнес. Я остался без бизнеса, но с психологической травмой. После того, как от неё избавился с помощью психотерапии, запустил Мета.

Психотерапевт поможет, если:

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

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

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

Давай сходим к специалисту, он тебе поможет наладить сон.

Не нужно говорить, что близкий болен. Говорите, что есть проблема, и мы её решим. Никто не хочет, чтобы его считали больным.

Что мешает обратиться за помощью?


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

На это накладывается установка родом из советского прошлого (а может и более раннего) Ты мужик, сам справишься. Это вторая по популярности причина (по статистике ФОМ), по которой люди не идут к психотерапевтам. Если обратился за помощью слабак и нытик. Но это не так. Чтобы признать проблему и обратиться к психотерапевту нужна смелость.

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

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


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

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

Также терапия включает домашние задания и практики, например, телесные.

Что ожидать от психотерапии?


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

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

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

Некоторые травмы также влияют на интеллект, потому что снижается чувствительно. Об этом в книге Странный порядок вещей: жизнь, чувство и создание культур рассказывал Антонио Дамасио, профессор нейробиологии, психологии и философии Южно-Калифорнийского университета. Он утверждал, что травмы сказываются на интеллекте потому что он может зависеть от чувствительности.

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

Как терапия влияет на работу?


Не считая того, что с терапией я смог пережить потерю бизнеса, она помогла избавиться от слабых сторон моего продукта. В работе мы всегда вносим свой отпечаток. Мы личности, у нас есть свои тараканы и установки в голове. Илья Красинский высказал мысль: Слепые зоны компании это слепые зоны основателя. У меня слабая сторона операционная деятельность. Например, невнимательное отношение к найму дорого обошлось. Я практически бессознательно избегал скучные части компании и она страдала 1,5 года.

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

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

При этом результат может быть отсрочен и появиться уже после лечения. Например, только спустя год после лечения и запуска Мета моя психика на 100% приняла, что работает над сервисом. Это похоже на то, как будто живешь в квартире, где одна комната нормальная, а другая всегда закрыта. Её как бы не существует. Проходишь и не видишь даже дверь также было у меня.

Полезные материалы


How people change: relationships and neuroplasticity in psychoterapy. Это сборник исследований нескольких авторов под кураторством Дениела Сигела и Марион Соломон. В книге много интересного про работу мозга, психотерапию и то, как люди меняются.

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

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

Другого способа не существует.

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

Скотт Адамс, How to Fail at Almost Everything and Still Win Big. Перевод на русском ужасен берите оригинал.

Макс Тегмарк, Live 3.0. Жизнь 1.0 простая биологическая, например, бактерии. Жизнь 2.0 человек. Жизнь 3.0 пока ещё не появилась на Земле, но она сможет быстро менять сама себя в обход эволюции.

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

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

The Systems Bible: The Beginner's Guide to Systems Large and Small, Джон Гал. Забавная книга, которая почти полностью состоит из тезисов. Например:

  • Эффективность сложной системы редко превышает 5%. Под системой можно подразумевать любую сложную сущность, хоть Пенсионный фонд.
  • Армия полностью готов к войне, когда она закончилась. Это пример, когда во Франции после Первой мировой построили защиту от немцев, а они просто взяли и обошли её.
  • Временное решение с высокой вероятностью станет постоянным.

Весь Нассим Талеб. Он прекрасен рациональностью мышления.

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

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

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

Подкасты можно послушать:


Делитесь в комментариях встречались ли с психотерапией, обращались ли к врачам и как помогло. Если хотите обсудить статью подробее присоединяйтесь в Telegram чат в Dodo Engineering chat.
Подробнее..

История архитектуры Dodo IS ранний монолит

01.10.2020 18:11:51 | Автор: admin

Или каждая несчастная компания с монолитом несчастлива по-своему.

Разработка системы Dodo IS началась сразу же, как и бизнес Додо Пиццы в 2011 году. В основе лежала идея полной и тотальной оцифровки бизнес-процессов, причем своими силами, что еще тогда в 2011 году вызывало много вопросов и скептицизма. Но вот уже 9 лет мы идем по такому пути с собственной разработкой, которая начиналась с монолита.

Эта статья ответ на вопросы Зачем переписывать архитектуру и делать такие масштабные и долгие изменения? к предыдущей статье История архитектуры Dodo IS: путь бэкофиса. Начну с того как начиналась разработка Dodo IS, как выглядела изначальная архитектура, как появлялись новые модули, и из-за каких проблем пришлось проводить масштабные изменения.

Серия статей Что такое Dodo IS? расскажет про:

  1. Ранний монолит в Dodo IS (2011-2015 годы). (You are here)

  2. Путь бэкофиса: раздельные базы и шина.

  3. Путь клиентской части: фасад над базой (2016-2017 годы). (In progress)

  4. История настоящих микросервисов. (2018-2019 годы). (In progress)

  5. Законченный распил монолита и стабилизация архитектуры. (In progress)

Изначальная архитектура

В 2011 году архитектура Dodo IS выглядела так:

Первый модуль в архитектуре прием заказа. Бизнес-процесс был такой:

  • клиент звонит в пиццерию;

  • трубку берет менеджер;

  • принимает по телефону заказ;

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

Интерфейс информационной системы выглядел примерно так

Первая версия от октября 2011:

Чуть улучшенная в январе 2012

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

Их первое решение определило дальнейшую судьбу технологического стека:

  • Backend на ASP.NET MVC, язык C#. Разработчики были дотнетчиками, этот стек был им знаком и приятен.

  • Фронтенд на Bootstrap и JQuery: интерфейсы пользователя на самописных стилях и скриптах.

  • База данных MySQL: без затрат на лицензии, простая в использовании.

  • Серверы на Windows Server, потому что .NET тогда мог быть только под Windows (Mono обсуждать не будем).

Физически это все выражалось в дедике у хостера.

Архитектура приложения приема заказа

Тогда уже все говорили о микросервисах, а SOA лет 5 использовалось в крупных проектах, например, WCF вышел в 2006 году. Но тогда выбрали надежное и проверенное решение.

Вот оно.

Asp.Net MVC это Razor, который выдаёт по запросу с формы или от клиента HTML-страницу с рендерингом на сервере. На клиенте уже CSS и JS-скрипты отображают информацию и, по необходимости, выполняют AJAX-запросы через JQuery.

Запросы на сервере попадают в классы *Controller, где в методе происходит обработка и генерация итоговой HTML-страницы. Контроллеры делают запросы на слой логики, называемый *Services. Каждый из сервисов отвечал какому-то аспекту бизнеса:

  • Например, DepartmentStructureService выдавал информацию по пиццериям, по департаментам. Департамент это группа пиццерий под управлением одного франчайзи.

  • ReceivingOrdersService принимал и рассчитывал состав заказа.

  • А SmsService отправлял смс, вызывая API-сервисы по отправке смс.

Сервисы обрабатывали данные из базы, хранили бизнес-логику. В каждом сервисе был один или несколько *Repository с соответствующим названием. В них уже находились запросы к хранимым процедурам в базе и слой мапперов. В хранимках была бизнес-логика, особенно много в тех, которые выдавали отчетные данные. ОРМ не использовался, все полагались на написанный руками sql.

Еще был слой доменной модели и общих классов-хелперов, например, класс Order, хранивший заказ. Там же, в слое, находился хелпер для преобразования текста отображения по выбранной валюте.

Всё это можно представить такой моделью:

Путь заказа

Рассмотрим упрощенный первоначальный путь создания такого заказа.

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

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

  • Клиент называет продукты, которые хочет добавить в заказ.

  • Называет свой адрес и имя.

  • Оператор принимает заказ.

  • Заказ отображается в интерфейсе принятых заказов.

Все начинается с отображения меню. Залогиненный пользователь-оператор в один момент времени принимает лишь один заказ. Поэтому draft-корзина может храниться в его сессии (сеанс пользователя хранится в памяти). Там объект Cart, в котором продукты и информация о клиенте.

Клиент называет продукт, оператор нажимает на + рядом с продуктом, и на сервер отправляется запрос. По продукту вытаскивается информация из базы и добавляется информация о продукте в корзину.

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

Далее вводим адрес и имя клиента.

При нажатии Создать заказ:

  • Запрос отправляем в OrderController.SaveOrder().

  • Получаем Cart из сессии, там лежат продукты в нужном нам количестве.

  • Дополняем Cart информацией о клиенте и передаем в метод AddOrder класса ReceivingOrderService, где он сохраняется в базу.

  • В базе есть таблицы с заказом, составом заказа, клиентом и они все связаны.

  • Интерфейс отображения заказа идет и вытаскивает последние заказы и отражает их.

Новые модули

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

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

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

Технически модули оформлялись как Area (вот такая идея даже осталась в asp.net core). Там были отдельные файлы для фронтенда, моделей, а также свои классы контроллеров. В итоге система преобразовалась из такой...

в такую:

Некоторые модули реализованы отдельными сайтами(executable project), по причине совсем уже отдельного функционала и частично из-за несколько отдельной, более сфокусированной разработки. Это:

  • Site первая версия сайта dodopizza.ru.

  • Export: выгрузка отчетов из Dodo IS для 1C.

  • Personal личный кабинет сотрудника. Отдельно разрабатывался и имеет свою точку входа и отдельный дизайн.

  • fs проект для хостинга статики. Позже мы ушли от него, переведя всю статику на CDN Akamai.

Остальные же блоки находились в приложении BackOffice.

Пояснение по названиям:

  • Cashier Касса ресторана.

  • ShiftManager интерфейсы для роли Менеджер смены: оперативная статистика по продажам пиццерии, возможность поставить в стоп-лист продукты, изменить заказ.

  • OfficeManager интерфейсы для роли Управляющий пиццерии и Франчайзи. Здесь собраны функции по настройке пиццерии, её бонусных акций, прием и работа с сотрудниками, отчеты.

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

Они использовали общий слой сервисов, общий блок доменных классов Dodo.Core, а также общую базу. Иногда еще могли вести по переходам друг к другу. В том числе к общим сервисам ходили и отдельные сайты, вроде dodopizza.ru или personal.dodopizza.ru.

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

Для лучшего понимания масштаба модулей, сделанных в системе, вот схема из 2012 года с планами развития:

К 2015 году всё на схеме и даже больше было в продакшн.

  • Прием заказа перерос в отдельный блок Контакт Центра, где заказ принимается оператором.

  • Появились общедоступные экраны с меню и информацией, висящие в пиццериях.

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

  • Блок доставки стал отдельной Кассой Доставки, где заказ выдавался курьеру, который предварительно встал на смену. Учитывалось его рабочее время для начисления зарплаты.

Параллельно с 2012 по 2015 появилось более 10 разработчиков, открылось 35 пиццерий, развернули систему на Румынию и подготовили к открытию точек в США. Разработчики уже не занимались всеми задачами, а были разделены на команды. каждая специализировалась на своей части системы.

Проблемы

В том числе из-за архитектуры (но не только).

Хаос в базе

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

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

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

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

К одним и тем же таблицам производились очень разнородные запросы. Особенно страдали популярные таблицы, вроде упоминавшейся таблицы orders или таблицы pizzeria. Они использовались для вывода оперативных интерфейсов на кухне, аналитики. Еще к ним обращался сайт(dodopizza.ru), куда в любой момент времени могло придти внезапно много запросов.

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

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

Связность и запутанность в коде

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

Сервисы (классы в рамках одного монолитного большого проекта) могли вызывать друг друга для обогащения своих данных.

С самими классами-моделей, которые хранят данные, работа в коде велась различно. Где-то были конструкторы, через которые можно было указать обязательные поля. Где-то это делалось через публичные свойства. Конечно, получение и преобразование данных из базы было разнообразным.

Логика была либо в контроллерах, либо в классах сервисов.

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

Сложность большой разработки

Трудности возникли и в самой разработке. Нужно было делать разные блоки системы, причем параллельно. Вместить нужды каждого компонента в единый код становилось все труднее. Было не просто договориться и угодить всем компонентам одновременно. К этому добавлялись ограничения в технологиях, особенно касаемо базы и фронтэнда. Нужно было отказываться от JQuery в сторону высокоуровневых фреймворков, особенно в части клиентских сервисов (сайт).

В каких-то частях системы могли бы использоваться базы, более подходящие для этого. Например, позднее у нас был прецедент перехода с Redis на CosmosDB для хранения корзины заказа.

Команды и разработчики, занимающиеся своей областью явно хотели большей самостоятельности для своих сервисов, как в части разработки, так и в части выкатки. Конфликты при мерже, проблемы при релизах. Если для 5 разработчиков эта проблема несущественна, то при 10, а уж тем более при планируемом росте, все стало бы серьёзнее. А а впереди должна была быть разработка мобильного приложения (она стартанула в 2017, а в 2018 было большое падение).

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

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

Как блог Сила ума положил кассы в ресторанах

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

В блоге Сила ума был виджет, который показывал данные по выручке за год всей сети. Виджет обращался к публичному API Dodo, которое предоставляет эти данные. Сейчас эта статистика доступна на http://dodopizzastory.com/. Виджет показывался на каждой странице и делал запросы по таймеру каждые 20 секунд. Запрос уходил в api.dodopizza.ru и запрашивал:

  • количество пиццерий в сети;

  • общую выручку сети с начала года;

  • выручку за сегодня.

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

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

Схема выглядела так:

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

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

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

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

Бурный рост бизнеса

Почему нельзя было сделать сразу хорошо? Достаточно посмотреть на следующие графики.

Также в 2014-2015 было открытие в Румынии и готовилось открытие в США.

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

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

Быстрые решения, которые помогли

Проблемы требовали решения. Условно, решения можно разделить на 2 группы:

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

  • Системные и, поэтому, долгие. Реинжиниринг ряда модулей, разделение монолитной архитектуры на отдельные сервисы (большинство из них вполне не микро, а скорее макросервисы и про это есть доклад Андрея Моревского).

Сухой список быстрых изменений таков:

Scale up мастер базы

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

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

Реплики базы на чтение

Реплик для базы сделали две:

ReadReplica для запросов на справочники. Применяется для чтения справочников, типа, города, улицы, пиццерии, продуктов (slowly changed domain), и в тех интерфейсах, где допустима небольшая задержка. Этих реплик было 2, мы обеспечивали их доступность также, как и мастера.

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

Кэши в коде

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

Несколько серверов для бэкэнда

Бэкэнд приложения тоже надо было масштабировать, чтобы выдерживать повышенные нагрузки. Необходимо было сделать из одного iis-сервера кластер. Мы перенесли сессию приложений из памяти на RedisCache, что позволило сделать несколько серверов, стоящих за простым балансировщиком нагрузки с round robin. Сначала использовался тот же Redis, что и для кэшей, потом разнесли на несколько.

В итоге архитектура усложнилась

но часть напряженности удалось снять.

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

Подробнее..

Как мы накосячили пока делали Бриллиантовый чекаут и что из этого вышло

17.02.2021 16:08:10 | Автор: admin

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

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

При оформлении заказа у нас есть два обязательных экрана: для выбора адреса и для выбора способа оплаты.

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

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

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

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

Относим проблему дизайнерам.

Думаем: дизайн и обработка ошибок в приложении

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

Наш дизайнер Паша изучает задачу.Наш дизайнер Паша изучает задачу.

По ходу мы отмечали проблемные места.

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

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

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

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

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

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

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

Первая мысль

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

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

Пришлось задвинуть новый чекаут далеко и надолго.

Переключаемся на кастомизируемые комбо

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

Переключаемся на Приложение в ресторане

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

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

Ну, привет, Бриллиантовый Чекаут. Мы возвращаемся к тебе.

Рисуем: 12 макетов на одно и то же

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

Примеры макетов с разных подходов и итераций.Примеры макетов с разных подходов и итераций.

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

http://personeltest.ru/aways/www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/

Мы работаем во многих странах и везде свои особенности: где-то есть додо-рубли, где-то нет,где-то есть электронные чеки, где-то нет. А в Британии так вообще город можно сменить на экране оплаты. Все эти проблемы приходилось обдумывать и решать.

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

Разрабатываем и ошибаемся

Мы оценили разработку нового чекаута в 2 месяца, а закончили через 9. И вот почему.

Начали со сложного дизайна

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

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

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

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

Меняли дизайн на ходу

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

Решение: попробовать затащить прототипы. При планировании закладывать пару дополнительных дней на хоть как-то работающий вариант, который можно посмотреть, потыкать, ощутить.

Переиспользовали код, когда не нужно было

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

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

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

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

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

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

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

Недостаточно точно описывали таски

Один разработчик заболел, его задачи пришлось передать другому. При передаче несколько требований пропустили. А когда таски получили статус Готово готово было не то что нужно. Пришлось переделывать.

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

Обсуждали одни и те же места по нескольку раз

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

Решение: копить причины принятых решений.

Неправильно оценили сроки

Оценивали сроки, сейчас не шучу, чуть ли не на глаз. Код мест, с которыми пришлось бы работать, даже не открывали. Ну а че, мы уже ого как долго в проекте, все умные и красивые и так сможем! Оказалось, что нет.

Решение: оценивать таски глядя в код. Прямо берём, открываем исходники и смотрим от начала до конца как мы будем делать фичу. Заползаем в самые дальные уголки кода, которые зацепим. Смотрим, как там написан код. Нет ли старых паттернов, от которых мы уже отходим? Покрыто ли это место тестами? Если какого-то кода нет обсуждаем, каким он будет и как подцепится к имеющемуся.

Так дольше, но оценка будет честнее.

Решили сэкономить на тестах

Начали резать скоуп, когда поняли, что не успеваем: минус там, минус тут. Под нож попали и тесты. В итоге иногда что-то отпадало, приходилось чинить. Иногда отпадало снова и снова. Классика.

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

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

Не пошарили знания

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

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

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

Решение: Активнее шарить знания на встречах или во внутренней документации.

Самое главное решение

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

Так инкремент можно получать гораздо раньше. А не раз в год, как получилось у нас.

Re: Подводим итоги Final_v3

Спустя год разработки наконец-то доталкиваем новый чекаут до релиза. Сначала на Россию, здесь 90% заказов. А потом и на остальные страны, после разных допиливаний: додо-рубли, электронные чеки и прочее, о чем рассказывал выше.

Полезли в аналитику сравнивать конверсию, А ТАМ ТАКОЕ. Цифры просто космические: миллионы посыпались на нас сверху, разработка всего чекаута окупилась буквально за неделю. Потому что конверсия выросла аж на 5%!

А потом поняли, что аналитика кривая. Собрали новую и увидели, что конверсия выросла только на 0,5%. В целом неплохо, но хотелось чуть получше.

Подумали, посовещались, посоветовались и собрали аналитику в третий раз. На этот раз точно, железобетонно и финально: конверсия выросла на 1,5%. В рублях это дополнительные 2 000 000 в неделю.

Работаем над ошибками

БЧ сдан. Возвращаемся к Приложению в Ресторане.

  • Тратим на оценку задачи несколько дней.

  • Декомпозируем до посинения.

  • Постоянно смотрим в код, включаем в оценку время тесты.

  • На аналитику время заложили.

  • И на тестирование тоже.

  • И на возможные баги с прода.

  • И ещё всякого по мелочи.

Составили, в общем, самый настоященский и максимально проработанный роадмап.

И релизнули фичу день в день с планом.

Вот такой вот хеппи енд


Также будет интересно почитать:

Подписывайтесь начат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на каналDodo Engineering, где мы постим всё, что с нами интересного происходит.

А если хочешь присоединиться к нам в Dodo Engineering, то будем рады сейчас у нас открыты вакансииiOS-разработчиков(а ещё для Android, frontend, SRE и других). Присоединяйся, будем рады!

Подробнее..

Как заблокировать приложение с помощью runBlocking

10.02.2021 14:12:50 | Автор: admin

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

Напишите где-нибудь в UI потоке (например в методе onStart) такой код:

//где-то в UI потокеrunBlocking(Dispatchers.Main) {  println(Hello, World!)}

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


Сравним код выше с более низкоуровневым подходом с потоками. Вы можете написать в главном потоке вот так:

//где-то в UI потокеHandler().post {println("Hello, World!") // отработает в UI потоке}

Или даже так:

//где-то в UI потокеrunOnUiThread {  println("Hello, World!") // и это тоже отработает в UI потоке}

Вроде конструкция очень похожа на наш проблемный код, но здесь обе части кода работают (по-разному под капотом, но работают). Чем они отличаются от кода с runBlocking?

Как работает runBlocking

Для начала небольшой дисклеймер. runBlocking редко используется в продакшн коде Android-приложения. Обычно он предназначен для использования в синхронном коде, вроде функций main или unit-тестах.

Несмотря на это, мы всё-таки рассмотрим этот билдер при вызове в главном потоке Android-приложения потому, что:

  • Это наглядно. Ниже мы придем к тому, что это актуально и не только для UI-потока Android-приложения. Но для наглядности лучше всего подходит пример на UI-потоке.

  • Интересно разобраться, почему всё именно так работает.

  • Всё-таки иногда мы можем использовать runBlocking, пусть даже в тестовых приложениях.

Билдер runBlocking работает почти так же, как и launch: создает корутину и вызывает в ней блок кода. Но чтобы сделать вызов блокирующим runBlocking создает особую корутину под названием BlockingCoroutine, у которой есть дополнительная функция joinBlocking(). runBlocking вызывает joinBlocking() сразу же после запуска корутины.

Фрагмент из runBlocking():

// runBlocking() function// val coroutine = BlockingCoroutine<T>(newContext, )coroutine.start(CoroutineStart.DEFAULT, coroutine, block)return coroutine.joinBlocking()

Функция joinBlocking() использует механизм блокировки Java LockSupport для блокировки текущего потока с помощью функции park(). LockSupport это низкоуровневый и высокопроизводительный инструмент, обычно используется для написания собственных блокировок.

Кроме того, BlockingCoroutine переопределяет функцию afterCompletion(), которая вызывается после завершения работы корутины.

override fun afterCompletion(state: Any?) {//wake up blocked threadif (Thread.currentThread ()! = blockedThread)LockSupport.unpark (blockedThread)}

Эта функция просто разблокирует поток, если она была заблокирована до этого с помощью park().

Как это всё работает примерно показано на схеме работы runBlocking.

Что здесь делает Dispatchers

Хорошо, мы поняли, что делает билдер runBlocking. Но почему в одном случае он блокирует UI-поток, а в другом нет? Почему Dispatchers.Main приводит к дедлоку...

// Этот код создает дедлокrunBlocking(Dispatchers.Main) {  println(Hello, World!)}

...,а Dispatchers.Default нет?

// А этот код создает дедлокrunBlocking(Dispatchers.Default) {  println(Hello, World!)}

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

Диспатчер определяет, какой поток или потоки использует корутина для своего выполнения. Это некий высокоуровневый аналог Java Executor. Мы даже можем создать диспатчер из Executorа с помощью удобного экстеншна:

public fun Executor.asCoroutineDispatcher(): CoroutineDispatcher

Dispatchers.Default реализует класс DefaultScheduler и делегирует обработку исполняемого блока кода объекту coroutineScheduler. Его функция dispatch() выглядит так:

override fun dispatch (context: CoroutineContext, block: Runnable) =  try {    coroutineScheduler.dispatch (block)  } catch (e: RejectedExecutionException) {    //    DefaultExecutor.dispatch(context, block)  }

Класс CoroutineScheduler отвечает за наиболее эффективное распределение обработанных корутин по потокам. Он реализует интерфейс Executor.

override fun execute(command: Runnable) = dispatch(command)

А что же делает функция CoroutineScheduler.dispatch()?

  • Добавляет исполняемый блок в очередь задач. При этом существует две очереди: локальная и глобальная. Это часть механизма приоритезации внешних задач.

  • Создает воркеры. Воркер это класс, унаследованный от обычного Java Thread (в данном случае daemon thread). Здесь создаются рабочие потоки. У воркера также есть локальная и глобальная очереди, из которых он выбирает задачи и выполняет их.

  • Запускает воркеры.

Теперь соединим всё, что разобрали выше про Dispatchers.Default, и напишем, что происходит в целом.

  • runBlocking запускает корутину, которая вызывает CoroutineScheduler.dispatch().

  • dispatch() запускает воркеры (под капотом Java потоки).

  • BlockingCoroutine блокирует текущий поток с помощью функции LockSupport.park().

  • Исполняемый блок кода выполняется.

  • Вызывается функция afterCompletion(), которая разблокирует текущий поток с помощью LockSupport.unpark().

Эта последовательность действий выглядит примерно так.

Перейдём к Dispatchers.Main

Это диспатчер, который создан специально для Android. Например, при использовании Dispatchers.Main фреймворк бросит исключение, если вы не добавляете зависимость:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:..*'

Перед началом разбора Dispatchers.Main стоит поговорить о HandlerContext. Это специальный класс, который добавлен в пакет coroutines для Android. Это диспатчер, который выполняет задачи с помощью Android Handler всё просто.

Dispatchers.Main создаёт HandlerContext с помощью AndroidDispatcherFactory через функцию createDispatcher().

override fun createDispatcher() =  HandlerContext(Looper.getMainLooper().asHandler(async = true))

И что мы тут видим? Looper.getMainLooper().asHandler() означает, что он принимает Handler главного потока Android. Получается, что Dispatchers.Main это просто HandlerContext с Handlerом главного потока Android.

Теперь посмотрим на функцию dispatch() у HandlerContext:

override fun dispatch(context: CoroutineContext, block: Runnable) {  handler.post(block)}

Он просто постит исполняемый код через Handler. В нашем случае Handler главного потока.

Итого, что же происходит?

  • runBlocking запускает корутину, которая вызывает CoroutineScheduler.dispatch().

  • dispatch() отправляет исполняемый блок кода через Handler главного потока.

  • BlockingCoroutine блокирует текущий поток с помощью функции LockSupport.park().

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

  • Из-за этого afterCompletion() никогда не вызывается.

  • И из-за этого текущий поток не будет разблокирован (через unparked) в функции afterCompletion().

Эта последовательность действий выглядит примерно так.

Вот почему runBlocking с Dispatchers.Main блокирует UI-поток навсегда.

Главный потокблокируется и ждёт завершения исполняемого кода. Но он никогда не завершается, потому что Main Looper не может получить сообщение на запуск исполняемого кода. Дедлок.

Совсем простое объяснение

Помните пример с Handler().post в самом начале статьи? Там код работает и ничего не блокируется. Однако мы можем легко изменить его, чтобы он был в значительной степени похож на наш код с Dispatcher.Main, и стал ещё нагляднее. Для этого можем добавить операции parking и unparking к текущему потоку, иммитируя работу функций afterCompletion() и joinBlocking(). Код начинает работать почти так же, как с билдером runBlocking.

//где-то в UI потокеval thread = Thread.currentThread()Handler().post {  println("Hello, World!") // это никогда не будет вызвано  // имитируем afterCompletion()  LockSupport.unpark(thread)}// имитируем joinBlocking()LockSupport.park()

Но этот трюк не будет работать с функцией runOnUiThread.

//где-то в UI потокеval thread = Thread.currentThread()runOnUiThread {  println("Hello, World!") // этот код вызовется  LockSupport.unpark(thread)}LockSupport.park()

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

Если всё же очень хочется использовать runBlocking в UI-потоке, то у Dispatchers.Main есть оптимизация Dispatchers.Main.immediate. Там аналогичная логика как у runOnUiThread. Поэтому этот блок кода будет работать и в UI-потоке:

//где-то в UI потокеrunBlocking(Dispatchers.Main.immediate) {   println(Hello, World!)}

Выводы

В статье я описал как безобидный билдер runBlocking может заморозить ваше приложение на Android. Это произойдет, если вызвать runBlocking в UI-потоке с диспатчером Dispatchers.Main. Приложение заблокируется по следующему алгоритму:

  • runBlocking создаёт блокирующую корутину BlockingCoroutine.

  • Dispatchers.Main отправляет на запуск исполняемый блок кода через Handler.post.

  • Но BlockingCoroutine тут же заблокирует UI поток.

  • Поэтому Main Looper никогда не получит сообщение с исполняемым блоком кода.

  • А UI не разблокируется, потому что корутина ждёт завершения исполняемого кода.

Эта статья больше теоретическая, чем практическая. Просто потому, что runBlocking редко встречается в продакшн-коде. Но примеры с UI-потоком наглядны, потому что можно сразу заблокировать приложение и разобраться, как работает runBlocking.

Но заблокировать исполнение можно не только в UI-потоке, но и с помощью других диспатчеров, если поток вызова и корутины окажется одним и тем же. В такую ситуацию можно попасть, если мы будем пытаться вызвать билдер runBlocking на том же самом потоке, что и корутина внутри него. Например, мы можем использовать newSingleThreadContext для создания нового диспатчера и результат будет тот же. Здесь UI не будет заморожен, но выполнение будет заблокировано.

val singleThreadDispatcher = newSingleThreadContext("Single Thread")GlobalScope.launch (singleThreadDispatcher) {  runBlocking (singleThreadDispatcher) {    println("Hello, World!") // этот кусок кода опять не выполнится  }}

Если очень надо написать runBlocking в главном потоке Android-приложения, то не используйте Dispatchers.Main. Используйте Dispatchers.Default или Dispatchers.Main.immediate в крайнем случае.


Также будет интересно почитать:

Оригинал статьи на английском How runBlocking May Surprise You.
Как страдали iOS-ники когда выпиливали Realm.
О том, над чем в целом мы тут работаем: монолит, монолит, опять монолит.
Кратко об истории Open Source просто развлечься (да и статья хорошая).

Подписывайтесь начат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на канал Dodo Engineering, где мы постим всё, что с нами интересного происходит.

Подробнее..

Как захватить новую страну за 3 недели

11.09.2020 12:04:11 | Автор: admin
Представим сферическую сеть пиццерий в вакууме, которая хочет захватить мир (никогда такого не было и вот опять). Она уже открыла пиццерии в 13 странах мира и планирует увеличивать эту цифру. Всего год назад запуск (сайта, приложения и информационной системы) был редким 1 страна за год, а сейчас срок сократился до 3 недель. Что мешало сделать это раньше и как получилось ускориться, расскажем в статье.



Dodo Pizza международная компания. Мы работаем в 13 странах и не планируем останавливаться. Большая часть пиццерий расположена в регионе Евразия: Россия, Казахстан, Беларусь, Кыргызстан, Узбекистан. Это уже большой действующий бизнес, мы лидеры по количеству пиццерий. Этот бизнес надо только поддерживать и развивать вглубь, потому что здесь бизнес и IT работают вместе над понятными фичами.

  • Основная часть разработчиков сосредоточена на развитии бизнеса в Евразии.
  • Разработчики работают в командах.
  • Команды разделены по продуктам.
  • Внутри каждого продукта несколько команд.
  • Каждый продукт прокачивает свои метрики: ресторан ускоряет обслуживание клиентов в зале, доставка увеличивает LTV и средний чек в приложении и на сайте.

И тут (внезапно) приходит бизнес и говорит: Хотим запускаться в Нигерии (та самая 13-я страна, в которой работают уже 2 пиццерии) До 2019 года запуск был редким 1 страна в год. Команда разработки, которая сопровождала запуск, постоянно менялась. Фокуса на ускорении при таком подходе не было.

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

Трудности запуска для бизнеса


Открытие новой страны сюрприз (может быть). Иногда в предпринимательстве появляются возможности, которые просто нельзя упускать, даже если не готов. Например:

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

Мы стараемся планировать открытия заранее, но иногда жизнь вносит свои коррективы.

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

Когда приходит очередь открывать новую страну, все чек-листы устарели, а команда, которая запускала прошлую страну, может помогать только урывками. Фокуса на ускорении запуска системы при таком подходе не было. Запустили и ладно через год вернёмся :)

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

Много компонентов, огромная система и много связей нет выстроенной системы запуска и экспертов. Запуск новой страны, с точки зрения Dodo IS, это не просто копипаст кода или нажатие одной волшебной кнопки. Это отдельный проект со своими локальными особенностями. Это 3-4 месяца на запуск (иногда и дольше).

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

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

Технические трудности


Dodo Pizza появилась в апреле 2011 года. В июне 2011 началась разработка Dodo IS. В то время никто даже не думал, что скоро мы будем запускать пиццерии в других странах, потому что нужно было быстро (очень-очень быстро) поддерживать растущий бизнес в России. Например, первая касса ресторана появилась в системе за 2 недели разработки, так как без неё нельзя было открывать ресторан в первой пиццерии.

Не было времени всё хорошенько продумать и заложить масштабируемость и country-agnostic компоненты в архитектуру системы. Меньше всего мы думали, что код, который сейчас пишем, будет использоваться где-нибудь в Словении или США. Поэтому за годы бурного роста накопилось много технического долга, который сейчас замедляет запуск.

Развернуть сайт и бэк-офис Dodo IS это долго. Нельзя просто так взять и прописать в конфигах Nginx домен для новой страны и развернуть систему по кнопке. А жаль.

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

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

В Excel мы выгружали партнёру строки с терминами, он переводил (без контекста и картинок), мы загружали Excel с переводами обратно и повторяли упражнение. Это не то, для чего создан Excel. Да, и в ресурсах, к сожалению, были не все проекты и строки код приходилось править.


Пример файла с переводами.

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

Кассы и налоги. Касса ресторана и касса доставки это компоненты Dodo IS. Без них ничего нельзя продать ни в ресторане, ни на доставку. Кассы необходимо адаптировать для новой страны, а код в монолите и он тянет за собой множество зависимостей. Получается, кроме бизнес-проработки (налоги, ставки, требования к чеку), необходимо аккуратно написать логику для новой страны, так, чтобы не сломалась печать чеков в России.

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

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

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

Как решили бизнес-проблемы


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

Мы выделили новый независимый продукт запуск новых стран и поставили амбициозную цель запуск страны по кнопке. С кнопкой мы могли бы:

  • разворачивать систему как есть;
  • проверять, что все сценарии работают: заказы принимаются и готовятся, чеки печатаются, отчёты строятся;
  • отдавать систему партнёрам, бизнес-девелоперам, маркетологам и всем остальным для донастройки под конкретную страну.

Команда. Цель невозможно достичь без команды. Поэтому в мае 2019 года команда MyLittleCoders согласилась стать командой открытия стран на 100% времени. Мы выделили открытие в новый продукт: метрика скорость запуска есть, команда есть, бэклог по ускорению полон задачами через край. Всё сошлось пора действовать.


Логотип команды MyLittleCoders (MLC)

Story Map. Долгосрочная стратегия понятна: ускоряться, искать людей, перенимать компетенции. Нужно было научиться запускать страну под ключ самостоятельно, не отвлекая другие команды.

Мы начали с запуска Нигерии, за 3 месяца до открытия. Нигерия стала тестовой страной, с которой начали системно выстраивать четкий процесс запуска. И вот как мы это делали.

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


Фрагмент story map запуска новой страны.

Как решили технические проблемы


Мастер начальной настройки страны. Анализ story map показал, что часть этапов можно автоматизировать, что значительно ускорит запуск. Поэтому первым существенным улучшением стал мастер начальной настройки страны или Country Wizard.

После покупки нового iPhone настройки со старого телефона переносятся за пару кликов 3 экрана и новый телефон готов к работе. Мы хотели сделать для Dodo IS что-то похожее.

  • Запускаешь мастер начальной настройки страны.
  • Говоришь: Запускаю Нигерию.
  • Выбираешь язык, валюту, вводишь маску телефона, типы улиц и типы населенных пунктов.
  • Вводишь логин и пароль для временного пользователя (для первого входа в систему).
  • Next, next, next и готово заходишь в свежеразвёртную копию системы для Нигерии и создаёшь маркетологам и бизнес-девелоперам их персональные учётки.

В ходе работы концепция поменялась. Изначально мы хотели добавить все-все настройки системы в Country wizard (РО продукта хотел и грезил, команда была сдержанно-оптимистична). Но у нас уже была внутренняя админка, дублировать которую в мастере настройки оказалось бессмысленно. Тогда мы оставили в нём только тот минимум настроек, без которых система просто не могла запуститься. Продукты, меню, цены поставщиков и тару можно донастроить уже потом.

Почему система не может запуститься без такого количества параметров? Исторически сложилось, что такие параметры необходимы для разработки Dodo IS. Развязать такие зависимости и сделать их опциональными для запуска отдельная задача для команды.

В итоге, всё получилось прекрасно. Вместо множества мест, где надо что-то править мы:

  • проходим 1 мастер начальной настройки;
  • получаем свежий дамп системы;
  • отдаем его ребятам из инфраструктуры для раскатки.

Два дня (а не месяц как раньше) и система готова к работе.


Пример настройки параметров доставки через мастер начальной настройки.

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

Поэтому связка аналитик-разработчик скрупулезно описала весь процесс. В результате у нас есть большой документ с цифрами и пунктами: что сделать до запуска, что во время, а что после.


Фрагмент чек-листа запуска.

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

Настройки страны из одной точки. Собрать все настройки из Country wizard недостаточно. Важно, чтобы система и её компоненты также читали эти настройки из одного хранилища. Иначе получаются забавные ситуации. Например, когда менеджер офиса показывает правильную валюту (для Нигерии найра), а витрина кусочков предательски показывает рубли. Каждый сервис считал своим долгом завести собственные настройки. Приходилось проходить 7-8 мест в системе, чтобы все наконец показывали правильную валюту.

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

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

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

Переводы. Вместо Excel хотелось простой и понятной системы для локализации, чтобы:

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

Мы поискали сервисы для локализации, пообщались с поддержкой и остановились на сервисе Crowdin. Простой, с разграничением ролей подошёл как влитой. Особенно понравилась фича in-context. Когда открываем сайт или бэк-офис на специальном окружении и правим строки прямо в интерфейсе. Так сразу видно где и что конкретно исправляем удобно.


Crowdin in-context редактирование.

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

Больше никакого Excel.

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

Раньше переводы были в коде и бизнес-процесс перевода были вынужденно связаны с разработкой и релизами. За счет сервиса Crowdin получилось сделать независимыми эти 2 процесса. Сейчас мы стараемся все новые сервисы, лендинги и везде, где есть что-то для переводов, подключать сразу к Crowdin.Это сильно упрощает жизнь и даёт партнёру возможность делать переводы в своём темпе, а нам разворачивать систему в своём.

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

Что в итоге


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

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

Планы


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

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

Приложение. С января 2020 года к нам присоединилась мобильная команда Легионеры (3 человека). Теперь нам надо раздать долги запустить приложение во всех странах, и научиться запускать новые страны сразу с приложением.

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

Напоследок


Запустить страну это полдела теперь нужно ещё и поддерживать существующих партнёров (которых только что стало +1):

  • писать трекинг заказа в UK;
  • адаптировать адресную систему;
  • допиливать систему напильником под региональные особенности;
  • помогать выходить на азиатский рынок.

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

Именно поэтому мы собираем новую команду под регион ЕМЕА. Эта команда будет адаптировать систему под локальные рынки, создавая ту самую уникальность, отличающую бизнес в UK от бизнеса в Нигерии. Мы ищем в команду опытных разработчиков. Если интересно открывать мир, запускать новые пиццерии на карте и решать не рутинные задачи ждем вас в команду. Напишите мне на d.pavlov@dodopizza.com буду рад пообщаться:)

Примечание. В посте Катя Ландырева подробнее описала, что вас ждет чуть и почему круто работать в нашем продукте.
Подробнее..

Как DDD помог нам построить новые ревизии в пиццериях

15.10.2020 18:13:18 | Автор: admin
В пиццериях важно выстраивать систему учёта и управления запасами. Система нужна, чтобы не терять продукты, не проводить лишние списания и правильно прогнозировать закупки на следующий месяц. Важная роль в учёте у ревизий. Они помогают проверять остатки продуктов и сверять фактическое количество и то, что есть в системе.



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

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

Схема движения продуктов и зачем нужна ревизия


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

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

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

Ревизии


Для подсчета фактического значения как раз используются ревизии (ещё их называют инвентаризациями).

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

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

Проблемы в процессе проведения ревизий, или Как работали старые ревизии


Ревизии трудоемкий процесс. Он занимает много времени и состоит из нескольких этапов: подсчет и фиксация остатков сырья, суммирование результатов сырья по зонам хранения, внесение результатов в информационную систему Dodo IS.

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

Как решить проблему


Наша команда Game of Threads занимается развитием учета в пиццериях. Мы решили запустить проект планшет ревизора, который упростит проведение ревизий в пиццериях. Всё решили делать в собственной информационной системе Dodo IS, в которой реализованы основные компоненты для ведения учета, поэтому нам не нужны интеграции со сторонними системами. К тому же инструментом смогут пользоваться все страны нашего присутствия, не прибегая к дополнительным интеграциям.

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

В статье я расскажу о тактических паттернах DDD, которые мы применяли в разработке: агрегатах, командах, доменных эвентах, прикладной службе и интеграции ограниченных контекстов. Стратегические паттерны и основы DDD не будем описывать, иначе статья будет очень длинной. Об этом мы уже рассказывали в материале Что можно узнать о Domain Driven Design за 10 минут?

Новая версия ревизий


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

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

Для этой сущности реализован CRUD-функционал и подробно на ней останавливаться не будем.

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

Начиная ревизию ревизор выбирает зону, например холодильник, и идёт считать сырьё там. В холодильнике он видит 5 пачек сыра по 10 кг, вводит в калькулятор 10 кг * 5, нажимает Ввести ещё. Затем замечает на верхней полке ещё 2 пачки, и нажимает Добавить. В результате у него есть 2 замера по 50 и 20 кг.

Замером мы называем введенное количество сырья ревизором в определённой зоне, но не обязательно суммарное. Ревизор может ввести два замера по одному килограмму или просто два килограмма в одном замере сочетания могут быть любыми. Главное, чтобы самому ревизору было понятно.


Интерфейс калькулятора.

Так, по шагам, ревизор за 1-2 часа считает всё сырьё, а потом завершает ревизию.

Алгоритм действий довольно простой:

  • ревизор может начать ревизию;
  • ревизор может добавлять замеры в начатой ревизии;
  • ревизор может завершить ревизию.

Из этого алгоритма формируются бизнес-требования к системе.

Реализация первой версии агрегата, команды и события предметной области


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

Тактические шаблоны DDD


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

Граница агрегата набор объектов, которые должны быть согласованы в рамках одной транзакции: должны быть соблюдены все инварианты в рамках этого кластера.

Инварианты бизнес-правила, которые не могут быть противоречивыми.

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

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

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

Команды и события


Опишем бизнес-требование командой. Команды это просто DTO с описательными полями.

В команде добавление замера есть следующие поля:

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

Код команды добавления замера
public sealed class AddMeasurementCommand{    // ctor    public double? Value { get; }    public int Version { get; }    public UUId MaterialTypeId { get; }    public UUId MeasurementId { get; }    public UnitOfMeasure UnitOfMeasure { get; }    public UUId InventoryZoneId { get; }}


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

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

Код события замер
public class MeasurementEvent : IPublicInventoryEvent{    public UUId MaterialTypeId { get; set; }    public double? Value { get; set; }    public UUId MeasurementId { get; set; }    public int MeasurementVersion { get; set; }    public UUId AggregateId { get; set; }    public int Version { get; set; }    public UnitOfMeasure UnitOfMeasure { get; set; }    public UUId InventoryZoneId { get; set; }}


Когда мы описали команды и события, можем реализовать агрегат Inventory.

Реализация агрегата Inventory



UML диаграмма агрегата Inventory.

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

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

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

  • Изменения (changes) хранятся с момента последнего восстановления агрегата.
  • Состояние восстанавливается методом Restore, который проигрывает все предыдущие события, отсортированные по версии, на текущем экземпляре агрегата Inventory.

Это реализация идеи Event Sourcing в рамках агрегата. О том, как реализовать идею Event Sourcing в рамках хранилища поговорим немного позже. Есть хорошая иллюстрация из книги Вон Вернона:


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

Дальше происходит несколько замеров командой AddMeasurementCommand. Ревизия завершается командой FinishInventoryCommand. Агрегат валидирует своё состояние в мутирующих методах для соблюдения своих инвариантов.

Важно отметить, что агрегат Inventory версионируется целиком, а также каждый его замер. С замерами сложнее приходится решать конфликты в методе обработки события When(MeasurementEvent e). В коде я приведу только обработку команды AddMeasurementCommand.

Код агрегата Inventory
public sealed class Inventory : IEquatable<Inventory>{    private readonly List<IInventoryEvent> _changes = new List<IInventoryEvent>();    private readonly List<InventoryMeasurement> _inventoryMeasurements = new List<InventoryMeasurement>();    internal Inventory(UUId id, int version, UUId unitId, UUId inventoryTemplateId,        UUId startedBy, InventoryState state, DateTime startedAtUtc, DateTime? finishedAtUtc)        : this(id)    {        Version = version;        UnitId = unitId;        InventoryTemplateId = inventoryTemplateId;        StartedBy = startedBy;        State = state;        StartedAtUtc = startedAtUtc;        FinishedAtUtc = finishedAtUtc;    }    private Inventory(UUId id)    {        Id = id;        Version = 0;        State = InventoryState.Unknown;    }    public UUId Id { get; private set; }    public int Version { get; private set; }    public UUId UnitId { get; private set; }    public UUId InventoryTemplateId { get; private set; }    public UUId StartedBy { get; private set; }    public InventoryState State { get; private set; }    public DateTime StartedAtUtc { get; private set; }    public DateTime? FinishedAtUtc { get; private set; }    public ReadOnlyCollection<IInventoryEvent> Changes => _changes.AsReadOnly();    public ReadOnlyCollection<InventoryMeasurement> Measurements => _inventoryMeasurements.AsReadOnly();    public static Inventory Restore(UUId inventoryId, IInventoryEvent[] events)    {        var inventory = new Inventory(inventoryId);        inventory.ReplayEvents(events);        return inventory;    }    public static Inventory Restore(UUId id, int version, UUId unitId, UUId inventoryTemplateId,        UUId startedBy, InventoryState state, DateTime startedAtUtc, DateTime? finishedAtUtc,        InventoryMeasurement[] measurements)    {        var inventory = new Inventory(id, version, unitId, inventoryTemplateId,            startedBy, state, startedAtUtc, finishedAtUtc);        inventory._inventoryMeasurements.AddRange(measurements);        return inventory;    }    public static Inventory Create(UUId inventoryId)    {        if (inventoryId == null)        {            throw new ArgumentNullException(nameof(inventoryId));        }        return new Inventory(inventoryId);    }    public void ReplayEvents(params IInventoryEvent[] events)    {        if (events == null)        {            throw new ArgumentNullException(nameof(events));        }        foreach (var @event in events.OrderBy(e => e.Version))        {            Mutate(@event);        }    }    public void AddMeasurement(AddMeasurementCommand command)    {        if (command == null)        {            throw new ArgumentNullException(nameof(command));        }        Apply(new MeasurementEvent        {            AggregateId = Id,            Version = Version + 1,            UnitId = UnitId,            Value = command.Value,            MeasurementVersion = command.Version,            MaterialTypeId = command.MaterialTypeId,            MeasurementId = command.MeasurementId,            UnitOfMeasure = command.UnitOfMeasure,            InventoryZoneId = command.InventoryZoneId        });    }    private void Apply(IInventoryEvent @event)    {        Mutate(@event);        _changes.Add(@event);    }    private void Mutate(IInventoryEvent @event)    {        When((dynamic) @event);        Version = @event.Version;    }    private void When(MeasurementEvent e)    {        var existMeasurement = _inventoryMeasurements.SingleOrDefault(x => x.MeasurementId == e.MeasurementId);        if (existMeasurement is null)    {        _inventoryMeasurements.Add(new InventoryMeasurement        {            Value = e.Value,            MeasurementId = e.MeasurementId,            MeasurementVersion = e.MeasurementVersion,            PreviousValue = e.PreviousValue,            MaterialTypeId = e.MaterialTypeId,            UserId = e.By,            UnitOfMeasure = e.UnitOfMeasure,            InventoryZoneId = e.InventoryZoneId        });    }    else    {        if (!existMeasurement.Value.HasValue)        {            throw new InventoryInvalidStateException("Change removed measurement");        }        if (existMeasurement.MeasurementVersion == e.MeasurementVersion - 1)        {            existMeasurement.Value = e.Value;            existMeasurement.MeasurementVersion = e.MeasurementVersion;            existMeasurement.UnitOfMeasure = e.UnitOfMeasure;            existMeasurement.InventoryZoneId = e.InventoryZoneId;        }        else if (existMeasurement.MeasurementVersion < e.MeasurementVersion)        {            throw new MeasurementConcurrencyException(Id, e.MeasurementId, e.Value);        }        else if (existMeasurement.MeasurementVersion == e.MeasurementVersion &&            existMeasurement.Value != e.Value)        {            throw new MeasurementConcurrencyException(Id, e.MeasurementId, e.Value);        }        else        {            throw new NotChangeException();        }    }}// Equals// GetHashCode}


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

Если есть нужны дополнительные проверки:

  • нельзя редактировать удаленный замер;
  • входящая версия должна быть больше предыдущей.

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

Сущность замер содержит точно такие же поля, что и команда Добавление замера.

Код сущности замер
public class InventoryMeasurement{    public UUId MeasurementId { get; set; }    public UUId MaterialTypeId { get; set; }    public UUId UserId { get; set; }   public double? Value { get; set; }  public int MeasurementVersion { get; set; }  public UnitOfMeasure UnitOfMeasure { get; set; }  public UUId InventoryZoneId { get; set; }}


Использование публичных методов агрегата хорошо демонстрируют Unit-тесты.

Код юнит теста добавление замера после начала ревизии
[Fact]public void WhenAddMeasurementAfterStartInventory_ThenInventoryHaveOneMeasurement(){    var inventoryId = UUId.NewUUId();    var inventory = Domain.Inventories.Entities.Inventory.Create(inventoryId);    var unitId = UUId.NewUUId();    inventory.StartInventory(Create.StartInventoryCommand()        .WithUnitId(unitId)        .Please());    var materialTypeId = UUId.NewUUId();    var measurementId = UUId.NewUUId();    var measurementVersion = 1;    var value = 500;    var cmd = Create.AddMeasurementCommand()        .WithMaterialTypeId(materialTypeId)        .WithMeasurement(measurementId, measurementVersion)        .WithValue(value)        .Please();    inventory.AddMeasurement(cmd);    inventory.Measurements.Should().BeEquivalentTo(new InventoryMeasurement    {        MaterialTypeId = materialTypeId,        MeasurementId = measurementId,        MeasurementVersion = measurementVersion,        Value = value,        UnitOfMeasure = UnitOfMeasure.Quantity    });}


Собираем всё вместе: команды, события, агрегат Inventory



Жизненный цикл агрегата Inventory при выполнении команды Finish Inventory.

На схеме изображен процесс обработки команды FinishInventoryCommand. Перед обработкой необходимо восстановить состояние агрегата Inventory на момент выполнения команды. Для этого мы загружаем все события, которые были произведены над данным агрегатом, в память и проигрываем их (п. 1).

На момент завершения ревизии у нас уже есть следующие события начало ревизии и добавление трех замеров. Эти события появились в результате обработки команд StartInventoryCommand и AddMeasurementCommand, соответственно. В базе данных каждая строка в таблице содержит идентификатор ревизии, версию и тело самого события.

На этом этапе мы выполняем команду FinishInventoryCommand (п. 2). Эта команда сначала проверит валидность текущего состояния агрегата то, что ревизия находится в состоянии InProgress, а затем породит новое изменение состояния, добавив событие FinishInventoryEvent в список changes (п. 3).

Когда команда завершится, все изменения сохранятся в базу данных. В результате в базе появится новая строка с событием FinishInventoryEvent и последней версией агрегата (п. 4).

Тип Inventory (ревизия) агрегат и корневой элемент по отношению к своим вложенным сущностям. Таким образом, тип Inventory определяет границы агрегата. В границы агрегата входит список сущностей типа Measurement (замер), и список всех событий, произведенных над агрегатом (changes).

Реализация всей фичи


Под фичей мы понимаем реализацию конкретного бизнес-требования. В нашем примере мы рассмотрим фичу Добавление замера. Для реализации фичи нам понадобится разобраться с понятием прикладная служба (ApplicationService).

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

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

Код фичи добавление замера
public class AddMeasurementChangeHandler    : IRequestHandler<AddMeasurementChangeRequest, AddMeasurementChangeResponse>{    // dependencies    // ctor    public async Task<AddMeasurementChangeResponse> Handle(        AddMeasurementChangeRequest request,        CancellationToken ct)    {        var inventory =            await _inventoryRepository.GetAsync(request.AddMeasurementChange.InventoryId, ct);        if (inventory == null)        {            throw new NotFoundException($"Inventory {request.AddMeasurementChange.InventoryId} is not found");        }        var user = await _usersRepository.GetAsync(request.UserId, ct);        if (user == null)        {            throw new SecurityException();        }        var hasPermissions =        await _authPermissionService.HasPermissionsAsync(request.CountryId, request.Token, inventory.UnitId, ct);        if (!hasPermissions)        {            throw new SecurityException();        }        var unit = await _unitRepository.GetAsync(inventory.UnitId, ct);        if (unit == null)        {            throw new InvalidRequestDataException($"Unit {inventory.UnitId} is not found");        }        var unitOfMeasure =Enum.Parse<UnitOfMeasure>(request.AddMeasurementChange.MaterialTypeUnitOfMeasure);        var addMeasurementCommand = new AddMeasurementCommand(            request.AddMeasurementChange.Value,            request.AddMeasurementChange.Version,            request.AddMeasurementChange.MaterialTypeId,            request.AddMeasurementChange.Id,            unitOfMeasure,            request.AddMeasurementChange.InventoryZoneId);        inventory.AddMeasurement(addMeasurementCommand);        Handle(inventory, ct);        return new AddMeasurementChangeResponse(request.AddMeasurementChange.Id, user.Id, user.GetName());    }    private void Handle(Domain.Inventories.Entities.Inventory inventory, CancellationToken ct)    {        Task.Run(async () =>        {            try            {                await _inventoryRepository.AppendEventsAsync(inventory.Changes, ct);                await _localQueueDataService.Publish(inventory.Changes, ct);            }            catch (Exception ex)            {                _logger.LogError(ex, "error occured while handling action");            }        }, ct);    }}


Event sourcing


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

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

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

Код хранилища агрегатов Inventory
internal sealed class InventoryRepository : IInventoryRepository{    // dependencies    // ctor    static InventoryRepository()    {        EventTypes = typeof(IEvent)            .Assembly.GetTypes().Where(x => typeof(IEvent).IsAssignableFrom(x))            .ToDictionary(t => t.FullName, x => x);    }    public async Task AppendAsync(IReadOnlyCollection<IEvent> events, CancellationToken ct)    {        using (var session = await _dbSessionFactory.OpenAsync())        {            if (events.Count == 0) return;            try            {                foreach (var @event in events)                {                    await session.ExecuteAsync(Sql.AppendEvent,                        new                        {                            @event.AggregateId,                            @event.Version,                            @event.UnitId,                            Type = @event.GetType().FullName,                            Data = JsonConvert.SerializeObject(@event),                            CreatedDateTimeUtc = DateTime.UtcNow                        }, cancellationToken: ct);                }            }            catch (MySqlException e)                when (e.Number == (int) MySqlErrorCode.DuplicateKeyEntry)            {                throw new OptimisticConcurrencyException(events.First().AggregateId, "");            }        }    }    public async Task<Domain.Models.Inventory> GetInventoryAsync(        UUId inventoryId,        CancellationToken ct)    {        var events = await GetEventsAsync(inventoryId, 0, ct);        if (events.Any()) return Domain.Models.Inventory.Restore(inventoryId, events);        return null;    }        private async Task<IEvent[]> GetEventsAsync(        UUId id,        int snapshotVersion,        CancellationToken ct)    {        using (var session = await _dbSessionFactory.OpenAsync())    {            var snapshot = await GetInventorySnapshotAsync(session, inventoryId, ct);            var version = snapshot?.Version ?? 0;                    var events = await GetEventsAsync(session, inventoryId, version, ct);            if (snapshot != null)            {                snapshot.ReplayEvents(events);                return snapshot;            }            if (events.Any())            {                return Domain.Inventories.Entities.Inventory.Restore(inventoryId, events);            }            return null;        }    }    private async Task<Inventory> GetInventorySnapshotAsync(        IDbSession session,        UUId id,        CancellationToken ct)    {        var record =            await session.QueryFirstOrDefaultAsync<InventoryRecord>(Sql.GetSnapshot, new {AggregateId = id},                cancellationToken: ct);        return record == null ? null : Map(record);    }    private async Task<IInventoryEvent[]> GetEventsAsync(        IDbSession session,        UUId id,        int snapshotVersion,        CancellationToken ct)    {        var rows = await session.QueryAsync<EventRecord>(Sql.GetEvents,            new            {                AggregateId = id,                Version = snapshotVersion            }, cancellationToken: ct);        return rows.Select(Map).ToArray();    }    private static IEvent Map(EventRecord e)    {        var type = EventTypes[e.Type];        return (IEvent) JsonConvert.DeserializeObject(e.Data, type);    }}internal class EventRecord{    public string Type { get; set; }    public string Data { get; set; }}


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

Интеграция с внешними ограниченными контекстами


Так выглядит схема взаимодействия ограниченного контекста Inventory с внешним миром.


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

В случае с Auth, Inventory и Datacatalog на каждый сервис приходится один ограниченный контекст. Монолит выполняет несколько функций, но сейчас нас интересуют только функционал учета в пиццериях. Помимо ревизий, к учету также относится движения сырья в пиццериях: поступления, перемещения, списания.

HTTP


Сервис Inventory взаимодействует с Auth по HTTP. Первым делом пользователь сталкивается с Auth, который предлагает пользователю выбрать одну из доступных ему ролей.

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

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

Примечание. Как работает сервис Auth мы подробнее рассказали в статье Тонкости авторизации: обзор технологии OAuth 2.0.

С остальными сервисами Inventory взаимодействует посредством очередей сообщений. В качестве брокера сообщений в компании используется RabbitMQ, а также обвязка над ним MassTransit.

RMQ: потребление событий


Сервис справочников Datacatalog обеспечит Inventory всеми необходимыми сущностями: сырьем для учета, странами, подразделениями и пиццериями.

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

Код контракта события Datacatalog
namespace Dodo.DataCatalog.Contracts.Products.v1{    public class MaterialType  {    public UUId Id { get; set; }    public int Version { get; set; }    public int CountryId { get; set; }    public UUId DepartmentId { get; set; }    public string Name { get; set; }    public MaterialCategory Category { get; set; }    public UnitOfMeasure BasicUnitOfMeasure { get; set; }    public bool IsRemoved { get; set; }    }  public enum UnitOfMeasure  {    Quantity = 1,    Gram = 5,    Milliliter = 7,    Meter = 8,  }  public enum MaterialCategory  {    Ingredient = 1,    SemiFinishedProduct = 2,    FinishedProduct = 3,    Inventory = 4,    Packaging = 5,    Consumables = 6  }}


Это сообщение публикуется в exchange. Каждый сервис может создать свою связку exchange-queue для потребления событий.


Схема публикации события и его потребление через примитивы RMQ.

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

Код потребителя события из Datacatalog
public class MaterialTypeConsumer : IConsumer<Dodo.DataCatalog.Contracts.Products.v1.MaterialType>{  private readonly IMaterialTypeRepository _materialTypeRepository;  public MaterialTypeConsumer(IMaterialTypeRepository materialTypeRepository)  {    _materialTypeRepository = materialTypeRepository;  }  public async Task Consume(ConsumeContext<Dodo.DataCatalog.Contracts.Products.v1.MaterialType> context)  {    var materialType = new AddMaterialType(context.Message.Id,    context.Message.Name,    (int)context.Message.Category,    (int)context.Message.BasicUnitOfMeasure,    context.Message.CountryId,    context.Message.DepartmentId,    context.Message.IsRemoved,    context.Message.Version);    await _materialTypeRepository.SaveAsync(materialType, context.CancellationToken);  }}


RMQ: публикация событий


Часть монолита, которая отвечает за учёт, потребляет данные Inventory для поддержки остального функционала, где требуются данные ревизий. Все события, о которых мы хотим уведомить другие сервисы, мы помечали интерфейсом IPublicInventoryEvent. Когда происходит событие подобного рода, мы их вычленяем из списка изменений (changes) и отправляем в очередь на отправку. Для этого используются две таблицы publicqueue и publicqueue_archive.

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

Если сообщение получилось отправить, то оно удаляется из очереди publicqueue. Если нет, то будет предпринята попытка отправить сообщение позднее. Далее подписчики монолита и пайплайны данных потребляют сообщения. Таблица publicqueue_archive вечно хранит данные для удобной переотправки событий, если это в какой-то момент потребуется.

Код публикации событий в брокер сообщений
internal sealed class BusDataService : IBusDataService{  private readonly IPublisherControl _publisherControl;  private readonly IPublicQueueRepository _repository;  private readonly EventMapper _eventMapper;  public BusDataService(    IPublicQueueRepository repository,    IPublisherControl publisherControl,    EventMapper eventMapper)  {    _repository = repository;    _publisherControl = publisherControl;    _eventMapper = eventMapper;  }  public async Task ConsumePublicQueueAsync(int batchEventSize, CancellationToken cancellationToken)  {    var events = await _repository.GetAsync(batchEventSize, cancellationToken);    await Publish(events, cancellationToken);  }  public async Task Publish(IEnumerable<IPublicInventoryEvent> events, CancellationToken ct)  {    foreach (var @event in events)    {    var publicQueueEvent = _eventMapper.Map((dynamic) @event);    await _publisherControl.Publish(publicQueueEvent, ct);    await _repository.DeleteAsync(@event, ct);  }  }}


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

Зачем отправлять события пайплайну данных? Все также для отчетов, но только на новых рельсах. Раньше все отчеты жили в монолите, но теперь их выносят. Это разделяет две ответственности хранение и обработку производственных и аналитических данных: OLTP и OLAP. Это важно как с точки зрения инфраструктуры, так и разработки.

Заключение


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

Больше информации о DDD вы можете найти в нашем сообществе DDDevotion и на Youtube-канале DDDevotion. Обсудить статью можно в Телеграм в Dodo Engineering chat.
Подробнее..

Как мы разогнали команду QA, и что из этого получилось

22.10.2020 18:09:59 | Автор: admin
Или как получить неочевидные последствия, если отказаться от команды тестирования.

Полтора года назад мы разрушили команду тестирования: отказались от регресса, передали E2E автотесты на Selenium в поддержку разработчикам и разошлись по командам, которые пилят фичи, чтобы предотвращать ошибки в зародыше. В розовых мечтах нам казалось, что так будет больше пользы: QA работают над качеством, тестирование начинается рано, а разработчики пишут автотесты сами и никто им не мешает.



Но не фартануло получилось не совсем так. Розовые мечты окрасились дополнительными оттенками: никто не думает о качестве, автотесты всё хуже, а у разработчиков без команды QA (внезапно) стало больше работы. Так проявились последствия второго порядка, к которым мы не были готовы. Сейчас мы их исправляем и можем рассказать, что это за последствия, как они возникают, какой урон наносят и как попробовать их предугадать, чтобы не было так больно.

Что такое последствия первого и второго порядка


У Рэя Далио в книге Принципы есть понятие последствия второго порядка. Это неочевидные следствия наших решений, которые мы часто не можем предугадать. Например, в 60-х годах 20-го века в Китае развернули войну с воробьями. Воробьи съедали зерно, и чтобы они перестали его есть, Китай открыл охоту на птиц. За время охоты китайцы массово убили почти два миллиарда птиц.



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

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

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

У нас была команда QA и мы её разогнали


Теперь расскажу, как мы ощутили на себе последствия и первого и второго порядка. У нас была уютная выделенная команда тестировщиков из 7 человек: 4 из них писали автотесты, а 3 тестировали вручную. Мы в какой-то момент решили разделиться и разойтись по командам. Почему?

Потому что разработчики получали обратную связь слишком поздно.

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

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

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

Примечание. В компании есть продукт Ресторан. В него входят все сервисы и наш монолит. Цель продукта максимально автоматизировать и оптимизировать работу всех сотрудников ресторана. Сейчас наша работа направлена больше на предотвращение ошибок. Теперь мы QA в продукте Ресторан: развиваем качества в продукте, участвуем на всех этапах проработки задач.

Последствия первого и второго порядка


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

А теперь то, что мы не планировали это последствия второго порядка.

Никто не драйвит качество продукта. В этой проблеме есть 2 стороны:

  • качество с точки зрения процессов;
  • качество автотестов и пайплайна.

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

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

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

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

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

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

Разработчики прошли несколько стадий принятия смерти пайплайна.

Отрицание. Все релизы Dodo IS катят разработчики. Они организуют процесс, комуницируют с командой нагрузочного тестирования, смотрят логи и мониторинг во время релиза. Разработчики, которые катили релиз, сталкиваясь с красным тестом, не пытались разобраться в его причине, а просто перезапускали пайплайн, пока он не становился зеленым 5710 раз. Всё потому, что не было доверия к автотестам.

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

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

Торга и депрессии не было, наступило сразу принятие: разработчики теперь могут сами писать E2E-тесты UI и API, стабилизируют и улучшают их.

Количество багов на проде стало увеличиваться. На продакшн стали просачиваться не критичные баги. На это есть несколько причин:

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

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

Как мы решаем эти проблемы


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

Создали гильдию QA Ресторана или Community of practice, в которую вошли все QA Ресторана. Цель сообщества драйвить качество всего продукта, распространять хорошие практики тестирования на все команды продукта. Это образование, которое совмещает в себе преимущества выделенной QA-команды и также мы получаем преимущества от нахождения QA в команде разработки.



Мы встречаемся раз в неделю: делимся результатами, открытиями и планируем совместную работу над качеством. Также выделяем несколько слотов в неделю для работы над задачами гильдии. Например, допиливаем нашего бота-помощника для релизменов.

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



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

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

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

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

Что бы мы сделали сейчас?


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

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

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

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

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


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

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

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

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

Как я теперь буду пытаться предугадывать последствия


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

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

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

P.S. Нам сейчас требуются 2 мобильных QA-инженера в продукт Открытие стран и в Дринкит. Мы ищем человека которому не все равно на качество тестируемого продукта и который развивается в автоматизацию. Задач и возможностей полно: можно расти и углубляться в автоматизацию, можно менять процессы и подходы в командах, а можно двигаться в сторону SRE-практик и стать mobile SRE инженером. Все зависит от ваших целей и предпочтений. Если вы хотите у нас работать, пишите в Телеграм: мне (@EvgenSkt) или нашему HR Саше (@alexpanev).

Если хотите узнать, как мы собеседуем и онбордим кандидатов, то оставим ссылки на статьи, где об этом писали: Собеседование в Додо Пиццу и Онбординг разработчиков (на слово разработчик можно не ориентироваться процесс практически идентичен для всех). А больше узнать о нашей культуре QA, можно в статье А не фигню ли я опять делаю? Как и зачем внедрять метрики качества.

Наш Телеграм-чат, если захотите обсудить статью и задать вопросы.
Подробнее..

История Open Source кратко от калькулятора до миллиардных сделок

06.11.2020 18:09:43 | Автор: admin

Когда говорят Open Source, обычно первые ассоциации это Ричард Столлман и Линус Торвальдс. Но Open Source начался не с них. Когда в 50-х учёные и инженеры писали ПО, например, для IBM 701, они безвозмездно обменивались результатами своего труда и работали над улучшениями программ своих коллег. Тогда еще не было проприетарного (закрытого) ПО, но Open Source проекты уже были. Это было задолго до Столлмана и Торвальдса.В истории Open Source было много интересного: программы для Оборонного калькулятора, коммерциализация UNIX, письмо Билла Гейтса,манифест GNU, Linux и миллиардные сделки покупок Open Source компаний. Мы попробовали разобраться в истории и узнать с чего начался Open Source, какие события способствовали его развитию и почему без Open Source IT не был бы таким, какой он есть.

Если вам интересен Open Source, то, возможно, наш взгляд на историю тоже будет занимателен.

Что такое Open Source для тех, кто совсем не слышал

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

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

Примерно так можно представить себе Open Source ПО.

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

Брюс Перенс, автор набора правил соответствия Open Source, руководитель проекта Debian 1996-1997.

Брюс ПеренсБрюс Перенс

В определении на Open Source Initiative указано, что Open Source Software (OSS) это ПО, исходники которого доступны для просмотра и изменения. На основе исходного кода можно создавать свои модификации ПО, а также свободно распространять и продавать. OSS это, в первую очередь, про распространение, свободу использования, а не про деньги, потому что OSS может быть и платным, и бесплатным.

Предпосылки свободного ПО: всё началось с калькулятора

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

С 1952 по 1955 компания IBM стала выпускать IBM 701. Это первая коммерчески доступная ЭВМ, которая производилась серийно. Аппараты сдавали в аренду научным институтам, военным компаниям и государственным предприятиям. Физическим лицам не давали, да и стоило это космических денег от 12 до 20 тыс долларов в месяц или больше 100 тыс современных.

IBM 701 называли также Defense Calculator. ИсточникIBM 701 называли также Defense Calculator. Источник

Выпуск этой модели первая точка отсчета в истории. Всё потому, что в комплекте с компьютером было только железо никакой ОС и программ. Все программы ученые и инженеры писали сами и делились с коллегами из других компаний, у которых была такая же ЭВМ. Можно сказать, что IBM 701 это первые компьютеры, к которым начали писать свободно распространяемое ПО.

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

За несколько лет для 701 и его потомков семисотой серии успели наработать достаточно большую базу ПО:

  • Компиляторы РАСТ, которые использовали методы хеширования (в том числе для 704. Их совместно разработали компания PACT (военный подрядчик) и IBM.

  • ОС SOS Share Operating System. Это примитивная ОС, которая основана на разработках General Motors.

  • Появились языки программирования Interlisp, UCI Lisp и высокоуровневый ЯП Speedcoding для IBM 701, который написал Джон Бэкус для облегчения работы с ассемблером. Для IBM 704 он же разработал FORTRAN и компилятор (1956).

IBM 701 подтолкнул рынок к разработке других коммерческих ЭВМ. Например, появился Bendix G-15 (1956) с массой всего 450 кг, Librascope LGP-30 (1956) или первый мини-компьютер PDP-1 (1960) для одного оператора. А когда компания в 1965 выпустила PDP-8, он стал хитом. Это первый коммерчески успешный мини-компьютер и продавался тысячами предприятиям и научным лабораториям: небольшой, быстрый и стоил всего 18 000 долларов.

Рождение, рассвет и коммерциализация UNIX

Моделей коммерческих компьютеров было достаточно, они стали появляться у любителей, рынок рос. Но у всех ПК был один недостаток нет ПО. Под каждую модель компьютера ОС писали с нуля. Компании-производители создавали каждый свою операционную систему, например, BESYS, Compatible Time-Sharing System или CP/CMS.

Из всех ОС нас больше интересует BESYS. Ее создала Bell Labs для IBM 7090 и IBM 7094. На основе этой ОС с 1965 по 1969 MIT, Bell Labs и General Electrics, разрабатывали ОС Multics. Это должна была быть инновационная ОС: централизованная файловая система с иерархическим деревом, разделение памяти процессов, виртуальная память, динамическое связывание и другие фишки. Но что-то пошло не так: работа затянулась, возникли разногласия и компания Bell Labs покинула проект.

Но два сотрудника компании Кен Томпсон и Деннис Ритчи решили переиспользовать модульный дизайн Multics и написать не её основе другую ОС.

Кен Томпсон и Деннис Ритчи (справа)Кен Томпсон и Деннис Ритчи (справа)

Первую версию ОС Томпсон написал в отпуске на домашнем PDP-7, презентовал руководству и получил команду разработчиков. Проект получил название UNIX.

Примечание. Полное описание системы и историю на русском можно прочитать в учебном пособии Операционная система UNIX.

Только через 4 года (1973) систему вывели в свет с открытым кодом. Неожиданно она начала захватывать рынок. На это были причины:

Уже существовал рынок программ. Все модели семисотых IBM после 701 поставлялись в комплекте с ПО, и в том числе и продавались физическим лицам. Цена, естественно, была включена в стоимость железа. Но под давлением антимонопольных регуляторов в 1969 году начала продавать ПО отдельно. ОС UNIX здесь пришелся как нельзя кстати.

Небольшая цена. За Bell Labs тоже следили регуляторы. Компания принадлежала гигантам телекома AT&T и Western Electric и на них всех распространялось антимонопольное законодательство. Поэтому на UNIX нельзя было завышать цены ОС продавалась по цене не намного выше себестоимости физической копии.

Ориентация на массовость. UNIX изначально разрабатывался именно для персональных машин, например, для PDP-11, которых выпустили 170 тыс. ОС сразу была нацелена на массовый рынок любителей.

Запуск UNIX на ПК. ИсточникЗапуск UNIX на ПК. Источник

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

Переносимость. UNIX было не сложно перенести с одного ПК на другой. Поэтому рабочие версии UNIX под новые процессоры появлялись быстро.

Но UNIX уже не была свободным ПО: пользователи не имели права делиться или менять исходный код. Однако, благодаря тому, что AT&T была монополией, компания была обязана предоставлять исходный код (с некоторыми ограничениями) разным университетам. Один из университет Калифорнийский университет в Беркли, который начал заниматься улучшениями ОС. Так получилась собственная университетская ОС BSD Berkley Software Distribution.

В первой версии BSD содержался доработанный компилятор языка Pascal, текстовый редактор Ex, а апдейты из BSD переносили в UNIX. BSD был лучше, постоянно обновлялся, в нём появлялись передовые сетевые технологии. Но когда университет стал продавать коммерческие лицензии BSD от 750 до 1000 долларов, корпорация AT&T поняла, что теряет прибыль и в 1979 году ограничила распространение исходного кода ОС. Позже, в 1983 году Bell Labs отделилась от AT&T, и антимонопольные законы уже не мешали коммерциализировать UNIX. Цена ОС выросла теперь она стоила тысячи и десятки тысяч долларов.

Немного цен на UNIXНемного цен на UNIX

Проприетарное ПО и Гейтс

Превратить UNIX полностью в коммерческую ОС у Bell Labs получилось, потому что примерно в то же время, что родился UNIX, разработчики ПО все больше начинают защищать авторские права на свои технологии. Когда всё начиналось с IBM 701, за ОС, компиляторы, языки и программы никто не брал деньги и не требовал авторских отчислений. Все ЭВМ содержались в лабораториях, компаниях, научных центрах в академической среде, где информация распространялась свободно.

На рынке появлялось больше ПК: они уменьшались, дешевели, их покупало всё больше коммерческих компаний и частных лиц. Появлялось все больше ПО (в основном на высокоуровневых FORTRAN и COBOL), которое писали энтузиасты или сотрудники коммерческих компаний и всё чаще возникал вопрос, нужно ли лицензировать программы. В итоге в 1974 программы приравняли к литературным произведениям. Началась разработка лицензий для программ, появились юридические запреты на изменение продуктов сторонними разработчиками.

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

Брюс Перенс, автор набора правил соответствия Open Source, руководитель проекта Debian 1996-1997.

В 1975 году Билл Гейтс и Пол Аллен разработали интерпретатор языка Basic для тогда еще нового персонального компьютера Altair 8800 от компании MITS. Как ни странно, но интерпретатор работал, MITS заключила контракт с Алленом и еще тогда студентом Гейтсом. По контракту они получали отчисления за проданные копии BASIC: от 30 до 60 долларов. Цена на копию программы начиналась с 500 долларов, тогда как железо могло стоить меньше сотни.

Возможно, покупатели компьютеров не хотели платить в разы больше за ПО, чем за железо, потому что компьютеры продавались тысячами, а BASIC сотнями. Тогда в 1976 году Билл опубликовал своё знаменитое Открытое письмо энтузиастам компьютерного клуба.

В письме было много обвинений в краже ПО (прим: дальше вольный перевод).

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

Большинство из вас воры, вы платите за железо без вопросов, но почему-то думаете, что софт можно копировать друг у друга бесконечно! Вам плевать, платят ли за его разработку программистам. Своим воровством вы препятствуете появлению хорошего софта, потому что никто не хочет заниматься профессиональным программированием бесплатноКто из вас согласен потратить три года на то, чтобы написать программу, отловить все баги, задокументировать проект и раздать все бесплатно? При этом среди вас есть те, кто перепродает наш BASICОни поганят имя компьютерщиков-энтузиастов и должны быть изгнаны из клуба на первом же собрании, где они появятся!

Всё закончилось в 1983. Apple Computer, Inc. подала иск к компании Franklin Computer Corp. Вторая просто скопировала Apple II и ОС и продавала. Apple это не понравилось и они подали в суд. Суд первой инстанции не удовлетворил иск, зато апелляционный подтвердил, что на всё ПО распространяется авторское право. С того момента проприетарный мир победил. Но он же и породил мир Open Source.

Движение свободного ПО, Ричард Столлман и GNU

В 1971 году Ричард Столлман учился в Гарварде и присоединился к лаборатории искусственного интеллекта (AI Lab) при MIT. В процессе разработки ПО всё ещё чувствовался дух товарищества, а до письма Гейтса было целых 5 лет. Столлман отлично вписался и поучаствовал в работе над свободным ПО, например, над EMACS текстовым редактором для миникомпьютеров семейства PDP.

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

Ричард Столлман, основатель проекта GNU.

Когда машина проприетарного ПО начала заводиться (начало 80-х) лаборатория закрылась: появилось NDA, сотрудничества все меньше, разработчики уходят в частные компании и даже создатель версии EMACS для UNIX в 1983 году продал её коммерческому дистрибьютору. Но Столлман считал, что людям необходима свобода решений и информации, свобода менять и улучшать ПО, возможность делиться.

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

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

Ричард Столлман, основатель проекта GNU.

Ричард СтоллманРичард Столлман

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

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

Давление проприетарного ПО, убеждения и размышления привели Ричарда к осознанию того, что он мог бы сам создавать открытое ПО и, как автор, разрешить его распространение. Тогда он мог бы сам пользоваться программами не предавая свои убеждения. Начать решил с ОС, которая станет основой, на которой другие энтузиасты смогут создавать свободное ПО (уже тогда UNIX стала закрытой). По замыслу Столлмана, ПО должно было быть доступным.

Так, в декабре 1984, появился проект GNU. В январе 1984 Столлман увольняется из MIT и погружается в работу. Увольнение было необходимо, чтобы институт не смог предъявить права на разработки. Столлман решил использовать UNIX в качестве основы, чтобы она была переносима и чтобы пользователи ОС могли легко перейти на новую.

Само название GNU это уже хак. Это рекурсивная аббревиатура GNUs Not UNIXЭто имя означает, что я фактически сделал копию коммерческой системы, но это не была она сама. Свою систему я писал, конечно, с нуля, потому что кодом UNIX нельзя было делиться и он был совершенно бесполезен

Ричард Столлман, основатель проекта GNU.

Юридические нюансы и лицензии

Кроме системы GNU Ричард и энтузиасты работали над философской и юридической стороной свободного ПО:

  • Придумали термин свободноеПО.

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

  • Опубликовали манифестGNU.

Для финансирования работы, Столлман в 1985 году основал Free Software Foundation (FSF). Это благотворительная организация, которая занимается развитием свободного ПО. Например, сотрудники ФСПО написали библиотеку GSL и командный интерпретатора BASH.

Чтобы ПО оставалось свободным, нужно была юридическая защита. Поэтому к 1989году была создана первая версия лицензии GPL General Public License. Переводится как Универсальная общественная лицензия GNU. По замыслу, она должна защитить свободу всех пользователей программ, дать права на копирование, модификацию и распространение (в том числе коммерческое) программ. Кроме того, Ричард добавил в лицензию авторское лево, в противовес авторскому праву, по которому пользователи всех производных программ также получат все оригинальные права.

GPL это одна из немногих лицензий, написанных с позиции сообщества, не ставящая во главу угла защиту интересов компаний или правительственных грантов, как в MIT, например. Но это не просто лицензия, а целая философия, которая повлияла на определение Open Source

Брюс Перенс, автор набора правил соответствия Open Source, руководитель проекта Debian 1996-1997.

Эта первая каноничная лицензия на свободное ПО. Но принцип авторского лева приняли не все и впоследствии появились другие лицензии, которые позволяют использовать свободное ПО в проприетарном, например, лицензия MIT от Массачусетского технологического института или лицензия BSD от Калифорнийского университета в Беркли (с несколькими вариациями).

В 1991 году появилась новый вариант GPL LGPL (GNU Lesser General Public License). ПО с этой лицензией можно добавлять в проприетарный софт если это независимый продукт и он отличается от оригинала.

Linux и GNU

Обычно про Ричарда Столлмана принято говорить, что он прежде всего великий философ, а меня воспринимают, как инженера, который воплощает его идею

Линус Торвальдс, создатель ядра Linux

Линус ТорвальдсЛинус Торвальдс

UNIX состоял из разных модулей подпрограмм. Чтобы создать полный аналог ОС нужно было заменять каждый модуль один за другим: командные оболочки, ассемблеры, компиляторы, интерпретаторы, отладчики, текстовые редакторы, почтовые программы. Ричард написал анонс, просьбу о помощи, и стал ее получать.

Часть оригинала сообщенияЧасть оригинала сообщения

К 1991 году заменили практически все модули, некоторые из которых стали использоваться вне ОС, например, GCC, GNUDebugger и Emacs. Но в системе не хватало ядраоперационнойсистемы, а проект GNUHurd по разработке ядра не развивался.

В 1991 Линус Торвальдс выпустил ядро Linux с открытым кодом, а в 1992 лицензировал ядро по GPL. Linux, также как и GNU, использовал для основы UNIX. Это и привлекло к проекту сначала внимание любителей, а потом и Ричарда.

Народ в интернете скачивал компоненты GNU и запускал их на ядре Linux. В конце концов у них получалась ОС, которую они называли Linux

Ричард Столлман, основатель проекта GNU.

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

Собор и Базар и Mozilla

В 1997 году уже была готова версия 2.1 ядра Linux на 800 тыс строк кода. Системой пользовалось уже 3, 5 млн человек. В это время Эрик Реймонд написал эссе Собор и Базар где демонстрирует два противоположных подхода к разработке.

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

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

Конгресс Linux в 1997 году был первым местом, где я представил свое эссе публично. Одним из тех, кто его тогда услышал был Тим ОРайли. Он решил, что мой текст был довольно интригующим и предложил выступить на конференции Perl. А уже на той конференции кто-то из Netscape послушал моё эссе и рассказал о нём в компании. И вот тогда там загорелся огонёк

Эрик Реймонд, автор Собор и Базар.

Эрик РеймондЭрик Реймонд

Примечание. Тим ОРайли один из идеологов движения Open Source, основатель издательства OReilly и популяризатор термина Open Source Software.

Netscape первая крупная компания, которая пришла в Open Source. В середине 90-х браузер компании Netscape Navigator был одним из самых популярных в мире. Но когда появился Internet Explorer, он стал вытеснять с рынка Netscape, потому что распространялся бесплатно вместе с ОС Windows. Netscape теряла рынок и приходилось снижать цены.

Своей политикой браузер от Microsoft мог получить монополию на стандарты HTTP и HTML, от которых полностью зависит веб. В это время Билл Гейтс финансировал разработчиков HTML и все новшества согласовывались с Microsoft. Если бы Microsoft выдавил бы компанию с рынка, то получил бы монополию на веб, что ударило бы и по другому бизнесу компании разработке серверного ПО.

Примечательно, что Netscape начиналась с того, что основатель компании Джеймс Кларк разослал письма перспективным инженерам из научного мира. Его предложение состояло в том, чтобы совместить научную работу и зарабатывание денег. Первый сотрудник компании аспирант-программист из университета штата Иллинойс Марк Андрессен, автор первого в мире веб-браузера Mosaic. Это, а также агрессия со стороны Microsoft, возможно, и стало причинами заинтересованности основателя компании произведением Собор и Базар. В итоге в Netscape приняли решение в 1998 открыть исходный код своего браузера.

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

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

Фрэнк Хеккер, в то время инженер компании Netscape.

В 1999 компании не стало, а бесплатный Explorer забрал 90% рынка. Но исходный код браузера стал основой для одного из самых популярных браузеров в мире MozillaFirefox. Забавно, что браузер Netscape вытеснил с рынка Internet Explorer, а уже его обогнал MozillaFirefox.

Рождение термина Open Source

После события с Netscape, несколько человек решили, что необходимо придумать замену термина свободное ПО, чтобы убратьассоциации с чем-то дешевым, непонятным, бесплатным и никому не нужным. Чтобы поменять эту парадигму в 1998 в офисе компании VA Linux Systems встретились:

  • Эрик Реймонд, автор Собор и Базар;

  • Ларри Августин, доктор наук, со-основатель и генеральный директор VA Linux Systems;

  • Кристин Питерсон (присутствовала по телефону);

  • Джон Хол;

  • Тод Андерсон;

  • Сэм Окланд (тогда сотрудник VA).

Они и придумали замену термину свободное ПО Open Source, чтобы сменить парадигму бесплатности на доступность.

Мы приняли решение, что нам нужен некий экзаменационный лист, который бы чётко определял, что такое Open Source. В результате мы пришли к документу, который называется Определение Open Source. Он вырос из путеводителя по Debian, который написал Брюс Перенс

Эрик Реймонд, автор Собор и Базар.

Чтобы закрепить новую парадигму Эрик и Брюс написали Определение Open Source, основываясь на политике Debian. В него входят 10 правил, которые и определяют текущее развитие движения. В том же 1998 они основали организацию Open Source Initiative (OSI), которая занимается популяризацией Open Source. Можно сказать, что с этого момента и возник Open Source.

Примечание. OSI и FSF пошли разными путями. Во второй больше внимания на free на свободы. Для OSI основной термин Open Source Software.

Настоящее свободного ПО

Мне кажется, что коммерциализация важна. Мы хотим чтобы наш софт был мейнстримом

Брюс Перенс, автор набора правил соответствия Open Source, руководитель проекта Debian 1996-1997.

Проекты GNU и ядро Linux стали основой на которой выросли многие другие продукты и проекты. А поступок компании Netscape привлёк внимание людей и компаний к движению свободного ПО и стал одной из причин того, что сейчас IT-гиганты активно развивают свободное ПО и вкладываются в Open Source компании. Всё это вместе привело к тому, что ничто не мешало (и не мешает) развиваться Open Source в коммерческом направлении.

Debian. Этот проект полностью основан на философии GNU и связан с фондом свободного ПО, от которого также получал финансирование.

Apache. Apache сегодня воспринимают, как Apache Software Foundation большую компанию, которая поддерживает много OSS-проектов. Но компания начинала с веб-сервера Apache, очень популярного в свое время. Программа предрешила популярность Linux для веб-серверов. В 1993 году когда появился Apache, рынок интернет-провайдеров стал расти огромными темпами благодаря тому, что Linux и Apache выгоднее для бизнеса, чем проприетарное ПО.

Gnome. Разработка графической среды Gnome привлекла к GNU/Linux пользователей, которые раньше и не задумывались об Open Source ПО. Теперь ОС можно было пользоваться всем, вне зависимости от уровня технических знаний.

RedHat. Возникла в 1995 году и выпускала продукты на основе свободного ПО, ОС Red Hat Linux, занималась технической поддержкой и обучением системных администраторов и разработчиков. Red Hat это пример компании, вся деятельность которой основывалась на открытом ПО. Она была очень успешной: на пике карьеры в ней работало 3 500 сотрудников и она была включена в S&P500. В 2018 году компанию купила IBM.

Без Linux и открытого ПО не было бы Google. Сейчас корпорация поддерживает 2000 Open Source проектов, среди которых TensorFlow, Go и Kubernetes.

В 2001 Oracle обратила внимание на Linux, как важную платформу для баз данных, и опубликовала под лицензией GPL OOCFS2 для Linux и перевела часть внутренних систем на Linux. Это помогло закрепить успех Open Source в сфере баз данных. Сейчас Oracle активно участвует в проектах Java, Linux, Kubernetes.

Microsoft признали ошибку в том, что относились к Open Source предвзято, купила GitHub, вывела .NET Core в Open Source и сейчас активно принимает участие в развитие открытых проектов, например, Linux.

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

  • Участие в Open Source проектах привлекает внимание к своим проектам, привлекает энтузиастов, что помогает развивать экосистему вокруг продуктов.

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

  • Банально, но скупка Open Source компаний позволяет удерживать создателей проектов и обеспечивать поддержку, а значит работоспособность ПО.

  • Это помогает мотивировать разработчиков открытого ПО: когда его замечают крупные компании, это привлекает внимание, продукт становится востребованнее и появляется больше желания над ним работать.

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

Из всей истории наша главная мысль в том, что Open Source важная часть современного интернета и IT. Без Open Source или свободного ПО, как его предшественников, не существовало бы интернета, веба и IT, как он есть.

Если захотите обсудить статью добро пожаловать в нашТелеграм-чат. Там же можно обсудить и Open Source проекты.

Подробнее..

Зачем и как мы пишем постмортемы по критичным багам

30.04.2021 16:23:36 | Автор: admin

В какой-то момент у нас стало много хотфиксов стабильно больше половины деплоев на проде были хотфиксы или откаты. Мы решили анализировать каждый хотфикс, чтобы понять причины, найти системные закономерности и устранить их, не допуская два раза одних и тех же ошибок. Как говорил Джейсон Стейтем (Стэтхэм? Стэтэм?): Не страшно ошибаться, страшно повторять одну ошибку 2 раза. Ну и мы решили не повторяться. В статье расскажу как мы анализируем хотфиксы и другие критичные проблемы, что у нас получается, а что нет, с какими сложностями столкнулись и как их решали.

Как было раньше

Раньше на проде случались хотфиксы-откаты из-за критичных багов. Мы в QA понимали что нам нужно проводить какую-то ретроспективу по пропущенным багам на прод. Поэтому после каких-то очень проблемных релизов:

  • собирались и обсуждали проблему;

  • звали разработчиков, участвующих в этом релизе;

  • принимали решения (некоторые работают до сих пор).

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

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

Что такое постмортем?

Вообще, постмортем это посмертная фотография родственников.

Мы узнали об этой практике у нашей команды Платформы. Они уже с 2018 года ведут постмортемы по всем инцидентам в системе.

Постмортем для примера.Постмортем для примера.

Наши люди даже выступали с этими темами на конференциях.

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

Как начали вести

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

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

Скрин с Kaiten.Скрин с Kaiten.

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

  • По хотфиксам на проде.

  • По откатам релизов.

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

  • По STL (Stop the Line). Подробнее что это такое можно почитать в статье Stop the line или прокачай свой pipeline, йоу

Пример шаблона для хотфиксов из Kaiten.Пример шаблона для хотфиксов из Kaiten.

Структура и способ ведения

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

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

Авторы постмортема. Знаем к кому идти за подробностями инцидента или с уточняющими вопросами по решению. Наличие авторов постмортема не противоречит принципу написания постмортемов blameless culture. Мы не обвиняем никого в доведении до проблемы, а хотим лишь отобразить участников разбора.

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

Проблемы. Пишем о том, с какими проблемами могли столкнуться наши пользователи. Какую функциональность мы задели, какие непотребства видели наши пользователи. Или что видели мы, если мы заметили проблему раньше пользователей (алерты, подскочившие графики в Grafana).

Потери. В этом блоке пишем влияние на бизнес, сколько задето пиццерий или пользователей, сколько потеряли в деньгах или во времени (это импакт на пользователей).

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

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

Шаблон

Здесь оставлю пример шаблона наших постмортемов без моих комментариев.

## Дата

## Автор

## Проблемы

## Причина

## Последствия для бизнеса

## Предложения по недопущению в будущем

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

**Создать картохи на написание _автотеста_ по этой проблеме в своем бэклоге и прикрепи их сюда как дочерние**

**Создать картохи по _недопущению хотфикса_ в будущем в своем бэклоге или бэклоге владельцев компонента и прикрепи их сюда как дочерние**

## Что ещё хочется добавить

**Не забудь поставить теги компонента в котором случилась проблема**

Берите себе, адаптируйте и пользуйтесь.

Сложности и как их решали

С нахрапа не получилось ввести постмортемы и вести их идеально. Вот наш список проблем.

Не заполняли постмортемы. Банально да: поначалу люди ответственные за релиз забывали заполнять постмортемы

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

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

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

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

Скрин с чек-листа.Скрин с чек-листа.

Не создавали карточки на решение проблем. Мы используем Kaiten и настроили доски так:

  • прикреплённая дочерняя карточка с устранением проблемы берется в работу;

  • постмортем автоматически переезжает в In progress;

  • когда задачу завершают завершается и постмортем.

Это помогает не мониторить исполнение постмортемов.

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

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

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

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

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

Результаты в цифрах

Мы проанализировали постмортемы за пол года, и вот какая картина получается.

  • Треть наших постмортемов не имеют конкретных решений на доработку и недопущение проблем в будущем.

  • Половина наших постмортемовв которых есть конкретные решения ещё не выполнена.

  • 49 дней медианное время жизни постмортема от появления на доске до выполнения (из тех что выполнили и в которых были решения), а среднее 82 дня. Такие различия обусловлены большим разбросом значений. Критичные решения или очень простые решаются достаточно быстро, сложные и не критичные могут долго провисеть в ожидании.

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

Выводы

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

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

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


Что ещё почитать.

Stop the line или прокачай свой pipeline, йоу

Будущее интерактивного дизайна в руках

История Open Source кратко: от калькулятора до миллиардных сделок

Как выйти на китайский рынок с mini-app для WeChat, чтобы не прогореть

Как мы разогнали команду QA, и что из этого получилось

История архитектуры Dodo IS: ранний монолит

Код без тестов легаси

Подписывайтесь начат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на каналDodo Engineering, где мы постим всё, что с нами интересного происходит. А ещё естьгруппа в ВК(ну мало ли).

Подробнее..

Будущее, которое мы потеряли

21.01.2021 18:22:37 | Автор: admin

Если бы сбылись предсказания футурологов и фантастов космической эры, вроде Кларка или Азимова, мы бы жили в совсем другом мире. Базы на Луне и колонии на Марсе, к которым мы летим на ракетах с фотонными двигателями, антропоморфные роботы-помощники, подземные города, освоенная Арктика и вечная весна вот будущее, что мы потеряли. Но потеряли ли? Могло ли вообще сбыться это будущее?

Космической эрой 20-го века я называю 50-е и 60-е время, когда человек стремился в космос и надеялся на научно-технический прогресс. Это время великих предсказаний: мир работает во благо людей, новые технологии направлены на то, чтобы упростить нашу жизнь, мы освоили космос, а люди стали добрее и наступило всеобщее благоденствие. Отсылки на эту эпоху постоянно появляются в поп-культуре, например, в Футураме, а ракета Starship Илона маска выглядит будто срисованная с фантастических фильмов 50-х.

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

Примечание. Включите судля атмосферы и поехали.

Наивное будущее начала 20-го века

В начале 20-го века газета The New York Times опубликовала предсказание журналиста, оккультиста и кавалера ордена Почётного Легиона Анри Антуана Жюль-Буа. В 2009 году Анри видел, что на летающих велосипедах (мускулолётах) и летающих автомобилях люди добираются с работы в пригороды, потому что в городах больше никто не живет, а только работает.

А в 1900 году в журнале Ladies' Home Journal Джон Элфрет Уоткинс-младший рассказал, что в 21 веке комаров и мух больше не останется: все страны осушат болота, застойные бассейны и химически обработают все водоемы.

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

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

Что объединяет все эти предсказания? В них нет ничего, что не знали бы люди того времени:

  • Есть железные дороги.

  • В 1899 году началось производство первых дирижаблей-Цеппелинов для полётов.

  • Прототипы самолётов вовсю тестируют. Например, 6 мая 1896 на Аэродроме Лэнгли номер 5 впервые успешно испытан аппарат тяжелее воздуха с двигателем. А в 1900 братья Райт начинают свои эксперименты с планерами.

  • Подводная лодка известна еще с 18 века, когда в 1776 году французский изобретатель Бушнелл сделал подводную лодку Черепаха. Она даже поучаствовала в боевых действиях в войне за независимость США.

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

Индустриализация и гигантизм

Начало 20-го века время индустриализации, которая сменила традиционное аграрное общество. Новое ядро общества промышленность, которая влияла на экономику, культуру и политику. Жизнь обывателя 19 и 20 веков отличается кардинально.

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

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

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

Специализация. Например, заводы в Англии производили детали для станков в США, а Германия заняла рынок красителей. Здесь неизбежно возникновение жёсткой специализации и разделения труда уже в мировом масштабе. Целые страны становились цехами в мировом разделении производства.

Мечты о будущем усиление настоящего.

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

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

Обратите внимание на странную конструкцию из колеса и башни. Это танк, а рядом с ним дома. Так представляли себе войну. Это неудивительно прототипы танков уже существовали, а уже в 1915 году их начали серийно производить британцы. Модель называлась Mk I и их выпустили 48 штук. Правда, в первом же сражении в Первой Мировой 32 из них не доехали до цели. Странно, ведь их производил не Land Rover.

На примере танков хорошо прослеживается гигантомания времени после Mk I машины всё росли и росли. Но подвижность и функциональность ухудшалась, пока Луи Рено не додумался отказаться от тяжелого корпуса и сделать упор на подвижности. Так появился серийный танк Рено FT-17 лёгкий, манёвренный и быстрый.

FT-17 слева, Mk I справа.FT-17 слева, Mk I справа.

Космическая эра

50-е и 60-е золотая эра футуризма. Резкий рост популярности научной фантастики при содействии Азимова, Бредбери или Стругацких, подстегнул изучение научно-технического прогресса. Именно поэтому большинство предсказаний связаны с освоением космоса и колонизацией других планет. Притом они появились до того, как вообще первый человек попала в космос. Люди начали мечтать масштабно о:

  • термоядерных реакторах;

  • подводной агрономии на шельфах северных морей;

  • переводе всего транспорта на аккумуляторы;

  • генетической модификации живых организмов;

  • полётах в космос и на другие планеты.

Человек 50-х и 60-х чувствует, что живет будущим.

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

Пластик стал настолько популярен, что футурологи предсказывали, как люди будут есть из пластиковой посуды, сидеть на пластиковых диванах и жить в пластиковых домах. В 1957 году химический концерн Monsanto даже открыл Дом будущего Monsanto (в Диснейленде) дом полностью из пластика: стены, облицовка, мебель, посуда.

Мечты о космосе отражаются на автомобилях. Не только концепты, но и серийные автомобили становятся похожи на космические корабли.

Футуризм 50-х и 60-х в США хорошо описывает серия работ Артура Радебо (с 1958 по 1962 год). Футуристичные автомобили, которые перекрашиваются электромагнитной пушкой, почтальоны с реактивными ранцами, модифицированные растения на автоматизированных сельхозугодьях узнаваемый стиль времени, яркие цвета, улыбающиеся люди. Но на картинках мы сразу видим прокачанные 50-е и 60-е.

Дух времени покорение: космоса, Арктики, морей, природы.

Что советские, что американские фантасты они мечтали практически об одном и том же. Например, в диафильме В 2017 году в СССР построили плотину через Берингов пролив, пустили Енисей и Обь в Каспийское море, создали подлёдные города. Тепло собирают из глубин Земли, а каналы строят атомными взрывами.

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

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

Люди мечтали масштабно.

На Западе прогнозисты предсказывали, что через несколько десятков лет мы построим базы на Луне и посетим Марс. Мы сможем посещать другие планеты, как туристы, например, с помощью космического лайнера на 50 000 тонн, вместимостью 10 000 пассажиров, что предсказывал инженер Дэндридж М.Коул. А математик и учёный Д. Г. Бреннан описывал, что к 2018 году мы откроем антигравитацию и сможем летать.

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

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

Работа будет привилегией и благом. Как следствие, без дела будут сидеть 90% населения, но будут получать хорошие пособия и не голодать. Но те, кто будут работать, будут работать недолго пенсия сдвинется к 50 годам.

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

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

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

Экстраполяция хорошо заметна на примере Айзека Азимова. В 1964 году писатель опубликовал эссе в The New York Times, где описал 2014 год. Колонизация полюсов, подводные гостиницы, подземные дома, автоматизированные бытовые приборы, много компьютеров, 3D-фильмы, автономная техника на батареях и парящие над землей машины. Половина электроэнергии будет вырабатываться на АЭС, а в космосе солнечные батареи будут передавать энергию Солнца на Землю.

Может Азимов взял это всё из головы и предсказал, например, IoT или умные колонки? Нет, это всё он увидел на Всемирной выставке в Нью-Йорке в 1964 году. Все эти чудеса именно там и демонстрировались: футуристичные автомобили, поля с культурами с автоматическим орошением, порт в шельфе Арктики, подводные дома. Азимов даже не стал ничего придумывать просто описал, что увидел.

Чудеса Всемирной выставки: футуристические автомобили, города будущего, подводные, арктические и космические колонии.Чудеса Всемирной выставки: футуристические автомобили, города будущего, подводные, арктические и космические колонии.

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

Почему будущее так и не случилось?

Потому что люди представляли не будущее, а усовершенствованное настоящее.

  • Вокруг фабрики и заводы на которы ломят спины люди? Хотим автоматическое производство или роботов.

  • Вокруг космическая гонка? Хотим летать на Марс на работу, а отдыхать на даче на Луне, самолеты с атомным двигателем и покорение арктики.

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

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

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

  • Первый мобильны телефон создала Моторола в году. Но это устройство они увидели в сериале Стартрек.

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

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

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

В предсказаниях прошлого о будущем нет будущего только аллюзия настоящего.

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

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

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

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

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

Примечание автора. Рассказав о том, что предсказания прошлого лишь фантазии, я не ставил целью кого-то принизить. Я сам был бы рад, если бы предсказания о великих достижениях сбылись. Больше всего мне грустно, что не наступило будущее космической эры 50-х и 60-х. У меня в руках коробочка, которая в 1000 раз мощнее компьютеров, что запустили людей на Луну. И что я с ней делаю? Запускаю эмодзи с ракетами в чатиках. Поэтому мне грустно, когда ночью я смотрю на звёзды. У нас есть всё технологии, чтобы создать будущее, как его представляли в космическую эру и создать эпоху больших достижений. У нас есть всё, но нет желания. Почему? Я не знаю.

Заходите к нам Телеграм-чат, чтобы покритиковать статью и автора. Подписывайтесь на канал Dodo Engineering: там мы постим анонсы статей, подкасты и делимся тем, что нам интересно.

Подробнее..

Быстрый, простой, сложный как мы выпилили Realm

27.01.2021 12:10:40 | Автор: admin

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

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

Примечание. Realm читается как рэлм или рилм, но давайте только не реалм

Зачем нужна база данных для заказа пиццы?

Кратко незачем. База данных сначала прикрывала плохое API.

В 2017 году Dodo Pizza решила написать свое приложение. Серверная часть уже работала 6 лет и обслуживала 250+ пиццерий (на начало 2021 почти 700). Много работы было сделано для бизнеса, а для клиентов был только сайт нужно делать приложение.

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

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

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

Realm vs Core Data

Сложно вспомнить почему выбрали Realm, а не Core Data. Скорее всего, так было проще: схему базы рисовать не нужно, объекты создаются сразу в коде, работает быстрее, да и опыт работы с ней был. Так и поехало.

Как работало

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

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

  • получили данные из сети;

  • положили в базу, разметили связи между таблицами;

  • прочитали из базы, связанные объекты подтянулись сами;

  • переложили данные во view-модели, а дальше уже MVVM.

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

Недостатки Realm

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

Realm накладывает ряд ограничений сам по себе.

Хранит только сырые данные. Enum надо перекладывать в String или Int, Optional в RealmOptional, массивы в List, обратные ссылки в LinkedList. Чтобы превращать это в нормальные объекты надо писать какие-то конвертеры. В итоге кода становится сильно больше, модели дублируются, проект становится хрупче.

По всему коду размазано обращение к Realm: он импортируется в файл, передается в качестве параметра, из базы тянутся объекты. Мы активно заворачивали всё в репозитории, чтобы скрыть работу с базой, а интерфейсом выходил доменный объект. Но это дополнительный код и слой в архитектуру.

Работа с базой превратилась в целый слой, который надо поддерживать: писать маперы, обертки. Добавить новую сущность это слишком много ручной работы: создать Entity, переложить из DTO в нее, потом из Entity в доменную модель. Это всё ещё и протестировать надо, а мы даже на UI выводить ничего не начали.

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

Realm большая и очень тяжелая зависимость. Наш проект весил 55 Мб, Realm занимал 7 и очень долго билдился. Мы решили проблему пребилдом перенесли билд на этап pod install, стало реже и легче. Но плагин компиляции стал влиять и на другие поды, например, он не работал с XCFramework и мы не могли обновить поды, которые перешли на него. Убрать пребилд мы уже не могли, потому что привыкли к нормальной скорости сборки.

Ну и Realm мог бы и складывать свои файлы в одну папку!

По умолчанию Realm складывает всё в папку DocumentsПо умолчанию Realm складывает всё в папку Documents

Проблемы в проекте из-за недостатков

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

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

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

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

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

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

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

// К каким таблицам пойдёт запись? От чего зависит работа функции?public func saveOrder(_ order: Order, to realm: Realm) 

При обновлении Xcode каждый раз ломался CI. Обновление Realm его быстро чинило, но это лишние нервы каждый год.

Всё вместе это приводило к тому, что весь код вокруг Realm превращался в легаси:

  • его сложно рефакторить;

  • надо помнить про миграции;

  • могли быть неожиданные ошибки.

Это всё неприятно, но не критично: чуть больше кода, чуть меньше контроля, но работает.

Реально бесил лишь перформанс меню, но это можно было решить, стоило только сфокусироваться и попрофилировать. Коллеги на Android столкнулись с отсутствием каскадного удаления, но на iOS мы достаточно хорошо обработали это вручную, когда перед добавлением удаляли все прошлые объекты одного типа. Это же спасало и от разбухания базы.

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

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

Почему решили удалить две последние капли

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

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

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

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

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

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

Realm мигрирует без учета версии схемы. Могут быть сложности при повторном переименовании Property.

Откат. Через два месяца мы столкнулись в непонятным крешем Realm accessed in incorrect thread. Это было очень странно, потому что мы были точно уверены, что работаем с потоком правильно: вся работа с базой велась строго в отдельном потоке. Креш случался в самых разных местах, стабильности не было. Искали его неделю: у нас был pull request на версию с ошибкой, мы отревьювили 700 файлов 3 раза, но не смогли найти проблему.

Миграций базы уже не было, поэтому в качестве быстрого решения мы откатились на прошлую версию приложения. Это была ошибка. С откатом всё стало только хуже: Realm не мог прочитать свой файл из-за разницы версий самого Realm, он не смог прочитать свой файл. Повезло, что мы обновили только 1% пользователей и вовремя остановили. Откат обошелся в 3000 крешей.

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

Стало ясно, что так выкатывать приложение нельзя, каждый раз что-то случается, в этом каждый раз задействован Realm. Конечно, на ошибках мы учились, но так подставлять нельзя ни пользователей, ни бизнес. Каждый новый релиз стал восприниматься как смертельное решение, страшно было катнуть даже маленький фикс с переводами. Есть ли креши? Никто не знает, он рандомный: UI-тесты иногда показывали, а иногда и по 5 раз проходили без проблем.

Команда устала, давление огромное, такое отношение к релизам терпеть нельзя. Стало понятно, что вылечить не получается, надо резать.

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

Краткий итог критичных проблем:

  • проблемы с несколькими миграциями одного поля;

  • проблемы многопоточности в новой версии Realm.

Примечание. Забегая вперед скажу, что ошибку в Realm поправили в версии 5.3.5 20-го августа, а столкнулись мы 6-го. Фикс Realm вышел через две недели после наших проблем, но брейкинчедж появился 16 мая проблему починили только спустя 3 месяца. Нам просто повезло, что мы не обновились раньше.

Как продали бизнесу удаление Realm

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

iOS команда не нашла аргументов за то, чтобы оставить Realm. При этом он мог нам заблокировать любой релиз новым неожиданным образом.

Увы, тут было не до продажи просто поставили перед фактом.

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

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

План работ по сносу

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

Ключевые строки. Мы выписали ключевые строки, по которым можно отслеживать как много Realm используется в проекте. Это могло бы быть мерилом качества инкапсуляции Realm. Нашли 3300 мест. Погнали выпиливать.

Но такая верхнеуровневая метрика не рассказывает о сложности работы, только её количество.

Домены. Тогда мы выписали наши домены. За 3 года работы над приложением мы развязали домены, работать над ними можно было параллельно. Получилось так:

  • меню;

  • города и страны;

  • профиль;

  • адреса;

  • активные заказы;

  • корзина и детали заказа;

  • оценка заказа;

  • очередь синхронизации продуктов в корзине.

По каждому домену оценили сколько упоминаний их объектов, а потом всё сложили. Получилось 1500 мест.

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

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

Ревизии. Каждый день делали ревизию по количеству упоминаний, строили график нашей скорости. Дольше всего выпиливали Realm из меню, в нём было 26 видов объектов с 852 упоминаниями. Над ним работало 2 человека и потратили 112 человеко-часов.

Многие домены очистились меньше чем за день. Это было шоком: мы пару лет бесились с того, как сложно работает корзина, а оказалось, что отказаться от кеширования в Realm можно за несколько часов.

Как удаляли

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

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

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

Обычно работы по замене выглядели так:

  • Убираем наследование от Object, убираем всё @objc dynamic декларации у property, меняем класс на структуру (если надо).

  • Меняем запросы к Realm на обращение в наш репозиторий.

  • Правим мелочи: тесты, доступ.

  • Чистим: меняем типы property с сырых на доменные. Больше никаких непонятных Int, только Enum.

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

Ещё проблемы, которые нашли

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

Адреса. Они состоят из 3-х слоев: объект адреса, набор полей, которые его описывают, у каждого поля есть его тип. Например: нужна улица, её значение Ленина и она часть адреса Ленина 25. Простая система, но из-за обратных ссылок в коде можно было ходить по вложенности в любом порядке: не только 1-2-3, но и 1-2-1-2-3-2. Это сильно усложняло код. Написали тесты, поменяли структуру моделей, отрефакторили, теперь можно двигаться только в одном направлении 1-2-3 читать стало проще.

Города. В нашем домене встречаются две модели городов:

  • короткая нужна только для списка городов на старте приложения;

  • полная, которая подгружается после того, как выбрали город и нужна для работы приложения.

Оказалось, что в Realm они были описаны одной полной моделью, а данные могли как оказаться в ней, так и нет. При этом приложение стартовало с простой модели, а потом докачивало данные. Естественно, могло и не докачать, и приложение бы работало как есть.

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

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

Пауза: фидбек и переоценка сроков

После недели интенсивного рефакторинга мы взяли паузу: стабилизировали релиз, начали брать бизнес-задачи. За неделю мы сделали очень много по доменам упоминание снизилось на 60%. В итоге у нас осталось 3 несвязанных домена: города, оценка заказа и очередь продуктов в корзине.

Релизить всё ещё было страшно, но мы были вдохновлены результатами и даже выпустили релиз.

С новым XCode мы получили новые проблемы с Realm, но и новые пути решений у нас тоже были:

Как нам казалось, до конца проекта оставалось пару недель, поэтому 2 из 3-х команд начали брать бизнес-задачи, а одна продолжила рефакторить проект.

Не так страшны первые 90% рефакторинга, как вторые 90%

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

  • На старте приложения мы берем города из JSON в бандле приложения, чтобы можно было показать список городов без задержек и как можно быстрее перейти к меню.

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

  • После выбора города скачивалась полная модель она хранится отдельно. Приложение должно работать только с ней.

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

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

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

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

Раньше в приложении много где дублировался код:

  • Взять текущий идентификатор города.

  • Получить запись из базы по идентификатору.

  • Взять первый объект в ответе, это считаем текущим городом.

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

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

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

Примечание. О модульности и тестах напишу как-нибудь потом, подписывайтесь на канал.

Миграция

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

В этом нам сильно повезло: когда мы отказались от миграций в Realm, мы перенесли все нужные для работы ID в UserDefaults. Мы знали ID корзины или выбранного города, поэтому на старте нужно было только получить новые данные от API.

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

Храните критичные ID вне базы пригодятся.

Механизм миграции пригодился и для UI-тестов: можно пропустить выбор городов если сразу передать правильный ID. За счет миграции мы получим все нужные данные с сервера и сразу покажем меню, пропуская выбор страны и города.

Чистка после Realm

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

/// Давным давно, когда API был не очень, мы использовали Realm: собирали все ответы в одном базе, а потом читали из неё./// Больше такой фигни нет и мы всё аккуратно раскладываем по репозиториям./// Теперь на месте Realm вот такой маленький шрам, для того чтобы очистить старых клиентов./// Удали этот код, если читаешь это в 2022 году.internal final class RealmCleaner {    let fileManager = FileManager.default     /// Remove all realm files    /// - Returns: total size of removed files    func removeRealmFiles() {        let pathes = filePathes()        fileManager.removeItems(at: pathes)    }     private func filePathes() -> [URL] {        let baseURL = fileManager.documentsDirectory().appendingPathComponent("default.realm")        let realmURLs = [            baseURL,            baseURL.appendingPathExtension("lock"),            baseURL.appendingPathExtension("note"),            baseURL.appendingPathExtension("management"),            baseURL.appendingPathExtension("log_a"),            baseURL.appendingPathExtension("log_b")        ]         return realmURLs    }}

Мы замерили размер удаляемых файлов: в основном меньше 15 МБ, но было и несколько пользователей с размером в 150 и даже 300 МБ. И это не девайсы тестировщиков.

Новое хранилище

Какие-то данные всё равно хочется хранить. Мы уже избавились от Realm-объектов, перевели все на доменные. Хочется использовать их так, чтобы больше не надо было конвертировать из одного типа в другой только для хранения. Core Data таким образом тоже не подходит.

Мы собрали требования к хранению:

  • Хотим работать с доменным объектами.

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

  • Хранить можно в памяти или с кешем на диск. Приложение должно работать даже если на диске нет места. Кеш на диске опционален.

  • Для кеша на диск готовы запариться чуть больше, если надо.

  • Объемы данных всегда небольшие (меньше мегабайта) и слабо связанные реляционная БД не нужна.

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

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

public class ProfileRepository: SingleRepository<ProfileModel> {    public init() {        super.init(storage: InMemoryStorageWithFilePersistance().toAny())    }}
  • SingleRepository хранит один объект.

  • Хранит только модель ProfileModel.

  • Хранит объект в памяти и кеширует на диск.

  • Ещё есть InMemoryStorage и FileStorage. Для хранения на диске модель должна реализовать протокол Codable, а для хранения в памяти это не нужно. Для доменной модели это вполне подходит и легко поддерживать. Теперь отдельную модель для записи в базе создавать не нужно.

Коллекция пиццерий хранится в CollectionRepository: синтаксис похож, только наследуемся от другого класса.

public class PizzeriaRepository: CollectionRepository<PizzeriaModel> {    public init() {        super.init(storage: InMemoryStorageWithFilePersistance().toAny())    }}

Примечание. Про устройство рассказывать долго: там и box typing, и работа с асинхронностью. Пишите в комментарии, если интересно узнать как работает внутри.

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

Мониторинг

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

Запросы во время миграции. Мы пропустили, что во время миграции может восстановиться push-токен от Firebase и мы отправим его в наше API. Хедеры запроса зависят от текущей страны, а она в процессе миграции. Запрос не проходил, возник фон некритичных ошибок.

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

Крешрейт к Новому годы мы довели до 99.95%. Можно улучшать ещё, ведь теперь креши не в рандомных местах Realm, а только в нашем продукте и понятно как их чинить.

Результаты

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

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

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

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

Объём. Приложение уменьшилось на 8 МБ от Realm, запустили процесс по ревизии размера и уменьшили ещё на 10 МБ за счет бандла. Начали трекать размер приложения при каждом релизе.

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

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

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

Блокировки. Перестали блокироваться релизами Realm при обновлении Xcode, смогли обновить Cocoapods и поды на XCFramework.


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

Realm сложный инструмент и его надо уметь обслуживать. Простота интеграции бывает обманчива.

Больше новостей про разработку в Додо Пицце я пишу в канале Dodo Pizza Mobile. Также подписывайтесь на чат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы.

Подробнее..

Перевод Доступность на iOS началась с 36 секунд

27.05.2021 16:21:56 | Автор: admin

8 июня 2009 года Фил Шиллер выступил на WWDC. Всего 36 секунд он неловко говорил о VoiceOver, Zoom, White on Black (с iOS 6 называется Invert Colors) и Mono Audio. Это были первые реальные специальные функции на платформе iPhone OS, как её тогда называли. Однако, они не произвели большого впечатления 36 секунд закончились, а потом не было никакой демонстрации или аплодисментов, и Шиллер просто перешел к описанию приложения Nike+.

Но в сообществе людей с проблемами зрения всё было иначе. Казалось, что время остановилось где-то после 1:51:54. Произошло нечто совершенно удивительное, и только несколько человек, казалось, понимали, что это значит.

Примечание. Статья переведена и адаптирована в качестве поддержки книги Миши Рубанова Про доступность iOS. Надеемся, что статья поможет вам глубже погрузиться в мир доступности и начать разрабатывать доступные приложения. В этом и поможет книга. Первые четыре главы уже доступны на сайте. Остальные будут выходить в канале Dodo Mobile. Подписывайтесь.

Что произошло 8 июня 2009 года

8 июня 2009 года Фил Шиллер два часа выступал на WWDC. В 1:51:54, после того, как он продемонстрировал голосовое управление и новое приложение Compass, на экране появился логотип Apple accessibility фигура в виде пряника с вытянутыми руками и ногами, которая используется по сей день.

Мы также очень заботимся о доступности.

Сказал Шиллер, и слайд переключился на экран настроек iPhone с перечислением функций доступности: VoiceOver, Zoom, White on Black (с iOS 6 называется Invert Colors) и Mono Audio.

36 неловких секунд и всё кончилось: никакой демонстрации или аплодисментов. Доступность не произвела большого впечатления, и от первых реальных специальных функциях на платформе iPhone OS, Фил перешёл к описанию приложения Nike+.

Но в сообществе людей с проблемами зрения всё было совсем по-другому. Время, казалось, остановилось где-то после 1:51:54 на видео. Произошло нечто совершенно удивительное: одни были вне себя от радости, другие сомневались, третьи пребывали в шоке.

У всех возникли вопросы: смогут ли теперь пользоваться iPhone люди, которым он был недоступен? Спустя 10 лет мы знаем ответ теперь у Apple лучшая мобильная доступность. Но всё это происходило не сразу, и не каждый шаг на этом пути был верным. Я бы хотел рассказать об этом пути.

В дополнении к моему аудио-документальному фильму 36 секунд, которые изменили все: как iPhone научился говорить, я собрал список основных этапов доступности iOS за последние 10 лет. Я сосредоточился на аппаратном обеспечении и ОС Apple. Обновления приложений Apple и сторонние приложения, которые открыли двери для новых способов использования iOS, тоже важны, но с ними список будет слишком длинным. Поэтому, за некоторыми исключениями, я обратился к особенностям доступности iOS. Многие основные функции имеют специальные приложения и преимущества, даже если они не подходят напрямую.

2007-2009: до появления iPhone

В 2007 году на Mac всего пару лет было доступно ПО для чтения экрана для людей с проблемами зрения Mac Screen Reader. Это теперь он называется VoiceOver, а в 2005 его представили как Tiger версии 10.4. До Tiger ни одна версия Mac OS X не предоставляла инструменты доступности.

Поэтому немного пользователей Mac с проблемами зрения были настроены услышать что-то кардинальное на MacWorld Expo в 2007 году. До Mac OS X Apple действительно предлагала несколько настроек доступности, и программу для чтения с экрана от сторонних разработчиков.

Источник: https://www.macintoshrepository.org/2483-outspoken-8Источник: https://www.macintoshrepository.org/2483-outspoken-8

Некоторые энтузиасты технологий с проблемами зрения перешли на Mac после прихода VoiceOver с Tiger. Именно они больше всего интересовались, будет ли доступен iPhone.

Потому что в 2007 году среди покупателей iPhone не было людей с проблемами зрения iPhone был им недоступен. Многие люди, как незрячие, так и зрячие, полагали, что этого не будет никогда.

В конце концов, как перемещаться по холодному гладкому стеклу без зрения?

Примечание: о проблеме холодного стекла у нас есть перевод статьи Будущее интерактивного дизайна в руках.

Весной 2008 года Apple добавила VoiceOver в iPod Nano. В то же время iTunes на Mac стал доступен для VoiceOver. Старое приложение Carbon раньше не работало с устройством чтения экрана.

Это означало, что незрячий человек теперь мог подключить Nano к iTunes, включить VoiceOver на устройстве, скопировать на него песни и использовать VoiceOver для поиска и воспроизведения.

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

Теперь, когда iTunes стал доступен через Mac, Apple заложила основу для доступности iPhone, сделав возможным для человека с проблемами зрения синхронизировать iPhone со своим компьютером.

Следующая остановка настоящий, доступный iPhone.

2009: iPhone OS 3, 3GS и iPod Touch

Анонс iPhone, которого все ожидали на WWDC 2009, обещал быть грандиозным. Все ждали существенного обновления с длинным списком новых функций, учитывая, что App Store существовал уже год, а за плечами Apple два года разработки. iPhone 3GS был солидным релизом, а iPhone OS 3.0 принесла такие важные и запоздалые достижения, как copy/paste.

Июнь: iPhone OS 3 и 3GS

Поспешное, позднее открытие VoiceOver, Zoom, White on Black и Mono Audio принесло только неопределенность отсутствие демо-версии не внушало доверия. Кроме того, существующие устройства даже не были совместимы с iPhone OS 3.

Чтобы получить доступ, придется подождать и купить iPhone 3GS. Пользователи, которые были довольны или, по крайней мере, смирились со своими телефонами, внезапно обнаружили, что подписались на AT&T и перешли на новый, доступный iPhone.

Как работает VoiceOver

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

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

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

Источник: https://support.apple.com/ru-ru/HT204783Источник: https://support.apple.com/ru-ru/HT204783

Если, например, вы находитесь в Заметках, эти параметры позволяют читать по строкам, словам или символам, позволяя редактировать текст.

Остальные функции специальных возможностей

  • Масштабирование увеличивает экран iPhone. Pinch-to-Zoom был доступен в оригинальной ОС iPhone, но работал только в некоторых поддерживаемых приложениях, таких как Safari. Общесистемный зум увеличивал экран по всему интерфейсу.

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

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

Осень: iPod Touch третьего поколения

Следующая возможность получить доступное устройство от Apple появилась с выпуском iPod Touch третьего поколения старые модели не поддерживали iPhone OS 3.

Самым большим недостатком iPod, конечно, было отсутствие навигационных функций, предоставляемых GPS-оборудованным iPhone. Навигация представляла огромный интерес для незрячих пользователей.

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

2010: iPad, iBooks, iPod Touch

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

Весна: iPad первого поколения и iBooks

Но ещё одна особенность первого iPad также была интересна людям с ограниченными возможностями чтения iPad был первым устройством Apple, с приложением iBooks и iBooks Store. Можно не только добавить любую книгу Apple на iPad, но и использовать VoiceOver, чтобы прочитать её вслух. Это означало, что если книга не была доступна в шрифте Брайля, на ленте или в другом доступном формате, владелец iPad всё равно мог её изучить.

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

Осень: iOS 4

Ввод текста одним касанием (Touch typing). Сначала он был представлен как второй режим набора текста на iPad. Сенсорный ввод на виртуальной клавиатуре работает быстрее для пользователей VoiceOver, чем стандартный. Не нужно выбирать, а затем дважды нажимать клавишу на клавиатуре, чтобы ввести её. Теперь это проще: опустите палец на клавиатуру, наведите на нужную кнопку и отпустите палец для ввода буквы под ним. Разделенное нажатие делает сенсорный ввод гораздо эффективнее с помощью VoiceOver. Перейти в такой режим набора можно через ротор.

В iOS 4 добавили веб-ориентированный ротор с определенными опциями для навигации по элементам в Safari. Эта функция в конечном итоге будет свернута в ротор общего назначения, но iOS 4 это то место, где впервые появились веб-опции.

Использование ротора в Safari.Использование ротора в Safari.

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

Дисплей Брайля. Источник: http://com-v.ru/tiflomarket/braille-edge-40/90_m_bild1_brailleedge/Дисплей Брайля. Источник: http://com-v.ru/tiflomarket/braille-edge-40/90_m_bild1_brailleedge/

Количество таких дисплеев, поддерживаемых iOS, увеличивается с каждой версией, и Apple ведет текущий список в Интернете.

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

Доступность получила важные обновления в iOS 4, некоторые из которых придали легитимность iOS в глазах скептиков, которые предпочитали Windows.

2011: iPhone 4s, Siri и iOS 5

iPhone 4S был первым телефоном с Siri. Это не функция доступности, как таковая, но человек с физической или зрительной инвалидностью может управлять iOS голосом, что быстрее и проще.

Осень: iOS 5

iOS 5 освободила всех пользователей от необходимости настраивать устройство с компьютера, а пользователи VoiceOver получили возможность выполнять свою собственную настройку с помощью программы чтения с экрана. Начать настройку можно было через iTunes, поскольку Mac OS X также включала программу чтения с экрана.

Тройной щелчок. Незрячим людям стало проще благодаря корректировке поведения кнопки Домой. Раньше тройной щелчок вызывал выбор между масштабированием и голосом за кадром. К сожалению, пользователь VoiceOver с проблемами зрения не мог определить нужную опцию. В iOS 5 теперь можно было сделать тройной щелчок во время процесса установки, чтобы вызвать VoiceOver он включался по умолчанию.

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

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

Выбор речи и автотекст. В iOS также добавили эти новые функции речи. При включенном Speak Selection можно выбрать текст в любом месте iOS появившееся меню теперь включает кнопку Speak Selection. Speak Auto-Text делает именно это, когда применяется автокоррекция или автокапитализация. Обе эти функции речи используют тот же выбор голосов, что и VoiceOver можно выбрать голос, который вы хотите использовать, независимо от VoiceOver, и скорость.

VoiceOver Item Chooser также дебютировал в iOS 5. При посещении веб-страницы с множеством ссылок и элементов контента вызовите средство выбора элементов, чтобы вызвать алфавитный список элементов на странице. Если знаете, что вам нужно, вводите текст, чтобы сузить поиск. Затем дважды коснитесь элемента, который хотите выбрать.

Средство выбора элементов экономит время при навигации по знакомому сайту. В качестве альтернативы выбору элементов для пользователей Bluetooth-клавиатуры также был добавлен ряд веб-специальных сочетаний клавиш VoiceOver, например, набрав H (от heading) на подключенной клавиатуре можно переходить по заголовкам

Обнаружение лиц. Дебютировало в iOS 5 в приложении камеры. При включенном VoiceOver наведите устройство на объект или объекты, и VoiceOver покажет, сколько лиц видит камера и где они находятся в кадре.

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

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

Особенности слуха

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

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

Новый режим слухового аппарата. Позволил устройствам iOS лучше работать со слуховыми аппаратами Bluetooth. Это было началом расширенной поддержки слуховых аппаратов, которая расцветет в более крупную инициативу Made for iPhone для слуховых аппаратов. Начиная с iPhone 5, они сертифицированы как совместимые со слуховыми аппаратами (Hearing Aid Compatible, HAC) Федеральной комиссией по связи США.

2012: iOS 6

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

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

Осень: iOS 6

Home-Click Speed. Позволяла пользователю регулировать чувствительность кнопки Home, что облегчало человеку с моторной задержкой эффективное нажатие на неё. Guided Access даже получил демо-версию WWDC Keynote.

Контекстные действия в iOS 6. У некоторых элементов появились дополнительные действия. Чтобы переключиться на них проведите пальцем вверх или вниз, двойное нажатие выполняет действие. Например, так можно пометить письмо как прочитанное или удалить. Это немного похоже на использование ротора VoiceOver, но без необходимости делать жест открытия ротора.

Карты. Получили серьезное обновление в iOS 6. Pause to Follow позволяет найти и выбрать улицу, а затем провести пальцем когда вы слышите Pause to Follow.

Применение Pause to Follow на карте.Применение Pause to Follow на карте.

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

Guided Access. Новинка в iOS 6. Позволило ограничить доступ к элементам iOS, например, отключить кнопки громкости или запретить пользователям доступ к определенным приложениям. Управляемый доступ позволяет учителям сосредоточить внимание учащихся, часто детей со спектром аутизма. Вызовите управляемый доступ и свободно передайте ученику iPad, который может запускать только то образовательное приложение или обучающую игру, которую вы хотите использовать. Также можно замаскировать кнопки или другие элементы интерфейса в выбранном приложении.

AssistiveTouch. AssistiveTouch делает ключевые элементы управления устройствами доступнее.

AssistiveTouchAssistiveTouch

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

2013: iOS 7

Apple лихо переработала внешний вид интерфейса iOS и практически всех своих собственных приложений. Сквеоморфизм исключили, а тонкие шрифты, прозрачные слои и анимация добавили.

Шаг назад с iOS 7

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

Благодаря отрицательной реакции на iOS 7 Apple исправила то, что сломала: откатила первоначальный дизайн ОС и добавила настройки доступности, которые могли бы компенсировать проблематичность определенных элементов дизайна. Например, они включали в себя формы кнопок, уменьшающие движения и метки включения/выключения. Кроме того, iOS 7 представила увеличенную контрастность, полужирный текст и настройку размера шрифта. Они помогли компенсировать проблемы 7.0.

Все выровнялось в iOS 7.1. Это был единственный релиз iOS, созданный специально для слабовидящих пользователей вроде меня.

Более крупный текст используется внутри сообщений.Более крупный текст используется внутри сообщений.

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

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

Обновления. iOS 7 представила множество основных обновлений интерфейса, включая Центр управления и усиленный переключатель приложений. Они были сразу же доступны VoiceOver и AssistiveTouch, а также новому интерфейсу управления коммутатором.

Динамичный Текст. Функция Увеличенный текст была доступна в iOS 6, хотя и не сильно повлияла на iOS. Можно выбрать один из шести увеличенных размеров текста и увидеть их в нескольких приложениях, таких как Сообщения или Почта.

Более крупный текст в iOS 7 и поздних версиях это пользовательский интерфейс для Динамичного Текста. Если разработчик поддерживает Динамичный тип в приложении, пользователь может использовать ползунок Размера текста, чтобы сделать его больше или меньше. Даже Apple не поддерживала Динамический тип во всех своих приложениях iOS 7, но постепенно исправила. Многие другие разработчики приняли эту функцию, но не все.

Слуховые аппараты iPhone. Apple никогда не создавала собственные слуховые аппараты, но работала с производителями, чтобы увеличить количество доступных продуктов MFi программу Made for iPhone. Технологические усовершенствования в характеристиках Bluetooth Low Energy (BLE) и согласованные усилия Apple распространили MFi на слуховые аппараты, которые компания начала сертифицировать как совместимые с устройствами iOS.

Субтитры. Фильмы и телепередачи, купленные в iTunes или приобретенные из других источников, можно просматривать с подробными субтитрами или обычными. В iOS 7 появился интерфейс для настройки стиля и размера текста, появляющегося на видео с субтитрами.

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

Switch Control. AssistiveTouch был частью iOS начиная версии 6. Switch Control расширил идею интерфейса, который поддерживает пользователей с моторными задержками.

Сканирование главного экрана с помощью переключателя управления.Сканирование главного экрана с помощью переключателя управления.

С его помощью мы используем:

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

  • сам экран iOS в качестве переключателя или даже камеры: глядя влево или вправо, мы активируем переключатель на основе камеры.

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

Многие пользователи Switch Control оснащают свои iOS-устройства несколькими устройствами. А iPad, установленный на инвалидном кресле вместе с переключателями, может создать очень эффективную iOS-установку для человека с тяжелыми двигательными нарушениями.

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

2014: iPhone 6 и 6 Plus

Первые большие айфоны включили новую функцию под названием Display Zoom.

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

Осень: iOS 8

Несколько новых функций в iOS 8 были важны для доступности.

Клавиатура QuickType. Предсказывала текст и отображала панель с предложениями по набору текста над виртуальной клавиатурой.

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

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

Использование контроллера масштабирования.Использование контроллера масштабирования.

Алекс. В iOS 8 из Mac OS X пришёл голос Алекс. У Алекса есть естественное звучание дыхания во время разговора, что намного удобнее чтения длинных текстов, чем более старые альтернативы из iOS 5.

Оттенки серого. В iOS 8 появился Режим оттенков серого для некоторых людей отсутствие ярких цветов на экране легче для восприятия.

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

Ввод с экрана Брайля. Как и функция рукописного ввода, экранный ввод Брайля облегчает ввод текста на экране.

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

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

Экран Speak используется внутри Safari.Экран Speak используется внутри Safari.

2015: Apple Watch и iOS 9

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

3D Touch. Функция появилась на новых iPhone в iOS 9. Несмотря на альтернативные голосовые жесты и необходимость сильно нажимать, чтобы вызвать 3D Touch, пользователи VoiceOver с поддерживаемыми телефонами получили доступ к жестам 3D Touch первого уровня, а также Peek и Pop.

Многозадачность iPad. Появились новые жесты, которые позволяли пользователю VoiceOver работать с Split View или Slide Over. Опять же, пользователи специальных инструментов могли пользоваться новыми инструментами прямо из коробки.

С точки зрения доступности iOS 9 не выдающаяся версия. Но в ней появились новые настройки для людей с физическими недостатками, а также некоторые обновления VoiceOver. Вот исчерпывающий список обновлений, связанных с VoiceOver.

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

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

Switch Recipes для Switch Control. При использовании управления переключателем каждый переключатель выполняет одну функцию. Функция Switch Recipes эффективно позволяет пользователю создавать комбинации действий.

Изменения Ротора выбора текста VoiceOver. Изменения в роторе VoiceOver облегчили выбор текста по символу, слову, строке или странице.

2016: iOS 10

Изменения в iOS 10 в основном косметические. Но не все.

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

Новый редактор произношения. Позволяет произносить или вводить имя так, как его должен произносить VoiceOver, и сохранять правильное произношение. А с VoiceOver audio routing можно передавать звук на устройство по вашему выбору, например на Bluetooth-динамик. Теперь Switch Control позволяет пользователям управлять устройствами, подключенными к устройству iOS, включая Apple TV.

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

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

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

Цветовые фильтры. iOS предлагала переключение оттенков серого с iOS 8, но теперь появились цветовые фильтры, предназначенные для настройки вида экрана в зависимости от потребностей пользователей с тремя распространенными формами дальтонизма.

Программное обеспечение TTY. Пользователи iPhone всегда могли подключать свои телефоны к TDD (телекоммуникационное устройство для глухих) если у них был соответствующий аппаратный адаптер. Начиная с iOS 10, можно звонить или отвечать на звонок TTY без допоборудования, а также сохранять стенограммы звонков.

2017: iOS 11

iPhone X первое iOS-устройство без кнопок Home. Это повлияло на доступность: отсутствие кнопки Home изменило жесты по умолчанию, используемые для таких вещей, как open Control Center или App Switcher.

Face ID. Но не только из-за этого стало хуже Face ID создал проблемы для некоторых незрячих пользователей.

iOS 11 была первой версией iOS, которая поддерживает устройства Face ID. Некоторые люди с ограниченными возможностями, особенно незрячие и с протезами глаз, не могут уделять необходимое внимание датчику Face ID. Поэтому iOS добавила переключатель, который позволяет разблокировать телефон без зрительного контакта. Из-за этого Face ID вызывает споры в сообществе людей с проблемами зрения из-за предполагаемого риска безопасности. Также некоторым слепым пользователям трудно заставить Face ID работать.

Однако функция всё равно полезна. Переключатель Require Attention, который включен по умолчанию (функция безопасности) предотвращает разблокировку телефона, если он просто проходит перед лицом своего владельца. Выключение этого переключателя позволяет большему количеству незрячих людей разблокировать телефон с помощью Face ID. Но пользователь потенциально рискует получить телефон разблокированным без их согласия. При настройке устройства, оснащенного Face ID, с помощью VoiceOver пользователю предлагается оставить его включенным или выключить.

Новые возможности. iOS 11 также принесла несколько новых и обновленных версий специальные возможности, например, поддержку VoiceOver для перетаскивания и специальные жесты VoiceOver для владельцев iPhone X. VoiceOver также добавил новый метод перемещения приложений, который был частью iOS 10. Теперь можно использовать ротор для выбора и перетаскивания нескольких приложений в любое место на любом домашнем экране.

Смарт-инверсия цвета. Invert Colors появились ещё в iOS 3, и отображали всё как обратное нормальному внешнему виду. Но Smart Invert Colors отображает изображения как положительные, а не отрицательные, до тех пор, пока рассматриваемое приложение поддерживает его.

Вы все еще можете выбрать между Invert Colors и Smart Invert Colors и добавить их в ярлык специальных возможностей.

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

В основном это проблема разработчиков, которые не задумывались как будет вести себя их интерфейс и изображения при включенном Smart Invert Colors. Сочетание этих двух проблем причина того, что ни одна из опций инвертирования не работает как эффективный тёмный режим для iOS. Из того, что я видел, тёмный режим предлагает разработчикам возможность использовать затемнение и слои для настройки внешнего вида интерфейса. Это может быть полезно и для пользователей Smart Invert Colors, а также для общего понимания того, что люди, не имеющие слабого зрения, пытаются использовать iOS в тёмном режиме.

Type to Siri. Если вы не можете говорить или это неудобно, Type to Siri даёт возможность выдавать команды Siri с клавиатуры. Можно печатать с виртуальной, с Bluetooth-клавиатуры или в шрифте Брайля с дисплея Брайля.

2018: iOS 12

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

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

Живое Прослушивание. Уже некоторое время можно использовать микрофон iPhone как усилитель окружающего звука, посылая звук на слуховой аппарат. В iOS 12 функция Live Listen была расширена до AirPods. Теперь можно слышать собеседника даже если вокруг шумно.

2019: iOS 13

Доступности уделили много внимания на WWDC 2019. Вот несколько важных особенностей.

Голосовое управление. Последняя и совершенно новая функция специальных возможностей, анонсированная для iOS 13, а также Mac OS Catalina, имеет очень старое название. До появления Siri в iOS была функция голосового управления (и до сих пор существует). Но можно было только инициировать телефонный звонок или воспроизвести песню с помощью голосовой команды.

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

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

Итог

Если вы измеряете доступность iOS по количеству доступных сегодня функций, 2009 год кажется примитивным временем. В тот год на программном слайде WWDC Фила Шиллера появилось всего четыре пункта. Но один из них VoiceOver точно революционизировал реальную доступность на iPhone почти сразу.

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


Полезные материалы:

Статьи по VoiceOver:

Voice Control и VoiceOver: как адаптировать приложение для незрячих или неподвижных.

VoiceOver на iOS: каждый контрол ведёт себя по-разному.

VoiceOver на iOS: решение типовых проблем.

Другие полезные:

Зачем и как мы пишем постмортемы по критичным багам.

Будущее интерактивного дизайна в руках.

Как выйти на китайский рынок с mini-app для WeChat, чтобы не прогореть.

На пути к 10x инженеру: шорткаты, сниппеты, шаблоны.

Подписывайтесь на Dodo Engineering chat в Телеграм будем обсуждать статью, и на канал там новости и разное интересное.

Подробнее..

Перевод Код без тестов легаси

26.02.2021 12:14:08 | Автор: admin

Если вы работаете в IT, то о легаси вы слышите часто обычно с множеством негативных коннотаций. Понятно, что это не хороший код, но какой? Может старый, может не поддерживаемый или не обновляемый, а может просто чужой? Есть ли полноценное определение легаси, на которое можно ссылаться? А когда разберемся что нам делать с легаси? Попробуем разобраться. Спойлер: выводы неочевидны.

Автор Николас Карло, веб-разработчик в Busbud (Монреаль, Канада). Специализируется на легаси. В свободное время организует митап Software Crafters и помогает с конференциями SoCraTes Canada и The Legacy of SoCraTes.

Данная статья была скомпилирована (и отредактирована) их двух статей Николаса: What is Legacy Code? Is it code without tests? и The key points of Working Effectively with Legacy Code. Показалось логичным рассказать о том, что такое легаси, а потом как с ним работать.

Что такое легаси?

Возможно, если вы задавались этим вопросом, то встречали определение от Майкла Физерса. Майкл выпустил книгу Working Effectively with Legacy Code в 2004 году, но она до сих пор актуальна. Комикс это отлично иллюстрирует.

В своей книге Майкл пишет своё определение:

Для меня легаси это просто код без тестов.

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

Это хорошее определение: чаще всего тесты отсутствуют, так что это хорошее начало. Но это ещё не всё есть нюансы.

Код с тестами также может быть легаси. Если вы читаете тесты, но не можете понять, что должен делать код они отстой. Плохие тесты только мешают: тестируемый код так же трудно отрефакторить, как если бы у него не было тестов, а может даже и сложнее!

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

Перейдём к моему определению легаси.

Легаси это ценный код, который вы боитесь менять.

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

Но есть нюансы.

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

Хорошие тесты помогают легко менять незнакомый код. А плохие тесты не помогают. Отсюда и определение Физерса.

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

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

А теперь один из важнейших нюансов.

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

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

В итоге мы получаем, что легаси это:

  • код без тестов;

  • который мы пытаемся понять, чтобы отрефакторить;

  • но боимся.

Что же делать?

Как же эффективно работать с легаси?

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

Добавить тесты, а затем внести изменения

Логично, что если добавить тесты, они помогут его прощупать и он перестанет быть устаревшим. Поэтому первое, что нужно сделать написать тесты. Только тогда мы будем в безопасности, чтобы рефакторить код.

Но чтобы запустить тесты, мы должны поменять код. Возникает парадокс легаси. Мы обречены? Нет. Поменяем как можно меньше кода для тестов:

  • Определим точки изменения швы.

  • Разорвём зависимости.

  • Напишем тесты.

  • Внесём изменения.

  • Отрефакторим.

Первые два пункта самые сложные, а как только доберёмся до тестов, мы знаем, что делать.

Найти швы для разрыва зависимостей

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

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

Шов место, где можно изменить поведение программы, не меняя код.

Швы бывают разные. Если это объектно-ориентированный ЯП, то обычно это объект, например, в JavaScript.

export class DatabaseConnector {// A lot of codeconnect() {// Perform some calls to connect to the DB.}}

Допустим, метод connect() вызывает проблемы, когда мы пытаемся поместить код в тесты. Получается, что весь класс это шов, который можно поменять. Можно расширить этот класс в тестах, чтобы предотвратить его подключение к реальной БД.

class FakeDatabaseConnector extends DatabaseConnector {connect() {// Override the problematic calls to the DBconsole.log("Connect to the DB")}}

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

Напишем unit-тесты

Дискуссии о лучших практиках тестирования обычно перерастают в холивары. Применять принцип пирамиды тестов, и писать максимум unit-тестов? Или использовать Кубок тестирования и писать в основном интеграционные?

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

Чтобы избежать путаницы, Майкл даёт четкое определение того, что такое НЕ unit-тест:

  • он не работает быстро (< 100ms / test);

  • он взаимодействует с инфраструктурой, например, базой данных, сетью, файловой системой, переменными;

Напишите максимум тестов, которые обладают этими 2 качествами, при этом неважно, как вы их назовёте.

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

Тесты для определения характеристик

Это тесты, которые формализуют фактическое поведение части кода.

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

Это мощная техника, потому что:

  • В большинстве систем то, что код делает важнее того, что он должен делать.

  • Мы можем быстро покрыть легаси с помощью этих тестов. Так мы подстрахуемся для рефакторинга.

Этот метод также называют Approval Testing (тестированием одобрения), Snapshot Testing или Golden Master.

Но обычно на всё это очень мало времени.

Когда совсем нет времени на рефакторинг

Несколько советов, если предыдущие не подходят.

Большие куски кода обладают гравитацией и привлекают ещё больше кода. Теория разбитых окон в действии: небольшой беспорядок влечёт за собой беспорядок серьёзнее. Если класс уже содержит 2000 строк, то какая разница, что вы добавите еще 3 if оператора и будете поддерживать класс длиной в 2010 строк?

Это всего лишь 3 if: тяжело себя убедить, что нужно потратить на них 2 дня, хотя и должны. Что делать, если действительно нет времени писать тесты для этого класса? Используйте техники Sprout (прорастание), Wrap (обёртывание) и скретч-рефакторинг.

Sprout

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

Рассмотрим на примере:

class TransactionGate {//  a lot of codepostEntries(entries) {for (let entry of entries) {entry.postDate()}//  a lot of codetransactionBundle.getListManager().add(entries)}//  a lot of code}

Допустим, нам нужно убрать дубли файла entries, но postEntries() трудно проверить нет на это времени. Мы можем прорастить код где-то ещё, например, в новом методе uniqueEntries(). Этот новый метод легко протестировать, потому что он изолирован. Затем вставим вызов этого метода в существующий, не проверенный код.

class TransactionGate {//  a lot of codeuniqueEntries(entries) {// Some clever logic to dedupe entries, fully tested!}postEntries(entries) {const uniqueEntries = this.uniqueEntries(entries)for (let entry of uniqueEntries) {entry.postDate()}//  a lot of codetransactionBundle.getListManager().add(uniqueEntries)}//  a lot of code}

Минимальные изменения, минимальный риск. Можете вырастить один метод, целый класс или что-то ещё, что изолирует новый код.

Wrap

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

  • Переименуем старый метод, который хотим обернуть.

  • Создадим новый с тем же именем и подписью, что и старый.

  • Вызовем старый метод из нового.

  • Поместим новую логику до/после вызова другого метода.

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

class TransactionGate {//  a lot of codepostEntries(entries) {for (let entry of entries) {entry.postDate()}//  a lot of codetransactionBundle.getListManager().add(entries)}//  a lot of code}

Ещё один способ решить эту проблему это обернуть её, поэтому мы переходим к postEntries(), списку записей, из которых мы удалили дубли.

class TransactionGate {//  a lot of codepostEntries(entries) {// Some clever logic to retrieve unique entriesthis.postEntriesThatAreUnique(uniqueEntries)}postEntriesThatAreUnique(entries) {for (let entry of entries) {entry.postDate()}//  a lot of codetransactionBundle.getListManager().add(entries)}//  a lot of code}

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

class TransactionGate {//  a lot of code+ postEntries(entries) {+  // Some clever logic to retrieve unique entries+  this.postEntriesThatAreUnique(uniqueEntries)+ }+ postEntriesThatAreUnique(entries) {- postEntries(entries) {for (let entry of entries) {entry.postDate()}//  a lot of codetransactionBundle.getListManager().add(entries)}//  a lot of code}

Эти методы не идеальны, и у них есть недостатки. Но это полезные инструменты при работе с легаси. А при необходимости можно даже немного нарушить правила.

Скретч-рефакторинг

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

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

Выводы

Легаси будет везде, где бы вы ни работали, в каждой кодовой базе. Можно сопротивляться и чувствовать себя плохо, когда вы застряли в нём. А можно рассматривать это как возможность. Работа со старым кодом это очень ценный навык, его надо изучать теоретически (почитайте книгу Working Effectively with Legacy Code) и практиковать в ежедневных задачах.


Похожие и интересные статьи:

Больше новостей про разработку в Додо Пицце я пишув канале Dodo Pizza Mobile. Также подписывайтесь начат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на каналDodo Engineering, где мы постим всё, что с нами интересного происходит.

А если хочешь присоединиться к нам в Dodo Engineering, то будем рады сейчас у нас открыты вакансииiOS-разработчиков(а ещё для Android, frontend, SRE и других).

Подробнее..

Категории

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

  • Имя: Макс
    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