- PVSM.RU - https://www.pvsm.ru -

Я работаю в команде Онлайн. Мы делаем веб-версию [1] справочника 2ГИС. Это долгоживущий активно развивающийся проект, в котором JavaScript используется как основной язык как на клиенте, так и на сервере.
Важное место в работе занимают инструменты анализа и отладки приложения. Популярные JavaScript фреймворки как правило обладают собственным инструментарием, заточенным под конкретную идеологию. Наша ситуация осложняется тем, что под капотом Онлайна гудит фреймворк собственного производства — Slot — также находящийся в стадии активной доработки.
В этой статье я расскажу, как мы используем стандартные браузерные инструменты разработчика для эффективной отладки и исследования. Эти рецепты направлены в первую очередь на изучение приложения снаружи-внутрь, поэтому подойдут для любого проекта.
Динамика разработки не позволяет участникам команды полностью погрузиться в детали задач друг друга. Контекст конкретного компонента быстро ускользает; вернувшись к участку кода спустя месяц, можно его не узнать. Кроме того, команда постоянно пополняется новобранцами.
В такой ситуации необходимо быстро восстанавливать понимание логики происходящего в коде. С учетом изменчивости проекта, эта задача носит скорее исследовательский характер. Понимая общие принципы работы, ты все равно каждый раз становишься первооткрывателем конкретных аспектов реализации.
Цена за скорость разработки — наличие багов. Этот класс задач требует быстрого реагирования, а значит и входа в контекст происходящего.
Часто баг проявляется в каком-то некорректном внешнем событии: непредвиденное изменение DOM-дерева, асинхронный запрос с ошибочными данными и т.д. В этом случае удобно рассматривать код приложения как черный ящик, вход которого — сценарий использования, а выход — результат бага.
Помимо стандартных брейкпоинтов на строчке кода, в DevTools (здесь и далее речь идет об инструментах браузера Google Chrome) есть возможность остановить выполнение по определенному событию.
DOM Breakpoint устанавливается на узел дерева в инспекторе. Остановиться можно при удалении этого элемента, изменении его поддерева или атрибутов (напомню, что style и class — это тоже атрибуты).

XHR Breakpoint устанавливается на весь документ и позволяет найти строчку кода, из которой посылается подпадающий под заданный паттерн запрос.

Эти брэйкпоинты отлично работают в связке с асинхронным режимом стэка вызовов (Async сall stack [2]). Он не обрывается на асинхронных операциях и дает возможность, например, перейти из обработчика setTimeout к коду, который его установил. Таким образом, можно заглянуть намного дальше в историю и найти корни даже сложных багов.

Пример сценария:
1. Происходит непредвиденное изменение в DOM-дереве.
2. Поведение воспроизводится.
3. Устанавливаем нужный тип DOM-брэйкпоинта.
4. Включаем Async режим в отладчике.
5. Воспроизводим баг и путешествуем по истории вызовов, пока не найдем корни проблемы.
Не все баги заметны невооруженным глазом. В ходе выполнения может меняться только внутреннее состояние, которое уже позже повлияет на поведение системы в целом. Отлавливать некорректные изменения этого состояния можно, используя воображение.
Предположим, что состояние — это глобальный объект. Тогда для слежки за ним можно использовать следующий код:
var watchMe = {};
Object.observe(watchMe, function() {
debugger;
});
watchMe.foo = ‘bar’; // Выполнение остановится
Используя продвинутые возможности консоли (о которых подробно рассказано далее), можно добавить логирование изменений, не останавливая выполнение на каждый чих.
var watchMe = {};
Object.observe(watchMe, function(options) {
options.forEach(function(option) {
var groupName = option.name + ' changed';
console.groupCollapsed(groupName);
console.log('Old value: ', option.oldValue);
console.log('New value: ', option.object[option.name]);
console.groupEnd(groupName);
});
});
Этот пример будет выводить консоль Chrome компактные группы логов при изменении свойств объекта. Кода теперь намного больше, и каждый раз писать его по памяти не удобно. Поэтому можно сохранить его как сниппет и запускать по необходимости.

Конечно, этот код придется каждый раз адаптировать к проекту и задаче. Редко все состояние хранится в одном глобальном объекте. Иногда придется редактировать исходники чтобы вклиниться в контекст выполнения. Но польза от такого подхода стоит прилагаемых усилий.
Сценарий использования:
1. Если наблюдаемый объект глобальный, то просто запускаем на нем сниппет.
2. Если объект доступен только в локальной области видимости, добавляем нужный код в приложение на время отладки.
Работа программиста не ограничивается исправлением багов. Для добавления новой функциональности важно понимание работы приложения в целом на сложных сценариях.
Консоль в DevTools — это не только способ быстро выполнить небольшой скрипт. Она обладает мощным API [3], реализующим недоступные в языке удобные функции и связки с другими инструментами DevTools.
Например, чтобы вывести в консоль DOM-элемент, не обязательно использовать сложные селекторы. Вместо этого в рамках консоли реализован стэк выделенных в инспекторе элементов. Доступ к нему происходит через команду $N, где N — отступ от конца стэка. Таким образом, обращение к $0 в консоли вернет последний выбранный в инспекторе DOM-узел, $1 — предпоследний и так далее.
Использовать эту возможность удобно в связке с функциями над DOM-узлами:
— monitorEvents(object) — следит за событиями элемента;
— getEventListeners(object) — выводит в консоль список обработчиков событий элемента, из которого можно перейти к коду функции.
Вырисовывается простой сценарий:
1. Выделяем элементы в инспекторе.
2. Вызываем в консоли нужную команду, аргументом передаем $0.
3. Радуемся, как дети.
Многие разработчики удаляют из кода console.log() сразу по окончании отладки. Но некоторая ключевая функциональность требует постоянного логирования. В результате каждый разработчик сначала пишет отладочный код, а потом его удаляет.
Есть способ не допустить вывод в консоль на продакшене и в то же время удобно логировать происходящее на этапе разработки. Мы используем UglifyJS [4] для сжатия JavaScript, и в нем есть опция переопределения значений глобальных переменных.
// Конфиг UglifyJS
global_defs: {
DEBUG: false
}
// Где-то в коде приложения
if (DEBUG) {
console.log(‘Что-то важное’);
}
В примере выше DEBUG не просто будет равен false после сборки. UglifyJS поймет, что условие никогда не выполнится, и просто удалит его из кода.
Теперь можно использовать более сложные возможности вывода в консоль для разных типов данных. В примере с отладкой состояния вывод будет более компактным за счет console.groupCollapsed. Это используется в нашем проекте для отслеживания изменения URL:

Для массивов хорошо подходит console.table. Полный список вариантов оформления вывода находится здесь [5].
Встроенный инструмент записи событий уровня браузера — Timeline. До недавнего времени он мог использоваться для анализа производительности приложения. С появлением экспериментальных функций, его возможности значительно расширились.
Одна из долгожданных фич — отображение стэков вызовов прямо в диаграмме событий. По сути это объединяет классический Timeline с отчетом CPU Profiler. Такая диаграмма дает понимание, как приложение работает в динамике: последовательности вызовов, общее время выполнения функций, точки взаимодействия JavaScript с DOM. Даже когда необходимости в оптимизации нет, его можно использовать для изучения внутреннего устройства проекта.

Если интересен конкретный участок логики приложения, а событий в отчете слишком много, можно воспользоваться console.time [6]. Помимо замера времени выполнения этот метод добавляет метку в Timeline.

Пример использования:
1. Записываем пользовательский сценарий.
2. Изучаем стэки вызовов.
3. По необходимости переходим к конкретной строчке кода прямо из Timeline.
4. Если информации слишком много, оборачиваем интересующий код в console.time и console.timeEnd.
5. Повторяем до полного понимания логики происходящего.
При работе над большими проектами имеет смысл инвестировать время в удобство разработки. Часы, потраченные на изучение и адаптацию инструментов, экономят дни отладки и дают лучшее понимание работы проекта.
В этой статье описана только малая часть возможностей Chrome Developer Tools. В работе над 2ГИС Онлайн у нас возникают и более экзотические задачи, требующие постоянного изучения новых средств разработки. Более подробно об этих инструментах, а также о производительности приложений на JavaScript, я рассказывал [7] на конференции FrontTalks.
Автор: Bardt
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/77938
Ссылки в тексте:
[1] веб-версию: http://2gis.ru/?utm_source=news&utm_medium=habr&utm_campaign=post_chromedevtools
[2] Async сall stack: http://habrahabr.ru/post/218397/
[3] мощным API: https://developer.chrome.com/devtools/docs/commandline-api
[4] UglifyJS: https://github.com/mishoo/UglifyJS2
[5] здесь: https://developer.chrome.com/devtools/docs/console-api
[6] console.time: https://developer.chrome.com/devtools/docs/console-api#consoletimelabel
[7] рассказывал: http://techno.2gis.ru/lectures/33
[8] Источник: http://habrahabr.ru/post/246557/
Нажмите здесь для печати.