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

в 16:00, , рубрики: debugger, debugging, javascript, Веб-разработка, отладка javascript

Перевод статьи «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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js