Happy Little Accidents - Debugging Javascript
Juha-Matti Santala
Posted on February 21, 2019
Last year I gave a talk in HelsinkiJS and Turku ❤️ Frontend meetups titled Happy Little Accidents - The Art of Debugging (slides).
This week I was spending a lot of time debugging weird timezone issues and the talk popped back up from my memories. So I wanted to write a more detailed and Javascript focused post about the different options.
Print to console
All of the examples below are ones you can copy-paste to your developer console and starting playing around with.
console.log
One of the most underrated but definitely powerful tool is console.log
and its friends. It's also usually the first and easiest step in inspecting what might be the issue.
console.log(`The value of variable foo is ${foo}`)
The most simple way is just to log some extra text with variable values. ES6 and its template literals make it a breeze. No need to worry about string concatenation.
To print out multiple values, you can do
console.log(`name: ${name}, age: ${age}, city: ${city)`)
but thankfully, ES6 brings us object property value shorthand. It allows us to create objects where the key will become the variable name and value its value.
console.log({name, age, city})
You can even color your output with CSS!
console.log(`%c This is blue,%c and this is green`, 'color: blue', 'color: green')
console.table
For the longest time, the usage above was the extent of how I used printing to the console. But lately I've learned so many new tools that might be new for you as well.
let dog = {
name: 'Bolt',
age: 3,
isGood: true
}
let cat = {
name: 'Grumpy',
age: 5,
isGood: false
}
console.table([dog, cat])
With console.table
you get a nicely outputted tabular view into your data. It's very nice when you have objects that share the same keys.
console.trace
If you want to inspect the stack trace of your function calls, you can use console.trace()
function foo(bar) {
console.trace();
return bar * 2;
}
console.log(foo(21));
console.count
Sometimes you need to keep track of how many times a line has been executed. Adding a new variable, keeping track of it and console.log'ing it is cumbersome and can end up leaving unused variables in your code.
function foo(bar) {
if(bar > 0) {
console.count('Bar was positive')
return true;
} else {
console.count('Bar was 0 or negative')
return false;
}
}
foo(1)
foo(0)
foo(42)
foo(-210)
console.group
If your application prints out a lot of output, sometimes you might want to group it bit better. Instead of inserting manual console.log('Here be dragons')
, you can use console.group
function foo(bar) {
console.group('At function body')
console.log(`Executing function foo with a list of ${bar.length} items`)
console.group('Inside loop')
bar.forEach(baz => {
console.log(`Value of baz is ${baz}`)
})
console.groupEnd()
console.log(`Function foo has finished`)
console.groupEnd()
}
foo([1,2,3,4])
foo(['Bolt', 'Lassie', 'Doggo'])
Debugger
Javascript's debugger
keyword is a magical creature. It gives you access to the very spot with full access to local and global scope. Let's take a look at a hypothetical example with a React Component that gets some props passed down to it.
const CardComponent = props => {
debugger;
return (
<h1>{props.title}</h1>
<ul>
{props.items.map(item => (<li>{item}</li>))}
</ul>
);
}
When the component is rendered, if our browser's developer tools are open, it will halt the execution and give you access to console. There you can take advantage of knowledge from the previous section and inspect what is inside props or any other variable available in the scope where debugger was executed.
I often use debugger as a quick prototyping tool: I jump into a particular spot in the code, look into what data I have and in what format and build small pieces of what's to follow inside developer tools console and then move those things into the code itself.
This approach shortens the feedback loop from "write code in editor -> refresh browser" to "write code in console -> see the result".
Once you're in the debugger mode, you can also continue the execution line by line to see where your execution fails.
Browser Dev Tools
If you're using the console.log and debugger, you are probably familiar with the Browser Developer Tools. And if not, you should familiarize yourself with them. They are bit different between browsers and I'm most familiar with Google Chrome so my examples will be from that. If you're using Safari, Edge or Firefox, you can quite easily figure this out with some exploration or an Internet search.
Conditional Breakpoints
You can access the debugger state also without changing any code. In Sources
tab of Chrome Dev Tools, you can open a Javascript file and click any line number to turn it into a blue arrow
What's even more awesome, is you can make these breakpoints conditional by right-clicking the line number and selecting Edit breakpoint.
While it instructs you to provide a boolean expression there, you don't necessarily have to. I've used these breakpoint conditionals for adding a console.log
to be run when that breakpoint conditional is hit. It will return undefined which is a falsy value – it will still print it but won't stop execution.
Watch
On the right side of the Chrome Dev Tools is a collection on buttons and panels. While they all provide valuable tools to you, I'll only highlight one (you can learn about the other ones yourself): watch.
Inside watch panel, you add variables and as the code is executed, it will show their current value. Instead of jumping in console after every breakpoint (for example in a loop), you can just look at the value in watch and see what happens.
Network tab
In the modern web development, HTTP calls to API endpoints are one of the most common ways to transfer data. Chrome Dev Tool's Network tab is a wonderful example of how to inspect what's going out and what's coming in.
Here I have made an API call to https://api.pokemontcg.io/v1/cards?name=charizard and I can directly inspect the response from the Network tab. This can help you figure out what data is returned and if it's in the right format for your use.
By clicking around in the different tabs you can find out more information about the headers and responses as well as the performance of the call.
Framework specific extensions
The support for different frameworks via extensions is amazing. If you're working on React, Redux, Vue, or Angular, looking at those extensions is definitely worth the while.
I have my Redux tab open almost all of the time when working in React+Redux app because it gives me best visibility into what data is available.
Install the plugin (and restart your browser) and you'll have great extended tools at your fingertips right in the Chrome Dev Tools.
Logging
While printing to console is helpful, it's only useful when you're developing the app. They do not persist and they don't leave the client's browser console so you won't be able to look into the history of issues.
That's where logging services come handy. Using tools like Elasticsearch or BugSnag, you can save your logs into the service and gain analytics and search into the issues. This is especially valuable when your user reports that something went wrong and you can just open your log service and go through what has happened.
Logging your logs to these services is simple. In BugSnag, you use their SDK and whenever something worth logging happens, just run
try {
something.risky()
} catch (e) {
bugsnagClient.notify(e)
}
For small projects, you can take a look at Gentry created by a friend of mine.
Non-Technical Approaches
In addition to writing Javascript code and interacting with your browser, there are non-technical ways to debug your software.
Rubber Duck Debugging
Explaining what you are trying to do, what's the encountered problem and what you have tried already to your colleague is a good tool. It forces you to think about the problem from multiple different aspects and phrase the question in a way that often helps you figure out the solution before your colleague has time to answer.
To save your colleagues time from this rather monologue-y approach, you should talk to a duck. I have one at my desk at work and while someone might think talking to a rubber duck is silly or insane, the approach really works. My duck is a special breed, it's a detective duck and like everyone knows, detective ducks are super effective against programming bugs.
Sleep & Walk (not necessarily in that order)
Step away from the computer and the problem. Let your unconscious mind work on it and think about something totally different. I often take 20 minute walks every afternoon and especially if there's a nasty problem. Sleeping is another very good approach because it lets your mind clear itself from excess thoughts and crystallize the solution.
When I'm facing a particularly nasty issue, I do it like this:
1) Brain dump: writing down everything to my notebook. What's the problem, which cases it happens with, what I have tried, what assumptions I have.
2) Go for a 20-minute walk, think about completely other stuff, enjoying fresh air and nature. Because I've written down everything, I don't have to worry about forgetting things.
3) Back to desk. If I have gotten new fresh ideas about the problem while walking, I add those to my notes. After that, getting back to problem solving.
It sounds always counterintuitive to take breaks but it can improve both your problem-solving skills, your productivity and your happiness when you're not pounding your head to the wall but giving your brain some slack. Best solutions are not forced but come from when you give yourself time and space to think about stuff.
Wrap-up
So what did we learn today?
console is a powerhouse in Javascript. It's most often used just for console.log but learning about its different abilities and capabilities is worth it.
Debugger is a friend. Whether it's a tool to jump into state to figure out what's wrong or to help you build the next piece of code interactively, using it effectively will improve your workflow.
Invest time in learning your browser's Dev Tools. You can develop a React+Redux app with no extra tools but once you install extensions for both, you will gain much easier access to your Redux state, component state and props and basically everything that otherwise would require lots of console.logging and debugging. I have Firefox as my secondary browser solely because of their great built in CSS Grid tooling.
Start logging what happens in your app if you aren't already doing that. It will give you insights into how it's working, how it's used and will let you solve some of the issues that pop up before all of your clients are sending angry emails to support.
Take breaks, talk to a duck, walk and sleep. A developer's work is not (or at least, should not be) measured in hours typing the keyboard nor the lines of code created. I spend quite a lot of time away from my keyboard: writing notes to my notebook, sketching, designing, planning, taking walks, discussing matters with colleagues.
Posted on February 21, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.