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'и .
 
Спасибі за увагу.
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.