Пользователь
0,0
рейтинг
6 июля 2014 в 20:00

Разработка → Deb.js: самый крохотный отладчик в мире

Перевод статьи «Deb.js: the Tiniest Debugger in the World», Krasimir Tsonev

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


Пример


Начнем с создания маленькой страницы с небольшим JavaScript взаимодействием. Мы создадим форму с двумя полями и кнопкой. По нажатии кнопки мы будем будем собирать данные и выводить сообщение в консоль. Вот и разметка страницы:

<form>
    <label>Name:</label>
    <input type="text" name="name" />
    <label>Address:</label>
    <input type="text" name="address" />
    <input type="button" value="register" />
</form>
<div data-element="output"></div>

Чтобы упростить пример, мы будем использовать jQuery для DOM выборок и событий. Мы обернем функциональность в следующий модуль:

var Module = {
    collectData: function(cb) {
        var name = $('[name="name"]').val();
        var address = $('[name="address"]').val();
        if(name != '' && address != '') {
            cb(null, { name: name, address: address });
        } else {
            cb({msg: 'Missing data'});
        }
    },
    error: function(err) {
        $('[data-element="output"]').html(err.msg);
    },
    success: function(data) {
        $('[data-element="output"]').html('Hello ' + data.name + '!');
    }
}

Функция collectData собирает значения с полей и проверяет, не ввел ли пользователь чего-нибудь. Если нет, то происходит обратный вызов с переданным объектом, содержащим короткое сообщение об ошибке. Если все в порядке, функция отвечает с null в первом аргументе и объектом, содержащим данные, как второй параметр. Разработчик, использующий модуль, должен проверять, передан ли объект ошибки. Если ошибка не передана, тогда можно использовать второй переданный аргумент. Например:

$('[value="register"]').on('click', function() {
    Module.collectData(function(err, data) {
        if(typeof err === 'object') {
            Module.error(err);
        } else {
            Module.success(data);
        }
    });
});

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

image

Если данные не введены, наш скрипт работает как и ожидалось. Под формой показывается сообщение о том, что данные не введены. В то же время, Если мы заполним поля и нажмем кнопку, то получим сообщение:
Uncaught TypeError: Cannot read property 'msg' of null
Давайте же выследим и исправим эту ошибку.

Традиционный подход


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

image

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

image

Похоже что мы получаем правильный объект data и error равен null, и это правильное поведение. Таким образом, проблема должна быть где-то в if предложении. Добавим console.log и увидим, движемся ли мы в правильном направлении:
Module.collectData(function(err, data) {
    console.log(typeof err);
    if(typeof err === 'object') {
        Module.error(err);
    } else {
        Module.success(data);
    }
});

И в самом деле, typeof err возвращает object. Вот почему мы всегда показываем ошибку.

image

И вуаля, мы нашли проблему. Нам, всего-то, нужно изменить конструкцию if на if (err) и наш маленький эксперимент будет работать, как ожидалось.

Тем не менее, иногда такой подход может быть довольно утомительным по следующим причинам:

  • Как вы видели, мы прибегли к логированию переменной. Так что не всегда можно обойтись установкой точки останова. Мы также должны переходить в консоль. В то же время, мы должны смотреть в редактор кода и в панель отладки Chrome. Это несколько мест, где нужно работать, что может быстро наскучить.
  • Если у нас много данных залогировано в консоли, это тоже может стать проблемой. Временами, может быть трудно найти нужную информацию.
  • Также этот подход не поможет, если у нас есть проблемы с производительность. Чаще всего, нам нужно знать время выполнения.

Иметь возможность остановить выполнение программы и посмотреть её состояние — бесценно, но Chrome не может знать, что именно мы хотим увидеть. Как в нашем случае, мы должны дважды проверять конструкцию if. Не было бы лучше, если бы у нас был инструмент доступный напрямую из нашего кода? Библиотека, которая предоставляет такую же информацию, что и отладчик, но находится внутри консоли? Так вот, Deb.js и есть ответ на этот вопрос.

Используем Deb.js


Deb.js это маленький кусочек JavaScript кода, 1,5кб в минифицированном виде, который отправляет информацию в консоль. Он может быть присоединен к любой функции и выводит:

  • Время и место выполнения функции
  • Трассировку стека
  • Отформатированный и сгруппированный возврат

Давайте взглянем, как выглядит наш пример при использовании Deb.js:

image

Мы, в точности, видим переданные аргументы и трассировку стека. Но заметьте изменения в консоли. Мы работаем над нашим кодом, находим, где может находиться проблема и добавляем .deb() после определения функции. Заметьте, что тип err помещен внутри функции. Таким образом, нам не нужно это искать. Возврат также сгруппирован и раскрашен. Каждая функция, которую мы отлаживаем будет напечатана отдельным цветом. Давайте исправим наш ошибку и поместим другой deb(), чтобы увидеть, как это выглядит.

image

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

Заметьте, что мы использовали debc, а не deb. Это та же функция, только со свернутым выводом. Если вы начнете использовать Deb.js, вы очень быстро заметите, что вам не всегда нужно видеть все подробности.

Как был придуман Deb.js


Изначальная идея была изложена в статьеРеми Шарпа(Remy Sharp) о нахождении вызовов console.log. Он предлагал создавать новую ошибку и получать оттуда трассировку:
['log', 'warn'].forEach(function(method) {
  var old = console[method];
  console[method] = function() {
    var stack = (new Error()).stack.split(/\n/);
    // Chrome includes a single "Error" line, FF doesn't.
    if (stack[0].indexOf('Error') === 0) {
      stack = stack.slice(1);
    }
    var args = [].slice.apply(arguments).concat([stack[1].trim()]);
    return old.apply(console, args);
  };
})

Оригинал статьи можно найти в блоге Реми. Это особенно полезно, если мы пишем в окружении Node.js.

Итак, имея трассировку стека, мне как-то нужно было внедрить код в начало и конец функции. Тогда-то мне вспомнился паттерн использованный в вычисляемых свойствах Ember'а. Это хороший способ, запатчить Function.prototype. Например:
Function.prototype.awesome = function() {
    var original = this;
    return function() {
        console.log('before');
        var args = Array.prototype.slice.call(arguments, 0);
        var res = original.apply(this, args);
        console.log('after');
        return res;
    }
}
 
var doSomething = function(value) {
    return value * 2;
}.awesome();
console.log(doSomething(42));

Ключевое слово this в нашем методе, указывает на базовый класс функции. Мы можем вызывать метод, к которому мы подключаемся, позже, когда нам это потребуется, это именно то, что нам нужно, так как мы можем отслеживать время до и после выполнения. В то же время мы возвращаем нашу собственную функцию, которая теперь работает, как прокси. Мы использовали .apply(this, args) для того, чтобы сохранить контекст и переданные аргументы. И благодаря подсказке Реми, мы также можем получить трассировку стека.

Остальная часть реализации Deb.js это просто декорация. Некоторые браузеры поддерживают console.group и console.groupEnd, которые намного улучшают визуальное отображение при логировании. Chrome даже позволяет нам раскрашивать отображаемую информацию в разные цвета.

Итог


Я верю в использование отличных инструментов. Браузеры это умные инструменты, разработанные умными людьми, но иногда нам нужно нечто большее. Deb.js появился, как крошечная утилита и успешно помогла ускорить мне процесс отладки. Это, конечно же, опенсорсная библиотека. Не стесняйтесь создавать issues и pull request'ы.

Спасибо за внимание.
Йосиф Крошный @jojo97
карма
14,2
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (16)

  • 0
    Статья неплохая, но реферальные ссылки…
    • 0
      Не заметил ссылок, это видимо tuts+ каким-то образом при копировании ссылки подсовывает, попробую убрать.
    • +3
      А как вам «Мы видим ихние параметры»?
      • +1
        Да, так себе:) Исправил
  • +10
    Иметь возможность остановить выполнение программы и посмотреть её состояние — бесценно, но Chrome не может знать, что именно мы хотим увидеть.

    Ну, вообще-то может. Рецепт такой:

    1) Ставим breakpoint в месте ошибки, либо отмечаем опцию «Pause on exceptions»
    2) Используем панель Call Stack, переходим вверх по вызовам
    3) Смотрим состояния переменных/параметров функций, либо наведением на них курсором, либо через панель Scope Variables
    4) При желании, выполняем свой код в консоли в контексте текущего места в коде, либо используем панель Watch Expressions
    5) Возвращаемся к п.2 до тех пор, пока не нашли истинное место проблемы

    Заметьте, ни единой правки в коде не было произведено для отладки.
    Что вам еще нужно? :)
    • 0
      Я всего лишь переводчик, с этими замечаниями лучше обратиться к автору:) Но я смею предположить, что дописать .deb() будет удобнее, чем выполнять все вышеперечисленные действия. Возможно, это дело вкуса, кому-то проще дописать несколько буков, а потом не забыть их удалить, а кому-то удобнее все делать вручную.

      upd: похоже, это была шутка, слишком тонко:)
      • +5
        Нет, не шутка :)
        Действительно не могу определить use case для этой библиотеки. Вам же все равно приходится через stack trace (только текстовый) искать проблемное место. Потом нужно дописывать вызов библиотеки. Лог писать специально для того, чтобы найти проблему. А если вы нашли не то место, где нужно исправлять? Тогда нужно продолжать подниматься по стек трейсу и уже в другом месте писать вызов библиотеки, ну и лог при необходимости. А лог нужно будет писать часто, т.к. библиотека только аргументы функции сама может выводить, а промежуточные значения — нет.

        Все эти лишние действия только увеличивают время нахождения проблемы.
        • 0
          Возможно, не всегда нужны промежуточные значения, если логика функции очень элементарна, то может хватать простого .deb(). Ну в общем, я к тому, что в некоторых случаях, это все же может пригодиться. Сам, я вряд ли собираюсь использовать данную библиотеку, только из-за того, что мне всегда хватало стандартных средств отладки, поэтому не могу точно судить, о пользе или бесполезности данной библиотеки.
  • –5
    Статья интересная, но от меня получает минус. За то, что не отмечена как перевод при создании. Не думаю, что в данном случае это гонка за деньгами, как в случае одного известного желтого автора, но столь же раздражает.
    • +3
      Не знал, что нужно это как-то указывать, кроме как явного указания в начале статьи. Попробую исправить.

      upd: что же, исправить нельзя, спасибо за замечание. Запомню на будущее.
    • 0
      Даже в слово-которое-нельзя-называть набежали =)
  • +1
    Статья понравилась, достаточно легко читается. На мой взгляд в примерах использования отладчика картинки мелькают слишком быстро, возможно стоило бы разместить скриншоты вывода, т.к. хочется рассмотреть вывод подробнее. По библиотеке, как уже сказали выше, сомнительное преимущество, скорее на любителя, для тех, кто предпочитает дебажить логгированием в консоль.
    • 0
      Разместил те гифки, которые были. Можно, конечно, подоставать нужны кадри или как-то замедлить гифки, но это очень долго.
      • 0
        Мне кажется у Вас клавиатура сломалась.
        • –2
          Мне кажется, вы забыли поставить запятую;)
  • 0
    Присоединяюсь к комментарию @DenAmir.
    Я не понимаю, это тонкий троллинг автора или что? Вместо пошаговой отладки автор предлагает прикрутить сомнительный костыль, который идёт вразрез с best practice.

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