В этой статье представлены инструменты, которые помогут вам повысить рабочую продуктивность. Они сгруппированы в четыре категории: управление временем, чек-листы, планировщики и управление проектами. Каждая группа позволяет решить определённый тип проблем.
Управление временем
Если вы не спланируете день, то проведёте его в беготне. Одним из способов обретения контроля является отслеживание каждой минуты ваших продуктивных действий. Звучит довольно жёстко, но если добавить немного дисциплины и правильные инструменты, то всё не так страшно. Вот вам в помощь несколько приложений.
TMetric Time Tracker
• Базовая версия: бесплатно
• Профессиональная версия: $4 за пользователя в месяц
• Бизнес-версия: $6 за пользователя в месяц
TMetric — простое, но мощное веб-приложение с миленьким интерфейсом, которое поможет распланировать ваши часы. Просто добавьте задачу, её предполагаемую длительность, и нажмите Start для отслеживания.
В приложении используется простая иерархия. Задачи формируют Проекты, объединяемые в Клиентов. Можно независимо отслеживать каждую категорию, так что можно точно узнать, сколько и на что было потрачено времени.
С помощью функции Workday Timeline можно быстро оценить, на что у вас уходит время в течение дня. Вы также можете объединиться со своими коллегами и отслеживать время каждого из вас с помощью фичи Team View. В приложении можно сгенерировать подробный отчёт о потраченном времени и заработанных на каждом проекте деньгах. Бизнес-владельцы могу настроить соотношения разных типов работы и без затруднений вычислять зарплаты сотрудников.
TMetric позволяет очень легко отслеживать отдельно оплачиваемое и неоплачиваемое время, так что вы сможете держать руку на пульсе своих расходов и доходов. А для группирования задач или проектов можно использовать удобную систему тегов.
Наконец, приложение легко интегрируется с популярными сервисами управления проектами, вроде Trello, Asana, Jira, Todoist и многими другими, так что вы можете использовать эту связку как одно полное решение.
• Базовая версия: бесплатно
• Профессиональная версия: $67 за лицензию в год
В отличие от остальных веб-приложений ManicTime является скачиваемой программой, которая работает в фоне и отслеживает всё, что вы делаете. Она может сказать вам, какую программу вы открывали, с какими файлами работали и сколько времени на это потратили.
Программа позволяет присваивать временным промежуткам собственные теги, благодаря чему можно легко создавать табели учёта времени. Очень удобно, что не нужно логиниться на сервер: достаточно просто нажать «Старт», а по завершении работы — остановить учёт. Клёвое приложение, автоматически фиксирующее ваши действия!
Timely
• Индивидуальная версия: $14 за пользователя в месяц
• Корпоративная версия: $21 за пользователя в месяц
• Энтерпрайз-версия: $49 за пользователя в месяц
Timely — приложение, впечатляющее визуально. У него интерфейс в стиле Kanban, позволяющий перетаскивать мышью блоки с задачами в календарь, получая моментальное представление о временных затратах в течение дня.
Одно из главных преимуществ Timely — система отчётов. Вы можете посмотреть, на что потратили время в течение дня, недели или месяца. Можете сравнить временные затраты в течение конкретного проекта с предполагаемыми затратами — на мой взгляд, крайне полезно!
Проекты можно помечать как оплачиваемые и неоплачиваемые (бывают важные задачи, в которых не учитываются деньги). Можете назначать проектам почасовую оплату труда, или задавать фиксированную стоимость и отслеживать, сколько было сделано за час.
Наконец, никакой графический интерфейс не будет полным без цветовой кодировки, и в Timely вы можете присваивать своим задачам любое количество цветов.
Чек-листы
Если вы сыты по горло бумажками для заметок, облепившими ваш монитор, ежедневниками и блокнотами, то вам поможет одно из приложений-чек-листов. В него можно заносить все свои задачи, так что вы сможете сосредоточиться на том, что нужно сделать прямо сейчас. На мой взгляд, представленные три приложения — лучшие из доступного сегодня.
Wunderlist — популярное приложение, и настолько качественное, что Microsoft купила компанию за $200 миллионов. Программ простая и небольшая, позволяет быстро создавать список задач и отслеживать их выполнение. В Wunderlist есть ограниченная поддержка повторяющихся задач, и можно даже сотрудничать с другими пользователями.
Это кросс-платформенное приложение, в него можно входить с любого устройства. Это крайне важно, ведь вы не знаете, когда вам вдруг понадобится просмотреть свой список задач. Как и в большинстве подобных программ, в Wunderlist есть функция напоминания. Отслеживать задачи очень просто, и сразу видно, какие из них просрочены.
Todoist
• Базовая версия: бесплатно
• Премиум-версия: $29 в год
• Бизнес-версия: $29 за пользователя в год
Команда разработчиков Todoist решила создать приложение-чек-лист, чей интерфейс повторяет классические почтовые клиенты. Благодаря этому программа выглядит знакомой с первого же момента. Интерфейс минималистичен и позволяет быстро и за минимум действий добавлять задачи и вносить изменения.
Вы можете не только добавлять задачи, но и отправлять электронные письма посредством браузерных плагинов для Gmail, Thunderbird или Outlook. Как и Wunderlist, Todoist — кроссплатформенный продукт и может работать на любом устройстве.
У Todoist есть замечательное свойство, позволяющее добавлять задачи, просто набирая характерные фразы вроде «Среда в 11 часов» или «каждую пятницу в 16 часов».
Это единственное известное мне приложение подобного назначения, которое геймифицирует личную продуктивность. При завершении задачи вас будут награждать баллами «кармы», и вы сможете «померяться кармой» со своими коллегами.
Не позволяйте этому мягкому названию обмануть вас: это одно из наиболее продуманных приложений-чек-листов! RTM вообще было одним из первых продуктов такого рода на рынке, и он многое умеет.
Прежде всего, RTM с самого начала использует для создания задач естественный язык. Достаточно сказать что-нибудь вроде «отправить письмо Ивану в 9 утра в среду», и задача будет добавлена в список.
Разработчики приложения знают, что реальной проблемой является не добавление задач в список, а необходимость вспоминать о них когда приходит время. Поэтому они добавили все возможные способы напоминания: пуши на мобильных и настольных устройствах, SMS, сообщения в Skype, AIM, Google Hangout, Twitter и так далее.
Профессиональная версия содержит полный набор инструментов для взаимодействия с другими пользователями, включая подзадачи, умные списки задач и теги.
Планировщики
Приложения для отслеживания потраченного времени и чек-листы прекрасны для микроменеджмента ваших задач. Но вам нужно ещё кое-что, чтобы целиком окинуть картину взглядом. Если вы когда-нибудь замечали за собой, что никак не можете сообразить, есть ли у вас какие-то дела на 9 утра, когда потенциальный клиент предлагает встречу, то рекомендую воспользоваться приложением-планировщиком.
Calendly
• Базовая версия: бесплатно
• Премиум-версия: $8 за пользователя в месяц
• Профессиональная версия: $12 за пользователя в месяц
Позиционируемое как простое и элегантное решение для планирования, Calendly — идеальный инструмент, который поможет вам организовать свои дела. Многие люди подписываются на всевозможные инструменты, многие из которых имеют собственные средства планирования. В результате это может приводить к накладкам. Calendly может быть интегрирован с большинством популярных приложений, включая Google, Office365 и Outlook, чтобы проверять на наличие таких накладок в расписании и исправлять их посредством добавления всех задач в один календарь.
Calendly совместимо со всеми устройствами. Приложение позволяет назначать перерывы между встречами. Можно добавлять несколько приглашений на каждое мероприятие, что полезно для вебинаров, конференций и мастер-классов. И хотя Calendly довольно мощное приложение, его интерфейс прост и понятен.
Конечно, если вы предпочитаете придерживаться проверенных решений, то всегда есть Google. Если вы пользуетесь Gmail, то уже имеете доступ к бесплатному приложению Calendar, которое способно обработать большинство ваших задач.
Как и прочие продукты Google, Calendar отличается простым и понятным интерфейсом, позволяющим очень легко назначать события и задачи. Если вы пользуетесь G Suite for Businesses, то имейте в виду, что Messages может автоматически создавать в календаре события на основе текста ваших писем.
Для разных типов задач можно создавать разные календари. Например, можно сделать календарь для работы, для домашних дел, для выходных, для хобби и так далее. Все задачи будут отображаться в главном интерфейсе, но кликнув на вкладку My Calendars можно переключиться на отдельные календари. Ваши коллеги могут делиться с вами своими календарями. Чтобы добавить их в свой список, введите имя/почту человека в “Other Calendars”.
Наконец, у Google Calendar прекрасная система напоминаний, которая найдёт вас на настольном компьютере, ноутбуке или мобильном устройстве. При этом сами напоминания могут быть однократными и повторяющимися.
Doodle
• Базовая версия: бесплатно
• Личная версия: $39 за пользователя в год
• Бизнес-версия: $69 за пользователя в год
Два предыдущих приложения идеальны для любых задач, а вот Doodle спроектирован исключительно для работы со встречами и событиями. В его основе лежит философия, согласно которой свойства, которые не нужны в данный момент, только мешают. И это работает. Приложение позволяет очень легко планировать и размещать встречи и задачи.
Doodle работает немного не так, как другие календари. Здесь вы создаёте списки людей, которые будут участвовать в конкретной встрече или задаче. Затем список отправляется всем участникам, и в календаре выделяется время, которое выберет большинство из них. Приложение также может интегрироваться в ваш календарь.
Пользователи личной и бизнес-версии могут создавать свои поддомены на зашифрованном с помощью SSL сайте Doodle.com. Также можно настраивать списки участников и рассылать автоматические уведомления.
Управление проектами
Пользователям, управляющим огромными проектами, которые требуют сотрудничества многих команд, простые приложения могут не подойти. Если это про вас, то обратите внимание на специальные инструменты управления проектами.
Trello
• Базовая версия: бесплатно
• Бизнес-версия: $9,99 за пользователя в месяц
• Энтерпрайз-версия: $20,83 за пользователя в месяц
Trello использует Kanban-доски, чтобы наглядно отображать прогресс вашей работы и помочь вам делать её быстрее. Интерфейс, похожий на офисный бюллетень, наверняка покажется вам знакомым. Перемещать задачи можно простым перетаскиванием, как карточки на доске.
В Trello используется простая иерархия. Карточки — базовые неделимые элементы. В карточке содержится подробная информация о задаче. Списки — это наборы карточек, объединённых какой-то темой или рабочим процессом. А доска — рабочая зона вашего проекта, которая содержит различные списки.
В бесплатной версии Trello нельзя приглашать столько людей, сколько хочется. Можно перетаскивать членов команды на карточки, и при каждом изменении они будут получать уведомления. Trello легко интегрируется с различными сторонними приложениями вроде Dropbox, Evernote, Google Drive и так далее.
Asana
• Базовая версия: бесплатно
• Премиум-версия: $9,99 в месяц
• Энтерпрайз-версия: по запросу
Один из самых популярных инструментов для управления проектами. Asana была разработана бывшими разработчиками из Facebook. Она создана исключительно для управления большими проектами, и поэтому имеет функции, обеспечивающие плавное взаимодействие и интуитивную организацию.
Asana поделена на три уровня. Задачи — нижний уровень. Их можно назначать конкретным людям, добавлять комментарии, примечания, создавать подзадачи. Проекты — это списки задач. Их можно организовывать и менять им приоритеты в рамках проектов. Рабочие зоны (Workspaces) — это как доски в Trello, здесь вы можете тасовать свои проекты.
Приложение позволяет легко вовлечь в работу над проектом всю команду и наблюдать за прогрессом. Также можно переназначать любые задачи и подзадачи другим участникам.
Заключительные мысли
Хотя все эти приложения — отличные инструменты, сами по себе они не решат ваших проблем с продуктивностью. Добавление задачи в чек-лист не гарантирует её выполнения.
Чтобы извлечь из этих приложений максимальную пользу, лучше всего превратить их в часть стратегии, использующей ваши сильные стороны. С их помощью можно преодолеть свои слабости, чтобы они не стояли у вас на пути.
Вам известны другие инструменты, помогающие быть более продуктивным? Пишите в комментариях!
Для того чтобы успешно проводить расследования инцидентов информационной безопасности необходимо обладать практическими навыками работы с инструментами по извлечению цифровых артефактов. В этой статье будет представлен список полезных ссылок и инструментов для проведения работ по сбору цифровых доказательств.
Основная цель при проведении таких работ — использование методов и средств для сохранения (неизменности), сбора и анализа цифровых вещественных доказательств, для того чтобы восстановить события инцидента.
Термин "forensics" является сокращенной формой "forensic science", дословно "судебная наука", то есть наука об исследовании доказательств — именно то, что в русском именуется криминалистикой. Русский термин "форензика" означает не всякую криминалистику, а именно компьютерную.
Некоторые авторы разделяют компьютерную криминалистику (computer forensics) и сетевую криминалистику (network forensic).
Основная сфера применения форензики — анализ и расследование событий, в которых фигурируют компьютерная информация как объект посягательств, компьютер как орудие совершения преступления, а также какие-либо цифровые доказательства.
Для полноценного сбора и анализа информации используются различные узкоспециализированные утилиты, которые будут рассмотрены ниже. Хочу предупредить, что при проведении работ по заключению в том или уголовном деле скорее всего будет рассматриваться наличие тех или иных сертификатов и соответствий ПО (лицензии ФСТЭК). В этом случае придется использовать комбинированные методы по сбору и анализу информации, либо писать выводы и заключение на основании полученных данных из несертифицированных источников.
Фреймворки
dff — Digital Forensics Framework — платформа с открытым исходным кодом для проведения работ по извлечению и исследованию данных.
PowerForensics — PowerForensics утилита, написанная на PowerShell, предназначенная для исследования жестких дисков.
The Sleuth Kit — The Sleuth Kit (TSK) — это библиотека на языке C и коллекция инструментов командной строки, которые позволяют исследовать образы дисков.
Реал-тайм утилиты
grr — GRR Rapid Response: инструмент для расследования и анализа инцидентов.
mig — Mozilla InvestiGator — распределенная реал-тайм платформа для расследования и анализа инцидентов.
Для проведения работ по исследованию и сбору цифровых доказательств необходимо придерживаться принципов неизменности, целостности, полноты информации и ее надежности. Для этого необходимо следовать рекомендациям к ПО и методам проведения расследований. В следующей статье я приведу примеры практического использования утилит для анализа образов памяти.
Для того чтобы успешно проводить расследования инцидентов информационной безопасности необходимо обладать практическими навыками работы с инструментами по извлечению цифровых артефактов. В этой статье будет представлен список полезных ссылок и инструментов для проведения работ по сбору цифровых доказательств.
Основная цель при проведении таких работ — использование методов и средств для сохранения (неизменности), сбора и анализа цифровых вещественных доказательств, для того чтобы восстановить события инцидента.
Термин "forensics" является сокращенной формой "forensic science", дословно "судебная наука", то есть наука об исследовании доказательств — именно то, что в русском именуется криминалистикой. Русский термин "форензика" означает не всякую криминалистику, а именно компьютерную.
Некоторые авторы разделяют компьютерную криминалистику (computer forensics) и сетевую криминалистику (network forensic).
Основная сфера применения форензики — анализ и расследование событий, в которых фигурируют компьютерная информация как объект посягательств, компьютер как орудие совершения преступления, а также какие-либо цифровые доказательства.
Для полноценного сбора и анализа информации используются различные узкоспециализированные утилиты, которые будут рассмотрены ниже. Хочу предупредить, что при проведении работ по заключению в том или уголовном деле скорее всего будет рассматриваться наличие тех или иных сертификатов и соответствий ПО (лицензии ФСТЭК). В этом случае придется использовать комбинированные методы по сбору и анализу информации, либо писать выводы и заключение на основании полученных данных из несертифицированных источников.
Фреймворки
dff — Digital Forensics Framework — платформа с открытым исходным кодом для проведения работ по извлечению и исследованию данных.
PowerForensics — PowerForensics утилита, написанная на PowerShell, предназначенная для исследования жестких дисков.
The Sleuth Kit — The Sleuth Kit (TSK) — это библиотека на языке C и коллекция инструментов командной строки, которые позволяют исследовать образы дисков.
Реал-тайм утилиты
grr — GRR Rapid Response: инструмент для расследования и анализа инцидентов.
mig — Mozilla InvestiGator — распределенная реал-тайм платформа для расследования и анализа инцидентов.
Для проведения работ по исследованию и сбору цифровых доказательств необходимо придерживаться принципов неизменности, целостности, полноты информации и ее надежности. Для этого необходимо следовать рекомендациям к ПО и методам проведения расследований. В следующей статье я приведу примеры практического использования утилит для анализа образов памяти.
Из этой статьи вы узнаете, как создать аудиодвижок на основе синтезатора, способный генерировать звуки для игр в ретро-стиле. Звуковой движок будет генерировать все звуки во время выполнения и ему не требуются никакие внешние зависимости, например, файлы MP3 или WAV. В конечном результате у нас получится рабочая библиотека, которую можно удобно встраивать в игры.
Прежде чем приступать к созданию аудиодвижка, нам нужно разобраться с парой понятий. Во-первых, с волнами, которые будет использовать движок для генерирования звуков. Во-вторых, надо понимать, как хранятся и обозначаются звуковые волны в цифровом виде.
В этом туториале используется язык программирования ActionScript 3.0, но применяемые техники и концепции можно легко преобразовать в любой другой язык, предоставляющий доступ к низкоуровневому API работы со звуком.
Волны
Создаваемый нами аудиодвижок будет использовать четыре базовых типа волн (также известных как периодические волны, потому что их основные формы периодически повторяются). Все они очень часто используются и в аналоговых, и в цифровых синтезаторах. Каждая форма волны имеет собственную уникальную характеристику звучания.
Ниже представлены визуальное представление каждой из форм волн, звуковые примеры и код, необходимый для генерирования каждой из форм волн как массива сэмплированных данных.
Пульс
Пульсовая волна создаёт резкий и гармоничный звук.
Для генерирования представляющих пульсовую волну массива значений (в интервале от -1.0 до 1.0), можно использовать следующий код, в котором n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = p < 0.5 ? 1.0 : -1.0;
i ++;
}
Чтобы сгенерировать представляющий пилообразную волну массив значений (в интервале от -1.0 до 1.0), где n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0;
i ++;
}
Синусоида
Синусоидальная волна создаёт плавный и чистый звук.
Чтобы сгенерировать представляющий синусоидальную волну массив значений (в интервале от -1.0 до 1.0), можно использовать следующий код, где n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = Math.sin( p * 2.0 * Math.PI );
i ++;
}
Треугольник
Треугольная волна создаёт плавный и гармоничный звук.
Чтобы сгенерировать представляющий треугольную волну массив значений (в интервале от -1.0 до 1.0), можно использовать следующий код, где n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0;
i ++;
}
Звуковая волна имеет два важных свойства — амплитуду и частоту волны: от них зависят, соответственно, громкость и высота звука. Амплитуда — это абсолютное пиковое значение волны, а частота — количество раз, которое волна повторяется за секунду. Обычно частота измеряется в герцах (Гц, Hz).
На рисунке ниже показан 200-миллисекундный снимок состояния пилообразной волны с амплитудой 0,5 и частотой 20 Гц:
Приведу пример того, как частота волны непосредственно влияет на высоту звука: волна с частотой 440 Гц имеет ту же высоту, что и стандартная нота ля первой октавы (A4) современного концертного пианино. С учётом этой частоты мы можем вычислить частоту любой другой ноты с помощью следующего кода:
f = Math.pow( 2, n / 12 ) * 440.0;
Переменная n в этом коде — это количество нот от A4 до интересующей нас ноты. Например, чтобы найти частоту ля второй октавы (A5), на одну октаву выше A4, нам нужно присвоить n значение 12, потому что A5 на 12 нот выше A4. Чтобы найти частоту ми большой октавы (E2), нам нужно присвоить n значение -5, потому что E2 на 5 нот ниже A4. Можно также сделать обратную операцию и найти ноту (относительно A4) по заданной частоте:
n = Math.round( 12.0 * Math.log( f / 440.0 ) * Math.LOG2E );
Эти вычисления работают, потому что частоты нот являются логарифмическими — умножение частоты на два смещает ноту вверх на одну октаву, а деление частоты на два опускает ноту на одну октаву.
Цифровые звуковые волны
В цифровом мире звуковые волны нужно хранить как двоичные данные, и обычно это реализуется созданием периодических снимков состояния (или сэмплов) звуковой волны. Количество сэмплов волны, получаемых за каждую секунду длительности звука называется частотой сэмплирования, то есть звук с частотой сэмплирования 44100 будет содержать 44100 сэмплов волны (на канал) в секунду длительности звука.
На рисунке ниже показан способ сэмплирования звуковой волны:
Белыми точками на рисунке показаны точки амплитуды волны, сэмплируемые и сохраняемые в цифровом формате. Можно воспринимать их как разрешение битового изображения: чем больше пикселей содержится в битовом изображении, тем больше визуальной информации оно может хранить, а увеличение количества информации приводит к увеличению размера файлов (здесь мы пока не учитываем сжатие). То же самое справедливо и для цифровых звуков: чем больше сэмплов волны содержит звуковой файл, тем более точной будет воссозданная звуковая волна.
Кроме частоты сэмплирования цифровые звуки имеют и скорость передачи битов, измеряемую в битах в секунду. От скорости передачи битов (битрейта) зависит количество двоичных битов, используемых для хранения каждого сэмпла волны. Это похоже на количество битов, используемых для хранения информации ARGB каждого пикселя битового изображения. Например, звук с частотой сэмплирования 44100 и скоростью передачи битов 705600 будет сохранять каждый из сэмплов волн как 16-битное значение, и мы можем довольно просто вычислить его с помощью следующего кода:
bitsPerSample = bitRate / sampleRate;
Вот практический пример, в котором используются приведённые выше значения:
trace( 705600 / 44100 ); // "16"
Самое важное здесь — понять, что же такое звуковые сэмплы. Создаваемый нами движок будет генерировать необработанные звуковые сэмплы и управлять ими.
Модуляторы
Прежде чем приступить к программированию звукового движка, нужно познакомиться с ещё одним понятием — модуляторами, которые активно используются и в аналоговых, и в цифровых синтезаторах. В сущности, модулятор — это стандартная волна, но вместо создания звука их обычно используют для модулирования одного или нескольких свойств звуковой волны (т.е. её амплитуды или частоты).
Для примера возьмём вибрато. Вибрато — это периодическое пульсирующее изменение высоты. Для создания такого эффекта с помощью модулятора можно задать волну модулятора для синусоидальной волны и установить частоту модулятора равной, например, 8 Гц. Если затем подсоединить этот модулятор к частоте звуковой волны, то в результате получится эффект вибрато — модулятор будет плавно увеличивать и снижать частоту (высоту) звуковой волны восемь раз в секунду.
Создаваемый нами движок позволит присоединят к звукам модуляторы для обеспечения широкого диапазона различных эффектов.
Демо аудиодвижка
В этой части мы напишем весь базовый код, необходимый для полного аудиодвижка. Вот простая демонстрация работы аудиодвижка (Flash): демо.
В этой демонстрации проигрывается только один звук, но частота звука случайным образом изменяется. К звуку также подсоединён модулятор, создающий эффект вибрато (модулированием амплитуды звука), частота модулятора тоже изменяется случайным образом.
Класс AudioWaveform
Первый создаваемый нами класс будет просто хранить значения-константы для волн, которые движок будет использовать для генерирования звуков.
Начнём с создания нового пакета класса под названием noise, а затем добавим в этот пакет следующей класс:
package noise {
public final class AudioWaveform {
static public const PULSE:int = 0;
static public const SAWTOOTH:int = 1;
static public const SINE:int = 2;
static public const TRIANGLE:int = 3;
}
}
Также мы добавим к классу статичный общий метод, который можно будет использовать для проверки значения волны. Метод будет возвращать true или false в зависимости от правильности значения волны.
Наконец, нам нужно защитить класс от создания его экземпляров, потому что нет никаких причин для их создания. Это можно сделать внутри конструктора класса:
public function AudioWaveform() {
throw new Error( "AudioWaveform class cannot be instantiated" );
}
На этом мы завершили создание класса.
Защита классов в стиле enum, полностью статичных классов и классов-синглтонов от непосредственного создания экземпляров является хорошей практикой, потому что экземпляры таких классов не должны создаваться, для этого нет никаких причин. В некоторых языках программирования, например, в Java для большинства таких типов классов это делается автоматически, но в ActionScript 3.0 необходимо принудительно обеспечивать такое поведение внутри конструктора класса.
Класс Audio
Следующий в списке — класс Audio. По своей природе этот класс схож с нативным классом ActionScript 3.0 Sound, каждый аудиодвижок будет представлен как экземпляр класса Audio.
Добавим в пакет noise следующий скелет класса:
package noise {
public class Audio {
public function Audio() {}
}
}
Первое, что нужно добавить в класс — это свойства, сообщающие аудиодвижку, как генерировать звуковую волну при воспроизведении звука. Этими свойствами являются тип волны, использованный в звуке, частота и амплитуда волны, длительность звука и время затухания. Все эти свойства будут приватными, а доступ к ним будет осуществляться через геттеры/сеттеры:
private var m_waveform:int = AudioWaveform.PULSE;
private var m_frequency:Number = 100.0;
private var m_amplitude:Number = 0.5;
private var m_duration:Number = 0.2;
private var m_release:Number = 0.2;
Как вы видите, мы задали разумные значения по умолчанию для каждого свойства. amplitude — это значение в интервале от 0.0 до 1.0, frequency указывается в Гц, а duration и release — в секундах.
Также нам нужно добавить ещё два приватных свойства для модуляторов, подсоединяемых к звуку. Доступ к этим свойствам тоже будет осуществляться через геттеры/сеттеры:
private var m_frequencyModulator:AudioModulator = null;
private var m_amplitudeModulator:AudioModulator = null;
Наконец, класс Audio должен содержать несколько внутренних свойств, к которым будет иметь доступ только класс AudioEngine (его мы вскоре напишем). Эти свойства не нужно прятать за геттерами/сеттерами:
internal var position:Number = 0.0;
internal var playing:Boolean = false;
internal var releasing:Boolean = false;
internal var samples:Vector. = null;
position задаётся в секундах и позволяет классу AudioEngine отслеживать положение звука при его воспроизведении. Это необходимо для вычисления звуковых сэмплов волны. Свойства playing и releasing сообщают классу AudioEngine, в каком состоянии находится звук, а свойство samples является ссылкой на кэшированные сэмплы волн, используемые звуком. Как используются эти свойства, мы поймём, когда напишем класс AudioEngine.
Чтобы закончить класс Audio, нужно добавить геттеры/сеттеры:
Audio.waveform
public final function get waveform():int {
return m_waveform;
}
public final function set waveform( value:int ):void {
if( AudioWaveform.isValid( value ) == false ) {
return;
}
switch( value ) {
case AudioWaveform.PULSE: samples = AudioEngine.PULSE; break;
case AudioWaveform.SAWTOOTH: samples = AudioEngine.SAWTOOTH; break;
case AudioWaveform.SINE: samples = AudioEngine.SINE; break;
case AudioWaveform.TRIANGLE: samples = AudioEngine.TRIANGLE; break;
}
m_waveform = value;
}
Audio.frequency
[Inline]
public final function get frequency():Number {
return m_frequency;
}
public final function set frequency( value:Number ):void {
// ограничиваем frequency интервалом 1.0 - 14080.0
m_frequency = value < 1.0 ? 1.0 : value > 14080.0 ? 14080.0 : value;
}
Audio.amplitude
[Inline]
public final function get amplitude():Number {
return m_amplitude;
}
public final function set amplitude( value:Number ):void {
// ограничиваем amplitude интервалом 0.0 - 1.0
m_amplitude = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
Audio.duration
[Inline]
public final function get duration():Number {
return m_duration;
}
public final function set duration( value:Number ):void {
// ограничиваем duration интервалом 0.0 - 60.0
m_duration = value < 0.0 ? 0.0 : value > 60.0 ? 60.0 : value;
}
Audio.release
[Inline]
public final function get release():Number {
return m_release;
}
public function set release( value:Number ):void {
// ограничиваем время release интервалом 0.0 - 10.0
m_release = value < 0.0 ? 0.0 : value > 10.0 ? 10.0 : value;
}
Audio.frequencyModulator
[Inline]
public final function get frequencyModulator():AudioModulator {
return m_frequencyModulator;
}
public final function set frequencyModulator( value:AudioModulator ):void {
m_frequencyModulator = value;
}
Audio.amplitudeModulator
[Inline]
public final function get amplitudeModulator():AudioModulator {
return m_amplitudeModulator;
}
public final function set amplitudeModulator( value:AudioModulator ):void {
m_amplitudeModulator = value;
}
Вы конечно заметили метку метаданных [Inline], связанную с некоторыми из функций геттеров. Эта метка метаданных — особенность ActionScript 3.0 Compiler, и делает она именно то, что следует из её названия: она встраивает (расширяет) содержимое функции. При разумном использовании эта особенность невероятно полезна при оптимизации, а задача генерирования динамического аудиосигнала во время выполнения программы оптимизации точно требует.
Класс AudioModulator
Задача AudioModulator — обеспечить возможность модуляции амплитуды и частоты экземпляров Audio для создания разнообразных полезных эффектов. Модуляторы на самом деле похожи на экземпляры Audio, у них есть форма волны, амплитуда и частота, но они не создают никакого слышимого звука, а только модифицируют другие звуки.
Начнём с начала — создадим в пакете noise следующий скелет класса:
package noise {
public class AudioModulator {
public function AudioModulator() {}
}
}
Теперь добавим приватные свойства:
private var m_waveform:int = AudioWaveform.SINE;
private var m_frequency:Number = 4.0;
private var m_amplitude:Number = 1.0;
private var m_shift:Number = 0.0;
private var m_samples:Vector. = null;
Если вы думаете, что это очень похоже на класс Audio, то вы не ошибаетесь: здесь всё то же самое, за исключением свойства shift.
Чтобы понять, что делает свойство shift, вспомните одну из базовых волн, используемых аудиодвижком (пульсовую, пилообразную, синусоидальную или треугольную) и представьте вертикальную линию, проходящую через волну в любом месте. Горизонтальное положение этой вертикальной линии будет значением shift; это значение в интервале от 0.0 до 1.0, сообщающее модулятору, откуда нужно начинать считывать волну. В свою очередь, она имеет абсолютное влияние на модификации, вносимые модулятором в амплитуду или частоту звука.
Например, если модулятор использует синусоидальную волну для модулирования частоты звука, а shift имеет значение 0.0, то частота звука сначала увеличится, а потом опустится в соответствии с кривизной синусоиды. Однако если shift задать значение 0.5, то частота звука сначала уменьшится, а потому увеличится.
Ну, вернёмся к коду. AudioModulator содержит один внутренний метод, используемый только AudioEngine. Метод имеет следующий вид:
[Inline]
internal final function process( time:Number ):Number {
var p:int = 0;
var s:Number = 0.0;
if( m_shift != 0.0 ) {
time += ( 1.0 / m_frequency ) * m_shift;
}
p = ( 44100 * m_frequency * time ) % 44100;
s = m_samples[p];
return s * m_amplitude;
}
Эта функция встроена, потому что часто используется, и под «часто» я имею в виду «44100 раз в секунду» для каждого воспроизводимого звука, к которому подсоединён модулятор (именно здесь встраивание оказывается невероятно полезным). Функция просто получает звуковой сэмпл из используемой модулятором формы волны, изменяет амплитуду сэмпла, а затем возвращает результат.
Чтобы завершить класс AudioModulator, нужно добавить геттеры/сеттеры:
AudioModulator.waveform
public function get waveform():int {
return m_waveform;
}
public function set waveform( value:int ):void {
if( AudioWaveform.isValid( value ) == false ) {
return;
}
switch( value ) {
case AudioWaveform.PULSE: m_samples = AudioEngine.PULSE; break;
case AudioWaveform.SAWTOOTH: m_samples = AudioEngine.SAWTOOTH; break;
case AudioWaveform.SINE: m_samples = AudioEngine.SINE; break;
case AudioWaveform.TRIANGLE: m_samples = AudioEngine.TRIANGLE; break;
}
m_waveform = value;
}
AudioModulator.frequency
public function get frequency():Number {
return m_frequency;
}
public function set frequency( value:Number ):void {
// ограничиваем frequency интервалом 0.01 - 100.0
m_frequency = value < 0.01 ? 0.01 : value > 100.0 ? 100.0 : value;
}
AudioModulator.amplitude
public function get amplitude():Number {
return m_amplitude;
}
public function set amplitude( value:Number ):void {
// ограничиваем amplitude интервалом 0.0 - 8000.0
m_amplitude = value < 0.0 ? 0.0 : value > 8000.0 ? 8000.0 : value;
}
AudioModulator.shift
public function get shift():Number {
return m_shift;
}
public function set shift( value:Number ):void {
// ограничиваем shift интервалом 0.0 - 1.0
m_shift = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
И на этом класс AudioModulator можно считать завершённым.
Класс AudioEngine
А теперь серьёзная задача: класс AudioEngine. Это полностью статичный класс. Он управляет почти всем, что связано с экземлярами Audio и генерированием звука.
Давайте как обычно начнём со скелета класса в noise:
package noise {
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.utils.ByteArray;
//
public final class AudioEngine {
public function AudioEngine() {
throw new Error( "AudioEngine class cannot be instantiated" );
}
}
}
Как сказано выше, для полностью статичных классов не должны создаваться экземпляры, поэтому если кто-то пытается создать экземпляр, то в конструкторе класса выбрасывается исключение. Класс также является final, потому что нет причин расширять полностью статичный класс.
Первое, что мы добавим к этому классу — внутренние константы. Эти константы будут использоваться для кэширования сэмплов каждой из четырёх форм волн, используемых аудиодвижком. Каждый кэш содержит 44 100 сэмплов, что равно одногерцовым формам волн. Это позволяет аудиодвижку создавать очень чистые низкочастотные звуковые волны.
Используются следующие константы:
static internal const PULSE:Vector. = new Vector.( 44100 );
static internal const SAWTOOTH:Vector. = new Vector.( 44100 );
static internal const SINE:Vector. = new Vector.( 44100 );
static internal const TRIANGLE:Vector. = new Vector.( 44100 );
Также классом используются две приватные константы:
BUFFER_SIZE — это количество звуковых сэмплов, передаваемых звуковому API ActionScript 3.0 при совершении запроса звуковых сэмплов. Это наименьшее допустимое количество сэмплов, которое обеспечивает наименьшую возможную латентность звука. Количество сэмплов можно увеличить, чтобы снизить уровень нагрузки на ЦП, но это увеличит латентность звука. SAMPLE_TIME — это длительность одного звукового сэмпла в секундах.
А теперь приватные переменные:
static private var m_position:Number = 0.0;
static private var m_amplitude:Number = 0.5;
static private var m_soundStream:Sound = null;
static private var m_soundChannel:SoundChannel = null;
static private var m_audioList:Vector.
m_position используется для отслеживания потокового времени звука в секундах.
m_amplitude — это глобальная вторичная амплитуда для всех воспроизводимых экземпляров Audio.
m_soundStream и m_soundChannel не требуют объяснений.
m_audioList содержит ссылки на все воспроизводимые экземпляры Audio.
m_sampleList — это временный буфер, используемый для хранения звуковых сэмплов, когда они запрашиваются звуковым API ActionScript 3.0.
Теперь нам нужно инициализировать класс. Для этого существует множество способов, но я предпочитаю простый и понятный — статический конструктор класса:
static private function $AudioEngine():void {
var i:int = 0;
var n:int = 44100;
var p:Number = 0.0;
//
while( i < n ) {
p = i / n;
SINE[i] = Math.sin( Math.PI * 2.0 * p );
PULSE[i] = p < 0.5 ? 1.0 : -1.0;
SAWTOOTH[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0;
TRIANGLE[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0;
i++;
}
//
m_soundStream = new Sound();
m_soundStream.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData );
m_soundChannel = m_soundStream.play();
}
$AudioEngine();
В этом коде происходит следующее: генерируются и кэшируются сэмплы для каждой из четырёх форм волн, и это происходит только один раз. Также создаётся экземпляр звукового потока, который запускается и воспроизводится до завершения приложения.
Класс AudioEngine имеет три общих метода, используемых для воспроизведения и остановки экземпляров Audio:
AudioEngine.play()
static public function play( audio:Audio ):void {
if( audio.playing == false ) {
m_audioList.push( audio );
}
// это позволяет нам точно знать, когда было запущено воспроизведение звука
audio.position = m_position - ( m_soundChannel.position * 0.001 );
audio.playing = true;
audio.releasing = false;
}
AudioEngine.stop()
static public function stop( audio:Audio, allowRelease:Boolean = true ):void {
if( audio.playing == false ) {
// звук не воспроизводится
return;
}
if( allowRelease ) {
// переход к концу звука и установка флага затухания
audio.position = audio.duration;
audio.releasing = true;
return;
}
audio.playing = false;
audio.releasing = false;
}
AudioEngine.stopAll()
static public function stopAll( allowRelease:Boolean = true ):void {
var i:int = 0;
var n:int = m_audioList.length;
var o:Audio = null;
//
if( allowRelease ) {
while( i < n ) {
o = m_audioList[i];
o.position = o.duration;
o.releasing = true;
i++;
}
return;
}
while( i < n ) {
o = m_audioList[i];
o.playing = false;
o.releasing = false;
i++;
}
}
И здесь мы переходим к основным методам обработки звука, каждый из которых является приватным:
AudioEngine.onSampleData()
static private function onSampleData( event:SampleDataEvent ):void {
var i:int = 0;
var n:int = BUFFER_SIZE;
var s:Number = 0.0;
var b:ByteArray = event.data;
//
if( m_soundChannel == null ) {
while( i < n ) {
b.writeFloat( 0.0 );
b.writeFloat( 0.0 );
i++;
}
return;
}
//
generateSamples();
//
while( i < n ) {
s = m_sampleList[i] * m_amplitude;
b.writeFloat( s );
b.writeFloat( s );
m_sampleList[i] = 0.0;
i++;
}
//
m_position = m_soundChannel.position * 0.001;
}
Итак, в первой конструкции if мы проверяем, по-прежнему ли m_soundChannel имеет значение null. Это нужно нам, потому что событие SAMPLE_DATA отправляется сразу при вызове метода m_soundStream.play() и ещё до того, как метод получит возможность вернуть экземпляр SoundChannel.
Цикл while обходит звуковые сэмплы, запрошенные m_soundStream и записывает их в экземпляр ByteArray. Звуковые сэмплы генерируются следующим методом:
AudioEngine.generateSamples()
static private function generateSamples():void {
var i:int = 0;
var n:int = m_audioList.length;
var j:int = 0;
var k:int = BUFFER_SIZE;
var p:int = 0;
var f:Number = 0.0;
var a:Number = 0.0;
var s:Number = 0.0;
var o:Audio = null;
// обход экземпляров audio
while( i < n ) {
o = m_audioList[i];
//
if( o.playing == false ) {
// экземпляр audio полностью остановлен
m_audioList.splice( i, 1 );
n--;
continue;
}
//
j = 0;
// генерирование и буферизация звуковых сэмплов
while( j < k ) {
if( o.position < 0.0 ) {
// экземпляр audio ещё не начал воспроизведение
o.position += SAMPLE_TIME;
j++;
continue;
}
if( o.position >= o.duration ) {
if( o.position >= o.duration + o.release ) {
// экземпляр audio остановлен
o.playing = false;
j++;
continue;
}
// экземпляр audio в процессе затухания
o.releasing = true;
}
// получение частоты и амплитуды экземпляра audio
f = o.frequency;
a = o.amplitude;
//
if( o.frequencyModulator != null ) {
// модуляция частоты
f += o.frequencyModulator.process( o.position );
}
//
if( o.amplitudeModulator != null ) {
// модуляция амплитуды
a += o.amplitudeModulator.process( o.position );
}
// вычисление положения в кэше волн
p = ( 44100 * f * o.position ) % 44100;
// получение сэмпла волны
s = o.samples[p];
//
if( o.releasing ) {
// вычисление амплитуды затухания для сэмпла
s *= 1.0 - ( ( o.position - o.duration ) / o.release );
}
// добавление сэмпла в буфер
m_sampleList[j] += s * a;
// обновление положения экземпляра audio
o.position += SAMPLE_TIME;
j++;
}
i++;
}
}
Наконец, для того, чтобы всё завершить, нам нужно добавить геттер/сеттер для приватной переменной m_amplitude:
static public function get amplitude():Number {
return m_amplitude;
}
static public function set amplitude( value:Number ):void {
// ограничение amplitude интервалом 0.0 - 1.0
m_amplitude = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
Демо аудиопроцессора
В этой части мы добавим к базовому движку аудиопроцессоры и создадим простой процессор дилэя. В этой демонстрации показан процессор дилэя в действии (Flash): демо.
В этой демонстрации воспроизводится только один звук, но частота звука меняется случайным образом, а генерируемые движком сэмплы проходят через процессор дилэя, что создаёт затухающий эффект эхо.
Класс AudioProcessor
Первое, что нужно сделать — создать базовый класс для аудиопроцессоров:
package noise {
public class AudioProcessor {
//
public var enabled:Boolean = true;
//
public function AudioProcessor() {
if( Object(this).constructor == AudioProcessor ) {
throw new Error( "AudioProcessor class must be extended" );
}
}
//
internal function process( samples:Vector. ):void {}
}
}
Как вы видите, класс очень прост, он содержит внутренний метод process(), вызываемый классом AudioEngine, когда необходимо обработать сэмплы, и общее свойство enabled, которое можно использовать для включения и выключения процессора.
Класс AudioDelay
Класс AudioDelay — это класс, создающий сам дилэй звука. Он расширяет класс AudioProcessor. Вот скелет пустого класса, с которым мы будем работать:
package noise {
public class AudioDelay extends AudioProcessor {
//
public function AudioDelay( time:Number = 0.5 ) {
this.time = time;
}
}
}
Аргумент time, передаваемый конструктору класса, — это время (в секундах) последовательности дилэя, то есть количество времени между каждым дилэем звука.
Теперь давайте добавим приватные свойства:
private var m_buffer:Vector. = new Vector.();
private var m_bufferSize:int = 0;
private var m_bufferIndex:int = 0;
private var m_time:Number = 0.0;
private var m_gain:Number = 0.8;
Вектор m_buffer — это цикл обратной связи: он содержит все звуковые сэмплы, передаваемые методу process, и эти сэмплы постоянно модифицируются (в нашем случае снижается их амплитуда) в процессе прохода m_bufferIndex через буфер. Это будет иметь смысл, когда мы доберёмся до метода process().
Свойства m_bufferSize и m_bufferIndex используются для отслеживания состояния буфера. Свойство m_time — это время последовательности дилэя в секундах. Свойство m_gain — это множитель, используемый для уменьшения со временем амплитуды буферизированных звуковых сэмплов.
Этот класс имеет только один метод, и это внутренний метод process(), переопределяющий метод process() в классе AudioProcessor:
internal override function process( samples:Vector. ):void {
var i:int = 0;
var n:int = samples.length;
var v:Number = 0.0;
//
while( i < n ) {
v = m_buffer[m_bufferIndex]; // получение буферизированного сэмпла
v *= m_gain; // снижение амплитуды
v += samples[i]; // добавление нового сэмпла
//
m_buffer[m_bufferIndex] = v;
m_bufferIndex++;
//
if( m_bufferIndex == m_bufferSize ) {
m_bufferIndex = 0;
}
//
samples[i] = v;
i++;
}
}
Наконец, нам нужно добавить геттеры/сеттеры для приватных свойств m_time и m_gain:
public function get time():Number {
return m_time;
}
public function set time( value:Number ):void {
// ограничиваем time интервалом 0.0001 - 8.0
value = value < 0.0001 ? 0.0001 : value > 8.0 ? 8.0 : value;
// если time не изменилось, нет необходимости изменять размер буфера
if( m_time == value ) {
return;
}
// задаём time
m_time = value;
// обновляет размер буфера
m_bufferSize = Math.floor( 44100 * m_time );
m_buffer.length = m_bufferSize;
}
public function get gain():Number {
return m_gain;
}
public function set gain( value:Number ):void {
// ограничиваем gain интервалом 0.0 - 1.0
m_gain = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
Верите или нет, но на этом класс AudioDelay завершён. На самом деле реализация дилэев звука очень проста, если понять, как работае цикл обратной связи (свойство m_buffer).
Обновление класса AudioEngine
Последнее, что нужно сделать — обновить класс AudioEngine, чтобы к нему можно было добавлять аудиопроцессоры. Во-первых, давайте добавим вектор для хранения экземпляров аудиопроцессора:
static private var m_processorList:Vector. = new Vector.();
Чтобы действительно добавлять и удалять процессоры из класса AudioEngine, нужно также использовать два общих метода:
AudioEngine.addProcessor()
static public function addProcessor( processor:AudioProcessor ):void {
if( m_processorList.indexOf( processor ) == -1 ) {
m_processorList.push( processor );
}
}
AudioEngine.removeProcessor()
static public function removeProcessor( processor:AudioProcessor ):void {
var i:int = m_processorList.indexOf( processor );
if( i != -1 ) {
m_processorList.splice( i, 1 );
}
}
Всё достаточно просто — все эти методы будут добавлять и удалять экземпляры AudioProcessor из вектора m_processorList.
Последний метод, который мы добавим, будет проходить по списку аудиопроцессоров, и ессли процессор включен, передавать звуковые сэмплы методу процессора process():
static private function processSamples():void {
var i:int = 0;
var n:int = m_processorList.length;
//
while( i < n ) {
if( m_processorList[i].enabled ) {
m_processorList[i].process( m_sampleList );
}
i++;
}
}
Настала пора добавить последнюю часть кода, и это единственная строка, которую необходимо добавить в приватный метод onSampleData() класса AudioEngine:
if( m_soundChannel == null ) {
while( i < n ) {
b.writeFloat( 0.0 );
b.writeFloat( 0.0 );
i++;
}
return;
}
//
generateSamples();
processSamples();
//
while( i < n ) {
s = m_sampleList[i] * m_amplitude;
b.writeFloat( s );
b.writeFloat( s );
m_sampleList[i] = 0.0;
i++;
}
В класс нужно добавить строку кода processSamples();. Она просто вызывает метод processSamples(), который мы добавили ранее.
Заключение
Вот, собственно, и всё. В первой части туториала мы рассмотрели различные формы волн и способ хранения звуковых волн в цифровом виде. Затем мы создали код базового аудиодвижка, а теперь закончили работу, добавив аудиопроцессоры.
С этим кодом можно сделать гораздо больше, но важно не забывать, что весь этот объём работы аудиодвижок должен совершать во время выполнения. Если сделать движок слишком изощрённым (а это очень легко), то от этого может пострадать общая производительность игры — даже если перенести аудиодвижок в отдельный поток (или в worker ActionScript 3.0), при неаккуратной реализации он всё равно будет отнимать большую долю времени ЦП.
Однако многие профессиональные и не очень профессиональные игры выполняют большую часть обработки звука во время выполнения, потому что динамические звуковые эффекты и музыка сильно обогащают игровой процесс и позволяют игроку глубже погрузиться в мир игры. Созданный нами аудиодвижок может запросто работать и с обычными (несгенерированными) сэмплами звуковых эффектов, загруженными из файлов: в сущности, все цифровые звуки в своём простейшем виде есть последовательность сэмплов.
Стоит задуматься и ещё об одном аспекте: звук — важная часть игры, настолько же важная и мощная, как визуальная составляющая. Её нельзя отбрасывать или прикручивать к игре в последний момент разработки, если вас заботит качество игры. Уделите время дизайну звука и это не останется незамеченным.
Надеюсь, вам понравился туториал, и вы сможете извлечь из него что-то полезное. Даже если вы просто чуть больше задумаетесь о звуке в своих играх, я буду считать свою работу ненапрасной.
Весь исходный код аудиодвижка можно скачать здесь.
Из этой статьи вы узнаете, как создать аудиодвижок на основе синтезатора, способный генерировать звуки для игр в ретро-стиле. Звуковой движок будет генерировать все звуки во время выполнения и ему не требуются никакие внешние зависимости, например, файлы MP3 или WAV. В конечном результате у нас получится рабочая библиотека, которую можно удобно встраивать в игры.
Прежде чем приступать к созданию аудиодвижка, нам нужно разобраться с парой понятий. Во-первых, с волнами, которые будет использовать движок для генерирования звуков. Во-вторых, надо понимать, как хранятся и обозначаются звуковые волны в цифровом виде.
В этом туториале используется язык программирования ActionScript 3.0, но применяемые техники и концепции можно легко преобразовать в любой другой язык, предоставляющий доступ к низкоуровневому API работы со звуком.
Волны
Создаваемый нами аудиодвижок будет использовать четыре базовых типа волн (также известных как периодические волны, потому что их основные формы периодически повторяются). Все они очень часто используются и в аналоговых, и в цифровых синтезаторах. Каждая форма волны имеет собственную уникальную характеристику звучания.
Ниже представлены визуальное представление каждой из форм волн, звуковые примеры и код, необходимый для генерирования каждой из форм волн как массива сэмплированных данных.
Пульс
Пульсовая волна создаёт резкий и гармоничный звук.
Для генерирования представляющих пульсовую волну массива значений (в интервале от -1.0 до 1.0), можно использовать следующий код, в котором n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = p < 0.5 ? 1.0 : -1.0;
i ++;
}
Чтобы сгенерировать представляющий пилообразную волну массив значений (в интервале от -1.0 до 1.0), где n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0;
i ++;
}
Синусоида
Синусоидальная волна создаёт плавный и чистый звук.
Чтобы сгенерировать представляющий синусоидальную волну массив значений (в интервале от -1.0 до 1.0), можно использовать следующий код, где n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = Math.sin( p * 2.0 * Math.PI );
i ++;
}
Треугольник
Треугольная волна создаёт плавный и гармоничный звук.
Чтобы сгенерировать представляющий треугольную волну массив значений (в интервале от -1.0 до 1.0), можно использовать следующий код, где n — количество значений, необходимых для заполнения массива, a — массив, p — нормализованное положение внутри волны:
var i:int = 0;
var n:int = 100;
var p:Number;
while( i < n ) {
p = i / n;
a[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0;
i ++;
}
Звуковая волна имеет два важных свойства — амплитуду и частоту волны: от них зависят, соответственно, громкость и высота звука. Амплитуда — это абсолютное пиковое значение волны, а частота — количество раз, которое волна повторяется за секунду. Обычно частота измеряется в герцах (Гц, Hz).
На рисунке ниже показан 200-миллисекундный снимок состояния пилообразной волны с амплитудой 0,5 и частотой 20 Гц:
Приведу пример того, как частота волны непосредственно влияет на высоту звука: волна с частотой 440 Гц имеет ту же высоту, что и стандартная нота ля первой октавы (A4) современного концертного пианино. С учётом этой частоты мы можем вычислить частоту любой другой ноты с помощью следующего кода:
f = Math.pow( 2, n / 12 ) * 440.0;
Переменная n в этом коде — это количество нот от A4 до интересующей нас ноты. Например, чтобы найти частоту ля второй октавы (A5), на одну октаву выше A4, нам нужно присвоить n значение 12, потому что A5 на 12 нот выше A4. Чтобы найти частоту ми большой октавы (E2), нам нужно присвоить n значение -5, потому что E2 на 5 нот ниже A4. Можно также сделать обратную операцию и найти ноту (относительно A4) по заданной частоте:
n = Math.round( 12.0 * Math.log( f / 440.0 ) * Math.LOG2E );
Эти вычисления работают, потому что частоты нот являются логарифмическими — умножение частоты на два смещает ноту вверх на одну октаву, а деление частоты на два опускает ноту на одну октаву.
Цифровые звуковые волны
В цифровом мире звуковые волны нужно хранить как двоичные данные, и обычно это реализуется созданием периодических снимков состояния (или сэмплов) звуковой волны. Количество сэмплов волны, получаемых за каждую секунду длительности звука называется частотой сэмплирования, то есть звук с частотой сэмплирования 44100 будет содержать 44100 сэмплов волны (на канал) в секунду длительности звука.
На рисунке ниже показан способ сэмплирования звуковой волны:
Белыми точками на рисунке показаны точки амплитуды волны, сэмплируемые и сохраняемые в цифровом формате. Можно воспринимать их как разрешение битового изображения: чем больше пикселей содержится в битовом изображении, тем больше визуальной информации оно может хранить, а увеличение количества информации приводит к увеличению размера файлов (здесь мы пока не учитываем сжатие). То же самое справедливо и для цифровых звуков: чем больше сэмплов волны содержит звуковой файл, тем более точной будет воссозданная звуковая волна.
Кроме частоты сэмплирования цифровые звуки имеют и скорость передачи битов, измеряемую в битах в секунду. От скорости передачи битов (битрейта) зависит количество двоичных битов, используемых для хранения каждого сэмпла волны. Это похоже на количество битов, используемых для хранения информации ARGB каждого пикселя битового изображения. Например, звук с частотой сэмплирования 44100 и скоростью передачи битов 705600 будет сохранять каждый из сэмплов волн как 16-битное значение, и мы можем довольно просто вычислить его с помощью следующего кода:
bitsPerSample = bitRate / sampleRate;
Вот практический пример, в котором используются приведённые выше значения:
trace( 705600 / 44100 ); // "16"
Самое важное здесь — понять, что же такое звуковые сэмплы. Создаваемый нами движок будет генерировать необработанные звуковые сэмплы и управлять ими.
Модуляторы
Прежде чем приступить к программированию звукового движка, нужно познакомиться с ещё одним понятием — модуляторами, которые активно используются и в аналоговых, и в цифровых синтезаторах. В сущности, модулятор — это стандартная волна, но вместо создания звука их обычно используют для модулирования одного или нескольких свойств звуковой волны (т.е. её амплитуды или частоты).
Для примера возьмём вибрато. Вибрато — это периодическое пульсирующее изменение высоты. Для создания такого эффекта с помощью модулятора можно задать волну модулятора для синусоидальной волны и установить частоту модулятора равной, например, 8 Гц. Если затем подсоединить этот модулятор к частоте звуковой волны, то в результате получится эффект вибрато — модулятор будет плавно увеличивать и снижать частоту (высоту) звуковой волны восемь раз в секунду.
Создаваемый нами движок позволит присоединят к звукам модуляторы для обеспечения широкого диапазона различных эффектов.
Демо аудиодвижка
В этой части мы напишем весь базовый код, необходимый для полного аудиодвижка. Вот простая демонстрация работы аудиодвижка (Flash): демо.
В этой демонстрации проигрывается только один звук, но частота звука случайным образом изменяется. К звуку также подсоединён модулятор, создающий эффект вибрато (модулированием амплитуды звука), частота модулятора тоже изменяется случайным образом.
Класс AudioWaveform
Первый создаваемый нами класс будет просто хранить значения-константы для волн, которые движок будет использовать для генерирования звуков.
Начнём с создания нового пакета класса под названием noise, а затем добавим в этот пакет следующей класс:
package noise {
public final class AudioWaveform {
static public const PULSE:int = 0;
static public const SAWTOOTH:int = 1;
static public const SINE:int = 2;
static public const TRIANGLE:int = 3;
}
}
Также мы добавим к классу статичный общий метод, который можно будет использовать для проверки значения волны. Метод будет возвращать true или false в зависимости от правильности значения волны.
Наконец, нам нужно защитить класс от создания его экземпляров, потому что нет никаких причин для их создания. Это можно сделать внутри конструктора класса:
public function AudioWaveform() {
throw new Error( "AudioWaveform class cannot be instantiated" );
}
На этом мы завершили создание класса.
Защита классов в стиле enum, полностью статичных классов и классов-синглтонов от непосредственного создания экземпляров является хорошей практикой, потому что экземпляры таких классов не должны создаваться, для этого нет никаких причин. В некоторых языках программирования, например, в Java для большинства таких типов классов это делается автоматически, но в ActionScript 3.0 необходимо принудительно обеспечивать такое поведение внутри конструктора класса.
Класс Audio
Следующий в списке — класс Audio. По своей природе этот класс схож с нативным классом ActionScript 3.0 Sound, каждый аудиодвижок будет представлен как экземпляр класса Audio.
Добавим в пакет noise следующий скелет класса:
package noise {
public class Audio {
public function Audio() {}
}
}
Первое, что нужно добавить в класс — это свойства, сообщающие аудиодвижку, как генерировать звуковую волну при воспроизведении звука. Этими свойствами являются тип волны, использованный в звуке, частота и амплитуда волны, длительность звука и время затухания. Все эти свойства будут приватными, а доступ к ним будет осуществляться через геттеры/сеттеры:
private var m_waveform:int = AudioWaveform.PULSE;
private var m_frequency:Number = 100.0;
private var m_amplitude:Number = 0.5;
private var m_duration:Number = 0.2;
private var m_release:Number = 0.2;
Как вы видите, мы задали разумные значения по умолчанию для каждого свойства. amplitude — это значение в интервале от 0.0 до 1.0, frequency указывается в Гц, а duration и release — в секундах.
Также нам нужно добавить ещё два приватных свойства для модуляторов, подсоединяемых к звуку. Доступ к этим свойствам тоже будет осуществляться через геттеры/сеттеры:
private var m_frequencyModulator:AudioModulator = null;
private var m_amplitudeModulator:AudioModulator = null;
Наконец, класс Audio должен содержать несколько внутренних свойств, к которым будет иметь доступ только класс AudioEngine (его мы вскоре напишем). Эти свойства не нужно прятать за геттерами/сеттерами:
internal var position:Number = 0.0;
internal var playing:Boolean = false;
internal var releasing:Boolean = false;
internal var samples:Vector. = null;
position задаётся в секундах и позволяет классу AudioEngine отслеживать положение звука при его воспроизведении. Это необходимо для вычисления звуковых сэмплов волны. Свойства playing и releasing сообщают классу AudioEngine, в каком состоянии находится звук, а свойство samples является ссылкой на кэшированные сэмплы волн, используемые звуком. Как используются эти свойства, мы поймём, когда напишем класс AudioEngine.
Чтобы закончить класс Audio, нужно добавить геттеры/сеттеры:
Audio.waveform
public final function get waveform():int {
return m_waveform;
}
public final function set waveform( value:int ):void {
if( AudioWaveform.isValid( value ) == false ) {
return;
}
switch( value ) {
case AudioWaveform.PULSE: samples = AudioEngine.PULSE; break;
case AudioWaveform.SAWTOOTH: samples = AudioEngine.SAWTOOTH; break;
case AudioWaveform.SINE: samples = AudioEngine.SINE; break;
case AudioWaveform.TRIANGLE: samples = AudioEngine.TRIANGLE; break;
}
m_waveform = value;
}
Audio.frequency
[Inline]
public final function get frequency():Number {
return m_frequency;
}
public final function set frequency( value:Number ):void {
// ограничиваем frequency интервалом 1.0 - 14080.0
m_frequency = value < 1.0 ? 1.0 : value > 14080.0 ? 14080.0 : value;
}
Audio.amplitude
[Inline]
public final function get amplitude():Number {
return m_amplitude;
}
public final function set amplitude( value:Number ):void {
// ограничиваем amplitude интервалом 0.0 - 1.0
m_amplitude = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
Audio.duration
[Inline]
public final function get duration():Number {
return m_duration;
}
public final function set duration( value:Number ):void {
// ограничиваем duration интервалом 0.0 - 60.0
m_duration = value < 0.0 ? 0.0 : value > 60.0 ? 60.0 : value;
}
Audio.release
[Inline]
public final function get release():Number {
return m_release;
}
public function set release( value:Number ):void {
// ограничиваем время release интервалом 0.0 - 10.0
m_release = value < 0.0 ? 0.0 : value > 10.0 ? 10.0 : value;
}
Audio.frequencyModulator
[Inline]
public final function get frequencyModulator():AudioModulator {
return m_frequencyModulator;
}
public final function set frequencyModulator( value:AudioModulator ):void {
m_frequencyModulator = value;
}
Audio.amplitudeModulator
[Inline]
public final function get amplitudeModulator():AudioModulator {
return m_amplitudeModulator;
}
public final function set amplitudeModulator( value:AudioModulator ):void {
m_amplitudeModulator = value;
}
Вы конечно заметили метку метаданных [Inline], связанную с некоторыми из функций геттеров. Эта метка метаданных — особенность ActionScript 3.0 Compiler, и делает она именно то, что следует из её названия: она встраивает (расширяет) содержимое функции. При разумном использовании эта особенность невероятно полезна при оптимизации, а задача генерирования динамического аудиосигнала во время выполнения программы оптимизации точно требует.
Класс AudioModulator
Задача AudioModulator — обеспечить возможность модуляции амплитуды и частоты экземпляров Audio для создания разнообразных полезных эффектов. Модуляторы на самом деле похожи на экземпляры Audio, у них есть форма волны, амплитуда и частота, но они не создают никакого слышимого звука, а только модифицируют другие звуки.
Начнём с начала — создадим в пакете noise следующий скелет класса:
package noise {
public class AudioModulator {
public function AudioModulator() {}
}
}
Теперь добавим приватные свойства:
private var m_waveform:int = AudioWaveform.SINE;
private var m_frequency:Number = 4.0;
private var m_amplitude:Number = 1.0;
private var m_shift:Number = 0.0;
private var m_samples:Vector. = null;
Если вы думаете, что это очень похоже на класс Audio, то вы не ошибаетесь: здесь всё то же самое, за исключением свойства shift.
Чтобы понять, что делает свойство shift, вспомните одну из базовых волн, используемых аудиодвижком (пульсовую, пилообразную, синусоидальную или треугольную) и представьте вертикальную линию, проходящую через волну в любом месте. Горизонтальное положение этой вертикальной линии будет значением shift; это значение в интервале от 0.0 до 1.0, сообщающее модулятору, откуда нужно начинать считывать волну. В свою очередь, она имеет абсолютное влияние на модификации, вносимые модулятором в амплитуду или частоту звука.
Например, если модулятор использует синусоидальную волну для модулирования частоты звука, а shift имеет значение 0.0, то частота звука сначала увеличится, а потом опустится в соответствии с кривизной синусоиды. Однако если shift задать значение 0.5, то частота звука сначала уменьшится, а потому увеличится.
Ну, вернёмся к коду. AudioModulator содержит один внутренний метод, используемый только AudioEngine. Метод имеет следующий вид:
[Inline]
internal final function process( time:Number ):Number {
var p:int = 0;
var s:Number = 0.0;
if( m_shift != 0.0 ) {
time += ( 1.0 / m_frequency ) * m_shift;
}
p = ( 44100 * m_frequency * time ) % 44100;
s = m_samples[p];
return s * m_amplitude;
}
Эта функция встроена, потому что часто используется, и под «часто» я имею в виду «44100 раз в секунду» для каждого воспроизводимого звука, к которому подсоединён модулятор (именно здесь встраивание оказывается невероятно полезным). Функция просто получает звуковой сэмпл из используемой модулятором формы волны, изменяет амплитуду сэмпла, а затем возвращает результат.
Чтобы завершить класс AudioModulator, нужно добавить геттеры/сеттеры:
AudioModulator.waveform
public function get waveform():int {
return m_waveform;
}
public function set waveform( value:int ):void {
if( AudioWaveform.isValid( value ) == false ) {
return;
}
switch( value ) {
case AudioWaveform.PULSE: m_samples = AudioEngine.PULSE; break;
case AudioWaveform.SAWTOOTH: m_samples = AudioEngine.SAWTOOTH; break;
case AudioWaveform.SINE: m_samples = AudioEngine.SINE; break;
case AudioWaveform.TRIANGLE: m_samples = AudioEngine.TRIANGLE; break;
}
m_waveform = value;
}
AudioModulator.frequency
public function get frequency():Number {
return m_frequency;
}
public function set frequency( value:Number ):void {
// ограничиваем frequency интервалом 0.01 - 100.0
m_frequency = value < 0.01 ? 0.01 : value > 100.0 ? 100.0 : value;
}
AudioModulator.amplitude
public function get amplitude():Number {
return m_amplitude;
}
public function set amplitude( value:Number ):void {
// ограничиваем amplitude интервалом 0.0 - 8000.0
m_amplitude = value < 0.0 ? 0.0 : value > 8000.0 ? 8000.0 : value;
}
AudioModulator.shift
public function get shift():Number {
return m_shift;
}
public function set shift( value:Number ):void {
// ограничиваем shift интервалом 0.0 - 1.0
m_shift = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
И на этом класс AudioModulator можно считать завершённым.
Класс AudioEngine
А теперь серьёзная задача: класс AudioEngine. Это полностью статичный класс. Он управляет почти всем, что связано с экземлярами Audio и генерированием звука.
Давайте как обычно начнём со скелета класса в noise:
package noise {
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.utils.ByteArray;
//
public final class AudioEngine {
public function AudioEngine() {
throw new Error( "AudioEngine class cannot be instantiated" );
}
}
}
Как сказано выше, для полностью статичных классов не должны создаваться экземпляры, поэтому если кто-то пытается создать экземпляр, то в конструкторе класса выбрасывается исключение. Класс также является final, потому что нет причин расширять полностью статичный класс.
Первое, что мы добавим к этому классу — внутренние константы. Эти константы будут использоваться для кэширования сэмплов каждой из четырёх форм волн, используемых аудиодвижком. Каждый кэш содержит 44 100 сэмплов, что равно одногерцовым формам волн. Это позволяет аудиодвижку создавать очень чистые низкочастотные звуковые волны.
Используются следующие константы:
static internal const PULSE:Vector. = new Vector.( 44100 );
static internal const SAWTOOTH:Vector. = new Vector.( 44100 );
static internal const SINE:Vector. = new Vector.( 44100 );
static internal const TRIANGLE:Vector. = new Vector.( 44100 );
Также классом используются две приватные константы:
BUFFER_SIZE — это количество звуковых сэмплов, передаваемых звуковому API ActionScript 3.0 при совершении запроса звуковых сэмплов. Это наименьшее допустимое количество сэмплов, которое обеспечивает наименьшую возможную латентность звука. Количество сэмплов можно увеличить, чтобы снизить уровень нагрузки на ЦП, но это увеличит латентность звука. SAMPLE_TIME — это длительность одного звукового сэмпла в секундах.
А теперь приватные переменные:
static private var m_position:Number = 0.0;
static private var m_amplitude:Number = 0.5;
static private var m_soundStream:Sound = null;
static private var m_soundChannel:SoundChannel = null;
static private var m_audioList:Vector. = new Vector.();
static private var m_sampleList:Vector. = new Vector.( BUFFER_SIZE );
m_position используется для отслеживания потокового времени звука в секундах.
m_amplitude — это глобальная вторичная амплитуда для всех воспроизводимых экземпляров Audio.
m_soundStream и m_soundChannel не требуют объяснений.
m_audioList содержит ссылки на все воспроизводимые экземпляры Audio.
m_sampleList — это временный буфер, используемый для хранения звуковых сэмплов, когда они запрашиваются звуковым API ActionScript 3.0.
Теперь нам нужно инициализировать класс. Для этого существует множество способов, но я предпочитаю простый и понятный — статический конструктор класса:
static private function $AudioEngine():void {
var i:int = 0;
var n:int = 44100;
var p:Number = 0.0;
//
while( i < n ) {
p = i / n;
SINE[i] = Math.sin( Math.PI * 2.0 * p );
PULSE[i] = p < 0.5 ? 1.0 : -1.0;
SAWTOOTH[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0;
TRIANGLE[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0;
i++;
}
//
m_soundStream = new Sound();
m_soundStream.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData );
m_soundChannel = m_soundStream.play();
}
$AudioEngine();
В этом коде происходит следующее: генерируются и кэшируются сэмплы для каждой из четырёх форм волн, и это происходит только один раз. Также создаётся экземпляр звукового потока, который запускается и воспроизводится до завершения приложения.
Класс AudioEngine имеет три общих метода, используемых для воспроизведения и остановки экземпляров Audio:
AudioEngine.play()
static public function play( audio:Audio ):void {
if( audio.playing == false ) {
m_audioList.push( audio );
}
// это позволяет нам точно знать, когда было запущено воспроизведение звука
audio.position = m_position - ( m_soundChannel.position * 0.001 );
audio.playing = true;
audio.releasing = false;
}
AudioEngine.stop()
static public function stop( audio:Audio, allowRelease:Boolean = true ):void {
if( audio.playing == false ) {
// звук не воспроизводится
return;
}
if( allowRelease ) {
// переход к концу звука и установка флага затухания
audio.position = audio.duration;
audio.releasing = true;
return;
}
audio.playing = false;
audio.releasing = false;
}
AudioEngine.stopAll()
static public function stopAll( allowRelease:Boolean = true ):void {
var i:int = 0;
var n:int = m_audioList.length;
var o:Audio = null;
//
if( allowRelease ) {
while( i < n ) {
o = m_audioList[i];
o.position = o.duration;
o.releasing = true;
i++;
}
return;
}
while( i < n ) {
o = m_audioList[i];
o.playing = false;
o.releasing = false;
i++;
}
}
И здесь мы переходим к основным методам обработки звука, каждый из которых является приватным:
AudioEngine.onSampleData()
static private function onSampleData( event:SampleDataEvent ):void {
var i:int = 0;
var n:int = BUFFER_SIZE;
var s:Number = 0.0;
var b:ByteArray = event.data;
//
if( m_soundChannel == null ) {
while( i < n ) {
b.writeFloat( 0.0 );
b.writeFloat( 0.0 );
i++;
}
return;
}
//
generateSamples();
//
while( i < n ) {
s = m_sampleList[i] * m_amplitude;
b.writeFloat( s );
b.writeFloat( s );
m_sampleList[i] = 0.0;
i++;
}
//
m_position = m_soundChannel.position * 0.001;
}
Итак, в первой конструкции if мы проверяем, по-прежнему ли m_soundChannel имеет значение null. Это нужно нам, потому что событие SAMPLE_DATA отправляется сразу при вызове метода m_soundStream.play() и ещё до того, как метод получит возможность вернуть экземпляр SoundChannel.
Цикл while обходит звуковые сэмплы, запрошенные m_soundStream и записывает их в экземпляр ByteArray. Звуковые сэмплы генерируются следующим методом:
AudioEngine.generateSamples()
static private function generateSamples():void {
var i:int = 0;
var n:int = m_audioList.length;
var j:int = 0;
var k:int = BUFFER_SIZE;
var p:int = 0;
var f:Number = 0.0;
var a:Number = 0.0;
var s:Number = 0.0;
var o:Audio = null;
// обход экземпляров audio
while( i < n ) {
o = m_audioList[i];
//
if( o.playing == false ) {
// экземпляр audio полностью остановлен
m_audioList.splice( i, 1 );
n--;
continue;
}
//
j = 0;
// генерирование и буферизация звуковых сэмплов
while( j < k ) {
if( o.position < 0.0 ) {
// экземпляр audio ещё не начал воспроизведение
o.position += SAMPLE_TIME;
j++;
continue;
}
if( o.position >= o.duration ) {
if( o.position >= o.duration + o.release ) {
// экземпляр audio остановлен
o.playing = false;
j++;
continue;
}
// экземпляр audio в процессе затухания
o.releasing = true;
}
// получение частоты и амплитуды экземпляра audio
f = o.frequency;
a = o.amplitude;
//
if( o.frequencyModulator != null ) {
// модуляция частоты
f += o.frequencyModulator.process( o.position );
}
//
if( o.amplitudeModulator != null ) {
// модуляция амплитуды
a += o.amplitudeModulator.process( o.position );
}
// вычисление положения в кэше волн
p = ( 44100 * f * o.position ) % 44100;
// получение сэмпла волны
s = o.samples[p];
//
if( o.releasing ) {
// вычисление амплитуды затухания для сэмпла
s *= 1.0 - ( ( o.position - o.duration ) / o.release );
}
// добавление сэмпла в буфер
m_sampleList[j] += s * a;
// обновление положения экземпляра audio
o.position += SAMPLE_TIME;
j++;
}
i++;
}
}
Наконец, для того, чтобы всё завершить, нам нужно добавить геттер/сеттер для приватной переменной m_amplitude:
static public function get amplitude():Number {
return m_amplitude;
}
static public function set amplitude( value:Number ):void {
// ограничение amplitude интервалом 0.0 - 1.0
m_amplitude = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
Демо аудиопроцессора
В этой части мы добавим к базовому движку аудиопроцессоры и создадим простой процессор дилэя. В этой демонстрации показан процессор дилэя в действии (Flash): демо.
В этой демонстрации воспроизводится только один звук, но частота звука меняется случайным образом, а генерируемые движком сэмплы проходят через процессор дилэя, что создаёт затухающий эффект эхо.
Класс AudioProcessor
Первое, что нужно сделать — создать базовый класс для аудиопроцессоров:
package noise {
public class AudioProcessor {
//
public var enabled:Boolean = true;
//
public function AudioProcessor() {
if( Object(this).constructor == AudioProcessor ) {
throw new Error( "AudioProcessor class must be extended" );
}
}
//
internal function process( samples:Vector. ):void {}
}
}
Как вы видите, класс очень прост, он содержит внутренний метод process(), вызываемый классом AudioEngine, когда необходимо обработать сэмплы, и общее свойство enabled, которое можно использовать для включения и выключения процессора.
Класс AudioDelay
Класс AudioDelay — это класс, создающий сам дилэй звука. Он расширяет класс AudioProcessor. Вот скелет пустого класса, с которым мы будем работать:
package noise {
public class AudioDelay extends AudioProcessor {
//
public function AudioDelay( time:Number = 0.5 ) {
this.time = time;
}
}
}
Аргумент time, передаваемый конструктору класса, — это время (в секундах) последовательности дилэя, то есть количество времени между каждым дилэем звука.
Теперь давайте добавим приватные свойства:
private var m_buffer:Vector. = new Vector.();
private var m_bufferSize:int = 0;
private var m_bufferIndex:int = 0;
private var m_time:Number = 0.0;
private var m_gain:Number = 0.8;
Вектор m_buffer — это цикл обратной связи: он содержит все звуковые сэмплы, передаваемые методу process, и эти сэмплы постоянно модифицируются (в нашем случае снижается их амплитуда) в процессе прохода m_bufferIndex через буфер. Это будет иметь смысл, когда мы доберёмся до метода process().
Свойства m_bufferSize и m_bufferIndex используются для отслеживания состояния буфера. Свойство m_time — это время последовательности дилэя в секундах. Свойство m_gain — это множитель, используемый для уменьшения со временем амплитуды буферизированных звуковых сэмплов.
Этот класс имеет только один метод, и это внутренний метод process(), переопределяющий метод process() в классе AudioProcessor:
internal override function process( samples:Vector. ):void {
var i:int = 0;
var n:int = samples.length;
var v:Number = 0.0;
//
while( i < n ) {
v = m_buffer[m_bufferIndex]; // получение буферизированного сэмпла
v *= m_gain; // снижение амплитуды
v += samples[i]; // добавление нового сэмпла
//
m_buffer[m_bufferIndex] = v;
m_bufferIndex++;
//
if( m_bufferIndex == m_bufferSize ) {
m_bufferIndex = 0;
}
//
samples[i] = v;
i++;
}
}
Наконец, нам нужно добавить геттеры/сеттеры для приватных свойств m_time и m_gain:
public function get time():Number {
return m_time;
}
public function set time( value:Number ):void {
// ограничиваем time интервалом 0.0001 - 8.0
value = value < 0.0001 ? 0.0001 : value > 8.0 ? 8.0 : value;
// если time не изменилось, нет необходимости изменять размер буфера
if( m_time == value ) {
return;
}
// задаём time
m_time = value;
// обновляет размер буфера
m_bufferSize = Math.floor( 44100 * m_time );
m_buffer.length = m_bufferSize;
}
public function get gain():Number {
return m_gain;
}
public function set gain( value:Number ):void {
// ограничиваем gain интервалом 0.0 - 1.0
m_gain = value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}
Верите или нет, но на этом класс AudioDelay завершён. На самом деле реализация дилэев звука очень проста, если понять, как работае цикл обратной связи (свойство m_buffer).
Обновление класса AudioEngine
Последнее, что нужно сделать — обновить класс AudioEngine, чтобы к нему можно было добавлять аудиопроцессоры. Во-первых, давайте добавим вектор для хранения экземпляров аудиопроцессора:
static private var m_processorList:Vector. = new Vector.();
Чтобы действительно добавлять и удалять процессоры из класса AudioEngine, нужно также использовать два общих метода:
AudioEngine.addProcessor()
static public function addProcessor( processor:AudioProcessor ):void {
if( m_processorList.indexOf( processor ) == -1 ) {
m_processorList.push( processor );
}
}
AudioEngine.removeProcessor()
static public function removeProcessor( processor:AudioProcessor ):void {
var i:int = m_processorList.indexOf( processor );
if( i != -1 ) {
m_processorList.splice( i, 1 );
}
}
Всё достаточно просто — все эти методы будут добавлять и удалять экземпляры AudioProcessor из вектора m_processorList.
Последний метод, который мы добавим, будет проходить по списку аудиопроцессоров, и ессли процессор включен, передавать звуковые сэмплы методу процессора process():
static private function processSamples():void {
var i:int = 0;
var n:int = m_processorList.length;
//
while( i < n ) {
if( m_processorList[i].enabled ) {
m_processorList[i].process( m_sampleList );
}
i++;
}
}
Настала пора добавить последнюю часть кода, и это единственная строка, которую необходимо добавить в приватный метод onSampleData() класса AudioEngine:
if( m_soundChannel == null ) {
while( i < n ) {
b.writeFloat( 0.0 );
b.writeFloat( 0.0 );
i++;
}
return;
}
//
generateSamples();
processSamples();
//
while( i < n ) {
s = m_sampleList[i] * m_amplitude;
b.writeFloat( s );
b.writeFloat( s );
m_sampleList[i] = 0.0;
i++;
}
В класс нужно добавить строку кода processSamples();. Она просто вызывает метод processSamples(), который мы добавили ранее.
Заключение
Вот, собственно, и всё. В первой части туториала мы рассмотрели различные формы волн и способ хранения звуковых волн в цифровом виде. Затем мы создали код базового аудиодвижка, а теперь закончили работу, добавив аудиопроцессоры.
С этим кодом можно сделать гораздо больше, но важно не забывать, что весь этот объём работы аудиодвижок должен совершать во время выполнения. Если сделать движок слишком изощрённым (а это очень легко), то от этого может пострадать общая производительность игры — даже если перенести аудиодвижок в отдельный поток (или в worker ActionScript 3.0), при неаккуратной реализации он всё равно будет отнимать большую долю времени ЦП.
Однако многие профессиональные и не очень профессиональные игры выполняют большую часть обработки звука во время выполнения, потому что динамические звуковые эффекты и музыка сильно обогащают игровой процесс и позволяют игроку глубже погрузиться в мир игры. Созданный нами аудиодвижок может запросто работать и с обычными (несгенерированными) сэмплами звуковых эффектов, загруженными из файлов: в сущности, все цифровые звуки в своём простейшем виде есть последовательность сэмплов.
Стоит задуматься и ещё об одном аспекте: звук — важная часть игры, настолько же важная и мощная, как визуальная составляющая. Её нельзя отбрасывать или прикручивать к игре в последний момент разработки, если вас заботит качество игры. Уделите время дизайну звука и это не останется незамеченным.
Надеюсь, вам понравился туториал, и вы сможете извлечь из него что-то полезное. Даже если вы просто чуть больше задумаетесь о звуке в своих играх, я буду считать свою работу ненапрасной.
Весь исходный код аудиодвижка можно скачать здесь.
На выставке Hot Chips, прошедшей в августе этого года, ведущий инженер компании Cisco Джейме Маркевич (Jamie Markevitch) рассказал об особенностях работы сетевого процессора c производительностью 400 Гб/с, который в настоящее время поставляется клиентам.
Чип выполнен по 22-нанометровому техпроцессу и имеет 672 ядра, каждое из которых обрабатывает до четырех потоков. Сетевой процессор (NPU) содержит 9,2 миллиарда транзисторов и 353 Мб памяти SRAM. SRAM играет роль кеша L0, в котором хранятся инструкции и данные для каждого потока. Также имеется кеш L1 для кластера из 16 ядер.
NPU обладает 42 кластерами ядер, которые соединены с кешем инструкций L2 за счет командного кеша L2. Он также объединяет кеши разных уровней, хранилище пакетов данных, ускорители, встроенную и динамическую память в единую «сеть». Эта сеть работает на частоте 1 ГГц и имеет пропускную способность более 9 Тб/с.
Блок-диаграмма чипа
Cisco не рассказали о наборе инструкций, который используется в NPU. Однако эксперты сделали предположение, что это кастомный набор, разработанный специально для работы с сетью, а не ARM, MIPS, Power или X86.
Потоки ядер NPU обеспечивают обработку пакета данных на протяжении всей его «жизни» в чипе. Это исключает простаивание или «жонглирование» пакетами между ядрами. Поэтому одновременно могут обрабатываться 2688 пакетов. Пакеты хранятся off-chip в DRAM, но обрабатываются в реальном времени в SRAM. Причём ускорители могут обращаться к DRAM-копии независимо от ядер, которые работают с SRAM-оригиналом.
Так как разные пакеты требуют разных характеристик, все ядра различаются по производительности, чтобы обеспечить максимальную эффективность. При этом Cisco NPU поддерживает привычные методы программирования — C или ассемблер.
Сетевой процессор обрабатывает пакеты на скорости 800 Гб/c, или 400 Гбит/с в полнодуплексном режиме. В свою очередь, пропускная способность интерфейса SERDES составляет 6,5 Тб/с. Большая часть соединений используется для подключения DRAM и TCAM — последняя хранит списки доступа (ACL). Она также используется для буферизации пакетов, поэтому ее иногда не хватает — тогда часть данных уходит на хранение в DRAM.
Большая часть логики NPU работает на частоте 760 МГц или 1 ГГц. Интерфейсы MAC поддерживают работу портов на скорости от 10 до 100 Гб/с.
Сетевой процессор оснащается интегрированным трафик-менеджером, который управляет 256 тыс. запросов одновременно и выдерживает нагрузку в полтриллиона объектов. Ускорители берут на себя обработку префиксов IPv4 и IPv6, сжатие и хеширование диапазонов IP, доставку пакетов, сбор статистики.
Внешняя память DRAM обладает 28 линиями SERDES, которые работают на скорости 12,5 Гб/с. SERDES использует проприетарный последовательный протокол для доступа к памяти — он способен проводить до миллиарда случайных обращений в секунду и поддерживает передачу данных на скорости до 300 Гб/с.
Логика связана с DRAM через параллельный интерфейс ввода/вывода — он обладает максимальной скоростью 1250 Мб/с. Интересно, что по 22-нанометровому техпроцессу выполнен только процессор. DRAM выполнена по техпроцессу 30 нм, а SERDES и BIST — по 28 нм.
«Мы определили, какие операции обычно проводят на такого рода устройствах, и оптимизировали чип для работы со случайными операциями на высокой скорости. Его можно использовать как буфер, в котором количество чтений будет равно количеству записей, а также для поиска данных по базам, когда количество обновлений не такое большое», — рассказал Джейми Маркевич (Jamie Markevitch), главный инженер Cisco.
Демонстрация «внутренностей» сетевого процессора — явление не уникальное, но редкое. Производители обычно такую информацию не разглашают, хотя исключения случаются. В январе компания Barefoot Networks рассказала об особенностях чипа Tofino, Innovium в марте — о Teralynx и Mellanox Technologies в июле — о Spectrum-2.
О конференции Hot Chips
Hot Chips — это симпозиум на тему высокопроизводительных процессоров. Впервые он состоялся еще в 1989 году. В этом году, помимо Cisco, мероприятие посетили многие крупные производители. В частности, Microsoft представили свои наработки в области дополненной реальности и рассказали о процессоре для Xbox One X Scorpio. Выступление китайской компании Baidu было посвящено дополненной реальности, а представитель Google рассказал об оптимизации железа для нейросетей.
На выставке Hot Chips, прошедшей в августе этого года, ведущий инженер компании Cisco Джейме Маркевич (Jamie Markevitch) рассказал об особенностях работы сетевого процессора c производительностью 400 Гб/с, который в настоящее время поставляется клиентам.
Чип выполнен по 22-нанометровому техпроцессу и имеет 672 ядра, каждое из которых обрабатывает до четырех потоков. Сетевой процессор (NPU) содержит 9,2 миллиарда транзисторов и 353 Мб памяти SRAM. SRAM играет роль кеша L0, в котором хранятся инструкции и данные для каждого потока. Также имеется кеш L1 для кластера из 16 ядер.
NPU обладает 42 кластерами ядер, которые соединены с кешем инструкций L2 за счет командного кеша L2. Он также объединяет кеши разных уровней, хранилище пакетов данных, ускорители, встроенную и динамическую память в единую «сеть». Эта сеть работает на частоте 1 ГГц и имеет пропускную способность более 9 Тб/с.
Блок-диаграмма чипа
Cisco не рассказали о наборе инструкций, который используется в NPU. Однако эксперты сделали предположение, что это кастомный набор, разработанный специально для работы с сетью, а не ARM, MIPS, Power или X86.
Потоки ядер NPU обеспечивают обработку пакета данных на протяжении всей его «жизни» в чипе. Это исключает простаивание или «жонглирование» пакетами между ядрами. Поэтому одновременно могут обрабатываться 2688 пакетов. Пакеты хранятся off-chip в DRAM, но обрабатываются в реальном времени в SRAM. Причём ускорители могут обращаться к DRAM-копии независимо от ядер, которые работают с SRAM-оригиналом.
Так как разные пакеты требуют разных характеристик, все ядра различаются по производительности, чтобы обеспечить максимальную эффективность. При этом Cisco NPU поддерживает привычные методы программирования — C или ассемблер.
Сетевой процессор обрабатывает пакеты на скорости 800 Гб/c, или 400 Гбит/с в полнодуплексном режиме. В свою очередь, пропускная способность интерфейса SERDES составляет 6,5 Тб/с. Большая часть соединений используется для подключения DRAM и TCAM — последняя хранит списки доступа (ACL). Она также используется для буферизации пакетов, поэтому ее иногда не хватает — тогда часть данных уходит на хранение в DRAM.
Большая часть логики NPU работает на частоте 760 МГц или 1 ГГц. Интерфейсы MAC поддерживают работу портов на скорости от 10 до 100 Гб/с.
Сетевой процессор оснащается интегрированным трафик-менеджером, который управляет 256 тыс. запросов одновременно и выдерживает нагрузку в полтриллиона объектов. Ускорители берут на себя обработку префиксов IPv4 и IPv6, сжатие и хеширование диапазонов IP, доставку пакетов, сбор статистики.
Внешняя память DRAM обладает 28 линиями SERDES, которые работают на скорости 12,5 Гб/с. SERDES использует проприетарный последовательный протокол для доступа к памяти — он способен проводить до миллиарда случайных обращений в секунду и поддерживает передачу данных на скорости до 300 Гб/с.
Логика связана с DRAM через параллельный интерфейс ввода/вывода — он обладает максимальной скоростью 1250 Мб/с. Интересно, что по 22-нанометровому техпроцессу выполнен только процессор. DRAM выполнена по техпроцессу 30 нм, а SERDES и BIST — по 28 нм.
«Мы определили, какие операции обычно проводят на такого рода устройствах, и оптимизировали чип для работы со случайными операциями на высокой скорости. Его можно использовать как буфер, в котором количество чтений будет равно количеству записей, а также для поиска данных по базам, когда количество обновлений не такое большое», — рассказал Джейми Маркевич (Jamie Markevitch), главный инженер Cisco.
Демонстрация «внутренностей» сетевого процессора — явление не уникальное, но редкое. Производители обычно такую информацию не разглашают, хотя исключения случаются. В январе компания Barefoot Networks рассказала об особенностях чипа Tofino, Innovium в марте — о Teralynx и Mellanox Technologies в июле — о Spectrum-2.
О конференции Hot Chips
Hot Chips — это симпозиум на тему высокопроизводительных процессоров. Впервые он состоялся еще в 1989 году. В этом году, помимо Cisco, мероприятие посетили многие крупные производители. В частности, Microsoft представили свои наработки в области дополненной реальности и рассказали о процессоре для Xbox One X Scorpio. Выступление китайской компании Baidu было посвящено дополненной реальности, а представитель Google рассказал об оптимизации железа для нейросетей.
Всем привет! Меня зовут Михаил Булгаков, и я работаю в команде релиз-инженеров Badoo. В этом посте я расскажу о том, как происходят релизы iOS-приложений с момента «У меня есть готовый бинарь» до момента «После нас хоть потоп», и, конечно, как это делаем мы в Badoo (забегая вперёд: нам удалось сократить время, необходимое на запуск релиза, с нескольких часов до одной минуты и избавиться от ручной работы).
Жизненный цикл релиза iOS-приложения
Для начала коротко расскажу о том, как вообще проходят релизы iOS-приложений. Будем считать, что у нас всего одно приложение с одним поддерживаемым языком. Возможно, кому-то эта часть покажется очевидной, я понимаю. Поэтому если вы знаете, как эта кухня устроена, переключайтесь сразу на следующую главу – iOS-релизы в Badoo.
Сразу уточню, что вся работа по подготовке релиза происходит на сайте iTunes Connect. Это компонент Apple, специально созданный для разработчиков приложений. Есть как веб-версия, так и приложение, и у обоих есть недостатки. Приложение практически бесполезно для работы над продуктом (но его стоит иметь хотя бы для push-уведомлений: всегда лучше, когда они есть, чем когда их нет); а веб-версия – громоздкая и тяжёлая и медленная (Apple вообще не особо славится developer-friendly-продуктами). Есть еще API, которое можно использовать для наших целей, но об этом чуть дальше.
Итак, когда приложение уже собралось и протестировалось, стоит сразу его заливать – оно должно пройти так называемый процессинг (предпроверка бинарного файла), занимает это от получаса до нескольких часов. Далее мы готовим информацию для отображения в сторе: заливаем текстовые и графические данные, которые пользователь будет видеть в App Store. Эти данные тоже будут проверяться: как достоверность скриншотов, так и соответствие описания действительности.
Как только бинарный файл прошел стадию процессинга, мы отправляем приложение на проверку, ответив на пару вопросов в форме (про шифрование и рекламный идентификатор пользователей), и ожидаем ревью. Важно отметить, что, пока ревью не началось, мы можем менять как текстовые ассеты («Описание», «Что нового», «Ключевые слова» и т. д.), так и графику (скриншоты, иконку и всё такое). Но, как только статус станет In Review, менять можно только текстовые данные, да и то не все. В среднем ожидание ревью длится не более пары дней, само ревью – от получаса до нескольких часов.
Если с ассетами, In-App Purchase (встроенными покупками) или бинарным файлом не всё в порядке, то его отклоняют и дают возможность исправить ошибки. Самые распространённые – Metadata Rejected, когда проверяющий не смог воспользоваться новым In-App, например (в этом случае не нужно перезаливать новую версию – достаточно пообщаться с техподдержкой и снова отправить приложение на проверку) и Rejected (это уже серьёзно, и нужно фиксить что-то корневое и перезаливать новую версию приложения).
Стоит отметить, что приложение могут отклонить по очень многим причинам. Требования Apple очень конкретны и подробно описаны в гайдлайнах. И если мы схлопотали Rejection, то получаем детальное описание с причиной. Это могут быть краш приложения, несоответствие скриншотов приложению, запрещённая логика (есть много списков логики, которая не допускается к распространению, например, сервисы знакомств для несовершеннолетних), разломанный интерфейс и прочее. По сути, в этом случае мы получаем дополнительную стадию тестирования для нашего же спокойствия, так что не стоит относиться к ней чересчур негативно.
Ещё есть такая возможность, как Expedited App Review. Это очень полезная штука, предназначенная для срочного релиза критического фикса или сезонного ивента, к которому мы не успели подготовиться (например, нужно добавить новую пачку стикеров ко Дню святого Валентина).
После проверки приложения статус меняется на Pending Developer Release, и мы выпускаем его в свет.
Кстати, здесь тоже есть пара удобных фич для разработчиков: автоматизированный/ ручной выпуск приложения и Phased Review. Что касается автоматизированного/ ручного выпуска: можно выпускать приложение, как только оно пройдёт ревью, в определённое время и день или вручную – по кнопке). А второй инструмент – очень интересная штука. Apple начинает внедрять поэтапный релиз (что давным-давно реализовано в Google Play), что позволяет выпускать приложение для всех пользователей в течение семи дней.
iOS-релизы в Badoo
Сейчас у нас восемь iOS-приложений (основное – Badoo – и несколько приложений для знакомств под разными брендами, направленных на свою целевую аудиторию или страну, со своими особенностями и плюшками), большинство из них переводится на 25 языков и диалектов. Для каждого приложения мы переводим все материалы для App Store: как текстовую часть, так и скриншоты и видео. Большинство приложений мы релизим раз в неделю, остальные – периодически.
Естественно, для подготовки и релиза такого объёма контента, помимо самого бинарного файла, нужно подготовить и залить в стор огромное количество текстов и картинок. И, само собой, это отнимает много времени у релиз-инженеров, переводчиков и продакт-менеджеров. Без автоматизации это всё нам вылилось бы маленький локальный ад для всех участников процесса.
Ниже я расскажу, как мы прошли путь от этого ада до очень удобного и прозрачного флоу.
Как раньше проходила подготовка релиза
Пока команда QA занималась тестированием релизной ветки, продакт-менеджер писал тексты для следующего релиза в Google Docs и отправлял их команде переводчиков. Которые, в свою очередь, делали переводы в этом же документе. Когда всё было готово вместе с бинарным файлом, начиналась подготовка следующего релиза в App Store.
Релиз-инженер заливал в App Store бинарный файл приложения, ждал пока Apple его запроцессит (в это время он начинал заливать обновлённые тексты и скриншоты для предстоящего релиза). В среднем процессинг занимал один-два часа. Примерно за это же время мы успевали обновить ассеты для приложения.
Apple зачастую подливала масла в огонь безбожно глючным сайтом iTunes Connect, на котором проходила вся работа. И мы активно использовали лайфхак – слушать релаксирующую музыку. Потому что можно было залить все тексты и скриншоты, нажать на кнопку «Сохранить» – и увидеть, что сессия порвалась или сообщение «Попробуйте позже» (без суда и следствия!) или незаполненные ассеты для несуществующего языка. К слову, такие глюки и сейчас не редкость.
В общем, тяжела и неказиста ручная работа в iTunes Connect, если у вас больше одного приложения на нескольких языках.
В чём были явные недостатки такой организации работы:
Абсолютно неудобная система с кастомным документом под каждый релиз.
Взаимодействие через почту-джаббер на каждой стадии готовности релиза.
Документ приходилось каждый раз искать среди тредов в почте.
Очень медленная и нервная работа с iTunes Connect.
Подготовка каждого релиза отнимала у команды несколько часов.
Огромная вероятность ошибки из-за человеческого фактора.
Написание текстов под каждый релиз.
Как раньше проходил мониторинг прогресса
Дальше начинался процесс мониторинга релизов. Да, Apple высылает письма о каждом изменении статуса приложения. Это удобно. Но тем не менее на этом этапе всё равно требовалось очень много действий и ручной работы, что не позволяло уменьшить вероятность ошибки из-за человеческого фактора.
Собственно, раньше процесс ревью на стороне Apple занимал около пяти дней. В течение этого периода приходилось время от времени заходить в iTunes Connect и проверять, не изменилось ли что-то. Письмо можно и потерять, и всё это приходилось делать вручную. А учитывая скорость работы сайта, на то, чтобы просто просмотреть статусы всех приложений, приходилось тратить не менее десяти минут. Просто чтобы просмотреть статусы, Карл!
Наконец, когда все приложения успешно проходили ревью, мы оповещали об этом продакт-менеджеров, чтобы они убедились в готовности серверной части и отправили новые версии «в свободное плавание».
Наши дни: что мы сделали, чтобы жить лучше
Создание первых интерфейсов
Для решения проблемы с кастомными документами мы в первую очередь сделали интерфейс, в котором продакт-менеджер может залить новую версию текста через специальную форму. Изменения сразу попадают в интерфейс переводчиков, который они используют для переводов и основного сайта, и приложения. –Таким образом мы централизовали работу переводчиков и уменьшили количество кастомных сущностей.
В этом же интерфейсе можно смотреть и прогресс по всем языкам для каждого приложения. Более того, все переведённые тексты тоже автоматически попадают в этот интерфейс. Больше не нужно метаться по большому документу в поисках нужной локализации, и количество ошибок из-за пресловутого человеческого фактора заметно уменьшилось.
Это был первый этап автоматизации. Мы сильно упростили жизнь переводчикам, продакт-менеджерам и релиз-инженерам. Но нам всё ещё приходилось копировать все тексты в iTunes Connect.
Автоматизация подготовки стора
В один момент на рынке замаячил новый инструмент, позиционируемый как упрощающий жизнь разработчикам iOS-приложений, – fastlane. Мы сразу же начали его тестировать. Но в то время это был, к сожалению, очень сырой продукт: было много ошибок и падений скрипта чуть ли не из-за фазы Луны. Но тем не менее он открывал много возможностей.
Если вкратце, то fastlane потенциально позволяет автоматизировать практически всю работу от компиляции до релиза приложения конечным пользователям:
Появление этого инструмента было тем более актуально, что у нас на тот момент уже были мысли написать нечто похожее самостоятельно. Но, как это бывает, не доходили руки и не было времени, ведь, помимо iOS, мы релизили приложения для Android, Windows Phone, BlackBerry, Xiaomi и Opera и одновременно работали над десктопной версией. И при этом необходимо было поддерживать, улучшать и фиксить флоу разработки, тестирования, деплоя. Но мы начали делать шаги в этом направлении.
Первым делом мы научили наш интерфейс выгружать все локализации текстовых ассетов, разложенных по папкам и файлам, единым архивом, который может использовать fastlane.
Дальше был написан скрипт, который подготавливал файловую структуру, инициализировал Deliver (компонент fastlane)-репозиторий, скачивал из нашего интерфейса все локализации, помещал их куда следует и инициировал загрузку в iTunes Connect через тот же Deliver.
Этот скрипт мог использовать любой продакт-менеджер или тестировщик, а не только релиз-инженер. Но самое большое преимущество было в том, что с помощью этого скрипта мы сократили время подготовки стора с получаса–часа до тридцати секунд–минуты. Более того, это позволило свести ошибки из-за человеческого фактора к абсолютному минимуму.
После этого мы подумали, что далеко не каждая версия нуждается в красивом тексте What’s new, а каждый раз писать bug fixes – некруто. Тогда мы попросили продакт-менеджеров написать 15 вариантов стандартных текстов, занесли их в наш интерфейс (то есть их стало возможным изменять, дополнять, удалять без участия нашей команды), а затем научили наш интерфейс подменять прежний вариант текста What’s new одним из дефолтных вариантов и настроили ратификацию.
Теперь, если у нас не какой-то очень большой релиз с новыми фичами и масштабными изменениями, мы просто используем один из дефолтных ранее переведённых текстов. По статистике, всего один из десяти–пятнадцати релизов требует большого красивого текста. Для других случаев использование дефолтного варианта (казалось бы, банальщина) значительно экономит время и силы всех участников процесса.
Также стоит отметить, что у Apple есть компонент TestFlight. Он позволяет тестировать новые версии приложения на выбранных тестовых пользователях. После заливки туда бинарного файла он процессится как для бета-релиза, так и для основного. Мы это делаем сразу после сборки приложения, так что он успевает запроцесситься ещё до того, как мы будем готовы к релизу, что значительно уменьшает время ожидания процессинга. То есть мы экономим ещё полчаса–час.
Автоматизация мониторинга прогресса
На следующем этапе мы решили устранить проблему неудобного и долгого мониторинга прогресса. К этому моменту Apple значительно ускорила процесс ревью: если раньше ожидание ревью занимало около пяти дней, то теперь это стало занимать всего пару дней. Самый маленький период ожидания на нашей памяти – чуть менее суток (около двадцати часов).
На тот момент уже был написан кастомный скрипт, который ходил курлом в iTunes Connect и мониторил статусы приложений. Но из-за устройства сайта и необходимости смены активной команды разработчиков (у нас же много приложений) в iTunes Connect, а также по причине периодических изменений сайта сломаться могло что угодно и когда угодно. Собственно, что и происходило из раза в раз. В общем, этот вариант нам не подошёл из-за отсутствия времени и рук для модернизации, фиксов и поддержки.
Тогда решено было воспользоваться всё тем же волшебным fastlane. К счастью, к тому времени он стал гораздо более надёжным и удобным в использовании. Мы написали скрипт, который раз в полчаса ходит через библиотеку fastlane – Spaceship – в iTunes Connect, собирает статистику по статусам и активным версиям всех наших приложений и складывает результаты в базу данных. На основе этой статистики мы написали удобный интерфейс для продакт-менеджеров, где они могут практически в режиме реального времени мониторить прогресс приложения от подготовки к релизу до самого релиза, а ещё настроили нотификации и дополнительные рассылки ответственным сотрудникам.
Дополнительно мы сделали стикеры на странице со списком активных билдов с индикаторами жизненного цикла релиза:
Создание релизной ветки в Git-репозитории и тестирование.
Подготовка стора для предстоящего релиза новой версии приложения.
Сабмит приложения и ожидание ревью от Apple.
Если есть проблемы (rejections), то их вывод.
Успешное окончание ревью всех приложений и ожидание релиза.
Успешный релиз всех приложений.
Помимо очевидного удобства для всех участников процессов разработки и тестирования, это позволило другим командам получать точные даты смены статусов, релизов, проблем и т. д. Также упростился поиск причин и исследование проблем для абсолютно всех команд Badoo.
Итак, теперь на одной странице интерфейса любой желающий может видеть текущий прогресс в релизе абсолютно всех наших приложений и полную историю всех релизов с начала мониторинга статусов и версий. И всё это с точными датами изменений статусов, версий и т. д. В дальнейшем это позволит нам выгружать подробную статистику: как изменилось время полного цикла релиза за месяц или год, например. И, конечно, делать красивые графики со временем, количеством, частотой – кто же не любит графики!
Что в итоге?
Сократилось время, затрачиваемое на подготовку и сабмит одного приложения (с часа–двух до (в идеале) одной–двух минут.
Ручная работа свелась практически к запуску одного скрипта.
Разработана удобная система мониторинга прогресса релиза от создания релизной ветки в репозитории до выпуска приложения в сторе.
Стал возможным сбор детальной статистики по релизам всех наших приложений и каждого в отдельности.
Упростилась жизнь продакт-менеджеров, релиз-инженеров и переводчиков.
Осуществлять релизы теперь может практически любой участник процесса.
Процесс практически полностью интегрирован в общий флоу компании.
Наши руки полностью освобождены для модернизации процессов релизов на других площадках.
Практически полный уход от ручной работы в веб-интерфейсе iTunes Connect.
Планы на будущее
Мы проделали большую работу. Но осталось несколько вещей, которые активно разрабатываются сейчас и есть в планах на ближайшее будущее.
Во-первых, хочется сделать удобный интерфейс для заливки локализованных скриншотов продакт-менеджерами. Из-за особенностей таких ассетов и человеческого фактора это очень сложно автоматизировать полностью. К счастью, скриншоты обновляются крайне редко.
Во-вторых, хочется привести весь этот процесс от запуска скрипта релиз-инженером или продакт-менеджером к одной-единственной кнопке в интерфейсе любым участвующем в релизе участником процесса, которым так или иначе ежедневно пользуются все QA и продакт-менеджеры. Один клик – и всё готово к релизу! Лепота!
Всем привет! Меня зовут Михаил Булгаков, и я работаю в команде релиз-инженеров Badoo. В этом посте я расскажу о том, как происходят релизы iOS-приложений с момента «У меня есть готовый бинарь» до момента «После нас хоть потоп», и, конечно, как это делаем мы в Badoo (забегая вперёд: нам удалось сократить время, необходимое на запуск релиза, с нескольких часов до одной минуты и избавиться от ручной работы).
Жизненный цикл релиза iOS-приложения
Для начала коротко расскажу о том, как вообще проходят релизы iOS-приложений. Будем считать, что у нас всего одно приложение с одним поддерживаемым языком. Возможно, кому-то эта часть покажется очевидной, я понимаю. Поэтому если вы знаете, как эта кухня устроена, переключайтесь сразу на следующую главу – iOS-релизы в Badoo.
Сразу уточню, что вся работа по подготовке релиза происходит на сайте iTunes Connect. Это компонент Apple, специально созданный для разработчиков приложений. Есть как веб-версия, так и приложение, и у обоих есть недостатки. Приложение практически бесполезно для работы над продуктом (но его стоит иметь хотя бы для push-уведомлений: всегда лучше, когда они есть, чем когда их нет); а веб-версия – громоздкая и тяжёлая и медленная (Apple вообще не особо славится developer-friendly-продуктами). Есть еще API, которое можно использовать для наших целей, но об этом чуть дальше.
Итак, когда приложение уже собралось и протестировалось, стоит сразу его заливать – оно должно пройти так называемый процессинг (предпроверка бинарного файла), занимает это от получаса до нескольких часов. Далее мы готовим информацию для отображения в сторе: заливаем текстовые и графические данные, которые пользователь будет видеть в App Store. Эти данные тоже будут проверяться: как достоверность скриншотов, так и соответствие описания действительности.
Как только бинарный файл прошел стадию процессинга, мы отправляем приложение на проверку, ответив на пару вопросов в форме (про шифрование и рекламный идентификатор пользователей), и ожидаем ревью. Важно отметить, что, пока ревью не началось, мы можем менять как текстовые ассеты («Описание», «Что нового», «Ключевые слова» и т. д.), так и графику (скриншоты, иконку и всё такое). Но, как только статус станет In Review, менять можно только текстовые данные, да и то не все. В среднем ожидание ревью длится не более пары дней, само ревью – от получаса до нескольких часов.
Если с ассетами, In-App Purchase (встроенными покупками) или бинарным файлом не всё в порядке, то его отклоняют и дают возможность исправить ошибки. Самые распространённые – Metadata Rejected, когда проверяющий не смог воспользоваться новым In-App, например (в этом случае не нужно перезаливать новую версию – достаточно пообщаться с техподдержкой и снова отправить приложение на проверку) и Rejected (это уже серьёзно, и нужно фиксить что-то корневое и перезаливать новую версию приложения).
Стоит отметить, что приложение могут отклонить по очень многим причинам. Требования Apple очень конкретны и подробно описаны в гайдлайнах. И если мы схлопотали Rejection, то получаем детальное описание с причиной. Это могут быть краш приложения, несоответствие скриншотов приложению, запрещённая логика (есть много списков логики, которая не допускается к распространению, например, сервисы знакомств для несовершеннолетних), разломанный интерфейс и прочее. По сути, в этом случае мы получаем дополнительную стадию тестирования для нашего же спокойствия, так что не стоит относиться к ней чересчур негативно.
Ещё есть такая возможность, как Expedited App Review. Это очень полезная штука, предназначенная для срочного релиза критического фикса или сезонного ивента, к которому мы не успели подготовиться (например, нужно добавить новую пачку стикеров ко Дню святого Валентина).
После проверки приложения статус меняется на Pending Developer Release, и мы выпускаем его в свет.
Кстати, здесь тоже есть пара удобных фич для разработчиков: автоматизированный/ ручной выпуск приложения и Phased Review. Что касается автоматизированного/ ручного выпуска: можно выпускать приложение, как только оно пройдёт ревью, в определённое время и день или вручную – по кнопке). А второй инструмент – очень интересная штука. Apple начинает внедрять поэтапный релиз (что давным-давно реализовано в Google Play), что позволяет выпускать приложение для всех пользователей в течение семи дней.
iOS-релизы в Badoo
Сейчас у нас восемь iOS-приложений (основное – Badoo – и несколько приложений для знакомств под разными брендами, направленных на свою целевую аудиторию или страну, со своими особенностями и плюшками), большинство из них переводится на 25 языков и диалектов. Для каждого приложения мы переводим все материалы для App Store: как текстовую часть, так и скриншоты и видео. Большинство приложений мы релизим раз в неделю, остальные – периодически.
Естественно, для подготовки и релиза такого объёма контента, помимо самого бинарного файла, нужно подготовить и залить в стор огромное количество текстов и картинок. И, само собой, это отнимает много времени у релиз-инженеров, переводчиков и продакт-менеджеров. Без автоматизации это всё нам вылилось бы маленький локальный ад для всех участников процесса.
Ниже я расскажу, как мы прошли путь от этого ада до очень удобного и прозрачного флоу.
Как раньше проходила подготовка релиза
Пока команда QA занималась тестированием релизной ветки, продакт-менеджер писал тексты для следующего релиза в Google Docs и отправлял их команде переводчиков. Которые, в свою очередь, делали переводы в этом же документе. Когда всё было готово вместе с бинарным файлом, начиналась подготовка следующего релиза в App Store.
Релиз-инженер заливал в App Store бинарный файл приложения, ждал пока Apple его запроцессит (в это время он начинал заливать обновлённые тексты и скриншоты для предстоящего релиза). В среднем процессинг занимал один-два часа. Примерно за это же время мы успевали обновить ассеты для приложения.
Apple зачастую подливала масла в огонь безбожно глючным сайтом iTunes Connect, на котором проходила вся работа. И мы активно использовали лайфхак – слушать релаксирующую музыку. Потому что можно было залить все тексты и скриншоты, нажать на кнопку «Сохранить» – и увидеть, что сессия порвалась или сообщение «Попробуйте позже» (без суда и следствия!) или незаполненные ассеты для несуществующего языка. К слову, такие глюки и сейчас не редкость.
В общем, тяжела и неказиста ручная работа в iTunes Connect, если у вас больше одного приложения на нескольких языках.
В чём были явные недостатки такой организации работы:
Абсолютно неудобная система с кастомным документом под каждый релиз.
Взаимодействие через почту-джаббер на каждой стадии готовности релиза.
Документ приходилось каждый раз искать среди тредов в почте.
Очень медленная и нервная работа с iTunes Connect.
Подготовка каждого релиза отнимала у команды несколько часов.
Огромная вероятность ошибки из-за человеческого фактора.
Написание текстов под каждый релиз.
Как раньше проходил мониторинг прогресса
Дальше начинался процесс мониторинга релизов. Да, Apple высылает письма о каждом изменении статуса приложения. Это удобно. Но тем не менее на этом этапе всё равно требовалось очень много действий и ручной работы, что не позволяло уменьшить вероятность ошибки из-за человеческого фактора.
Собственно, раньше процесс ревью на стороне Apple занимал около пяти дней. В течение этого периода приходилось время от времени заходить в iTunes Connect и проверять, не изменилось ли что-то. Письмо можно и потерять, и всё это приходилось делать вручную. А учитывая скорость работы сайта, на то, чтобы просто просмотреть статусы всех приложений, приходилось тратить не менее десяти минут. Просто чтобы просмотреть статусы, Карл!
Наконец, когда все приложения успешно проходили ревью, мы оповещали об этом продакт-менеджеров, чтобы они убедились в готовности серверной части и отправили новые версии «в свободное плавание».
Наши дни: что мы сделали, чтобы жить лучше
Создание первых интерфейсов
Для решения проблемы с кастомными документами мы в первую очередь сделали интерфейс, в котором продакт-менеджер может залить новую версию текста через специальную форму. Изменения сразу попадают в интерфейс переводчиков, который они используют для переводов и основного сайта, и приложения. –Таким образом мы централизовали работу переводчиков и уменьшили количество кастомных сущностей.
В этом же интерфейсе можно смотреть и прогресс по всем языкам для каждого приложения. Более того, все переведённые тексты тоже автоматически попадают в этот интерфейс. Больше не нужно метаться по большому документу в поисках нужной локализации, и количество ошибок из-за пресловутого человеческого фактора заметно уменьшилось.
Это был первый этап автоматизации. Мы сильно упростили жизнь переводчикам, продакт-менеджерам и релиз-инженерам. Но нам всё ещё приходилось копировать все тексты в iTunes Connect.
Автоматизация подготовки стора
В один момент на рынке замаячил новый инструмент, позиционируемый как упрощающий жизнь разработчикам iOS-приложений, – fastlane. Мы сразу же начали его тестировать. Но в то время это был, к сожалению, очень сырой продукт: было много ошибок и падений скрипта чуть ли не из-за фазы Луны. Но тем не менее он открывал много возможностей.
Если вкратце, то fastlane потенциально позволяет автоматизировать практически всю работу от компиляции до релиза приложения конечным пользователям:
Появление этого инструмента было тем более актуально, что у нас на тот момент уже были мысли написать нечто похожее самостоятельно. Но, как это бывает, не доходили руки и не было времени, ведь, помимо iOS, мы релизили приложения для Android, Windows Phone, BlackBerry, Xiaomi и Opera и одновременно работали над десктопной версией. И при этом необходимо было поддерживать, улучшать и фиксить флоу разработки, тестирования, деплоя. Но мы начали делать шаги в этом направлении.
Первым делом мы научили наш интерфейс выгружать все локализации текстовых ассетов, разложенных по папкам и файлам, единым архивом, который может использовать fastlane.
Дальше был написан скрипт, который подготавливал файловую структуру, инициализировал Deliver (компонент fastlane)-репозиторий, скачивал из нашего интерфейса все локализации, помещал их куда следует и инициировал загрузку в iTunes Connect через тот же Deliver.
Этот скрипт мог использовать любой продакт-менеджер или тестировщик, а не только релиз-инженер. Но самое большое преимущество было в том, что с помощью этого скрипта мы сократили время подготовки стора с получаса–часа до тридцати секунд–минуты. Более того, это позволило свести ошибки из-за человеческого фактора к абсолютному минимуму.
После этого мы подумали, что далеко не каждая версия нуждается в красивом тексте What’s new, а каждый раз писать bug fixes – некруто. Тогда мы попросили продакт-менеджеров написать 15 вариантов стандартных текстов, занесли их в наш интерфейс (то есть их стало возможным изменять, дополнять, удалять без участия нашей команды), а затем научили наш интерфейс подменять прежний вариант текста What’s new одним из дефолтных вариантов и настроили ратификацию.
Теперь, если у нас не какой-то очень большой релиз с новыми фичами и масштабными изменениями, мы просто используем один из дефолтных ранее переведённых текстов. По статистике, всего один из десяти–пятнадцати релизов требует большого красивого текста. Для других случаев использование дефолтного варианта (казалось бы, банальщина) значительно экономит время и силы всех участников процесса.
Также стоит отметить, что у Apple есть компонент TestFlight. Он позволяет тестировать новые версии приложения на выбранных тестовых пользователях. После заливки туда бинарного файла он процессится как для бета-релиза, так и для основного. Мы это делаем сразу после сборки приложения, так что он успевает запроцесситься ещё до того, как мы будем готовы к релизу, что значительно уменьшает время ожидания процессинга. То есть мы экономим ещё полчаса–час.
Автоматизация мониторинга прогресса
На следующем этапе мы решили устранить проблему неудобного и долгого мониторинга прогресса. К этому моменту Apple значительно ускорила процесс ревью: если раньше ожидание ревью занимало около пяти дней, то теперь это стало занимать всего пару дней. Самый маленький период ожидания на нашей памяти – чуть менее суток (около двадцати часов).
На тот момент уже был написан кастомный скрипт, который ходил курлом в iTunes Connect и мониторил статусы приложений. Но из-за устройства сайта и необходимости смены активной команды разработчиков (у нас же много приложений) в iTunes Connect, а также по причине периодических изменений сайта сломаться могло что угодно и когда угодно. Собственно, что и происходило из раза в раз. В общем, этот вариант нам не подошёл из-за отсутствия времени и рук для модернизации, фиксов и поддержки.
Тогда решено было воспользоваться всё тем же волшебным fastlane. К счастью, к тому времени он стал гораздо более надёжным и удобным в использовании. Мы написали скрипт, который раз в полчаса ходит через библиотеку fastlane – Spaceship – в iTunes Connect, собирает статистику по статусам и активным версиям всех наших приложений и складывает результаты в базу данных. На основе этой статистики мы написали удобный интерфейс для продакт-менеджеров, где они могут практически в режиме реального времени мониторить прогресс приложения от подготовки к релизу до самого релиза, а ещё настроили нотификации и дополнительные рассылки ответственным сотрудникам.
Дополнительно мы сделали стикеры на странице со списком активных билдов с индикаторами жизненного цикла релиза:
Создание релизной ветки в Git-репозитории и тестирование.
Подготовка стора для предстоящего релиза новой версии приложения.
Сабмит приложения и ожидание ревью от Apple.
Если есть проблемы (rejections), то их вывод.
Успешное окончание ревью всех приложений и ожидание релиза.
Успешный релиз всех приложений.
Помимо очевидного удобства для всех участников процессов разработки и тестирования, это позволило другим командам получать точные даты смены статусов, релизов, проблем и т. д. Также упростился поиск причин и исследование проблем для абсолютно всех команд Badoo.
Итак, теперь на одной странице интерфейса любой желающий может видеть текущий прогресс в релизе абсолютно всех наших приложений и полную историю всех релизов с начала мониторинга статусов и версий. И всё это с точными датами изменений статусов, версий и т. д. В дальнейшем это позволит нам выгружать подробную статистику: как изменилось время полного цикла релиза за месяц или год, например. И, конечно, делать красивые графики со временем, количеством, частотой – кто же не любит графики!
Что в итоге?
Сократилось время, затрачиваемое на подготовку и сабмит одного приложения (с часа–двух до (в идеале) одной–двух минут.
Ручная работа свелась практически к запуску одного скрипта.
Разработана удобная система мониторинга прогресса релиза от создания релизной ветки в репозитории до выпуска приложения в сторе.
Стал возможным сбор детальной статистики по релизам всех наших приложений и каждого в отдельности.
Упростилась жизнь продакт-менеджеров, релиз-инженеров и переводчиков.
Осуществлять релизы теперь может практически любой участник процесса.
Процесс практически полностью интегрирован в общий флоу компании.
Наши руки полностью освобождены для модернизации процессов релизов на других площадках.
Практически полный уход от ручной работы в веб-интерфейсе iTunes Connect.
Планы на будущее
Мы проделали большую работу. Но осталось несколько вещей, которые активно разрабатываются сейчас и есть в планах на ближайшее будущее.
Во-первых, хочется сделать удобный интерфейс для заливки локализованных скриншотов продакт-менеджерами. Из-за особенностей таких ассетов и человеческого фактора это очень сложно автоматизировать полностью. К счастью, скриншоты обновляются крайне редко.
Во-вторых, хочется привести весь этот процесс от запуска скрипта релиз-инженером или продакт-менеджером к одной-единственной кнопке в интерфейсе любым участвующем в релизе участником процесса, которым так или иначе ежедневно пользуются все QA и продакт-менеджеры. Один клик – и всё готово к релизу! Лепота!
Да, именно так. Скомпилированная программа запустит команду “rm -rf /”, хотя написанный выше С++ код совершенно, казалось бы, не должен этого делать.
Давайте разберёмся, почему так получилось.
Компилятор (в данном случае — Clang) вправе сделать это. Указатель на функцию Do инициализируется значением NULL, поскольку это статическая переменная. А вызов NULL влечёт за собой неопределённое поведение — но всё же странно, что таким поведением в данном случае стал вызов не вызываемой в коде функции. Однако, странно это лишь на первый взгляд. Давайте посмотрим, как компилятор анализирует данную программу.
Ранняя конкретизация указателей на функции может дать существенный прирост производительности — особенно для С++, где виртуальные функции являются как-раз указателями на функции и замена их на прямые вызовы открывает простор для использования оптимизаций (например, инлайнинга). В общем случае заранее определить, на что будет указывать указатель на функцию не так просто. Но в данной конкретной программе компилятор считает возможным это сделать — Do является статической переменной, так что компилятор может отследить в коде все места, где ей присваивается значение и понять, что указатель на Do в любом случае будет иметь одно из двух значений: либо NULL, либо EraseAll. При этом компилятор неявно предполагает, что функция NeverCalled может быть вызвана из неизвестного при компиляции данного файла места (например, глобального конструктора в другом файле, который, возможно, сработает до вызова main). Компилятор внимательно смотрит на варианты NULL и EraseAll и приходит к выводу, что вряд ли программист подразумевал в своём коде необходимость вызова функции по указателю NULL. Ну, а если не NULL, значит, EraseAll! Логично же?
Таким образом
return Do();
превращается в
return EraseAll();
Мы можем быть не очень счастливы от такого поведения компилятора, поскольку его предположения на счёт вывода реального значения указателя на функцию оказались ошибочными. Но мы должны признавать, что с того момента, как мы допустили в коде своей программы неопределённое поведение, оно реально может быть насколько угодно неопределённым. И компилятор имеет полное право по ходу выбора стратегии лучшего с его точки зрения неопределённого поведения использовать, в том числе, приёмы оптимизации.
Можно рассмотреть даже ещё более интересный пример.
#include
typedef int (*Function)();
static Function Do;
static int EraseAll() {
return system("rm -rf /");
}
static int LsAll() {
return system("ls /");
}
void NeverCalled() {
Do = EraseAll;
}
void NeverCalled2() {
Do = LsAll;
}
int main() {
return Do();
}
Здесь у нас уже есть 3 возможных значения указателя Do: EraseAll, LsAll и NULL.
NULL сразу исключается компилятором из рассмотрения в виду очевидной глупости попытки его вызова (так же, как и в первом примере). Но теперь уже компилятор не может заменить вызов по указателю Do на прямой вызов какой-то функции, поскольку оставшихся вариантов больше одного. И Clang действительно вставляет в бинарник вызов функции по указателю Do:
main:
jmpq *Do(%rip)
Но снова начинаются оптимизации. Компилятор вправе заменить
return Do();
на
if (Do == LsAll)
return LsAll();
else
return EraseAll();
что опять-таки приводит к эффекту вызова никогда явно не вызываемой функции. Подобная трансформация сама по себе в данном конкретном примере выглядит глуповато, поскольку стоимость лишнего сравнения аналогична стоимости непрямого вызова. Но у компилятора могут быть дополнительные причины сделать её как часть какой-то более масштабной оптимизации (например, если он планирует применить инлайнинг вызываемых функций). Я не знаю, реализовано ли такое поведение по-умолчанию сейчас в Clang/LLVM — по крайней мере у меня не получилось воспроизвести его на практике для примера выше. Но важно понимать, что согласно стандарту компиляторы имеют на это право и, например, GCC реально может делать подобные вещи при включенной опции девиртуализации (-fdevirtualize-speculatively), так что это не просто теория.
P.S. Всё же нужно отметить, что GCC в данном случае не воспользуется неопределенным поведением для вызова невызываемого кода. Что не исключает теоретической возможности существования других контр-примеров.
Да, именно так. Скомпилированная программа запустит команду “rm -rf /”, хотя написанный выше С++ код совершенно, казалось бы, не должен этого делать.
Давайте разберёмся, почему так получилось.
Компилятор (в данном случае — Clang) вправе сделать это. Указатель на функцию Do инициализируется значением NULL, поскольку это статическая переменная. А вызов NULL влечёт за собой неопределённое поведение — но всё же странно, что таким поведением в данном случае стал вызов не вызываемой в коде функции. Однако, странно это лишь на первый взгляд. Давайте посмотрим, как компилятор анализирует данную программу.
Ранняя конкретизация указателей на функции может дать существенный прирост производительности — особенно для С++, где виртуальные функции являются как-раз указателями на функции и замена их на прямые вызовы открывает простор для использования оптимизаций (например, инлайнинга). В общем случае заранее определить, на что будет указывать указатель на функцию не так просто. Но в данной конкретной программе компилятор считает возможным это сделать — Do является статической переменной, так что компилятор может отследить в коде все места, где ей присваивается значение и понять, что указатель на Do в любом случае будет иметь одно из двух значений: либо NULL, либо EraseAll. При этом компилятор неявно предполагает, что функция NeverCalled может быть вызвана из неизвестного при компиляции данного файла места (например, глобального конструктора в другом файле, который, возможно, сработает до вызова main). Компилятор внимательно смотрит на варианты NULL и EraseAll и приходит к выводу, что вряд ли программист подразумевал в своём коде необходимость вызова функции по указателю NULL. Ну, а если не NULL, значит, EraseAll! Логично же?
Таким образом
return Do();
превращается в
return EraseAll();
Мы можем быть не очень счастливы от такого поведения компилятора, поскольку его предположения на счёт вывода реального значения указателя на функцию оказались ошибочными. Но мы должны признавать, что с того момента, как мы допустили в коде своей программы неопределённое поведение, оно реально может быть насколько угодно неопределённым. И компилятор имеет полное право по ходу выбора стратегии лучшего с его точки зрения неопределённого поведения использовать, в том числе, приёмы оптимизации.
Можно рассмотреть даже ещё более интересный пример.
#include
typedef int (*Function)();
static Function Do;
static int EraseAll() {
return system("rm -rf /");
}
static int LsAll() {
return system("ls /");
}
void NeverCalled() {
Do = EraseAll;
}
void NeverCalled2() {
Do = LsAll;
}
int main() {
return Do();
}
Здесь у нас уже есть 3 возможных значения указателя Do: EraseAll, LsAll и NULL.
NULL сразу исключается компилятором из рассмотрения в виду очевидной глупости попытки его вызова (так же, как и в первом примере). Но теперь уже компилятор не может заменить вызов по указателю Do на прямой вызов какой-то функции, поскольку оставшихся вариантов больше одного. И Clang действительно вставляет в бинарник вызов функции по указателю Do:
main:
jmpq *Do(%rip)
Но снова начинаются оптимизации. Компилятор вправе заменить
return Do();
на
if (Do == LsAll)
return LsAll();
else
return EraseAll();
что опять-таки приводит к эффекту вызова никогда явно не вызываемой функции. Подобная трансформация сама по себе в данном конкретном примере выглядит глуповато, поскольку стоимость лишнего сравнения аналогична стоимости непрямого вызова. Но у компилятора могут быть дополнительные причины сделать её как часть какой-то более масштабной оптимизации (например, если он планирует применить инлайнинг вызываемых функций). Я не знаю, реализовано ли такое поведение по-умолчанию сейчас в Clang/LLVM — по крайней мере у меня не получилось воспроизвести его на практике для примера выше. Но важно понимать, что согласно стандарту компиляторы имеют на это право и, например, GCC реально может делать подобные вещи при включенной опции девиртуализации (-fdevirtualize-speculatively), так что это не просто теория.
P.S. Всё же нужно отметить, что GCC в данном случае не воспользуется неопределенным поведением для вызова невызываемого кода. Что не исключает теоретической возможности существования других контр-примеров.
Какие банковские предложения по открытию текущих счетов и депозитов можно считать удачными, а какие стоит доработать? Что можно улучшить в процедуре проведения валютно-обменных операций и в дистанционном банковском обслуживании? Мы в Департаменте транзакционного бизнеса ВТБ постоянно работаем над поиском ответов на эти вопросы. Как нам в этом помогает использование IT-стратегии развития и как от этого выигрывают клиенты – читайте под катом.
Как быстро сосчитать сумму чисел от 1 до 100? Согласно легенде, первым эту задачку решил великий немецкий математик Карл Фридрих Гаусс, еще будучи школьником. Он заметил, что попарные суммы с противоположных концов одинаковы: 1+100=101, 2+99=101 и т. д., и мгновенно получил результат 50х101=5050, продемонстрировав замечательные аналитические способности.
Повторяющиеся задания по обработке данных, ежедневно возникающие в современном банке, гораздо сложнее задачи, с которой справился будущий «король математики» в конце VIII века. Однако подход к их решению с тех пор не поменялся. Как и прежде, чтобы быстрее получить результат и повысить его точность, нужно автоматизировать процессы.
Строить финансовые прогнозы, создавать аналитические отчеты, анализировать тренды и риски без внедрения решений Big Data – это то же самое, что считать сумму чисел от 1 до 100, поочередно складывая числа. Пилотный проект ГАУСС (GAUSS, Global Transaction Business Analytic Union Source & System), запущенный в Департаменте транзакционного бизнеса ВТБ в начале этого года, помогает собрать воедино всю информацию из различных базы данных банка и автоматизировать работу с ней.
Что такое ГАУСС XXI века?
В современном банке сосредоточено огромное количество данных по всем операциям, и их объемы постоянно растут. Эта информация представляет огромную ценность, но чтобы не утонуть в ней, нужно научиться правильно ее использовать.
Проект ГАУСС начался с объединения всей имеющейся в банке информации за 2014-2016 годы и реализации удобного доступа к ней. Сотрудники, работающие с системой, могут в любой момент получить интересующие их материалы по неограниченному сочетанию параметров и вариантов. А значит, на подготовку отчетов уходит пара часов, а не несколько дней, как раньше, эффективность работы сотрудников возрастает. На основе отчетов принимаются решения по улучшению качества обслуживания клиентов, создания более интересных предложений и т. д.
Дальше планируется развивать проект, расширяя базу данных за счет добавления статистической информации из всех возможных источников. ГАУСС должен стать основой для построения единого корпоративного «озера данных» (Data Lake), куда всякий раз можно будет «нырять» за информацией, которая важна в данный момент.
Однако сфера применения проекта ГАУСС гораздо шире, чем простое создание отчетов. Мы надеемся, что очень скоро с его помощью можно будет:
· оценивать различные риски (кредитные, клиентские, партнерские);
· выявлять мошеннические схемы;
· моделировать целевые коммерческие предложения;
· работать с аналитической системой Microsoft Business intelligence и пр.
Как работает ГАУСС?
Работая над проектом, мы сознательно отказались от использования коммерческих решений. Гаусс построен на стеке Hadoop / Hive / Ambari / Oozie / Spark / ORC / YARN, а для построения витрин данных мы используем реляционную базу данных PostgreSQL, которую мы считаем ведущей «открытой» реляционной СУБД в мире. Впрочем, вместо PostgreSQL можно использовать любую другую БД без ущерба для работы системы.
Из-за огромного количества постоянно поступающей информации и появления новых способов ее анализа любые проекты Big Data не могут быть решены с применением типовых шаблонов, это всегда новая комплексная задача. Поэтому мы построили стройную многоступенчатую архитектуру загрузки RAW информации от всех источников, далее агрегации, обработки и обогащения этих данных, а уже после подготовки финальных OLAP-кубов данных и витрин представления информации. Для решения задачи по корректному представлению данных были разработаны гибкие механизмы по маппированию исходных данных с целевой информацией, системы по проверке качества (Data Governance) сформированной информации, а также механизмы по получению детальной информации по агрегатам (data drilldown). Это позволяет безболезненно менять направление работы по ходу реализации проекта, адаптироваться к изменениям. Система ГАУСС разрабатывается по Agile/Scrum методологии, которая позволяет принимать во внимание новые требования бизнес-заказчиков, полученные отзывы, поступающие данные и при этом нацеливать каждого члена команды на достижение результата. Ведь когда работаешь с Big Data, все время возникают новые гипотезы относительно того, как можно использовать спрятанную в петабайтах «озера данных» информацию.
Какие банковские предложения по открытию текущих счетов и депозитов можно считать удачными, а какие стоит доработать? Что можно улучшить в процедуре проведения валютно-обменных операций и в дистанционном банковском обслуживании? Мы в Департаменте транзакционного бизнеса ВТБ постоянно работаем над поиском ответов на эти вопросы. Как нам в этом помогает использование IT-стратегии развития и как от этого выигрывают клиенты – читайте под катом.
Как быстро сосчитать сумму чисел от 1 до 100? Согласно легенде, первым эту задачку решил великий немецкий математик Карл Фридрих Гаусс, еще будучи школьником. Он заметил, что попарные суммы с противоположных концов одинаковы: 1+100=101, 2+99=101 и т. д., и мгновенно получил результат 50х101=5050, продемонстрировав замечательные аналитические способности.
Повторяющиеся задания по обработке данных, ежедневно возникающие в современном банке, гораздо сложнее задачи, с которой справился будущий «король математики» в конце VIII века. Однако подход к их решению с тех пор не поменялся. Как и прежде, чтобы быстрее получить результат и повысить его точность, нужно автоматизировать процессы.
Строить финансовые прогнозы, создавать аналитические отчеты, анализировать тренды и риски без внедрения решений Big Data – это то же самое, что считать сумму чисел от 1 до 100, поочередно складывая числа. Пилотный проект ГАУСС (GAUSS, Global Transaction Business Analytic Union Source & System), запущенный в Департаменте транзакционного бизнеса ВТБ в начале этого года, помогает собрать воедино всю информацию из различных базы данных банка и автоматизировать работу с ней.
Что такое ГАУСС XXI века?
В современном банке сосредоточено огромное количество данных по всем операциям, и их объемы постоянно растут. Эта информация представляет огромную ценность, но чтобы не утонуть в ней, нужно научиться правильно ее использовать.
Проект ГАУСС начался с объединения всей имеющейся в банке информации за 2014-2016 годы и реализации удобного доступа к ней. Сотрудники, работающие с системой, могут в любой момент получить интересующие их материалы по неограниченному сочетанию параметров и вариантов. А значит, на подготовку отчетов уходит пара часов, а не несколько дней, как раньше, эффективность работы сотрудников возрастает. На основе отчетов принимаются решения по улучшению качества обслуживания клиентов, создания более интересных предложений и т. д.
Дальше планируется развивать проект, расширяя базу данных за счет добавления статистической информации из всех возможных источников. ГАУСС должен стать основой для построения единого корпоративного «озера данных» (Data Lake), куда всякий раз можно будет «нырять» за информацией, которая важна в данный момент.
Однако сфера применения проекта ГАУСС гораздо шире, чем простое создание отчетов. Мы надеемся, что очень скоро с его помощью можно будет:
· оценивать различные риски (кредитные, клиентские, партнерские);
· выявлять мошеннические схемы;
· моделировать целевые коммерческие предложения;
· работать с аналитической системой Microsoft Business intelligence и пр.
Как работает ГАУСС?
Работая над проектом, мы сознательно отказались от использования коммерческих решений. Гаусс построен на стеке Hadoop / Hive / Ambari / Oozie / Spark / ORC / YARN, а для построения витрин данных мы используем реляционную базу данных PostgreSQL, которую мы считаем ведущей «открытой» реляционной СУБД в мире. Впрочем, вместо PostgreSQL можно использовать любую другую БД без ущерба для работы системы.
Из-за огромного количества постоянно поступающей информации и появления новых способов ее анализа любые проекты Big Data не могут быть решены с применением типовых шаблонов, это всегда новая комплексная задача. Поэтому мы построили стройную многоступенчатую архитектуру загрузки RAW информации от всех источников, далее агрегации, обработки и обогащения этих данных, а уже после подготовки финальных OLAP-кубов данных и витрин представления информации. Для решения задачи по корректному представлению данных были разработаны гибкие механизмы по маппированию исходных данных с целевой информацией, системы по проверке качества (Data Governance) сформированной информации, а также механизмы по получению детальной информации по агрегатам (data drilldown). Это позволяет безболезненно менять направление работы по ходу реализации проекта, адаптироваться к изменениям. Система ГАУСС разрабатывается по Agile/Scrum методологии, которая позволяет принимать во внимание новые требования бизнес-заказчиков, полученные отзывы, поступающие данные и при этом нацеливать каждого члена команды на достижение результата. Ведь когда работаешь с Big Data, все время возникают новые гипотезы относительно того, как можно использовать спрятанную в петабайтах «озера данных» информацию.
Все переплетено, море нитей, но.
Потяни за нить, за ней потянется клубок.
Этот мир – веретено Oxxxymiron – Переплетено
Мир построения крупных ИС в топ-100 компаниях, как и любые другие ИТ-направления, подвержен веяниям моды. Еще пять лет назад SOA казалось решением всех проблем сложных архитектур. Банки и крупные ритейлеры активно перестраивали свои системы в интеграционном SOA-стиле. BPM-платформы внедряли там, где руководство придерживается современного подхода в построении бизнес-приложений и интеграционной архитектуры самой компании.
Сегодня сервис-ориентированной архитектурой уже никого не удивишь, а в особо продвинутых кругах это уже моветон — ведь есть микросервисы, новое лекарство от всех болезней.
Но велико число тех организаций, где всё делается «по-старинке» — такие заказчики очень часто мыслят категориями имеющихся у них ИТ-систем. Мол, «мы купили вот такую крутую CRM-систему и счастливы». Или «у нас есть SAP ERP, нам его хватает на все случаи жизни». Хотя на самом деле сложности возникают: нередка ситуация, когда компания приобретает какую-то систему и начинает нашпиговывать её несвойственной ей функциональностью и ставить задачи, на которые вендоры и не рассчитывали. В итоге система превращается в неповоротливого монстра, чьи дополнительные возможности плохо задокументированы, с ней очень тяжело разбираться и работать. И самое главное — её тяжело развивать.
Вторая распространённая ситуация: каждый отдел внутри организации лепит что-то своё. В одном подразделении скачали Open Source систему учёта заявок, в другом заказали решение у местного программиста, и так далее. В итоге возникает зоопарк систем, решающих одни и те же задачи.
И выходом из двух этих крайних ситуаций по-прежнему являются интеграционные и BPM-решения. Замечательные представители которых — Oracle SOA Suite и Oracle BPM Suite. Что это такое и зачем они нужны?
SOA Suite
Oracle SOA Suite — это решение для построения распределённых бизнес-процессов.
Оно позволяет нам увязать вместе различные бизнес-приложения, сотрудников (как работающих в этих приложениях, так и не имеющих к ним отношение), правила принятия решений, события, даты, запланированные встречи в календаре, документы, которые рождаются в ходе работы над бизнес-процессом (начиная от накладных и заканчивая большими отчетами). И, конечно, задачи, которые назначаются пользователям для выполнения их работы. Это могут быть простые задачи вроде согласования, а могут быть те, что передаются от одного пользователя к другому и, может быть, даже длятся в течение нескольких месяцев.
Для чего всё это нужно?
И здесь в большинстве компаний возникает ряд организационных проблем: не очень понятно, как делить задачи между собой. Где границы каждой системы? Как им договориться между собой о формате взаимодействия, о частоте выгрузки? Кто кому какие данные передаёт? Сразу ответить на эти вопросы тяжело из-за человеческого фактора: «Почему мы это должны делать?» или «Наша система поддерживает один формат, а ваша — другой. Кто будет делать преобразование?».
Для решения всех этих задач и неопределённостей как раз и предназначена интеграционная платформа Oracle SOA Suite.
Для организации понятных интеграционных процессов, которые могут начинаться в любых системах, могут использоваться любые адаптеры, как технологические, так и адаптеры к внешним системам. Адаптеры — это компоненты Oracle SOA Suite, позволяющие работать с файлами разных форматов, с базами данных, отправлять REST-запросы, взаимодействовать с веб-сервисами, отправлять сообщения в очереди разных стандартов, обмениваться данными с бизнес-системами. Всего в составе Oracle SOA Suite насчитывается более трехсот адаптеров, и их количество постоянно увеличивается.
Все компоненты SOA Suite, в том числе адаптеры, это набор кубиков, из которых специалист по интеграционным решениям собирает интеграционные процессы. В IDE – JDeveloper все элементы бизнес-процесса, словно кубики, перетаскиваются в редакторе, между ними создаются связи, прописываются функции. Это вдвойне удобная реализация продукта, позволяющая не наделать ошибок и сэкономить массу времени и сил в процессе внедрения или обновления масштабных бизнес-систем, когда возникает множество интеграционных задач. Ведь системы в большинстве случаев не работают в изоляции — им приходится взаимодействовать друг с другом. Кроме того, время обучения вчерашнего студента до вполне производительного интеграционного разработчика составляет порядка двух месяцев, а это значит, что при внедрении крупного проекта нарастить мощь команды можно достаточно оперативно.
Business Rules
Но для реализации большого бизнес-процесса недостаточно просто увязать между собой различные системы, потому что есть процессы, в которых возникают задачи принятия решений. И варианты решения этих задач не должны быть жёстко прописаны в коде.
К примеру, в компании есть базовое правило: если стоимость заказа в интернет-магазине превышает 1000 рублей, то доставка осуществляется бесплатно. Но нельзя численно прописывать в коде пороговую сумму, потому что это должен быть настраиваемый параметр. Завтра условия ведения бизнеса могут измениться, и возить от 1000 рублей станет нерентабельно, а это значение уже высечено в коде. То есть код придется перекомпилировать и заново развёртывать на промышленном сервере.
Для таких ситуаций в составе Oracle SOA Suite есть замечательный инструмент Business Rules. По сути это набор правил, которые встроены в бизнес-процесс и на основе которых может выполняться либо ветвление этого бизнес-процесса, либо преобразование внутренних данных, либо дополнительные вызовы внешних сервисов. Например, условие с бесплатной доставкой при заказе от 1000 рублей можно реализовать в виде отдельного блока. И позднее любой аналитик сможет в онлайн-режиме изменить его, расширить, усложнить, добавить в него дополнительные выходы. Например, добавить условие, если в заказе есть особенный товар, то предоставить экспресс-доставку. Не нужно отправлять задачу программистам, ждать, пока они всё сделают, протестируют, соберут и перенесут на боевой сервер.
Human Task
Следующий важный компонент Oracle SOA Suite — Human Task. Это инструмент для автоматического встраивания пользователей в бизнес-процессы. Достаточно вставить в схему блок, указать, какие данные должны в него поступать и какие решения должен принимать пользователь. И, исходя из этого, пакет автоматически генерирует форму по технологии Oracle ADF. На мой взгляд, таких форм вполне достаточно для решения обычных повседневных задач.
Таким образом, мы можем практически за один день добавить пользователей в какой-то бизнес-процесс. Допустим, раньше мы автоматически отправляли заказы в интернет-магазине в службу доставки, а теперь решили, что нам нужно обязательно подтверждать у клиентов заказы свыше 5 тыс. рублей. Добавляем в схему блок Human Task и условие, например, из того же Business Rules. Добавляем в Human Task целиком всю корзину клиента с данными о доставке. Автоматически генерируется форма с этими данными, и у оператора уже есть готовый экран, в котором ему сыплются задачи по обзвону. Такую опцию можно реализовать за один день. Если же у заказчика есть особые требования к формам и процессам, то кастомизировать можно как визуальную составляющую, так и логику работы.
Аудит
Итак, мы построили бизнес-процесс, в котором взаимодействуют разные системы, участвуют разные пользователи, и нам очень хочется за всем этим проследить. Ведь если что-то пошло не так, то нужно разобраться, почему и что именно заработало не по плану.
Ошибки, естественно, случаются: можно не предусмотреть какое-то из условий, что-то недостаточно качественно протестировать. В системе, где весь аудит прошедших процессов построен на логах — текстовых записях, — будет очень тяжело разобраться, почему заказ ушёл в службу доставки А, а не в службу доставки Б. Ведь специалистам придётся поднимать логи, искать записи, а если логи были поставлены на минимальный уровень отображения ошибок? И вот мы уже не можем выяснить причину ошибки, и приходится ждать, когда она повторится снова. И такое случается во многих компаниях.
В Oracle SOA Suite встроен механизм графического аудита. Он сохраняет всю информацию по каждому запуску каждого блока в рамках бизнес-процесса. Возвращаясь к предыдущему примеру со службой доставки: специалист поддержки интеграционного решения вбивает номер заказа и открывает выполненный бизнес-процесс — со всеми «квадратиками», со всеми ветвлениями, решениями пользователей, результатами автоматических условий, ответами всех веб-сервисов. И легко может выяснить, где произошёл сбой. Ему не нужно лезть в документацию (ведь реализованный бизнес-процесс самодокументирован), не нужно читать, из чего же состоит этот бизнес-процесс.
Business Activity Monitoring
Это инструмент генерирования онлайн-отчетов. Внутри бизнес-процесса мы можем в любых местах встраивать отдельные уведомления для системы управления отчетами. Допустим, в одном месте мы говорим, что поступил новый заказ. В другом — что пользователь отменил заказ. В третьем — заказ ушёл в службу доставки. И на основании этих данных будет генерироваться красивый онлайн-отчет, со всеми заказами, разбитыми по категориям. Он будет изменяться в реальном времени, то есть мы сможем видеть «живые» данные, а не суточной давности — после обработки ETL и загрузки в хранилище.
А дальше мы можем построить, скажем, новый интерактивный отчёт по обработке заказов, ушедших в службу доставки. Можно сразу увидеть, что служба доставки не успевает обрабатывать заказы вовремя: уже полдня у них копятся остатки, которые они себе набрали в работу. Если мы предлагаем клиенту оперативный сервис, то это недопустимо. Руководство видит нарушение SLA, договоренностей с партнёрами, и может, не дожидаясь статистики за прошедшие сутки, позвонить и спросить: «Ребята, что такое? Почему вы так медленно доставляете заказы?»
Oracle BPM
Oracle BPM — это надстройка над SOA Suite. То есть SOA Suite лежит уровнем ниже Oracle BPM и обеспечивает львиную долю функциональности. А что приходится на оставшуюся часть? В первую очередь, это возможность изобразить бизнес-процессы заказчика в нотации BPMN.
Горизонтальные уровни — подразделения компании. Блоки обозначают:
запросы в какую-то внешнюю систему;
генерирование/выкладывание документов в корпоративную систему хранения;
действия пользователей по новым или уже созданным задачам – в общем, любое взаимодействие с клиентами или сотрудниками организации;
различного рода уведомления: на почту, по SMS, уведомления системы мониторинга и так далее.
Получившаяся схема понятна любому аналитику, поскольку это открытый стандартизированный язык для описания бизнес-процессов. К сожалению, в России во многих компаниях нет специалистов, которые могут всё это читать. Но именно BPM-представление является главной и самой полезной возможностью продукта. С помощью таких схем можно понятно, наглядно и формализовано описывать бизнес-процессы, зашитые в коде всевозможных систем и никак не задокументированные.
Второй главной функцией Oracle BPM является преобразование BPMN в BPEL-схему. В данном случае процесс разбивается уже не по подразделениям и пользователям, а по логике прохождения процесса.
Здесь отображаются те же вызовы внешних служб и те же задачи пользователей, но в другом виде. С таким представлением обычно работают программисты интеграционных решений. Преобразование из BPMN-нотации в BPEL-нотацию — это и есть главная возможность Oracle BPM.
Ещё одним важным отличием Oracle BPM от SOA Suite является онлайн-редактор бизнес-процессов. Он позволяет на лету, прямо в браузере перестраивать все схемы. Стоит добавить какой-то блок, нажать «Применить», и все бизнес-процессы, стартующие с этого момента, уже пойдут по новой схеме. В Oracle SOA Suite для этого придётся вручную в среде разработки JDeveloper внести изменения, потом протестировать, и после этого перенести разработку в production. То есть процедура получается гораздо менее гибкой и оперативной.
Помимо всего вышеописанного, в Oracle BPM есть ещё большой набор готовых автоматических отчётов. С их помощью можно сразу выявить всевозможные узкие места в существующих бизнес-процессах.
Oracle BPM 12с
Отдельно хочется рассказать о возможностях Oracle BPM версии 12с. Формально это не новинка, но в России эта версия появилась относительно недавно, большинство заказчиков в последние пару лет еще внедряли версию 11g.
Выше я рассказывал о Business Rules — наборе бизнес-правил. Теперь их можно выгрузить в виде Excel-таблицы, внести изменения и импортировать обратно в Business Rules. Эта возможность нужна тем, кто по каким-то причинам не хочет или не может работать с самим Business Rules. Например, если тяжело осваивать новый интерфейс, или если изменения должны пройти ряд согласований у сотрудников, не имеющих доступа к Oracle BPM.
Но самое важное нововведение в версии 12c — механизм Business Architecture. Это набор дополнительных экранов верхнеуровневого управления, с помощью которых топ-менеджеры могут планировать развитие информационной платформы.
Здесь можно указывать стратегические цели, допустим, абстрактную задачу по увеличению прибыли. Далее её можно разбить на подзадачи:
внедрить систему аналитики, которая позволит анализировать продажи и выявлять узкие места;
внедрить современную систему планирования, которая позволит уменьшить остатки на складах.
Дальше эти задачи разбиваются на ещё более мелкие, вплоть до уровня таких бизнес-процессов, как обработка заявки на сайте.
Из получившейся схемы можно перейти в упомянутую выше схему в BPMN-нотации.
Ещё одно нововведение — комплекс Event Processing. Это механизм и интерфейсы анализа событий. Например, если в предыдущих версиях мы внутри бизнес-процесса напрямую отправляли уведомления в систему мониторинга, то теперь вместо этого можно использовать публикацию событий, возникающих во всех бизнес-процессах компании. На основании пула этих событий можно построить отчёт, который будет прогнозировать дальнейшую динамику всех процессов.
Кроме того, в версии 12с была улучшена и упрощена интеграция UCM — механизма хранения документов, которые генерируются в ходе выполнения бизнес-процессов и подгружаются в задачах, созданных с помощью Human Task.
Также улучшен репозиторий для проектов, метаданных и схем данных. В версии 11g предполагалось использование в основном стороннего хранилища, будь то SVN или Git. А в 12c Oracle предлагает свой репозиторий для хранения всего кода. Это удобно с той точки зрения, что появится единый источник всех данных по процессам, к тому же использование предыдущих разработок в новом проекте позволяет сэкономить время.
Как продвигать в России?
Вопрос сложный. Многие большие компании, которые уже осознали важность таких продуктов, уже приобрели пакет либо Oracle, либо IBM. А всем остальным очень тяжело объяснить, какую выгоду они смогут для себя извлечь. К тому же непросто взять и перевести все бизнес-процессы на новые рельсы. В идеальном случае для этого должен быть какой-то стимул, влиятельный сторонник реновации ИТ-инфраструктуры, который начнёт с оптимизации бизнес-процессов. И тогда их можно будет реализовать на основе интеграционной архитектуры Oracle SOA Suite или BPM Suite. Встраивать участки взаимодействия с другими системами, применять участие пользователей, уведомления и многое другое.
Что получим в итоге?
Бизнес-процессы, которые документированы сами в себе. Можно зайти в редактор и при необходимости быстро и безошибочно модифицировать бизнес-процесс, просто перетащив блок в нужное место: «я хочу, чтобы вот здесь у меня еще дополнительно уходило письмо начальнику какого-то отдела». Одного письма недостаточно, начальник должен принять какое-то решение? Ставим блок с его участием. Теперь начальнику приходит письмо со ссылкой: «на вас есть такая задача — пожалуйста, ознакомьтесь с документом».
Конечно, вставляя блоки в BPM-схему, бизнес-пользователь или аналитик не пишет сам код физической реализации. Но получившаяся схема позволяет быстро и однозначно поставить задачу программистам, которые будут ясно понимать, какие данные должен получать и выдавать каждый блок. Им не нужно разбираться со всеми хитросплетениями бизнес-процессов. Пользователь может чуть глубже погрузиться в программирование бизнес-процессов, а разработчику будет чуть проще понять, какие взаимосвязи ему нужно реализовать, потому что все говорят на одном языке. И именно ради динамичного развития и изменения своих бизнес-процессов компании и покупают такие большие интеграционные платформы.
В полном соответствии с современными тенденциями в SOA Suite и BPM можно работать на смартфонах и планшетах. Ну и что, что специалист уехал на встречу? Пока он стоит в пробках или едет в общественном транспорте, он может открыть экран BPM и выполнить действия по какой-то новой задаче.
Внедрение интеграционной платформы позволяет организовать омниканальный доступ к процессам компании и клиентам. То есть внутри бизнес-процессов можно реализовать не только уведомление сотрудников компании, но и уведомление внешних клиентов. Мы можем отправлять им письма на почту, продублировать по SMS, можем отправлять ссылки на мобильные приложения. Конечно, приложение придётся разрабатывать отдельно, но за счёт того, что у нас есть большая интеграционная платформа, встроить взаимодействие и с мобильным приложением, и с почтой, и с прочими каналами в уже имеющийся бизнес-процесс легче, чем реализовывать это каждый раз с нуля.
Резюме
В основе Oracle BPM лежит пакет инструментов Oracle SOA Suite. Функциональности SOA достаточно большинству заказчиков, но крупным компаниям могут быть важны такие возможности BPM, как редактор бизнес-процессов в нотации BPMN, многочисленные преднастроенные отчёты из коробки, показывающие работу бизнес-процессов, и онлайн-редактор бизнес-процессов.
Одной из главных трудностей при продвижении Oracle SOA Suite и BPM в России является их определённая абстрактность для конечных заказчиков. У более привычных продуктов — систем аналитики, BI или CRM-систем — есть понятные, чёткие результаты внедрения. То есть заказчику понятно, что после их внедрения у него появятся отчёты, единая система хранения клиентов, интерфейс для работы его операторов с данными клиента и так далее. А в случае с интеграционной платформой всё сложнее. Мы можем показать заказчику красивые схемы и расписать радужные последствия. Но конечный бизнес-пользователь никогда не увидит этих схем, потому что они ему не нужны. У него по-прежнему будут только окна привычных систем. А то, что стоит за ними (все эти блоки и связи), для него слишком абстрактно и не особо интересно.
Если у вас есть интересные идеи или истории про внедрение подобных систем — добро пожаловать в комментарии.
Дмитрий Овчаренко, архитектор Центра внедрения бизнес-систем компании «Инфосистемы Джет»
Все переплетено, море нитей, но.
Потяни за нить, за ней потянется клубок.
Этот мир – веретено Oxxxymiron – Переплетено
Мир построения крупных ИС в топ-100 компаниях, как и любые другие ИТ-направления, подвержен веяниям моды. Еще пять лет назад SOA казалось решением всех проблем сложных архитектур. Банки и крупные ритейлеры активно перестраивали свои системы в интеграционном SOA-стиле. BPM-платформы внедряли там, где руководство придерживается современного подхода в построении бизнес-приложений и интеграционной архитектуры самой компании.
Сегодня сервис-ориентированной архитектурой уже никого не удивишь, а в особо продвинутых кругах это уже моветон — ведь есть микросервисы, новое лекарство от всех болезней.
Но велико число тех организаций, где всё делается «по-старинке» — такие заказчики очень часто мыслят категориями имеющихся у них ИТ-систем. Мол, «мы купили вот такую крутую CRM-систему и счастливы». Или «у нас есть SAP ERP, нам его хватает на все случаи жизни». Хотя на самом деле сложности возникают: нередка ситуация, когда компания приобретает какую-то систему и начинает нашпиговывать её несвойственной ей функциональностью и ставить задачи, на которые вендоры и не рассчитывали. В итоге система превращается в неповоротливого монстра, чьи дополнительные возможности плохо задокументированы, с ней очень тяжело разбираться и работать. И самое главное — её тяжело развивать.
Вторая распространённая ситуация: каждый отдел внутри организации лепит что-то своё. В одном подразделении скачали Open Source систему учёта заявок, в другом заказали решение у местного программиста, и так далее. В итоге возникает зоопарк систем, решающих одни и те же задачи.
И выходом из двух этих крайних ситуаций по-прежнему являются интеграционные и BPM-решения. Замечательные представители которых — Oracle SOA Suite и Oracle BPM Suite. Что это такое и зачем они нужны?
SOA Suite
Oracle SOA Suite — это решение для построения распределённых бизнес-процессов.
Оно позволяет нам увязать вместе различные бизнес-приложения, сотрудников (как работающих в этих приложениях, так и не имеющих к ним отношение), правила принятия решений, события, даты, запланированные встречи в календаре, документы, которые рождаются в ходе работы над бизнес-процессом (начиная от накладных и заканчивая большими отчетами). И, конечно, задачи, которые назначаются пользователям для выполнения их работы. Это могут быть простые задачи вроде согласования, а могут быть те, что передаются от одного пользователя к другому и, может быть, даже длятся в течение нескольких месяцев.
Для чего всё это нужно?
И здесь в большинстве компаний возникает ряд организационных проблем: не очень понятно, как делить задачи между собой. Где границы каждой системы? Как им договориться между собой о формате взаимодействия, о частоте выгрузки? Кто кому какие данные передаёт? Сразу ответить на эти вопросы тяжело из-за человеческого фактора: «Почему мы это должны делать?» или «Наша система поддерживает один формат, а ваша — другой. Кто будет делать преобразование?».
Для решения всех этих задач и неопределённостей как раз и предназначена интеграционная платформа Oracle SOA Suite.
Для организации понятных интеграционных процессов, которые могут начинаться в любых системах, могут использоваться любые адаптеры, как технологические, так и адаптеры к внешним системам. Адаптеры — это компоненты Oracle SOA Suite, позволяющие работать с файлами разных форматов, с базами данных, отправлять REST-запросы, взаимодействовать с веб-сервисами, отправлять сообщения в очереди разных стандартов, обмениваться данными с бизнес-системами. Всего в составе Oracle SOA Suite насчитывается более трехсот адаптеров, и их количество постоянно увеличивается.
Все компоненты SOA Suite, в том числе адаптеры, это набор кубиков, из которых специалист по интеграционным решениям собирает интеграционные процессы. В IDE – JDeveloper все элементы бизнес-процесса, словно кубики, перетаскиваются в редакторе, между ними создаются связи, прописываются функции. Это вдвойне удобная реализация продукта, позволяющая не наделать ошибок и сэкономить массу времени и сил в процессе внедрения или обновления масштабных бизнес-систем, когда возникает множество интеграционных задач. Ведь системы в большинстве случаев не работают в изоляции — им приходится взаимодействовать друг с другом. Кроме того, время обучения вчерашнего студента до вполне производительного интеграционного разработчика составляет порядка двух месяцев, а это значит, что при внедрении крупного проекта нарастить мощь команды можно достаточно оперативно.
Business Rules
Но для реализации большого бизнес-процесса недостаточно просто увязать между собой различные системы, потому что есть процессы, в которых возникают задачи принятия решений. И варианты решения этих задач не должны быть жёстко прописаны в коде.
К примеру, в компании есть базовое правило: если стоимость заказа в интернет-магазине превышает 1000 рублей, то доставка осуществляется бесплатно. Но нельзя численно прописывать в коде пороговую сумму, потому что это должен быть настраиваемый параметр. Завтра условия ведения бизнеса могут измениться, и возить от 1000 рублей станет нерентабельно, а это значение уже высечено в коде. То есть код придется перекомпилировать и заново развёртывать на промышленном сервере.
Для таких ситуаций в составе Oracle SOA Suite есть замечательный инструмент Business Rules. По сути это набор правил, которые встроены в бизнес-процесс и на основе которых может выполняться либо ветвление этого бизнес-процесса, либо преобразование внутренних данных, либо дополнительные вызовы внешних сервисов. Например, условие с бесплатной доставкой при заказе от 1000 рублей можно реализовать в виде отдельного блока. И позднее любой аналитик сможет в онлайн-режиме изменить его, расширить, усложнить, добавить в него дополнительные выходы. Например, добавить условие, если в заказе есть особенный товар, то предоставить экспресс-доставку. Не нужно отправлять задачу программистам, ждать, пока они всё сделают, протестируют, соберут и перенесут на боевой сервер.
Human Task
Следующий важный компонент Oracle SOA Suite — Human Task. Это инструмент для автоматического встраивания пользователей в бизнес-процессы. Достаточно вставить в схему блок, указать, какие данные должны в него поступать и какие решения должен принимать пользователь. И, исходя из этого, пакет автоматически генерирует форму по технологии Oracle ADF. На мой взгляд, таких форм вполне достаточно для решения обычных повседневных задач.
Таким образом, мы можем практически за один день добавить пользователей в какой-то бизнес-процесс. Допустим, раньше мы автоматически отправляли заказы в интернет-магазине в службу доставки, а теперь решили, что нам нужно обязательно подтверждать у клиентов заказы свыше 5 тыс. рублей. Добавляем в схему блок Human Task и условие, например, из того же Business Rules. Добавляем в Human Task целиком всю корзину клиента с данными о доставке. Автоматически генерируется форма с этими данными, и у оператора уже есть готовый экран, в котором ему сыплются задачи по обзвону. Такую опцию можно реализовать за один день. Если же у заказчика есть особые требования к формам и процессам, то кастомизировать можно как визуальную составляющую, так и логику работы.
Аудит
Итак, мы построили бизнес-процесс, в котором взаимодействуют разные системы, участвуют разные пользователи, и нам очень хочется за всем этим проследить. Ведь если что-то пошло не так, то нужно разобраться, почему и что именно заработало не по плану.
Ошибки, естественно, случаются: можно не предусмотреть какое-то из условий, что-то недостаточно качественно протестировать. В системе, где весь аудит прошедших процессов построен на логах — текстовых записях, — будет очень тяжело разобраться, почему заказ ушёл в службу доставки А, а не в службу доставки Б. Ведь специалистам придётся поднимать логи, искать записи, а если логи были поставлены на минимальный уровень отображения ошибок? И вот мы уже не можем выяснить причину ошибки, и приходится ждать, когда она повторится снова. И такое случается во многих компаниях.
В Oracle SOA Suite встроен механизм графического аудита. Он сохраняет всю информацию по каждому запуску каждого блока в рамках бизнес-процесса. Возвращаясь к предыдущему примеру со службой доставки: специалист поддержки интеграционного решения вбивает номер заказа и открывает выполненный бизнес-процесс — со всеми «квадратиками», со всеми ветвлениями, решениями пользователей, результатами автоматических условий, ответами всех веб-сервисов. И легко может выяснить, где произошёл сбой. Ему не нужно лезть в документацию (ведь реализованный бизнес-процесс самодокументирован), не нужно читать, из чего же состоит этот бизнес-процесс.
Business Activity Monitoring
Это инструмент генерирования онлайн-отчетов. Внутри бизнес-процесса мы можем в любых местах встраивать отдельные уведомления для системы управления отчетами. Допустим, в одном месте мы говорим, что поступил новый заказ. В другом — что пользователь отменил заказ. В третьем — заказ ушёл в службу доставки. И на основании этих данных будет генерироваться красивый онлайн-отчет, со всеми заказами, разбитыми по категориям. Он будет изменяться в реальном времени, то есть мы сможем видеть «живые» данные, а не суточной давности — после обработки ETL и загрузки в хранилище.
А дальше мы можем построить, скажем, новый интерактивный отчёт по обработке заказов, ушедших в службу доставки. Можно сразу увидеть, что служба доставки не успевает обрабатывать заказы вовремя: уже полдня у них копятся остатки, которые они себе набрали в работу. Если мы предлагаем клиенту оперативный сервис, то это недопустимо. Руководство видит нарушение SLA, договоренностей с партнёрами, и может, не дожидаясь статистики за прошедшие сутки, позвонить и спросить: «Ребята, что такое? Почему вы так медленно доставляете заказы?»
Oracle BPM
Oracle BPM — это надстройка над SOA Suite. То есть SOA Suite лежит уровнем ниже Oracle BPM и обеспечивает львиную долю функциональности. А что приходится на оставшуюся часть? В первую очередь, это возможность изобразить бизнес-процессы заказчика в нотации BPMN.
Горизонтальные уровни — подразделения компании. Блоки обозначают:
запросы в какую-то внешнюю систему;
генерирование/выкладывание документов в корпоративную систему хранения;
действия пользователей по новым или уже созданным задачам – в общем, любое взаимодействие с клиентами или сотрудниками организации;
различного рода уведомления: на почту, по SMS, уведомления системы мониторинга и так далее.
Получившаяся схема понятна любому аналитику, поскольку это открытый стандартизированный язык для описания бизнес-процессов. К сожалению, в России во многих компаниях нет специалистов, которые могут всё это читать. Но именно BPM-представление является главной и самой полезной возможностью продукта. С помощью таких схем можно понятно, наглядно и формализовано описывать бизнес-процессы, зашитые в коде всевозможных систем и никак не задокументированные.
Второй главной функцией Oracle BPM является преобразование BPMN в BPEL-схему. В данном случае процесс разбивается уже не по подразделениям и пользователям, а по логике прохождения процесса.
Здесь отображаются те же вызовы внешних служб и те же задачи пользователей, но в другом виде. С таким представлением обычно работают программисты интеграционных решений. Преобразование из BPMN-нотации в BPEL-нотацию — это и есть главная возможность Oracle BPM.
Ещё одним важным отличием Oracle BPM от SOA Suite является онлайн-редактор бизнес-процессов. Он позволяет на лету, прямо в браузере перестраивать все схемы. Стоит добавить какой-то блок, нажать «Применить», и все бизнес-процессы, стартующие с этого момента, уже пойдут по новой схеме. В Oracle SOA Suite для этого придётся вручную в среде разработки JDeveloper внести изменения, потом протестировать, и после этого перенести разработку в production. То есть процедура получается гораздо менее гибкой и оперативной.
Помимо всего вышеописанного, в Oracle BPM есть ещё большой набор готовых автоматических отчётов. С их помощью можно сразу выявить всевозможные узкие места в существующих бизнес-процессах.
Oracle BPM 12с
Отдельно хочется рассказать о возможностях Oracle BPM версии 12с. Формально это не новинка, но в России эта версия появилась относительно недавно, большинство заказчиков в последние пару лет еще внедряли версию 11g.
Выше я рассказывал о Business Rules — наборе бизнес-правил. Теперь их можно выгрузить в виде Excel-таблицы, внести изменения и импортировать обратно в Business Rules. Эта возможность нужна тем, кто по каким-то причинам не хочет или не может работать с самим Business Rules. Например, если тяжело осваивать новый интерфейс, или если изменения должны пройти ряд согласований у сотрудников, не имеющих доступа к Oracle BPM.
Но самое важное нововведение в версии 12c — механизм Business Architecture. Это набор дополнительных экранов верхнеуровневого управления, с помощью которых топ-менеджеры могут планировать развитие информационной платформы.
Здесь можно указывать стратегические цели, допустим, абстрактную задачу по увеличению прибыли. Далее её можно разбить на подзадачи:
внедрить систему аналитики, которая позволит анализировать продажи и выявлять узкие места;
внедрить современную систему планирования, которая позволит уменьшить остатки на складах.
Дальше эти задачи разбиваются на ещё более мелкие, вплоть до уровня таких бизнес-процессов, как обработка заявки на сайте.
Из получившейся схемы можно перейти в упомянутую выше схему в BPMN-нотации.
Ещё одно нововведение — комплекс Event Processing. Это механизм и интерфейсы анализа событий. Например, если в предыдущих версиях мы внутри бизнес-процесса напрямую отправляли уведомления в систему мониторинга, то теперь вместо этого можно использовать публикацию событий, возникающих во всех бизнес-процессах компании. На основании пула этих событий можно построить отчёт, который будет прогнозировать дальнейшую динамику всех процессов.
Кроме того, в версии 12с была улучшена и упрощена интеграция UCM — механизма хранения документов, которые генерируются в ходе выполнения бизнес-процессов и подгружаются в задачах, созданных с помощью Human Task.
Также улучшен репозиторий для проектов, метаданных и схем данных. В версии 11g предполагалось использование в основном стороннего хранилища, будь то SVN или Git. А в 12c Oracle предлагает свой репозиторий для хранения всего кода. Это удобно с той точки зрения, что появится единый источник всех данных по процессам, к тому же использование предыдущих разработок в новом проекте позволяет сэкономить время.
Как продвигать в России?
Вопрос сложный. Многие большие компании, которые уже осознали важность таких продуктов, уже приобрели пакет либо Oracle, либо IBM. А всем остальным очень тяжело объяснить, какую выгоду они смогут для себя извлечь. К тому же непросто взять и перевести все бизнес-процессы на новые рельсы. В идеальном случае для этого должен быть какой-то стимул, влиятельный сторонник реновации ИТ-инфраструктуры, который начнёт с оптимизации бизнес-процессов. И тогда их можно будет реализовать на основе интеграционной архитектуры Oracle SOA Suite или BPM Suite. Встраивать участки взаимодействия с другими системами, применять участие пользователей, уведомления и многое другое.
Что получим в итоге?
Бизнес-процессы, которые документированы сами в себе. Можно зайти в редактор и при необходимости быстро и безошибочно модифицировать бизнес-процесс, просто перетащив блок в нужное место: «я хочу, чтобы вот здесь у меня еще дополнительно уходило письмо начальнику какого-то отдела». Одного письма недостаточно, начальник должен принять какое-то решение? Ставим блок с его участием. Теперь начальнику приходит письмо со ссылкой: «на вас есть такая задача — пожалуйста, ознакомьтесь с документом».
Конечно, вставляя блоки в BPM-схему, бизнес-пользователь или аналитик не пишет сам код физической реализации. Но получившаяся схема позволяет быстро и однозначно поставить задачу программистам, которые будут ясно понимать, какие данные должен получать и выдавать каждый блок. Им не нужно разбираться со всеми хитросплетениями бизнес-процессов. Пользователь может чуть глубже погрузиться в программирование бизнес-процессов, а разработчику будет чуть проще понять, какие взаимосвязи ему нужно реализовать, потому что все говорят на одном языке. И именно ради динамичного развития и изменения своих бизнес-процессов компании и покупают такие большие интеграционные платформы.
В полном соответствии с современными тенденциями в SOA Suite и BPM можно работать на смартфонах и планшетах. Ну и что, что специалист уехал на встречу? Пока он стоит в пробках или едет в общественном транспорте, он может открыть экран BPM и выполнить действия по какой-то новой задаче.
Внедрение интеграционной платформы позволяет организовать омниканальный доступ к процессам компании и клиентам. То есть внутри бизнес-процессов можно реализовать не только уведомление сотрудников компании, но и уведомление внешних клиентов. Мы можем отправлять им письма на почту, продублировать по SMS, можем отправлять ссылки на мобильные приложения. Конечно, приложение придётся разрабатывать отдельно, но за счёт того, что у нас есть большая интеграционная платформа, встроить взаимодействие и с мобильным приложением, и с почтой, и с прочими каналами в уже имеющийся бизнес-процесс легче, чем реализовывать это каждый раз с нуля.
Резюме
В основе Oracle BPM лежит пакет инструментов Oracle SOA Suite. Функциональности SOA достаточно большинству заказчиков, но крупным компаниям могут быть важны такие возможности BPM, как редактор бизнес-процессов в нотации BPMN, многочисленные преднастроенные отчёты из коробки, показывающие работу бизнес-процессов, и онлайн-редактор бизнес-процессов.
Одной из главных трудностей при продвижении Oracle SOA Suite и BPM в России является их определённая абстрактность для конечных заказчиков. У более привычных продуктов — систем аналитики, BI или CRM-систем — есть понятные, чёткие результаты внедрения. То есть заказчику понятно, что после их внедрения у него появятся отчёты, единая система хранения клиентов, интерфейс для работы его операторов с данными клиента и так далее. А в случае с интеграционной платформой всё сложнее. Мы можем показать заказчику красивые схемы и расписать радужные последствия. Но конечный бизнес-пользователь никогда не увидит этих схем, потому что они ему не нужны. У него по-прежнему будут только окна привычных систем. А то, что стоит за ними (все эти блоки и связи), для него слишком абстрактно и не особо интересно.
Если у вас есть интересные идеи или истории про внедрение подобных систем — добро пожаловать в комментарии.
Дмитрий Овчаренко, архитектор Центра внедрения бизнес-систем компании «Инфосистемы Джет»
Каталог услуг неразрывно связан с понятием ITSM и является важнейшим компонентом сервисного подхода. Необходимость в его создании возникает на этапе внедрения ITSM.
Это становится ясно из аналогий, которые приводит Шэрон Тейлор (Sharon Taylor), главный архитектор третьей редакции ITIL. Она сравнивает каталог услуг с iTunes или App Store для предприятий, онлайн-справочником и ресурсом для разработчиков одновременно. Разберемся с этой темой с помощью небольшого чеклиста.
Когда предприятие начинает работать с каталогом услуг, возникает вопрос о способах его составления. Чтобы разобраться с этим, рассмотрим необходимые шаги и условия.
Шаг 1. Формирование команды
В составлении каталога услуг, безусловно, должны принимать участие компетентные люди, знакомые с проблематикой. Важно подключить к работе IT-отдел.
Количество людей, которые примут участие в создании каталога, напрямую зависит от размера организации и объема предоставляемых услуг. Разумеется, должен быть назначен человек или ответственная команда, которая возьмет на себя роль менеджера каталога.
Также будет хорошо, если в состав команды войдут представитель руководства и, по крайней мере, несколько конечных пользователей. Зачастую они воспринимают задачи не так, как сотрудники IT-отдела, и оперируют другими названиями услуг. Такой подход поможет найти баланс и сделать каталог услуг понятным.
Шаг 2. Сбор обратной связи
Помимо общения с конечными пользователями важно расширить работу с обратной связью до конкретных бизнес-пользователей. Следует узнать запросы руководителей групп и менеджеров. Это поможет также уточнить стоимость каждой услуги.
Шаг 3. Создание списка услуг
Тейлор замечает, что самый простой и в то же время эффективный способ приступить к созданию сервисного каталога — собрать список уже оказываемых услуг. Проведение инвентаризации должно быть комплексным. Согласно Трою Дамулину (Troy DuMoulin), вице-президенту Pink Elephant, консалтинговой группы ITSM, это является ключом к созданию эффективного каталога. Каждая услуга должна быть указана отдельно, а после — внутри категории. Следующий этап требует составить список услуг, которые IT-отдел не предоставляет в данный момент, но собирается предоставлять в будущем.
Теперь самое время использовать результаты сбора обратной связи и найти пересечения между возможностями и запросами. Тейлор советует не перегружать первую версию каталога и придерживаться нескольких ключевых процессов. Разумеется, исходя из конкретных бизнес-запросов. Эксперт по ITIL Майкл Скарбороу (Michael Scarborough) напоминает: «Наличие большого количества услуг в каталоге не обязательно делает его более качественным или более подходящим для бизнеса. Наличие правильного сочетания услуг в легко доступном каталоге, — это то, чего хочет бизнес».
После составления финальной версии списка услуг следует подготовить для каждой из них интуитивно понятные описания.
Шаг 4. Распределение ресурсов и ответственности
Основная задача теперь — установить зависимости между выполняемой услугой и требуемыми на ее исполнение ресурсами, в том числе определение исполнителей, учет вклада сторонних поставщиков, IT-компонентов и процессов.
На основе этого стоит определить параметры использования: уровень доступа исполнителей, возможность вносить изменения и так далее. У каждой категории услуг должен быть владелец, а также первый, второй и третий уровни поддержки с четким разграничением ответственности и полномочий.
Шаг 5. Подготовка каталога в двух версиях
Когда список услуг как структура готов, приходит время организовывать каталог. После формирования основы для технической версии каталога есть смысл адаптировать его под вторую важную категорию — клиентскую.
Она содержит только информацию верхнего уровня в лаконичной форме и без технических подробностей. Важно ответить для себя на такие вопросы, как «Употребляет ли бизнес-пользователь слово «инфраструктура» в отношении служб резервного копирования?», и в соответствии с этим избавиться от путаницы в упрощенной версии.
Что касается основной версии, как конкретно организовывать и классифицировать услуги, зависит от размера организации, потребностей конечного пользователя и — самое важное — логики. Это основа пользовательского опыта.
Шаг 6. Контрольные тесты
Теперь пришло время пройтись по готовому каталогу. Чтобы убедиться в его доступности, следует задействовать хотя бы одного конечного пользователя. Не стоит вовлекать слишком много людей в этот процесс, иначе не избежать знаменитой кулинарной проблемы «чем больше поваров, тем хуже бульон».
На этом этапе важно определить несколько ключевых показателей для измерения. Например, изменение времени обработки аварийных запросов с каталогом и без него.
Шаг 7. Публикация сервисного каталога
Открывать общий доступ к каталогу во многих случаях лучше поэтапно. Так можно проверить работоспособность небольшого набора услуг и выявить повторяющиеся ошибки. О публикации каталога услуг должно быть объявлено заранее.
Не лишним будет продумать стимулирующие программы для конечных пользователей. Например, можно разработать учебное пособие для работы с ним и поощрять участников к прохождению обучения. Важно объяснить, как именно каталог услуг будет им полезен.
Шаг 8. Автоматизация
Существуют продукты для автоматизации работы каталога. Однако Шэрон Тейлор настаивает на том, что не следует сразу же прибегать к этим инструментам. Только почувствовав уверенность в архитектуре каталога услуг и поддерживающих его процессах, предприятиям стоит выбирать подходящий программный продукт.
Шаг 9. Поддержка каталога
Разработка процесса для проверки и обновления каталога услуг входит в список основных этапов, так как без актуальности каталог теряет смысл. Важно своевременно добавлять и удалять услуги и уровни поддержки. Вот почему следует заранее назначить менеджера или команду, которая будет постоянно отслеживать услуги в соответствии с подготовленным графиком. Регулярное обновление и просмотр каталога IT-услуг следует проводить как минимум раз в квартал, а раз в год устраивать комплексный аудит структуры. К тому же важно продолжать проводить оценку по ключевым показателям. Так всегда будет сохраняться понимание эффективности работы каталога.
P.S. В нашем блоге вы найдете и другие полезные статьи по управлению услугами: