📜 ⬆️ ⬇️

Deb.js: the tiniest debugger in the world

Translation of the article “Deb.js: the Tiniest Debugger in the World”, Krasimir Tsonev

We, as developers, write code. But we do not just write the code, we also check whether the code written by us works. We spend a lot of time and effort to make sure our programs do what we have to do. the debugging process is often painful. Especially if we do not use suitable tools. To deal with this problem, today's note introduces Deb.js , a small JavaScript library that helps with debugging in the browser.


Example


Start by creating a small page with a little javascript interaction. We will create a form with two fields and a button. By pressing the button, we will collect data and display a message in the console. Here is the page layout:
')
<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> 

To simplify the example, we will use jQuery for DOM samples and events. We wrap the functionality in the following module:

 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 + '!'); } } 

The collectData function collects values ​​from fields and checks if the user has entered anything. If not, a callback occurs with the transmitted object containing a short error message. If everything is OK, the function responds with null in the first argument and an object containing the data as the second parameter. The developer using the module must check if the object of the error is transmitted. If the error is not passed, then the second argument passed can be used. For example:

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

We check if the err parameter is an object, and if so, we display an error message. If you look at the code, you can find the problem, but for now let's see how everything works:

image

If no data is entered, our script works as expected. Under the form, a message appears stating that no data has been entered. At the same time, If we fill in the fields and press the button, we get the message:
Uncaught TypeError: Cannot read property 'msg' of null
Let's track down and correct this error.

Traditional approach


Google Chrome has great tools for solving such problems. We can click on the error and see its stackracks. We can even go to the place where the error was made.

image

It seems that the error method from our module gets something that is null . And of course, null does not have the msg property. This is why the browser throws an error. The error function is called only in one place. Let's set a breakpoint and see what happens:

image

It looks like we get the right object, data and error is null , and this is the right behavior. Thus, the problem must be somewhere in the if clause. Add console.log and see if we are moving in the right direction:
 Module.collectData(function(err, data) { console.log(typeof err); if(typeof err === 'object') { Module.error(err); } else { Module.success(data); } }); 

And in fact, typeof err returns an object . That is why we always show an error.

image

And voila, we found a problem. We just need to change the if construct to if (err) and our little experiment will work as expected.

However, sometimes this approach can be quite tedious for the following reasons:


Having the ability to stop the program and see its status is priceless, but Chrome cannot know what we want to see. As in our case, we have to double check the if construct. Wouldn't it be better if we had a tool available directly from our code? A library that provides the same information as the debugger, but is inside the console? So, Deb.js is the answer to this question.

We use Deb.js


Deb.js is a small piece of JavaScript code, 1.5kb in minifitsirovanny form, which sends information to the console. It can be attached to any function and displays:


Let's take a look at what our example looks like when using Deb.js:

image

We, in accuracy, see the transferred arguments and stack trace. But notice the changes in the console. We work on our code, find where the problem may be, and add .deb() after the function definition. Note that the type err placed inside the function. Thus, we do not need to look for it. Returns are also grouped and colored. Each function we are debugging will be printed in a separate color. Let's fix our mistake and put another deb() to see what it looks like.

image

Now we have two functions. We can easily distinguish them, as they are painted in different colors. We see their parameters, output and execution time. If the function has console.log instructions, we will see them inside the function, at the place where they occur. There is even an opportunity to leave the description of functions, for the best their recognition.

Notice that we used debc , not deb . This is the same function, only with minimized output. If you start using Deb.js , you will very quickly notice that you do not always need to see all the details.

How Deb.js was coined


The initial idea was set forth in Remy Sharp’s article on finding console.log calls. He suggested creating a new error and getting a trace from there:
 ['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); }; }) 

The original article can be found on Remy's blog . This is especially useful if we are writing in the Node.js environment.

So, having a stack trace, I somehow needed to embed the code at the beginning and end of the function. It was then that I remembered the pattern used in the calculated properties of Ember . This is a good way to patch Function.prototype . For example:
 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)); 

The keyword this in our method points to the base class of the function. We can call the method to which we connect, later when we need it, this is exactly what we need, since we can track the time before and after execution. At the same time, we are returning our own function, which now works as a proxy. We used .apply(this, args) to save context and passed arguments. And thanks to Remy’s hint, we can also get a stack trace.

The rest of the implementation of Deb.js is just decoration. Some browsers support console.group and console.groupEnd , which greatly improve visual display during logging. Chrome even allows us to color the displayed information in different colors.

Total


I believe in using great tools. Browsers are smart tools developed by smart people, but sometimes we need something more. Deb.js appeared as a tiny utility and successfully helped speed up my debugging process. This, of course, is an open source library. Feel free to create issues and pull requests .

Thanks for attention.

Source: https://habr.com/ru/post/228819/


All Articles