Суперсилы Chrome DevTools



    Я работаю в команде Онлайн. Мы делаем веб-версию справочника 2ГИС. Это долгоживущий активно развивающийся проект, в котором JavaScript используется как основной язык как на клиенте, так и на сервере.

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

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

    Проблема


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

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

    Отладка


    Цена за скорость разработки — наличие багов. Этот класс задач требует быстрого реагирования, а значит и входа в контекст происходящего.

    Неверный результат на выходе


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

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

    DOM Breakpoint устанавливается на узел дерева в инспекторе. Остановиться можно при удалении этого элемента, изменении его поддерева или атрибутов (напомню, что style и class — это тоже атрибуты).



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



    Эти брэйкпоинты отлично работают в связке с асинхронным режимом стэка вызовов (Async сall stack). Он не обрывается на асинхронных операциях и дает возможность, например, перейти из обработчика 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. Если объект доступен только в локальной области видимости, добавляем нужный код в приложение на время отладки.

    Исследование


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

    Console как источник знаний


    Консоль в DevTools — это не только способ быстро выполнить небольшой скрипт. Она обладает мощным API, реализующим недоступные в языке удобные функции и связки с другими инструментами DevTools.

    Например, чтобы вывести в консоль DOM-элемент, не обязательно использовать сложные селекторы. Вместо этого в рамках консоли реализован стэк выделенных в инспекторе элементов. Доступ к нему происходит через команду $N, где N — отступ от конца стэка. Таким образом, обращение к $0 в консоли вернет последний выбранный в инспекторе DOM-узел, $1 — предпоследний и так далее.

    Использовать эту возможность удобно в связке с функциями над DOM-узлами:

    — monitorEvents(object) — следит за событиями элемента;
    — getEventListeners(object) — выводит в консоль список обработчиков событий элемента, из которого можно перейти к коду функции.

    Вырисовывается простой сценарий:

    1. Выделяем элементы в инспекторе.
    2. Вызываем в консоли нужную команду, аргументом передаем $0.
    3. Радуемся, как дети.

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

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

    // Конфиг UglifyJS
    global_defs: {
        DEBUG: false
    }
    
    // Где-то в коде приложения
    if (DEBUG) {
        console.log(‘Что-то важное’);
    }
    


    В примере выше DEBUG не просто будет равен false после сборки. UglifyJS поймет, что условие никогда не выполнится, и просто удалит его из кода.

    Теперь можно использовать более сложные возможности вывода в консоль для разных типов данных. В примере с отладкой состояния вывод будет более компактным за счет console.groupCollapsed. Это используется в нашем проекте для отслеживания изменения URL:



    Для массивов хорошо подходит console.table. Полный список вариантов оформления вывода находится здесь.

    С высоты птичьего полета


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

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



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



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

    1. Записываем пользовательский сценарий.
    2. Изучаем стэки вызовов.
    3. По необходимости переходим к конкретной строчке кода прямо из Timeline.
    4. Если информации слишком много, оборачиваем интересующий код в console.time и console.timeEnd.
    5. Повторяем до полного понимания логики происходящего.

    Заключение


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

    В этой статье описана только малая часть возможностей Chrome Developer Tools. В работе над 2ГИС Онлайн у нас возникают и более экзотические задачи, требующие постоянного изучения новых средств разработки. Более подробно об этих инструментах, а также о производительности приложений на JavaScript, я рассказывал на конференции FrontTalks.
    2ГИС
    163,00
    Карта города и справочник предприятий
    Поделиться публикацией

    Похожие публикации

    Комментарии 13

      +7
      В дополнение хочу добавить очень полезный ресурс devtoolstips.com
        +2
        На счёт if (DEBUG) {...}
        Вдруг вам понравится UserStory
          +3
          Вместо if (DEBUG) можно использовать замечательную опцию UglifyJS: drop_console.

          Вот кратко:
          drop_console -- default false. Pass true to discard calls to console.* functions.
          


          Её описание также есть здесь: github.com/mishoo/UglifyJS2
            +2
            P.S. Собственно, достоинство этой опции — как раз в том, что она удаляет все вызовы console, включая time, groupCollapsed и другие, которые вряд ли понадобятся на production.

            Пишите ещё про замечательные возможности Chrome DevTools :) Там всё время что-то новое, не всё ж на английском читать :)
              0
              drop_console — крутая опция. Но в нашем случае в if (DEBUG) может быть завернута и другая отладочная логика, помимо console.*. Плюс, мы редко вызываем console.* напрямую: сделали прослойку, реализующую необходимые полифилы, и уже она вызывает методы консоли.
                +1
                В последнее время самые интересные новшества появляются в Timeline. В частности, расширяются инструменты по отладке операций отрисовки. Ждем, когда можно будет от событий Paint перейти напрямую у участку кода или стилям, которые его вызвали. Как только эти инструменты появятся и мы научимся их эффективно использовать, обязательно расскажем :)
              0
              Отлавливать некорректные изменения этого состояния можно, используя воображение.


              Боюсь продуктивность такого отлова будет крайне мала.
                +3
                По DevTools неплохой официальный Help и даже есть онлайн курс.
                  +1
                  Для отладки также можно использовать технику бэкдора, чтобы через eval внедряться в контекст без применения дебагера: github.com/ArtemGovorov/backdoor
                    0
                    Интересная техника использования замыканий для отладки. Только непонятно, зачем писать отдельный модуль для того, чтобы обслуживать глобальную переменную? Можно вот так, например:
                    > (function foo() { var v = 1; from_foo = function(p) { return eval(p) } })();
                    undefined
                    > from_foo('v');
                    1
                    
                      0
                      Если уж модуль, то можно хотя бы вынести определение самой замыкающейся функции, чтобы хоть как-то упростить работу. Как-то так:
                      > var backdoor = '(function(p) { return eval(p) })';
                      undefined
                      > (function foo(){ var v = 2; from_foo = eval(backdoor) })();
                      undefined
                      > from_foo('v');
                      2
                      
                    0
                    А что из этого не умеет Firefox Developer Edition?
                      +1
                      Вот полезный сниппет для Chrome, который позволяет быстро добавить breakpoint на чтение или изменение переменной/свойства в виде:
                      breakOn(document, 'cookie'); // вызвать debugger когда JS изменяет значение cookies
                      

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

                      Самое читаемое