SlideShare uma empresa Scribd logo
1 de 79
Baixar para ler offline
October 2nd, Moscow
Yet another Conference 2013
Welcome to SoundCloud!
Sunday, September 29, 13
Alexander Kovalev
Software Engineer @ SoundCloud
Sunday, September 29, 13
Добрый день! Меня зовут Александр Ковалев.
Последние 2,5 года я работаю в Берлинской компании SoundCloud
ОФициально моя должность называется Software Engineer.
Но в основном я фокусируюсь на клиентских технологиях. За это время успел поработать почти на всех главных проектах.
SOUNDCLOUD
SoundCloud
• Social audio platform
• 55 mln users and growing
• 240 mln unique visitors a month
• 12 hours of audio are uploaded every minute
• 15 engineering teams
Sunday, September 29, 13
Я думаю, многие из вас слышали о нашей компании, но вот несколько фактов и цифр. SoundCloud это социальная платформа, позволяющая музыкантам делиться своей музыкой, а обычным
пользователям - ее слушать. В настоящее время мы очень быстро растем. По данным нащей разведки, у нас более 50 млн пользователей. более 240 млн уникальных посетителей в месяц.
Каждую минуту мы обрабатываем порядка 12 часов нового аудил контента.
В SoundCloud порядка 20 команд, занимающихся разнымим сервисами, порой невидимыми снаружи. Но в своей презентации я сконцентрируюсь на клиентских проектах - главный сайт,
мобильная версия и виджет.
SOUNDCLOUD
Quick assumptions
• Awareness of MVC pattern
• you know that SPA is not a mineral spring
• why and when SPA makes a difference
• you heard of web app frameworks
Sunday, September 29, 13
Готовясь к этому докладу, я естественно делал некоторые допущения. Я думаю,
- что вы слышали о паттерне MVC,
- вы знаете о таком подходе к созданию веб приложений, как Single Page Application.
- вы понимаете где и зачем, его уместно применять
- и вы сами пытались применить один из современных фреймворков на практике
Sunday, September 29, 13
Мой доклад называется Добро пожаловать в SoundCloud! И за время доклада я постсраюсь рассказать об основных идеях и решениях, формирующих архитектуру новой версии SoundCloud,
изначально известной внутри компании как NEXT.
SOUNDCLOUD
What is NEXT?
• default production app since December, 2012
• SoundCloud API client
• the long running app
• ~45,000 LOC, 80% of that is JS
• supports only modern browsers
• better experience
Sunday, September 29, 13
Что такое NEXT? Это новая версия, доступная в production с декабря 2012 года, по сути является клиентом нашего собственного API, за редкими исключениями. Это Single Page
Application, протяженность сессии которой иногда достигает и превышает сутки. В разы увеличила кол-во прослушиваний и вовлеченность пользователей, что несомненно можно
расценивать как положительный результат.
SOUNDCLOUD
Our Stack
• Require.js (Almond.js)
• Node.js
• Uglify.js/Esprima
• Klang.js
• Backbone (Trombone)
• Handlebars (with plenty of helpers)
• Home-grown development tools
Sunday, September 29, 13
В разработке NEXT и практически всех client-side проектов используется следующий, я бы сказал, типичный хипстерский стэк -- ну разве что на coffeescript не пишем.
Тут нет ничего необычного - RequireJs и Almond.js используются для загрузки модулей во время разработки и в продакшене соответсвенно. Node.js/Uglify.js используется для
инструментов сборки, разработческого сервера и статического анализа кода._
В продакшене Node.js сервера при этом нет. так как являясь клиентом собственного API, сайт абсолютно статический. Важной компонентой является библиотека Klang.js,
предоставлюящая лучший способ для воспроизведения аудио в зависимости от конкретной платформы. Ну и не менее важной состовляющей нашего стека является Backbone, вернее то во
что мы его превратили. Но об этом попозже.
Why Backbone?
Sunday, September 29, 13
_Но почему Backbone спрсите вы? Ведь Backbone это не фреймворк, а всего лишь небольшая библиотека, абсолютно не даюшая каких-либо рекоммандаций о том, как ваше приложение
должно быть структурировано, какие практики применять, чтобы эффективно работать с памятью, на худой конец, просто, чтобы работа над приложением не превратилась в кошмар. А
сначала так оно и было.
Sunday, September 29, 13
Мы имели довольно печальный опыт работы с Backbone в первой мобильной версии, где мы собрали все возможные подводные камни. Это было больше 2.5 лет назад. Тогда все это
движение вокруг SPA, сейчас набравшее уже очень большую скорость, только начиналась. Никто толком не знал, как это делать правильно. Мы тоже не знали. Ни Chaplin, ни
Marionette, ни прочих фреймворков еще не было. Ember.js был только в зачаточном состоянии (только-только лишь был создан форк от SproutCore, который обещал тоже стать чем-то
большим и громоздким, чтобы стать фундаментом для проектов под разные платформы.
Sunday, September 29, 13
_Но, в следующем проекте, виджете, мы опять выбрали Backbone, потому что он имеет ряд достоинств
SOUNDCLOUD
Backbone
• small footprint
• Observable mixin
• doesn’t depend on third-party libraries
• helpful methods for data manipulation
• easy to understand and extend
• but not opinionated about app structure
Sunday, September 29, 13
- он небольшой, что довольно важно для мобильных устройств
- в нем хорошо реализован паттерн Observable, что важно для коммуникации между модулями приложения
- в нем есть компактный, но вполне достаточный, арсенал для работы с данными
- практически не зависит от сторонних библиотек, что опять-таки немаловажно для мобильных платформ_
_Но для организации большого приложения этого недостаточно. Нужен дополнительный уровень абстракции, особенно на уровне view компоненты. Из-за необходимости иметь более
высокйи уровень абстракции и появился Trombone
Trombone
Sunday, September 29, 13
_Что такое Trombone? Это наш форк backbone'а.
Он превращает небольшую библиотеку в небольшой, но полноценный фреймворк
Trombone удобен для создания как небольших проектов (виджет, мобильная версию), так и довольно крупных - главная версия сайта, которая была написана на его
основе, и до сих пор развивается без особых проблем.
SOUNDCLOUD
Trombone > Backbone
• dev tools
• build tools
• core view component
• view life cycle management
• layout management
• data management
More details here - snd.sc/16i6mhU
Sunday, September 29, 13
Trombone (в отличие от Backbone) предоставляет
- удобный набором инструментов для разработки
- и сборки проекта
- четко описывает подход для структурирования приложения
- и предлагает более понятный подход к работе с view компонентой,
на которой я и сфокусируюсь в своем докладе
SOUNDCLOUD
Distribution of modules in NEXT
315
views
50
collections
37
models
Sunday, September 29, 13
потому что это самая важная часть клиентской архитектуры, пожалуй, любого проекта.
Но при этом ее базовая реализация в Backbone черезчур минималистична.
В этом легко убедиться, взглянув в исходный код Backbone'а -- это всего лишь 70 строчек кода, без комментариев и того меньше. По сути это заглушка, с которой
можно делать, все что угодно. Но что делать не совсем понятно. Backbone не вносит ясности в работу с view, хотя она очень нужна. Эта чрезмерная гибкость - слабость и
одновременно сила Backbone'а.
SOUNDCLOUD
Trombone Views — they do a lot
• define a standard view declaration
• rendering of templates
• establish a data source
• support the nested views
• setup and teardown event listeners
• don’t depend on the context
Sunday, September 29, 13
Trombone убирает эту гибкость и вносит ясность в работу с view.
Базовая view компонента в Trombone делает много того, о чем не позаботился Backbone, а именно:
- более формализованный способ создания компоненты
- отрисовка шаблонов
- определение источника входных данных
- поддержка вложенных view
- установление и корректное удаление связи с DOM элеменотом и моделью/коллекцией
- обеспечение независимоти от контекста использования
Независимость View компоненты обеспечивается ее изолированностью, и минимальным набором входных данных. Как правило, это всего лишь идентификатор модели
или коллекции, данные из которой эта view отображает в той или иной форме.
SOUNDCLOUD
View declaration in Trombone
var Sound = require('models/sound');
var View = require('lib/view');
var WaveformView = module.exports = View.extend({
template: require('views/sound/waveform.tmpl'),
css: require('views/sound/waveform.css'),
ModelClass: Sound,
requiredAttributes: [‘waveform_url’]
});
Sunday, September 29, 13
Давайте рассмотрим создание простой view компоненты.
Кроме отдельного CSS, JS файла и шаблона, view так же в явном виде задает тип данных, которые она отображает. Не сами данные, а именно тип.
_Модули определяются в стиле CommonJS,
который во время разработки и сборки проекта на лету компилируются в формат AMD, совместимый с RequireJS. Использование стиля CommonJS позволяет очень легко
переиспользовать модули как на клиенте, так и на сервере (во время сборки, к примеру), и избавляет от необходимости писать лишний код.
CSS стили и шаблон тоже запрашиваются как отдельные модули, так же компилируемые в AMD формат. С шаблоном все просто, так как скомпилированный handlebars-
шаблон есть просто функция, которая и возвращается модулем,
¿ ?CSS ↝ AMD
Sunday, September 29, 13
а как же быть с CSS?
SOUNDCLOUD
Write CSS but serve AMD
.myView {
padding: 5px;
color: #f0f;
}
.myView__foo {
border: 1px solid #0f0;
}
Result is an AMD module
define("views/myView.css", [], function (require, exports, module) {
var style = module.exports = document.createElement('style');
style.appendChild(
document.createTextNode('.myView { padding: 5px; color ... } ...');
);
})
Sunday, September 29, 13
Приминив похожий подход, В результате компиляции исходный CSS файл загружается в виде AMD модуля. Единственная функция, определяемая данным модулем, это
функция, которая динамически добавляет стили данной view компоненты в документ.
SOUNDCLOUD
Write CSS but serve AMD
var WaveformView = module.exports = View.extend({
// it’s a `style` element
css: require('views/sound/waveform.css'),
render: function() {
if (!this.css.parentNode) {
document.body.appendChild(this.css);
}
// ...
}
});
Sunday, September 29, 13
Стили для данной компоненты активируются в момент, когда данная view отрисовывывается впервые.
SOUNDCLOUD
Nested views
<h1>{{title}}</h1>
{{view "view/waveform" id=123}}
var Waveform = require('views/waveform');
var waveform = new Waveform({ resource_id: 123 });
soundView.addSubview(waveform);
soundView.$el.append(waveform.render().el);
You can add subviews both in a template and in js file
Sunday, September 29, 13
View компоненты могут быть составными и включать в себя дочерние view. Глубина их вложенности произвольная.
В нужный момент Trombone позаботиться о том, что все дочерние view корректно осовободят занимаемые ими ресурсы и так далее.
Как уже было сказано, декларация view пытается свести к минимуму набор входных параметров, необходимых для ее создания. Данный слайд иллюстрирует 2
возможных способа создания вложенной view компоненты. Один способ прогаммный, а другой декларативный с помощью view helper'а прямо в шаблоне.
В обоих случаях достаточно указать лишь идентификатор трека, для которого мы хотим создать waveform'у. Не нужно передавать ссылку на уже созданную Sound
модель, или создавать новую, если таковой нет. Об этом позаботиться сама view компонента, сам Trombone.
SOUNDCLOUD
Looks good, right?
var Sound = require('models/sound');
var View = require('lib/view');
var WaveformView = module.exports = View.extend({
template: require('views/sound/waveform.tmpl'),
css: require('views/sound/waveform.css'),
ModelClass: Sound,
initialize: function(args) {
// new instance is created for each view
this.model = new this.ModelClass({ id: args.id });
this.setupModelListeners();
}
});
Sunday, September 29, 13
Такой подход очень удобен, не правда ли?! Но к сожалению содержит одну большую проблему._
_Каждый раз будет создаваться новый экземпляр модели. Много экземпляров ведет к чрезмерному потреблению пямяти, что приводит к более частому срабатыванию
GC. Ну и наконец, создание нового экземпляра разрушает саму идею синхронизации состояний между view компонентами, представляющими один и тот же объект,
одни и те же данные
Sharing is caring
Sunday, September 29, 13
Другими словами, нам надо найти способ использовать один и тот же экземпляр модели в контексте разных view компонент.
Many views, one model
Sunday, September 29, 13
_На этом слайде, я подсветил почти все view, которые тем или иным образом представляют один и тот же трек. Наша задача - передать экземпляр модели,
предствляющей данный трек, в каждую из этих view. Но как этого добиться?_
SOUNDCLOUD
Identity Map
IdentityMap.applyTo(Sound, {
hashFn: function(attrs) {
return attrs.id || null;
}
});
var s1 = new Sound({ id: 123, genre: 'Ambient' }) ,
s2 = new Sound({ id: 123, artist: 'Brian Eno’ });
s1 === s2; // true - these are the exact same objects.
Sunday, September 29, 13
_Очень просто_
_На помощь приходит паттерн IdentityMap, впервые описанный Мартином Фаулером., который, учитывая всю выразительность JavaScript'а, позволяет очень элегантно его
реализовать и решить нашу проблему._
_В очень очень очень упрощенной версии -- IdentityMap просто изменяет поведение конструктора таким образом, что он возвращает экземпляр нужной нам модели,
если такая модель уже существует, в противном случае -- просто создает ее. Чтобы определить наличие или отсутствие нужной нам модели, каждый ресурс имеет
уникальный индентификатор, hash, который высчитывается на основе входных аттрибутов. По умолчанию это аттрибут id._
SOUNDCLOUD
Don’t use it in production :)
var store = {};
Sound = function(attributes.id) {
var id = attributes.id;
// check if this model has already been created
if (store[id]) {
return store[id]; // ← return the other one
}
// otherwise, store this instance
store[id] = this;
// regular instantiation...
this.initialize();
}
};
Sunday, September 29, 13
_В крайне упрощенной форме, это то, как выглядит конструктор модели, к которой применен IdentityMap. Повторюсь, что это очень упрощенная версия, приведенная
лишь для иллюстрации основной идеи.
SOUNDCLOUD
IdentityMap is applied to subclasses
IdentityMap.applyTo(SoundModel, {
hashFn: function(attrs) {
return attrs.id || null;
}
});
// Subclasses inherit the “IdentityMap” behaviour
PromoSound = SoundModel.extend({});
s1 = new PromoSound({id : 123}),
s2 = new PromoSound({id : 123});
s1 === s2; // true
Sunday, September 29, 13
При этом нет необходимости применять этот миксин к каждой модели в вашем проекте.
Примение IdentityMap только к базовым классу модели автоматически добавляет аналогичное поведение на все его подклассы.
SOUNDCLOUD
Identity Map
var s1 = new Sound({ id: 123, genre: 'Ambient' }) ,
s2 = new Sound({ id: 123, artist: 'Brian Eno’ });
s1 === s2; // true - these are the exact same objects.
s1.get('genre'); // 'Ambient'
s2.get('artist'); // 'Music for Airports'
Sunday, September 29, 13
_Применив IdentityMap миксин, мы добились того, что хотели -- оператор new возвращает нужный нам экземпляр модели._
Wait a second
Isn’t it a massive memory leak?
Sunday, September 29, 13
_Но по-прежнему, такое решение не идеально. В случае SPA такой подход может банально привести к большой утечке памяти,
SOUNDCLOUD
Wait a second
var store = {};
Sound = function(attrs) {
var id = attributes.id;
// check if this model has already been created
if (store[id]) {
return store[id];
}
// otherwise, store this instance
store[id] = this; // ← it’s a massive a memory leak
// regular instantiation...
this.initialize();
}
};
Sunday, September 29, 13
потому что модель, сохраненная в кеше, никогда не будет подчищена GC._ _Каждый созданный экземпляр будет оставаться в памяти на протяжении всей сессии,
протяженность которой в случае SoundCloud иногда может составлять сутки._
Sunday, September 29, 13
_А если это так, то через какое-то время приложение просто станет недоступным._
SOUNDCLOUD
Release it!
var map = {},
counts = {}; // <-- usage counter
function Sound(id) {
if (map[id]) {
++counts[id];
return map[id];
}
map[id] = this;
counts[id] = 1;
this.initialize();
}
Sound.prototype.release = function () {
--counts[this.id];
}
Sunday, September 29, 13
_Но на самом деле все не так уж страшно. Как я уже сказал на предыдущем слайде была показана упрощенная версия модели, к которой был применен IdentityMap. На
самом деле, реальная, а не упрощенная реализация этого миксина, добавляет несколько служебных методов.
Один из них это метод *release*.
Его цель очень проста -- оповестить хранилище созданных моделей о том, что данный экземпляр больше не используется. Все это может показаться довольно
запутанным, но на самом деле, корректное освобождение ресурсов в Trombone происходит автоматически. Это делается на уровне базового view класса, к примеру, при
переходе с одной страницы на другую._
Garbage Collection
once a minute
Sunday, September 29, 13
_Затем, когда счетчик использования конкретной модели достиг нуля, она удаляется из памяти. Величина интервала срабатывания GC равна 1 минуте.
SOUNDCLOUD
Transition between layouts
Sunday, September 29, 13
Мгновенная очистка памяти не делается, так как есть вероятность того, что только что полностью освобожденная модель или коллекция будут использоваться на
следующей странице.
Этот подход очень полезен, так как он не заставляет нас запрашивать данные, необходимые для отрисовки страницы, в случае частых переходов назад-вперед
SOUNDCLOUD
Browsers are getting better
function leak() {
var A = {}, B = {};
A.b = B;
B.a = A;
}
// Circular reference does exist
// but A and B will be cleaned up
// as you can't reach either from global scope
leak();
Sunday, September 29, 13
_Мы знаем, что браузеры не стоят на месте._
_И, к примеру, круговая ссылка, пример которой здесь приведен, уже не является проблемой для современных JavaScript движков. Они не влекут утечек памяти, но это
по-прежнему JavaScript, который в силу своей природы, всегда оставляет возможность их создать._
SOUNDCLOUD
No longer used != No longer reachable
var View = require('lib/view'),
Sound = require('models/sound');
var WaveformView = module.exports = View.extend({
setup: function() {
this.sound = new SoundModel({ id: 123 });
},
// sound model is not disposed properly later on
// as we mistakenly redefined `dispose` method
dispose: function() {}
});
Sunday, September 29, 13
_По-прежнему очень велика вероятность, что, к примеру, экземпляр модели или коллекции не был в конечном итоге корректно "освобожден". Это значит, что он
останется в памяти на протяжении всего времени работы приложения.
Данный, возможно, довольно искуственный пример, иллюстрирует вполне жизненный случай. Все делают ошибки. В такие моменты на помощь приходят тесты и прочие
инструменты_
SOUNDCLOUD
Static analysis tools to the rescue
$ ./tools/memory-leak-finder views/waveform.js
Warnings: 1
Sound model is not disposed.
Release it in `dispose` method.
A very nice read - http://research.google.com/pubs/pub40738.html
Global leaking detector - https://github.com/kesla/node-leaky
Sunday, September 29, 13
_Кроме unit-test'ов, проверяющих корректное освобождение ресурсов, частью нашего предрелизного тестирования является коммандная утилита, которая производит
статический анализ кода. Она строит AST c помощью Esprima, анализирует его, находит объекты, создание которых может привести к утечке памяти и проверяет, были
ли они корректно освобождены. Если это не так, то оповещает об этом и предлагает решение. Это далеко не идеальный инструмент. В основе ее работы лежит знание
основных паттернов, которые мы используем для создания моделей или коллекций, а так же некоторая степень эвристики. Вдохновила на создание такого инструмента
утилита JSWhiz - расщирение для Google Closure compiler. Это утилита, которой активно пользуются разработчики команды Google Mail, столкнувашиеся с проблемами
утечки памяти. Это довольно занятный инструмент, ссылка на подробное описание того, как он работает, приведена на этом слайде._
SOUNDCLOUD
Monitor memory info
window.peformance.memory = {
// memory JS heap is limited to
jsHeapSizeLimit : 793000000,
// memory allocated by JS (including free space)
usedJSHeapSize : 18200000,
// memory currently being used
totalJSHeapSize : 39600000
}
setInterval(function() {
if (usedJSHeapSize/jsHeapSizeLimit > 0.9) {
console.log(‘He is almost dead, Jim!’);
}
}, 30 * 1000);
Sunday, September 29, 13
_Но естественно, нет идельаного инструмента, особенно для такой нетревиально задачи, как обнаружение и предотвращение утечек памяти. В этой связи, имеет смысл
следить за количеством потребляемой памяти. Благо сейчас есть нужные API для таких целей._
_Google Chrome предоставляет доступ к информации об использованной памяти. Поэтому грех этим не пользоваться. Мы используем эту информацию для отслеживания
динамики использования памяти на определенной выборке пользователей с разной степенью активности._
_Имея доступ к этим трем свойствам, можно легко определить как часто, пользователи близки или столкнулись с ситуацией, когда приложение стало абсолютно
неотзывчивым._
_jsHeapSizeLimit - максимальное доступный размер памяти_
_totalJSHeapSize - общий размер памяти, выделенный на процесс, включая освобожденную память, не занимаемую никакими объектами_
_usedJSHeapSize - общий размер памяти, используемый в текущий момент, включая внутренние объекты V8._
_Когда _usedJSHeapSize -> jsHeapSizeLimit, есть шанс увидеть прекрасную страницу. He's dead, Jim!_
Sunday, September 29, 13
_Чтобы избежать такой ситуации, есть ряд простых рекоммендаций, о которых было сказано уже тысячу раз, но о которых есть смысл напомнить в контексе этого
слайда._
Don’t modify the shape of an object
Sunday, September 29, 13
_Изменяя структуру объекта в рантайме, мы переводим объект из категории быстрых объектов в категорию медленных.
SOUNDCLOUD
Inline caching in V8
function Point(x, y, z) {
// introduce all properties in the constructor
// to enable “fast-object” mode.
this.x = x;
this.y = y;
this.z = z;
}
var point = new Point(11, 22, 33);
point
11
22
33
Point
‘x’
‘y’
Point Point
‘x’
‘y’
‘z’
Point
‘x’
x y z
Sunday, September 29, 13
Одной из особенностей двигателя V8 является то, что он для доступа к свойствам объекта пытается использовать не hash-образную структуру данных, а линейную
последовательность аттрибутов, которая содержит ссылку на описание layout’а объекта. Такой подход значительно увеличивает скорость доступа к свойствам и методам
JS объекта.
SOUNDCLOUD
Dictionary mode in V8
var point = new Point(11, 22, 33);
// forcing the object into dictionary mode
delete point.z;
// accessing a hash table is much slower
// than accessing a field at a known offset.
console.log(point.x);
point
11
22
HashMap
x : 11
y : 22
Sunday, September 29, 13
Но есть ряд действий, которые могут перевести объект из разряда быстрых в разряд медленных.
Одно из тахик действий - это удаление свойства объекта в рантайме. После этого структура данных, используемая для представления данного объекта меняется на
HashMap, что в разы замедляем доступ к его свойствам и делает работу с такими объектами с точки зрения потребления памяти не эффективной. Рассмотрим пример.
SOUNDCLOUD
Less is more
var FastSound = function (title, duration) {
this.title = title;
this.duration = duration;
}
var SlowSound = function (title, duration) {
this.title = title;
this.duration = duration;
};
var fast = new FastSound('Hommage a Rameau', '350000');
var slow = new SlowSound('Hommage a Rameau', '350000');
delete slow.duration;
Sunday, September 29, 13
_Кажется, что удалив свойство, которое нам не нужно, мы осводим память, на деле же мы изменили структуру данных, так называмый HiddenClass, что переводит данный
объект в категорию медленных. Добавление свойств за рамками конструктора замедляет чтение этих самых свойств.
Это довольно интересная тема, безусловно достойная более чем одного слайда, но чтобы не быть голосовным, вот цифры._
SOUNDCLOUD
Less is more
Constructor Objects count Shallow size Retained size
--------------------------------------------------------
SlowSound 10000 120 000 4 240 000
FastSound 10000 200 000 200 000
Slow object is using up 20 times more memory
Sunday, September 29, 13
После изменения структуры объекта, он занимает памяти почти в 20 раз больше. Это факт, с которым тяжело не считаться._
Unbind event listeners
Sunday, September 29, 13
Не менее важно удалять все обработчики событий перед тем, как view компонента (DOM element) будет удалена.
SOUNDCLOUD
Trombone takes care of it
View = module.exports = Backbone.View.extend({
✂ ✂ ✂ ✂ ✂
dispose: function() {
// remove event listeners
this.teardownModelListeners();
this.teardownCollectionListeners();
// tell GC that we don't need these resources anymore
this.model.release();
this.collection.release();
}
✂ ✂ ✂ ✂ ✂
})
Sunday, September 29, 13
Trombone это делает автоматически, удаляя все обработчики событий, добавленные к модели, коллекции и DOM элементу.
Manage local cache properly
Sunday, September 29, 13
Если вы полагаетесь на кэш, хранящий ссылки на объекты, то не забывайте освобождать ресурсы, которые вам больше не нужны. По сути это то, чем занимается
внутренний GC, подчищающий ненужные коллекции и модели.
Recycle objects
Sunday, September 29, 13
Иногда, можно переиспользовать уже созданный объект, вместо того, чтобы создавать новый.
SOUNDCLOUD
Use Object Pools
var soundPool = [], activeSounds = [];
var sound = getSound();
✂
releaseSound(sound);
sound = null;
function getSound() {
var sound;
if (soundPool.length) {
sound = soundPool.pop();
sound.reset(); // important!!!
} else {
sound = new Sound();
}
return activeSounds.push(sound);
}
function releaseSound(sound) {
var index = activeSounds.indexOf(soundPool);
if (index > -1) {
activeSounds.splice(index, 1);
}
}
Sunday, September 29, 13
Cоздание нового объекта напрямую связанно с процессом выделения памяти, что приближает нас к срабатыванию GC.
Этот паттерн нашел большое применение среди разработчиков игр, где часто приходиться иметь дело с очень большим кол-вом объектов при довольно ограниченном
кол-ве памяти.
Единственное о чем не стоит забывать при использовании такого подхода, что ранее использованный объект нужно вернуть в исходное состояние.
Premature optimization
is the root of all evil
Sunday, September 29, 13
Но, даже зная все это, никогда не стоит забывать, что не надо заниматься оптимизацией ради оптимизации. Ей стоит заниматься только тогда, когда в конечном итоге от
этого выигрывает пользователь.
И в этой связи я хотел бы рассказать вам об истории одной оптимизации, которая имела место прямо перед запуском новой версии SoundCloud в продакшен.
Umm yeah, comments
Sunday, September 29, 13
Это история о способе отображения комментариев поверх waveform'ы -- это своего рода визитная карточка SoundCloud'а.
SOUNDCLOUD
Many men, many minds
"SoundCloud users comments attached to songs are
iconic and life changing"
Sunday, September 29, 13
Кто-то не может представить SoundCloud без этого элемента.
SOUNDCLOUD
When N gets big
2600 comments
2450 comments
Sunday, September 29, 13
Но я сейчас не об этом, а том, что в тот момент, когда продукт еще находился в закрытом бета тестировании, реализация отрисовки этих комментариев была нашей
головной болью, по причиние того, что она сильно замедляла скроллирование страницы. Нас это сильно расстраивало.
SOUNDCLOUD
When N gets big
Template-based approach
• based on ListView component
• list item is 4 nodes: li > a > img + span
• 3 sounds: 4080 * 4 = 16,320 nodes
• 272,000 nodes per stream (50 sounds)
Sunday, September 29, 13
Мы понимали, что с таким решением в продакшен идти нельзя. Но в тоже самое время причина этой медленности была очевидна и лежала на поверхности --
cтандартный, основанный на классиеческом шаблоне способ отрисовки waveform'ы и комментрариев приводил к большому количеству DOM элементов, что и служило
причиной медленного скролирования.
SOUNDCLOUD
New waveform
Sunday, September 29, 13
ну а после появления еще одного требования -- сделать вейвформу абсолютно гибкой, для того чтобы вписать ее в произвольный фон -- стало ясно
yes we canvas!
Sunday, September 29, 13
что пришло время портировать отрисовку вейформы и комментариев на элемент canvas.
и тем самым попробовать решить две проблемы
- максимально минимизировать число DOM элементов (в иделе в одному)
- сделать waveform’у более гибкой
SOUNDCLOUD
Old waveform
Sunday, September 29, 13
Стоит заметить, что изображение с которым нам приходилось работатать раньше, это всего лишь маска, которая накладывалась поверх нужного фона.
SOUNDCLOUD
Old waveform
Sunday, September 29, 13
Стоит заметить, что изображение с которым нам приходилось работатать раньше, это всего лишь маска, которая накладывалась поверх нужного фона.
SOUNDCLOUD
Solution:
Canvas!
Problem:
Data!
Sunday, September 29, 13
Поэтому первой проблемой, с которой мы столкнулись, было отсутствие пиксельных данных, которые можно использовать для рисования waveform’ы на сanvas.
SOUNDCLOUD
Solution:
Images as data source!
Problem:
Performance!
Sunday, September 29, 13
Решение было тривиально -- использовать пискельные данные исходного негативного изображения. Мы решили просто находить положение первого непрозрачного
пикселя для каждой из частот, чтобы воссоздать нужный нам образ, но здесь нас поджидали большие проблемы по производительности.
SOUNDCLOUD
Gradual improvements
• Most efficient detection algorithm
• Cached scaled image
• Typed Arrays
• WebWorkers (Chromium Issue #51171)
Sunday, September 29, 13
Ни оптимизирование алгоритма,
ни кеширование даннных, ни использование типизированных массивов, ни даже использование WebWorkers нам не помогало. Последнее к сожалению невозможно было
использовать для решения этой задачи, потому что при передаче пиксельных данных в web worker, начиналась стремительная утечка памяти.
И мы уже было почти сдались, пока не решили зайти к этой проблеме с другой стороны
SOUNDCLOUD
Think about what’s important
• Moved calculation to the back end
• Ported algorithm to Go
• Front end recieves JSON, everyone happy
Sunday, September 29, 13
а именно -- перенести всю логику анализа пиксельных данных на backend, портировав алгоритм обнаружения грани или первого непрозрачного пикселя на Go. В итоге
Frontend просто получал готовый JSON, удобный для того, что отрисовать waveform’у в произвольном виде.
SOUNDCLOUD
Before: 1352ms
After: 10ms
Sunday, September 29, 13
Как результат, скорость отрисовки особо тяжелых страниц улучшилась почти в 100 раз.
Я считаю, что это очень хороший пример оптимизации, который доказывает, что не все задачи надо решать только средствами, доступными в браузере.
Оптмизировав скорость скроллирования с помощью отрисовки waveform'ы в canvas, мы невооруженным взглядом почувстовали улчшение.
Is it smooth enough?
Sunday, September 29, 13
Но на глаз пологаться можно, но не всегда удобно и оправданно. Именно по этой причиние мы решили автоматизировать замер скорости скроллирования с помощью
инструмента Telemetry.
SOUNDCLOUD
Telemetry
$ cd $CHROMIUM_SOURCE/tools/perf/page-sets
$ cat soundcloud.json
{
R "description" : "Is it smooth enough?",
R "smoothness" : { action : "scroll" },
R "pages"RR : [
R R { "url" : "https://soundcloud.com/akovalev" }
R ]
}
$ ../run-measurement smoothness soundcloud.json -o result.csv
Telemetry is a Python wrapper around of the DevTools Remote Debugging Protocol
Sunday, September 29, 13
Telemetry - это фреймворк для кроссплатформенного тестирования производительности в Chrome браузере. По сути это обертка над удаленным протоколом Chromium
DevTools, написанная на Python. Предоставляет набор готовых бенчмарков, используемых командой разработчиков Chromium для тестирования производительноси
отрисовки интерфейса. Так же позволяет реализовать механизм залогирования на сайт, что для тестирования некоторых случаев очень полезно.
Тесты описываются в json формате, где указывается адрес тестируемой страницы и действие, которое будет выполнено. Запуск осуществляется из коммнадной строки.
Результат сохраняется в текстовый файл в формате CSV, который не сложно распарсить и отобразить в виде графика.
SOUNDCLOUD
Telemetry
We're particularly interested in:
• mean_frame_time
• dropped_percent
• jank_count
Sunday, September 29, 13
_Среди предоставляемых метрик есть ряд очень полезных и наиболее интересных с точки зрения анализа поведения интерфейса. Это_
_mean_frame_time - среднее значение fps_
_dropped_percent - кол-во неудачных фреймов в процентах_
_jank_count - и в абсолютной величине_
Telemetry - это очень интересный и полезный инструмент, полный потенциал которого пока не раскрыт. Очень обидно, что на его тему пока очень мало материалов, и
что он незаслуженно обделен вниманием веб разработчиков, т.к. он предоставляет широкий арсенал для анализа производительности вашего интерфейса.
Насколько мне известно, кроме SoundCloud это инструмент используется в проекте Adobe Topcoat, которые замеряет производительность веб компонент.
Ship it!
Sunday, September 29, 13
_В самом начале доклада я уже сказал, что наш сайт в продакшене является абсолютно статическим. Превращение проекта в абсолютно статический сайт происходит во
время сборки, о который я и хочу сейчас рассказать чуть-чуть подробно. Этап сборки абсолютно автоматизирован и не требует ручной поддержки, что позволяет нам
производить релизы по несколько раз в день безо всяких проблем. Особенно когда под рукой у нас есть такой замечательный инструмент как Bazooka_
SOUNDCLOUD
Bazooka (An In House Heroku)
$ git push bazooka stable
remote: change-set accepted
...
remote: building...
...
remote: build finished
Bazooka is a deployment solution for 12 factors apps - http://12factor.net/
More details on bazooka - goo.gl/pfSTAf
Sunday, September 29, 13
_В двух словах, Bazooka это наш внутренний аналог Heroku, с помощью которого деплоятся все сервисы SoundCloud. Делается это одной командой, в результате которой
проект собирается и раскатывается по нужному количеству хостов._
SOUNDCLOUD
Structure of Trombone project
app
!"" lib
!"" models
!"" collections
!"" layouts
!"" views
!"" vendor
!"" application.js
#"" index.html
var coreDeps = getDeps('application.js'),
layoutSeeds = getLayoutSeeds('layouts');
writePackage(coreDeps);
layoutSeeds.forEach(function(layoutSeed) {
var layoutDeps = getLayoutDeps(layoutSeed);
writePackage(layoutDeps);
});
function getLayoutDeps(layoutSeed) {
var layoutDeps = getDeps(layoutSeed);
return _.difference(layoutDeps, coreDeps)
}
$ trombone build
Sunday, September 29, 13
_А что же происходит в процессе самой сборки?_
_Я уже упоминал Uglify.js, который мы используем, к примеру, для компиляции CommonJs/CSS файлов в AMD модули и для нахождения утечек памяти. Но наибольшую
пользу этот инструмент приносит нам на этапе сборки проекта. Благодаря Uglify.js наш build script обладает огромной гибкостью, в него можно добавлять и реализовать
почти все что угодно._
_Вот так вот выглядит структура любого Trombone проекта. Весь проект разбит на независимые модули. Входной точкой для приложения является файл application.js, с
анализа которого все и начинается. Build script строит AST деревро этого файла, рекурсивно находит всего его зависимости, и разбивает их на 3 проектных сборки:
вендорные файлы, шаблоны, и все остальное. _
SOUNDCLOUD
Building
app
!"" lib
!"" models
!"" collections
!"" layouts
!"" views
!"" vendor
!"" application.js
#"" index.html
public
!"" layouts
$   !"" home-f3c2-842c8906.js
$   !"" listen-2ED1-abef2203.js
$   #"" stream-KJsr-540a7923.js
#"" core
!"" sc-wfJV-abef2203.js
!"" vn-iAHt-e1684d31.js
#"" tm-idSh-540a7923.js
$ trombone build
Sunday, September 29, 13
Затем аналогичная процедура проделыватеся для каждой из страниц сайта: собираются зависимости для каждой из страниц, за вычетом базовых, которые уже были
включены в проектную сборку, в результате чего и создается постраничная сборка.
var client_id = __ENV__ === ‘prod’ ? ‘abc123’ : ‘def456’;
if (__DEBUG_MODE__) {
console.log(a);
}
uglify.ast_mangle(ast, { defines: {
__ENV__ : ‘prod’,
__DEBUG_MODE__ : false
}});
uglify.ast_squeeze(ast);
var a = ‘abc123’;
Dead code removal
Sunday, September 29, 13
_Но и это еще не все. Во время записи каждой из сборок на диск, код претерпивает ряд изменений в зависимости от переменных окружения. К примеру, части кода,
используемые только для отладки, удаляются с помощью такой просто операции и не попадают в продакшен._
Transform it!
Sunday, September 29, 13
Вообще идея транфсормации кода содержит всебе очень большой потенциал, который особенно уместно применять на пути вашего кода в продакшен.
AST is easy
• Meaningful code vs Regex magic
• Easy to change
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "soundId"
},
"init": {
"type": "Literal",
"value": 123,
"raw": "123"
}
}
]
var soundId = 123;
Sunday, September 29, 13
_Возможность работать с кодом на уровне AST -- это уровень абстракции, с которым куда удобнее работать, чем с исходным кодом, представленным в виде строки._
_Трансформации, которые вы можете применить к коду на его пути от вашего редактора
к браузеру, практически не ограничены, по крайней мере они не ограничены знанием регулярных выражений,
_Особенно, если у вас под рукой Esprima._
Esprima
• ECMAScript parser written in ECMAScript
• Accepts ECMAScriptcode and generates AST
• Meaningful code vs Regex magic
• compatible with Mozilla Parser AST
• Harmony branch
Sunday, September 29, 13
К примеру если вы вооружены парсером Esprima,
Это парсер JavaScript, написанный на JavaScript.
Так исторически сложилось, что на текущий момент для анализа кода мы используем Uglify.Js
Но сейчас я бы советовал работать с Esprima, в сторону который мы тоже смотрим. Она более активно развивается и предоставляет более удобный формат дерева,
совместимый с Mozilla Parser AST.
Esprima’s best friends
escodegen
escodegen
AST Code
estraverse
estraverse.traverse(ast, {
enter: function(node, parent) {
},
leave: function(node, parent) {
}
});
SourceMap
Sunday, September 29, 13
Незаменнимым дополнением в работе с Esprima являются две библиотеки, позволяющие - делать обход дерева
- и генерировать JS код на основе AST
Давайте рассмотрим пример простой трансформации, которая добавляет логирование выполнения всех функций в вашем приложении, используя эти библиотеки.
Function instrumentation
function addLogging(code) {
var ast = esprima.parse(code);
estraverse.traverse(ast, {
enter: function() {
if (node.type === 'FunctionDeclaration'
|| node.type === 'FunctionExpression') {
addBeforeCode(code);
}
}
});
return escodegen.generate(ast);
}
function addBeforeCode(node) {
var name = node.id ? node.id.name : '<anonymous function>',
beforeCode = "console.log('Entering " + name + "()')"
beforeNodes = esprima.parse(beforeCode).body;
node.body.body = beforeNodes.concat(node.body.body);
}
Sunday, September 29, 13
Вот с помощью всего каких-то 15 строчек читаемого кода
мы достигли своей цели -- добавили логирование всех функций.
Function instrumentation
FunctionDeclaration
Identifier BlockStatement
Array
Statement Statement
...
Sunday, September 29, 13
По сути же мы просто
- создали дерево, представляющее исходный код,
- видоизменили его,
- и сгенерировали на основе нового дерева нужный нам код
Function instrumentation
FunctionDeclaration
Identifier BlockStatement
Array
Statement Statement
...
Statement
esprima
estraverse
escodegen
Code
AST
Modified AST
Code
Sunday, September 29, 13
По сути же мы просто
- создали дерево, представляющее исходный код,
- видоизменили его,
- и сгенерировали на основе нового дерева нужный нам код
Thanks!
Sunday, September 29, 13
За время доклада мы проследили
- как формировались основные решений, сформировавшие архитектуру новой версии SoundCloud’а
- какие паттерны мы применяем для ее реализации
- какие инструменты и практкики мы используем, чтобы приложение оставалось отзывычивыми на протяжении долгой сессии
Я надеюсь, что вы вынесли хотя бы одну идею, которую захотите попробовать в вашем проекте, ну и учитывая то , что некоторые аспекты были освещены немного
вскользь -
Alexander Kovalev
sasha[at]soundcloud[dot]com
❓
Sunday, September 29, 13
я с удовольствием отвечу на все ваши вопросы

Mais conteúdo relacionado

Semelhante a "Добро пожаловать в SoundCloud!". Александр Ковалёв, SoundCloud

Создание дистрибутивов Drupal. Почему, зачем и как?
Создание дистрибутивов Drupal. Почему, зачем и как?Создание дистрибутивов Drupal. Почему, зачем и как?
Создание дистрибутивов Drupal. Почему, зачем и как?
Alexei Gorobets
 
Cerebro general overiew rus
Cerebro general overiew rusCerebro general overiew rus
Cerebro general overiew rus
CineSoft
 
Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016
Кирилл Толкачёв
 
CodeFest 2014. Сибирев А. — Управление инфраструктурой под Cocaine
CodeFest 2014. Сибирев А. — Управление инфраструктурой под CocaineCodeFest 2014. Сибирев А. — Управление инфраструктурой под Cocaine
CodeFest 2014. Сибирев А. — Управление инфраструктурой под Cocaine
CodeFest
 
Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0
Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0
Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0
beloslab
 
сравнение Mac-os-x-linux-ubuntu
сравнение Mac-os-x-linux-ubuntuсравнение Mac-os-x-linux-ubuntu
сравнение Mac-os-x-linux-ubuntu
Anyuta Roschina
 
Qomo AV-Club 06.2012
Qomo AV-Club 06.2012Qomo AV-Club 06.2012
Qomo AV-Club 06.2012
QOMO
 
Remote (dev)tools своими руками
Remote (dev)tools своими рукамиRemote (dev)tools своими руками
Remote (dev)tools своими руками
Roman Dvornov
 
сравнение Mac os x & linux ubuntu
сравнение Mac os x & linux ubuntuсравнение Mac os x & linux ubuntu
сравнение Mac os x & linux ubuntu
reeds62
 

Semelhante a "Добро пожаловать в SoundCloud!". Александр Ковалёв, SoundCloud (20)

UXPeople 2015: Юрий Ветров — Платформенное мышление
UXPeople 2015: Юрий Ветров — Платформенное мышлениеUXPeople 2015: Юрий Ветров — Платформенное мышление
UXPeople 2015: Юрий Ветров — Платформенное мышление
 
Drupal
DrupalDrupal
Drupal
 
Создание дистрибутивов Drupal. Почему, зачем и как?
Создание дистрибутивов Drupal. Почему, зачем и как?Создание дистрибутивов Drupal. Почему, зачем и как?
Создание дистрибутивов Drupal. Почему, зачем и как?
 
Alexey Savchenko, Evangelist, Unreal Engine/ Epic Games
Alexey Savchenko, Evangelist, Unreal Engine/ Epic GamesAlexey Savchenko, Evangelist, Unreal Engine/ Epic Games
Alexey Savchenko, Evangelist, Unreal Engine/ Epic Games
 
Drupal Camp Kyiv 2013. Удобная разработка drupal проекта. Полезные модули.
Drupal Camp Kyiv 2013. Удобная разработка drupal проекта. Полезные модули.Drupal Camp Kyiv 2013. Удобная разработка drupal проекта. Полезные модули.
Drupal Camp Kyiv 2013. Удобная разработка drupal проекта. Полезные модули.
 
Cerebro general overiew rus
Cerebro general overiew rusCerebro general overiew rus
Cerebro general overiew rus
 
Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016Эволюционный дизайн. Joker Students Day 2016
Эволюционный дизайн. Joker Students Day 2016
 
13 приложений для создания презентаций на планшетах
13 приложений для создания презентаций на планшетах13 приложений для создания презентаций на планшетах
13 приложений для создания презентаций на планшетах
 
Юрий Ветров — Алгоритмический дизайн
Юрий Ветров — Алгоритмический дизайнЮрий Ветров — Алгоритмический дизайн
Юрий Ветров — Алгоритмический дизайн
 
CodeFest 2014. Сибирев А. — Управление инфраструктурой под Cocaine
CodeFest 2014. Сибирев А. — Управление инфраструктурой под CocaineCodeFest 2014. Сибирев А. — Управление инфраструктурой под Cocaine
CodeFest 2014. Сибирев А. — Управление инфраструктурой под Cocaine
 
Встреча №9. AudioBus: Эволюция звука, Данил Пархоменко
Встреча №9. AudioBus: Эволюция звука, Данил ПархоменкоВстреча №9. AudioBus: Эволюция звука, Данил Пархоменко
Встреча №9. AudioBus: Эволюция звука, Данил Пархоменко
 
UXRussia2014: Юрий Ветров ― Burger-Driven Design. Фреймворк Mail.Ru для унифи...
UXRussia2014: Юрий Ветров ― Burger-Driven Design. Фреймворк Mail.Ru для унифи...UXRussia2014: Юрий Ветров ― Burger-Driven Design. Фреймворк Mail.Ru для унифи...
UXRussia2014: Юрий Ветров ― Burger-Driven Design. Фреймворк Mail.Ru для унифи...
 
Android UI Optimisation
Android UI OptimisationAndroid UI Optimisation
Android UI Optimisation
 
Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0
Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0
Konstantin Slisenko - OSGi, Equinox, Eclipse plug-in development, v 2.0
 
сравнение Mac-os-x-linux-ubuntu
сравнение Mac-os-x-linux-ubuntuсравнение Mac-os-x-linux-ubuntu
сравнение Mac-os-x-linux-ubuntu
 
Обзор и анализ инструментов проектирования и прототипирования интерфейсов
Обзор и анализ инструментов проектирования и прототипирования интерфейсовОбзор и анализ инструментов проектирования и прототипирования интерфейсов
Обзор и анализ инструментов проектирования и прототипирования интерфейсов
 
Qomo AV-Club 06.2012
Qomo AV-Club 06.2012Qomo AV-Club 06.2012
Qomo AV-Club 06.2012
 
Remote (dev)tools своими руками
Remote (dev)tools своими рукамиRemote (dev)tools своими руками
Remote (dev)tools своими руками
 
Azure - Visual Studio Team Services
Azure - Visual Studio Team ServicesAzure - Visual Studio Team Services
Azure - Visual Studio Team Services
 
сравнение Mac os x & linux ubuntu
сравнение Mac os x & linux ubuntuсравнение Mac os x & linux ubuntu
сравнение Mac os x & linux ubuntu
 

Mais de Yandex

Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...
Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...
Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...
Yandex
 
Структурированные данные, Юлия Тихоход, лекция в Школе вебмастеров Яндекса
Структурированные данные, Юлия Тихоход, лекция в Школе вебмастеров ЯндексаСтруктурированные данные, Юлия Тихоход, лекция в Школе вебмастеров Яндекса
Структурированные данные, Юлия Тихоход, лекция в Школе вебмастеров Яндекса
Yandex
 
Представление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров Яндекса
Представление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров ЯндексаПредставление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров Яндекса
Представление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров Яндекса
Yandex
 
Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...
Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...
Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...
Yandex
 
Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...
Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...
Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...
Yandex
 
Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...
Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...
Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...
Yandex
 
Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...
Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...
Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...
Yandex
 
Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...
Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...
Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...
Yandex
 
Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...
Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...
Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...
Yandex
 
Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...
Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...
Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...
Yandex
 
Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...
Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...
Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...
Yandex
 
Как защитить свой сайт, Пётр Волков, лекция в Школе вебмастеров
Как защитить свой сайт, Пётр Волков, лекция в Школе вебмастеровКак защитить свой сайт, Пётр Волков, лекция в Школе вебмастеров
Как защитить свой сайт, Пётр Волков, лекция в Школе вебмастеров
Yandex
 
Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...
Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...
Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...
Yandex
 
Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...
Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...
Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...
Yandex
 
Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...
Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...
Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...
Yandex
 
Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...
Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...
Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...
Yandex
 
Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...
Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...
Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...
Yandex
 
Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...
Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...
Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...
Yandex
 
Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...
Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...
Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...
Yandex
 

Mais de Yandex (20)

Предсказание оттока игроков из World of Tanks
Предсказание оттока игроков из World of TanksПредсказание оттока игроков из World of Tanks
Предсказание оттока игроков из World of Tanks
 
Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...
Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...
Как принять/организовать работу по поисковой оптимизации сайта, Сергей Царик,...
 
Структурированные данные, Юлия Тихоход, лекция в Школе вебмастеров Яндекса
Структурированные данные, Юлия Тихоход, лекция в Школе вебмастеров ЯндексаСтруктурированные данные, Юлия Тихоход, лекция в Школе вебмастеров Яндекса
Структурированные данные, Юлия Тихоход, лекция в Школе вебмастеров Яндекса
 
Представление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров Яндекса
Представление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров ЯндексаПредставление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров Яндекса
Представление сайта в поиске, Сергей Лысенко, лекция в Школе вебмастеров Яндекса
 
Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...
Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...
Плохие методы продвижения сайта, Екатерины Гладких, лекция в Школе вебмастеро...
 
Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...
Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...
Основные принципы ранжирования, Сергей Царик и Антон Роменский, лекция в Школ...
 
Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...
Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...
Основные принципы индексирования сайта, Александр Смирнов, лекция в Школе веб...
 
Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...
Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...
Мобильное приложение: как и зачем, Александр Лукин, лекция в Школе вебмастеро...
 
Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...
Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...
Сайты на мобильных устройствах, Олег Ножичкин, лекция в Школе вебмастеров Янд...
 
Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...
Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...
Качественная аналитика сайта, Юрий Батиевский, лекция в Школе вебмастеров Янд...
 
Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...
Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...
Что можно и что нужно измерять на сайте, Петр Аброськин, лекция в Школе вебма...
 
Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...
Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...
Как правильно поставить ТЗ на создание сайта, Алексей Бородкин, лекция в Школ...
 
Как защитить свой сайт, Пётр Волков, лекция в Школе вебмастеров
Как защитить свой сайт, Пётр Волков, лекция в Школе вебмастеровКак защитить свой сайт, Пётр Волков, лекция в Школе вебмастеров
Как защитить свой сайт, Пётр Волков, лекция в Школе вебмастеров
 
Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...
Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...
Как правильно составить структуру сайта, Дмитрий Сатин, лекция в Школе вебмас...
 
Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...
Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...
Технические особенности создания сайта, Дмитрий Васильева, лекция в Школе веб...
 
Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...
Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...
Конструкторы для отдельных элементов сайта, Елена Першина, лекция в Школе веб...
 
Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...
Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...
Контент для интернет-магазинов, Катерина Ерошина, лекция в Школе вебмастеров ...
 
Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...
Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...
Как написать хороший текст для сайта, Катерина Ерошина, лекция в Школе вебмас...
 
Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...
Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...
Usability и дизайн - как не помешать пользователю, Алексей Иванов, лекция в Ш...
 
Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...
Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...
Cайт. Зачем он и каким должен быть, Алексей Иванов, лекция в Школе вебмастеро...
 

"Добро пожаловать в SoundCloud!". Александр Ковалёв, SoundCloud

  • 1. October 2nd, Moscow Yet another Conference 2013 Welcome to SoundCloud! Sunday, September 29, 13
  • 2. Alexander Kovalev Software Engineer @ SoundCloud Sunday, September 29, 13 Добрый день! Меня зовут Александр Ковалев. Последние 2,5 года я работаю в Берлинской компании SoundCloud ОФициально моя должность называется Software Engineer. Но в основном я фокусируюсь на клиентских технологиях. За это время успел поработать почти на всех главных проектах.
  • 3. SOUNDCLOUD SoundCloud • Social audio platform • 55 mln users and growing • 240 mln unique visitors a month • 12 hours of audio are uploaded every minute • 15 engineering teams Sunday, September 29, 13 Я думаю, многие из вас слышали о нашей компании, но вот несколько фактов и цифр. SoundCloud это социальная платформа, позволяющая музыкантам делиться своей музыкой, а обычным пользователям - ее слушать. В настоящее время мы очень быстро растем. По данным нащей разведки, у нас более 50 млн пользователей. более 240 млн уникальных посетителей в месяц. Каждую минуту мы обрабатываем порядка 12 часов нового аудил контента. В SoundCloud порядка 20 команд, занимающихся разнымим сервисами, порой невидимыми снаружи. Но в своей презентации я сконцентрируюсь на клиентских проектах - главный сайт, мобильная версия и виджет.
  • 4. SOUNDCLOUD Quick assumptions • Awareness of MVC pattern • you know that SPA is not a mineral spring • why and when SPA makes a difference • you heard of web app frameworks Sunday, September 29, 13 Готовясь к этому докладу, я естественно делал некоторые допущения. Я думаю, - что вы слышали о паттерне MVC, - вы знаете о таком подходе к созданию веб приложений, как Single Page Application. - вы понимаете где и зачем, его уместно применять - и вы сами пытались применить один из современных фреймворков на практике
  • 5. Sunday, September 29, 13 Мой доклад называется Добро пожаловать в SoundCloud! И за время доклада я постсраюсь рассказать об основных идеях и решениях, формирующих архитектуру новой версии SoundCloud, изначально известной внутри компании как NEXT.
  • 6. SOUNDCLOUD What is NEXT? • default production app since December, 2012 • SoundCloud API client • the long running app • ~45,000 LOC, 80% of that is JS • supports only modern browsers • better experience Sunday, September 29, 13 Что такое NEXT? Это новая версия, доступная в production с декабря 2012 года, по сути является клиентом нашего собственного API, за редкими исключениями. Это Single Page Application, протяженность сессии которой иногда достигает и превышает сутки. В разы увеличила кол-во прослушиваний и вовлеченность пользователей, что несомненно можно расценивать как положительный результат.
  • 7. SOUNDCLOUD Our Stack • Require.js (Almond.js) • Node.js • Uglify.js/Esprima • Klang.js • Backbone (Trombone) • Handlebars (with plenty of helpers) • Home-grown development tools Sunday, September 29, 13 В разработке NEXT и практически всех client-side проектов используется следующий, я бы сказал, типичный хипстерский стэк -- ну разве что на coffeescript не пишем. Тут нет ничего необычного - RequireJs и Almond.js используются для загрузки модулей во время разработки и в продакшене соответсвенно. Node.js/Uglify.js используется для инструментов сборки, разработческого сервера и статического анализа кода._ В продакшене Node.js сервера при этом нет. так как являясь клиентом собственного API, сайт абсолютно статический. Важной компонентой является библиотека Klang.js, предоставлюящая лучший способ для воспроизведения аудио в зависимости от конкретной платформы. Ну и не менее важной состовляющей нашего стека является Backbone, вернее то во что мы его превратили. Но об этом попозже.
  • 8. Why Backbone? Sunday, September 29, 13 _Но почему Backbone спрсите вы? Ведь Backbone это не фреймворк, а всего лишь небольшая библиотека, абсолютно не даюшая каких-либо рекоммандаций о том, как ваше приложение должно быть структурировано, какие практики применять, чтобы эффективно работать с памятью, на худой конец, просто, чтобы работа над приложением не превратилась в кошмар. А сначала так оно и было.
  • 9. Sunday, September 29, 13 Мы имели довольно печальный опыт работы с Backbone в первой мобильной версии, где мы собрали все возможные подводные камни. Это было больше 2.5 лет назад. Тогда все это движение вокруг SPA, сейчас набравшее уже очень большую скорость, только начиналась. Никто толком не знал, как это делать правильно. Мы тоже не знали. Ни Chaplin, ни Marionette, ни прочих фреймворков еще не было. Ember.js был только в зачаточном состоянии (только-только лишь был создан форк от SproutCore, который обещал тоже стать чем-то большим и громоздким, чтобы стать фундаментом для проектов под разные платформы.
  • 10. Sunday, September 29, 13 _Но, в следующем проекте, виджете, мы опять выбрали Backbone, потому что он имеет ряд достоинств
  • 11. SOUNDCLOUD Backbone • small footprint • Observable mixin • doesn’t depend on third-party libraries • helpful methods for data manipulation • easy to understand and extend • but not opinionated about app structure Sunday, September 29, 13 - он небольшой, что довольно важно для мобильных устройств - в нем хорошо реализован паттерн Observable, что важно для коммуникации между модулями приложения - в нем есть компактный, но вполне достаточный, арсенал для работы с данными - практически не зависит от сторонних библиотек, что опять-таки немаловажно для мобильных платформ_ _Но для организации большого приложения этого недостаточно. Нужен дополнительный уровень абстракции, особенно на уровне view компоненты. Из-за необходимости иметь более высокйи уровень абстракции и появился Trombone
  • 12. Trombone Sunday, September 29, 13 _Что такое Trombone? Это наш форк backbone'а. Он превращает небольшую библиотеку в небольшой, но полноценный фреймворк Trombone удобен для создания как небольших проектов (виджет, мобильная версию), так и довольно крупных - главная версия сайта, которая была написана на его основе, и до сих пор развивается без особых проблем.
  • 13. SOUNDCLOUD Trombone > Backbone • dev tools • build tools • core view component • view life cycle management • layout management • data management More details here - snd.sc/16i6mhU Sunday, September 29, 13 Trombone (в отличие от Backbone) предоставляет - удобный набором инструментов для разработки - и сборки проекта - четко описывает подход для структурирования приложения - и предлагает более понятный подход к работе с view компонентой, на которой я и сфокусируюсь в своем докладе
  • 14. SOUNDCLOUD Distribution of modules in NEXT 315 views 50 collections 37 models Sunday, September 29, 13 потому что это самая важная часть клиентской архитектуры, пожалуй, любого проекта. Но при этом ее базовая реализация в Backbone черезчур минималистична. В этом легко убедиться, взглянув в исходный код Backbone'а -- это всего лишь 70 строчек кода, без комментариев и того меньше. По сути это заглушка, с которой можно делать, все что угодно. Но что делать не совсем понятно. Backbone не вносит ясности в работу с view, хотя она очень нужна. Эта чрезмерная гибкость - слабость и одновременно сила Backbone'а.
  • 15. SOUNDCLOUD Trombone Views — they do a lot • define a standard view declaration • rendering of templates • establish a data source • support the nested views • setup and teardown event listeners • don’t depend on the context Sunday, September 29, 13 Trombone убирает эту гибкость и вносит ясность в работу с view. Базовая view компонента в Trombone делает много того, о чем не позаботился Backbone, а именно: - более формализованный способ создания компоненты - отрисовка шаблонов - определение источника входных данных - поддержка вложенных view - установление и корректное удаление связи с DOM элеменотом и моделью/коллекцией - обеспечение независимоти от контекста использования Независимость View компоненты обеспечивается ее изолированностью, и минимальным набором входных данных. Как правило, это всего лишь идентификатор модели или коллекции, данные из которой эта view отображает в той или иной форме.
  • 16. SOUNDCLOUD View declaration in Trombone var Sound = require('models/sound'); var View = require('lib/view'); var WaveformView = module.exports = View.extend({ template: require('views/sound/waveform.tmpl'), css: require('views/sound/waveform.css'), ModelClass: Sound, requiredAttributes: [‘waveform_url’] }); Sunday, September 29, 13 Давайте рассмотрим создание простой view компоненты. Кроме отдельного CSS, JS файла и шаблона, view так же в явном виде задает тип данных, которые она отображает. Не сами данные, а именно тип. _Модули определяются в стиле CommonJS, который во время разработки и сборки проекта на лету компилируются в формат AMD, совместимый с RequireJS. Использование стиля CommonJS позволяет очень легко переиспользовать модули как на клиенте, так и на сервере (во время сборки, к примеру), и избавляет от необходимости писать лишний код. CSS стили и шаблон тоже запрашиваются как отдельные модули, так же компилируемые в AMD формат. С шаблоном все просто, так как скомпилированный handlebars- шаблон есть просто функция, которая и возвращается модулем,
  • 17. ¿ ?CSS ↝ AMD Sunday, September 29, 13 а как же быть с CSS?
  • 18. SOUNDCLOUD Write CSS but serve AMD .myView { padding: 5px; color: #f0f; } .myView__foo { border: 1px solid #0f0; } Result is an AMD module define("views/myView.css", [], function (require, exports, module) { var style = module.exports = document.createElement('style'); style.appendChild( document.createTextNode('.myView { padding: 5px; color ... } ...'); ); }) Sunday, September 29, 13 Приминив похожий подход, В результате компиляции исходный CSS файл загружается в виде AMD модуля. Единственная функция, определяемая данным модулем, это функция, которая динамически добавляет стили данной view компоненты в документ.
  • 19. SOUNDCLOUD Write CSS but serve AMD var WaveformView = module.exports = View.extend({ // it’s a `style` element css: require('views/sound/waveform.css'), render: function() { if (!this.css.parentNode) { document.body.appendChild(this.css); } // ... } }); Sunday, September 29, 13 Стили для данной компоненты активируются в момент, когда данная view отрисовывывается впервые.
  • 20. SOUNDCLOUD Nested views <h1>{{title}}</h1> {{view "view/waveform" id=123}} var Waveform = require('views/waveform'); var waveform = new Waveform({ resource_id: 123 }); soundView.addSubview(waveform); soundView.$el.append(waveform.render().el); You can add subviews both in a template and in js file Sunday, September 29, 13 View компоненты могут быть составными и включать в себя дочерние view. Глубина их вложенности произвольная. В нужный момент Trombone позаботиться о том, что все дочерние view корректно осовободят занимаемые ими ресурсы и так далее. Как уже было сказано, декларация view пытается свести к минимуму набор входных параметров, необходимых для ее создания. Данный слайд иллюстрирует 2 возможных способа создания вложенной view компоненты. Один способ прогаммный, а другой декларативный с помощью view helper'а прямо в шаблоне. В обоих случаях достаточно указать лишь идентификатор трека, для которого мы хотим создать waveform'у. Не нужно передавать ссылку на уже созданную Sound модель, или создавать новую, если таковой нет. Об этом позаботиться сама view компонента, сам Trombone.
  • 21. SOUNDCLOUD Looks good, right? var Sound = require('models/sound'); var View = require('lib/view'); var WaveformView = module.exports = View.extend({ template: require('views/sound/waveform.tmpl'), css: require('views/sound/waveform.css'), ModelClass: Sound, initialize: function(args) { // new instance is created for each view this.model = new this.ModelClass({ id: args.id }); this.setupModelListeners(); } }); Sunday, September 29, 13 Такой подход очень удобен, не правда ли?! Но к сожалению содержит одну большую проблему._ _Каждый раз будет создаваться новый экземпляр модели. Много экземпляров ведет к чрезмерному потреблению пямяти, что приводит к более частому срабатыванию GC. Ну и наконец, создание нового экземпляра разрушает саму идею синхронизации состояний между view компонентами, представляющими один и тот же объект, одни и те же данные
  • 22. Sharing is caring Sunday, September 29, 13 Другими словами, нам надо найти способ использовать один и тот же экземпляр модели в контексте разных view компонент.
  • 23. Many views, one model Sunday, September 29, 13 _На этом слайде, я подсветил почти все view, которые тем или иным образом представляют один и тот же трек. Наша задача - передать экземпляр модели, предствляющей данный трек, в каждую из этих view. Но как этого добиться?_
  • 24. SOUNDCLOUD Identity Map IdentityMap.applyTo(Sound, { hashFn: function(attrs) { return attrs.id || null; } }); var s1 = new Sound({ id: 123, genre: 'Ambient' }) , s2 = new Sound({ id: 123, artist: 'Brian Eno’ }); s1 === s2; // true - these are the exact same objects. Sunday, September 29, 13 _Очень просто_ _На помощь приходит паттерн IdentityMap, впервые описанный Мартином Фаулером., который, учитывая всю выразительность JavaScript'а, позволяет очень элегантно его реализовать и решить нашу проблему._ _В очень очень очень упрощенной версии -- IdentityMap просто изменяет поведение конструктора таким образом, что он возвращает экземпляр нужной нам модели, если такая модель уже существует, в противном случае -- просто создает ее. Чтобы определить наличие или отсутствие нужной нам модели, каждый ресурс имеет уникальный индентификатор, hash, который высчитывается на основе входных аттрибутов. По умолчанию это аттрибут id._
  • 25. SOUNDCLOUD Don’t use it in production :) var store = {}; Sound = function(attributes.id) { var id = attributes.id; // check if this model has already been created if (store[id]) { return store[id]; // ← return the other one } // otherwise, store this instance store[id] = this; // regular instantiation... this.initialize(); } }; Sunday, September 29, 13 _В крайне упрощенной форме, это то, как выглядит конструктор модели, к которой применен IdentityMap. Повторюсь, что это очень упрощенная версия, приведенная лишь для иллюстрации основной идеи.
  • 26. SOUNDCLOUD IdentityMap is applied to subclasses IdentityMap.applyTo(SoundModel, { hashFn: function(attrs) { return attrs.id || null; } }); // Subclasses inherit the “IdentityMap” behaviour PromoSound = SoundModel.extend({}); s1 = new PromoSound({id : 123}), s2 = new PromoSound({id : 123}); s1 === s2; // true Sunday, September 29, 13 При этом нет необходимости применять этот миксин к каждой модели в вашем проекте. Примение IdentityMap только к базовым классу модели автоматически добавляет аналогичное поведение на все его подклассы.
  • 27. SOUNDCLOUD Identity Map var s1 = new Sound({ id: 123, genre: 'Ambient' }) , s2 = new Sound({ id: 123, artist: 'Brian Eno’ }); s1 === s2; // true - these are the exact same objects. s1.get('genre'); // 'Ambient' s2.get('artist'); // 'Music for Airports' Sunday, September 29, 13 _Применив IdentityMap миксин, мы добились того, что хотели -- оператор new возвращает нужный нам экземпляр модели._
  • 28. Wait a second Isn’t it a massive memory leak? Sunday, September 29, 13 _Но по-прежнему, такое решение не идеально. В случае SPA такой подход может банально привести к большой утечке памяти,
  • 29. SOUNDCLOUD Wait a second var store = {}; Sound = function(attrs) { var id = attributes.id; // check if this model has already been created if (store[id]) { return store[id]; } // otherwise, store this instance store[id] = this; // ← it’s a massive a memory leak // regular instantiation... this.initialize(); } }; Sunday, September 29, 13 потому что модель, сохраненная в кеше, никогда не будет подчищена GC._ _Каждый созданный экземпляр будет оставаться в памяти на протяжении всей сессии, протяженность которой в случае SoundCloud иногда может составлять сутки._
  • 30. Sunday, September 29, 13 _А если это так, то через какое-то время приложение просто станет недоступным._
  • 31. SOUNDCLOUD Release it! var map = {}, counts = {}; // <-- usage counter function Sound(id) { if (map[id]) { ++counts[id]; return map[id]; } map[id] = this; counts[id] = 1; this.initialize(); } Sound.prototype.release = function () { --counts[this.id]; } Sunday, September 29, 13 _Но на самом деле все не так уж страшно. Как я уже сказал на предыдущем слайде была показана упрощенная версия модели, к которой был применен IdentityMap. На самом деле, реальная, а не упрощенная реализация этого миксина, добавляет несколько служебных методов. Один из них это метод *release*. Его цель очень проста -- оповестить хранилище созданных моделей о том, что данный экземпляр больше не используется. Все это может показаться довольно запутанным, но на самом деле, корректное освобождение ресурсов в Trombone происходит автоматически. Это делается на уровне базового view класса, к примеру, при переходе с одной страницы на другую._
  • 32. Garbage Collection once a minute Sunday, September 29, 13 _Затем, когда счетчик использования конкретной модели достиг нуля, она удаляется из памяти. Величина интервала срабатывания GC равна 1 минуте.
  • 33. SOUNDCLOUD Transition between layouts Sunday, September 29, 13 Мгновенная очистка памяти не делается, так как есть вероятность того, что только что полностью освобожденная модель или коллекция будут использоваться на следующей странице. Этот подход очень полезен, так как он не заставляет нас запрашивать данные, необходимые для отрисовки страницы, в случае частых переходов назад-вперед
  • 34. SOUNDCLOUD Browsers are getting better function leak() { var A = {}, B = {}; A.b = B; B.a = A; } // Circular reference does exist // but A and B will be cleaned up // as you can't reach either from global scope leak(); Sunday, September 29, 13 _Мы знаем, что браузеры не стоят на месте._ _И, к примеру, круговая ссылка, пример которой здесь приведен, уже не является проблемой для современных JavaScript движков. Они не влекут утечек памяти, но это по-прежнему JavaScript, который в силу своей природы, всегда оставляет возможность их создать._
  • 35. SOUNDCLOUD No longer used != No longer reachable var View = require('lib/view'), Sound = require('models/sound'); var WaveformView = module.exports = View.extend({ setup: function() { this.sound = new SoundModel({ id: 123 }); }, // sound model is not disposed properly later on // as we mistakenly redefined `dispose` method dispose: function() {} }); Sunday, September 29, 13 _По-прежнему очень велика вероятность, что, к примеру, экземпляр модели или коллекции не был в конечном итоге корректно "освобожден". Это значит, что он останется в памяти на протяжении всего времени работы приложения. Данный, возможно, довольно искуственный пример, иллюстрирует вполне жизненный случай. Все делают ошибки. В такие моменты на помощь приходят тесты и прочие инструменты_
  • 36. SOUNDCLOUD Static analysis tools to the rescue $ ./tools/memory-leak-finder views/waveform.js Warnings: 1 Sound model is not disposed. Release it in `dispose` method. A very nice read - http://research.google.com/pubs/pub40738.html Global leaking detector - https://github.com/kesla/node-leaky Sunday, September 29, 13 _Кроме unit-test'ов, проверяющих корректное освобождение ресурсов, частью нашего предрелизного тестирования является коммандная утилита, которая производит статический анализ кода. Она строит AST c помощью Esprima, анализирует его, находит объекты, создание которых может привести к утечке памяти и проверяет, были ли они корректно освобождены. Если это не так, то оповещает об этом и предлагает решение. Это далеко не идеальный инструмент. В основе ее работы лежит знание основных паттернов, которые мы используем для создания моделей или коллекций, а так же некоторая степень эвристики. Вдохновила на создание такого инструмента утилита JSWhiz - расщирение для Google Closure compiler. Это утилита, которой активно пользуются разработчики команды Google Mail, столкнувашиеся с проблемами утечки памяти. Это довольно занятный инструмент, ссылка на подробное описание того, как он работает, приведена на этом слайде._
  • 37. SOUNDCLOUD Monitor memory info window.peformance.memory = { // memory JS heap is limited to jsHeapSizeLimit : 793000000, // memory allocated by JS (including free space) usedJSHeapSize : 18200000, // memory currently being used totalJSHeapSize : 39600000 } setInterval(function() { if (usedJSHeapSize/jsHeapSizeLimit > 0.9) { console.log(‘He is almost dead, Jim!’); } }, 30 * 1000); Sunday, September 29, 13 _Но естественно, нет идельаного инструмента, особенно для такой нетревиально задачи, как обнаружение и предотвращение утечек памяти. В этой связи, имеет смысл следить за количеством потребляемой памяти. Благо сейчас есть нужные API для таких целей._ _Google Chrome предоставляет доступ к информации об использованной памяти. Поэтому грех этим не пользоваться. Мы используем эту информацию для отслеживания динамики использования памяти на определенной выборке пользователей с разной степенью активности._ _Имея доступ к этим трем свойствам, можно легко определить как часто, пользователи близки или столкнулись с ситуацией, когда приложение стало абсолютно неотзывчивым._ _jsHeapSizeLimit - максимальное доступный размер памяти_ _totalJSHeapSize - общий размер памяти, выделенный на процесс, включая освобожденную память, не занимаемую никакими объектами_ _usedJSHeapSize - общий размер памяти, используемый в текущий момент, включая внутренние объекты V8._ _Когда _usedJSHeapSize -> jsHeapSizeLimit, есть шанс увидеть прекрасную страницу. He's dead, Jim!_
  • 38. Sunday, September 29, 13 _Чтобы избежать такой ситуации, есть ряд простых рекоммендаций, о которых было сказано уже тысячу раз, но о которых есть смысл напомнить в контексе этого слайда._
  • 39. Don’t modify the shape of an object Sunday, September 29, 13 _Изменяя структуру объекта в рантайме, мы переводим объект из категории быстрых объектов в категорию медленных.
  • 40. SOUNDCLOUD Inline caching in V8 function Point(x, y, z) { // introduce all properties in the constructor // to enable “fast-object” mode. this.x = x; this.y = y; this.z = z; } var point = new Point(11, 22, 33); point 11 22 33 Point ‘x’ ‘y’ Point Point ‘x’ ‘y’ ‘z’ Point ‘x’ x y z Sunday, September 29, 13 Одной из особенностей двигателя V8 является то, что он для доступа к свойствам объекта пытается использовать не hash-образную структуру данных, а линейную последовательность аттрибутов, которая содержит ссылку на описание layout’а объекта. Такой подход значительно увеличивает скорость доступа к свойствам и методам JS объекта.
  • 41. SOUNDCLOUD Dictionary mode in V8 var point = new Point(11, 22, 33); // forcing the object into dictionary mode delete point.z; // accessing a hash table is much slower // than accessing a field at a known offset. console.log(point.x); point 11 22 HashMap x : 11 y : 22 Sunday, September 29, 13 Но есть ряд действий, которые могут перевести объект из разряда быстрых в разряд медленных. Одно из тахик действий - это удаление свойства объекта в рантайме. После этого структура данных, используемая для представления данного объекта меняется на HashMap, что в разы замедляем доступ к его свойствам и делает работу с такими объектами с точки зрения потребления памяти не эффективной. Рассмотрим пример.
  • 42. SOUNDCLOUD Less is more var FastSound = function (title, duration) { this.title = title; this.duration = duration; } var SlowSound = function (title, duration) { this.title = title; this.duration = duration; }; var fast = new FastSound('Hommage a Rameau', '350000'); var slow = new SlowSound('Hommage a Rameau', '350000'); delete slow.duration; Sunday, September 29, 13 _Кажется, что удалив свойство, которое нам не нужно, мы осводим память, на деле же мы изменили структуру данных, так называмый HiddenClass, что переводит данный объект в категорию медленных. Добавление свойств за рамками конструктора замедляет чтение этих самых свойств. Это довольно интересная тема, безусловно достойная более чем одного слайда, но чтобы не быть голосовным, вот цифры._
  • 43. SOUNDCLOUD Less is more Constructor Objects count Shallow size Retained size -------------------------------------------------------- SlowSound 10000 120 000 4 240 000 FastSound 10000 200 000 200 000 Slow object is using up 20 times more memory Sunday, September 29, 13 После изменения структуры объекта, он занимает памяти почти в 20 раз больше. Это факт, с которым тяжело не считаться._
  • 44. Unbind event listeners Sunday, September 29, 13 Не менее важно удалять все обработчики событий перед тем, как view компонента (DOM element) будет удалена.
  • 45. SOUNDCLOUD Trombone takes care of it View = module.exports = Backbone.View.extend({ ✂ ✂ ✂ ✂ ✂ dispose: function() { // remove event listeners this.teardownModelListeners(); this.teardownCollectionListeners(); // tell GC that we don't need these resources anymore this.model.release(); this.collection.release(); } ✂ ✂ ✂ ✂ ✂ }) Sunday, September 29, 13 Trombone это делает автоматически, удаляя все обработчики событий, добавленные к модели, коллекции и DOM элементу.
  • 46. Manage local cache properly Sunday, September 29, 13 Если вы полагаетесь на кэш, хранящий ссылки на объекты, то не забывайте освобождать ресурсы, которые вам больше не нужны. По сути это то, чем занимается внутренний GC, подчищающий ненужные коллекции и модели.
  • 47. Recycle objects Sunday, September 29, 13 Иногда, можно переиспользовать уже созданный объект, вместо того, чтобы создавать новый.
  • 48. SOUNDCLOUD Use Object Pools var soundPool = [], activeSounds = []; var sound = getSound(); ✂ releaseSound(sound); sound = null; function getSound() { var sound; if (soundPool.length) { sound = soundPool.pop(); sound.reset(); // important!!! } else { sound = new Sound(); } return activeSounds.push(sound); } function releaseSound(sound) { var index = activeSounds.indexOf(soundPool); if (index > -1) { activeSounds.splice(index, 1); } } Sunday, September 29, 13 Cоздание нового объекта напрямую связанно с процессом выделения памяти, что приближает нас к срабатыванию GC. Этот паттерн нашел большое применение среди разработчиков игр, где часто приходиться иметь дело с очень большим кол-вом объектов при довольно ограниченном кол-ве памяти. Единственное о чем не стоит забывать при использовании такого подхода, что ранее использованный объект нужно вернуть в исходное состояние.
  • 49. Premature optimization is the root of all evil Sunday, September 29, 13 Но, даже зная все это, никогда не стоит забывать, что не надо заниматься оптимизацией ради оптимизации. Ей стоит заниматься только тогда, когда в конечном итоге от этого выигрывает пользователь. И в этой связи я хотел бы рассказать вам об истории одной оптимизации, которая имела место прямо перед запуском новой версии SoundCloud в продакшен.
  • 50. Umm yeah, comments Sunday, September 29, 13 Это история о способе отображения комментариев поверх waveform'ы -- это своего рода визитная карточка SoundCloud'а.
  • 51. SOUNDCLOUD Many men, many minds "SoundCloud users comments attached to songs are iconic and life changing" Sunday, September 29, 13 Кто-то не может представить SoundCloud без этого элемента.
  • 52. SOUNDCLOUD When N gets big 2600 comments 2450 comments Sunday, September 29, 13 Но я сейчас не об этом, а том, что в тот момент, когда продукт еще находился в закрытом бета тестировании, реализация отрисовки этих комментариев была нашей головной болью, по причиние того, что она сильно замедляла скроллирование страницы. Нас это сильно расстраивало.
  • 53. SOUNDCLOUD When N gets big Template-based approach • based on ListView component • list item is 4 nodes: li > a > img + span • 3 sounds: 4080 * 4 = 16,320 nodes • 272,000 nodes per stream (50 sounds) Sunday, September 29, 13 Мы понимали, что с таким решением в продакшен идти нельзя. Но в тоже самое время причина этой медленности была очевидна и лежала на поверхности -- cтандартный, основанный на классиеческом шаблоне способ отрисовки waveform'ы и комментрариев приводил к большому количеству DOM элементов, что и служило причиной медленного скролирования.
  • 54. SOUNDCLOUD New waveform Sunday, September 29, 13 ну а после появления еще одного требования -- сделать вейвформу абсолютно гибкой, для того чтобы вписать ее в произвольный фон -- стало ясно
  • 55. yes we canvas! Sunday, September 29, 13 что пришло время портировать отрисовку вейформы и комментариев на элемент canvas. и тем самым попробовать решить две проблемы - максимально минимизировать число DOM элементов (в иделе в одному) - сделать waveform’у более гибкой
  • 56. SOUNDCLOUD Old waveform Sunday, September 29, 13 Стоит заметить, что изображение с которым нам приходилось работатать раньше, это всего лишь маска, которая накладывалась поверх нужного фона.
  • 57. SOUNDCLOUD Old waveform Sunday, September 29, 13 Стоит заметить, что изображение с которым нам приходилось работатать раньше, это всего лишь маска, которая накладывалась поверх нужного фона.
  • 58. SOUNDCLOUD Solution: Canvas! Problem: Data! Sunday, September 29, 13 Поэтому первой проблемой, с которой мы столкнулись, было отсутствие пиксельных данных, которые можно использовать для рисования waveform’ы на сanvas.
  • 59. SOUNDCLOUD Solution: Images as data source! Problem: Performance! Sunday, September 29, 13 Решение было тривиально -- использовать пискельные данные исходного негативного изображения. Мы решили просто находить положение первого непрозрачного пикселя для каждой из частот, чтобы воссоздать нужный нам образ, но здесь нас поджидали большие проблемы по производительности.
  • 60. SOUNDCLOUD Gradual improvements • Most efficient detection algorithm • Cached scaled image • Typed Arrays • WebWorkers (Chromium Issue #51171) Sunday, September 29, 13 Ни оптимизирование алгоритма, ни кеширование даннных, ни использование типизированных массивов, ни даже использование WebWorkers нам не помогало. Последнее к сожалению невозможно было использовать для решения этой задачи, потому что при передаче пиксельных данных в web worker, начиналась стремительная утечка памяти. И мы уже было почти сдались, пока не решили зайти к этой проблеме с другой стороны
  • 61. SOUNDCLOUD Think about what’s important • Moved calculation to the back end • Ported algorithm to Go • Front end recieves JSON, everyone happy Sunday, September 29, 13 а именно -- перенести всю логику анализа пиксельных данных на backend, портировав алгоритм обнаружения грани или первого непрозрачного пикселя на Go. В итоге Frontend просто получал готовый JSON, удобный для того, что отрисовать waveform’у в произвольном виде.
  • 62. SOUNDCLOUD Before: 1352ms After: 10ms Sunday, September 29, 13 Как результат, скорость отрисовки особо тяжелых страниц улучшилась почти в 100 раз. Я считаю, что это очень хороший пример оптимизации, который доказывает, что не все задачи надо решать только средствами, доступными в браузере. Оптмизировав скорость скроллирования с помощью отрисовки waveform'ы в canvas, мы невооруженным взглядом почувстовали улчшение.
  • 63. Is it smooth enough? Sunday, September 29, 13 Но на глаз пологаться можно, но не всегда удобно и оправданно. Именно по этой причиние мы решили автоматизировать замер скорости скроллирования с помощью инструмента Telemetry.
  • 64. SOUNDCLOUD Telemetry $ cd $CHROMIUM_SOURCE/tools/perf/page-sets $ cat soundcloud.json { R "description" : "Is it smooth enough?", R "smoothness" : { action : "scroll" }, R "pages"RR : [ R R { "url" : "https://soundcloud.com/akovalev" } R ] } $ ../run-measurement smoothness soundcloud.json -o result.csv Telemetry is a Python wrapper around of the DevTools Remote Debugging Protocol Sunday, September 29, 13 Telemetry - это фреймворк для кроссплатформенного тестирования производительности в Chrome браузере. По сути это обертка над удаленным протоколом Chromium DevTools, написанная на Python. Предоставляет набор готовых бенчмарков, используемых командой разработчиков Chromium для тестирования производительноси отрисовки интерфейса. Так же позволяет реализовать механизм залогирования на сайт, что для тестирования некоторых случаев очень полезно. Тесты описываются в json формате, где указывается адрес тестируемой страницы и действие, которое будет выполнено. Запуск осуществляется из коммнадной строки. Результат сохраняется в текстовый файл в формате CSV, который не сложно распарсить и отобразить в виде графика.
  • 65. SOUNDCLOUD Telemetry We're particularly interested in: • mean_frame_time • dropped_percent • jank_count Sunday, September 29, 13 _Среди предоставляемых метрик есть ряд очень полезных и наиболее интересных с точки зрения анализа поведения интерфейса. Это_ _mean_frame_time - среднее значение fps_ _dropped_percent - кол-во неудачных фреймов в процентах_ _jank_count - и в абсолютной величине_ Telemetry - это очень интересный и полезный инструмент, полный потенциал которого пока не раскрыт. Очень обидно, что на его тему пока очень мало материалов, и что он незаслуженно обделен вниманием веб разработчиков, т.к. он предоставляет широкий арсенал для анализа производительности вашего интерфейса. Насколько мне известно, кроме SoundCloud это инструмент используется в проекте Adobe Topcoat, которые замеряет производительность веб компонент.
  • 66. Ship it! Sunday, September 29, 13 _В самом начале доклада я уже сказал, что наш сайт в продакшене является абсолютно статическим. Превращение проекта в абсолютно статический сайт происходит во время сборки, о который я и хочу сейчас рассказать чуть-чуть подробно. Этап сборки абсолютно автоматизирован и не требует ручной поддержки, что позволяет нам производить релизы по несколько раз в день безо всяких проблем. Особенно когда под рукой у нас есть такой замечательный инструмент как Bazooka_
  • 67. SOUNDCLOUD Bazooka (An In House Heroku) $ git push bazooka stable remote: change-set accepted ... remote: building... ... remote: build finished Bazooka is a deployment solution for 12 factors apps - http://12factor.net/ More details on bazooka - goo.gl/pfSTAf Sunday, September 29, 13 _В двух словах, Bazooka это наш внутренний аналог Heroku, с помощью которого деплоятся все сервисы SoundCloud. Делается это одной командой, в результате которой проект собирается и раскатывается по нужному количеству хостов._
  • 68. SOUNDCLOUD Structure of Trombone project app !"" lib !"" models !"" collections !"" layouts !"" views !"" vendor !"" application.js #"" index.html var coreDeps = getDeps('application.js'), layoutSeeds = getLayoutSeeds('layouts'); writePackage(coreDeps); layoutSeeds.forEach(function(layoutSeed) { var layoutDeps = getLayoutDeps(layoutSeed); writePackage(layoutDeps); }); function getLayoutDeps(layoutSeed) { var layoutDeps = getDeps(layoutSeed); return _.difference(layoutDeps, coreDeps) } $ trombone build Sunday, September 29, 13 _А что же происходит в процессе самой сборки?_ _Я уже упоминал Uglify.js, который мы используем, к примеру, для компиляции CommonJs/CSS файлов в AMD модули и для нахождения утечек памяти. Но наибольшую пользу этот инструмент приносит нам на этапе сборки проекта. Благодаря Uglify.js наш build script обладает огромной гибкостью, в него можно добавлять и реализовать почти все что угодно._ _Вот так вот выглядит структура любого Trombone проекта. Весь проект разбит на независимые модули. Входной точкой для приложения является файл application.js, с анализа которого все и начинается. Build script строит AST деревро этого файла, рекурсивно находит всего его зависимости, и разбивает их на 3 проектных сборки: вендорные файлы, шаблоны, и все остальное. _
  • 69. SOUNDCLOUD Building app !"" lib !"" models !"" collections !"" layouts !"" views !"" vendor !"" application.js #"" index.html public !"" layouts $   !"" home-f3c2-842c8906.js $   !"" listen-2ED1-abef2203.js $   #"" stream-KJsr-540a7923.js #"" core !"" sc-wfJV-abef2203.js !"" vn-iAHt-e1684d31.js #"" tm-idSh-540a7923.js $ trombone build Sunday, September 29, 13 Затем аналогичная процедура проделыватеся для каждой из страниц сайта: собираются зависимости для каждой из страниц, за вычетом базовых, которые уже были включены в проектную сборку, в результате чего и создается постраничная сборка.
  • 70. var client_id = __ENV__ === ‘prod’ ? ‘abc123’ : ‘def456’; if (__DEBUG_MODE__) { console.log(a); } uglify.ast_mangle(ast, { defines: { __ENV__ : ‘prod’, __DEBUG_MODE__ : false }}); uglify.ast_squeeze(ast); var a = ‘abc123’; Dead code removal Sunday, September 29, 13 _Но и это еще не все. Во время записи каждой из сборок на диск, код претерпивает ряд изменений в зависимости от переменных окружения. К примеру, части кода, используемые только для отладки, удаляются с помощью такой просто операции и не попадают в продакшен._
  • 71. Transform it! Sunday, September 29, 13 Вообще идея транфсормации кода содержит всебе очень большой потенциал, который особенно уместно применять на пути вашего кода в продакшен.
  • 72. AST is easy • Meaningful code vs Regex magic • Easy to change "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "soundId" }, "init": { "type": "Literal", "value": 123, "raw": "123" } } ] var soundId = 123; Sunday, September 29, 13 _Возможность работать с кодом на уровне AST -- это уровень абстракции, с которым куда удобнее работать, чем с исходным кодом, представленным в виде строки._ _Трансформации, которые вы можете применить к коду на его пути от вашего редактора к браузеру, практически не ограничены, по крайней мере они не ограничены знанием регулярных выражений, _Особенно, если у вас под рукой Esprima._
  • 73. Esprima • ECMAScript parser written in ECMAScript • Accepts ECMAScriptcode and generates AST • Meaningful code vs Regex magic • compatible with Mozilla Parser AST • Harmony branch Sunday, September 29, 13 К примеру если вы вооружены парсером Esprima, Это парсер JavaScript, написанный на JavaScript. Так исторически сложилось, что на текущий момент для анализа кода мы используем Uglify.Js Но сейчас я бы советовал работать с Esprima, в сторону который мы тоже смотрим. Она более активно развивается и предоставляет более удобный формат дерева, совместимый с Mozilla Parser AST.
  • 74. Esprima’s best friends escodegen escodegen AST Code estraverse estraverse.traverse(ast, { enter: function(node, parent) { }, leave: function(node, parent) { } }); SourceMap Sunday, September 29, 13 Незаменнимым дополнением в работе с Esprima являются две библиотеки, позволяющие - делать обход дерева - и генерировать JS код на основе AST Давайте рассмотрим пример простой трансформации, которая добавляет логирование выполнения всех функций в вашем приложении, используя эти библиотеки.
  • 75. Function instrumentation function addLogging(code) { var ast = esprima.parse(code); estraverse.traverse(ast, { enter: function() { if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') { addBeforeCode(code); } } }); return escodegen.generate(ast); } function addBeforeCode(node) { var name = node.id ? node.id.name : '<anonymous function>', beforeCode = "console.log('Entering " + name + "()')" beforeNodes = esprima.parse(beforeCode).body; node.body.body = beforeNodes.concat(node.body.body); } Sunday, September 29, 13 Вот с помощью всего каких-то 15 строчек читаемого кода мы достигли своей цели -- добавили логирование всех функций.
  • 76. Function instrumentation FunctionDeclaration Identifier BlockStatement Array Statement Statement ... Sunday, September 29, 13 По сути же мы просто - создали дерево, представляющее исходный код, - видоизменили его, - и сгенерировали на основе нового дерева нужный нам код
  • 77. Function instrumentation FunctionDeclaration Identifier BlockStatement Array Statement Statement ... Statement esprima estraverse escodegen Code AST Modified AST Code Sunday, September 29, 13 По сути же мы просто - создали дерево, представляющее исходный код, - видоизменили его, - и сгенерировали на основе нового дерева нужный нам код
  • 78. Thanks! Sunday, September 29, 13 За время доклада мы проследили - как формировались основные решений, сформировавшие архитектуру новой версии SoundCloud’а - какие паттерны мы применяем для ее реализации - какие инструменты и практкики мы используем, чтобы приложение оставалось отзывычивыми на протяжении долгой сессии Я надеюсь, что вы вынесли хотя бы одну идею, которую захотите попробовать в вашем проекте, ну и учитывая то , что некоторые аспекты были освещены немного вскользь -
  • 79. Alexander Kovalev sasha[at]soundcloud[dot]com ❓ Sunday, September 29, 13 я с удовольствием отвечу на все ваши вопросы