Skip to main content

Experience Management

So You’ve Got Bugs, Huh?

Whenever you write JavaScript, you’re risking creating bugs. The risk of bugs increases proportionally as the amount of code and code complexity increases. There are ways to avoid bugs; I recommend ESLint, JSHint, or a language extension such as TypeScript to help reduce the introduction of bugs into your codebase.
This post isn’t meant to address bug prevention. Instead, it’s geared toward helping mitigate bugs that have already been introduced.

Got bugs, watdo?

There are myriad bugs that can exist in JavaScript. There are also bugs in JavaScript that can literally create more bugs. I’ll always recall one of my earliest memories of JavaScript debugging: I had 37 errors on a page and began fixing them one-by-one. After fixing the first two, the rest of the bugs fixed themselves. This is a good example of the nature of JavaScript. Since it is a scripting language, and therefore executed linearly, bugs and errors have a tendency to cascade.
If you find that you have JavaScript errors, you should always try to fix the error that arrives earliest in the call stack. This may seem obvious, but different people have different philosophies. As “easy” to fix as a later bug might seem, avoid this practice; you may find that you’ve wasted time “fixing” a non-issue.
Takeaway
Take it from the top. Fix the earliest bug first.

It’s not a bug, it’s a feature!

JavaScript bugs tend to have strange properties. Sometimes, often actually, a bug is not really the error; it may instead be a symptom of another error. In these instances, it may look like a function is broken or that you are calling the wrong method of an object, but I urge you to take a step back and try to follow the stack flow from the beginning. Think of fixing this type of JS error like figuring out why your speakers aren’t working:

  1. Are the speakers connected?
  2. Is the receiver on?
  3. Is the audio source connected to the receiver?
  4. Is the audio source on?
  5. Is there media for the audio source?

After all of this, you may find that you forgot to power on the audio source. You could have been trying to fix your speakers, but the problem was actually upstream the whole time.
Takeaway
Don’t just cure the symptoms. Locate and fix the root cause.

To alert or not to alert…

Back in ye olden days of JS debugging, when we had an issue, we would just use alert() to spit valuable feedback out to the developer. While this is still a valid tactic, it’s use case has slightly diminished over the years. Keep in mind: that alert() actually pauses script execution. This could be a good thing, or in the case of dealing with asynchronous calls, it could get you into trouble. When doing async JS, alerting could pause the execution long enough for an existing async call to complete; in this case, it would actually appear that the bug has been fixed (just by delaying the completion of a function). You could run into issues where you aren’t able to debug race conditions using alert().
Takeaway
Alert can still be used, but it has its issues; there are better ways. Consider this when attempting to debug with alert.

Console log is highly logical

Enter: console.log(). It’s a great way to debug by sending messages to the developer, without pausing script execution. Where you would have previously put an alert(), just put a console.log() with the same content. It’s fast, it’s easy, it’s slightly dirty, and it can get messy quick. There are a few problems with console.log(), however:

  • Anywhere you use one, you have to leave an artifact in your code until you remove it.
  • Like alert(), when you combine an object with a string, you wind up with something like: this is myObject: [Object, object] (it is possible, however, to avoid this behavior. Unlike alert(), console.log() can have multiple arguments passed to it. By separating the arguments with a comma, each argument will be logged to the console at the same time (e.g. console.log('this is myObject: ', myObject) as compared to something like alert('this is myObject: ' + myObject)).
  • In older browsers, using console.log() would blow them up when either there is no console object present on window, or the console wasn’t actually opened.
  • Objects logged to the console are evaluated when you look at them (this phenomenon has to do with the nature of JavaScript passing objects via reference, and the asynchronous way that the console inspects JS objects), and the values presented in the console are not always the value of the object at the time of execution. (This can lead to issues where you’re attempting to call a method of an object that you know is there, but it’s only there after you’re trying to call it, etc.)

Takeaway
Use console logging to print out simple or small things, but be aware of its caveats. Don’t always implicitly trust the value of an evaluated object in the console; it might not have always been that value, especially at the exact moment the log was fired.

Who you gonna call? Debugger!

Arguably the most useful way to debug code is to set a debugger in the code. A debugger acts as a hard-coded breakpoint in the JavaScript. When your developer tools in your browser-of-choice are open, and JS execution hits a debugger, it will immediately pause code execution (like alert() would), but it also allows you to continue to evaluate other JS expressions in the call stack. This is an extremely powerful debugging workflow. We will continue to discuss the breakpoint-style debugging in more detail, but I found it prudent to mention debugger first. One good thing about debugging this way is that, theoretically, you only need one debugger to step through a whole call stack.
Takeaway
Use the debugger to trigger a breakpoint-style debugging workflow. Just make sure you clean up after you’re done, or script execution will hang.

Bugs have you at your breakpoint?

Breakpoint debugging is very powerful, though seemingly underused. Entry level devs typically don’t use it; either they don’t know it exists, or they just haven’t had enough time to really figure out how it works or to grasp its full potential. Modern browsers with a developer panel have the ability to set breakpoints and navigate the call stack by executing one JavaScript expression at a time. While script execution is paused, you can display previously evaluated expressions and step into or over the next expression. Stepping over the next expression still causes it to be evaluated, but the debugging doesn’t break on that expression. Stepping over the next call would be beneficial on a jQuery selection (or any form of jQuery method, really). Stepping into the next call is typically used to traverse the call stack by entering into subsequent function calls. Debugging with this method offers a variety of benefits:

  • You can traverse the call stack.
  • You can evaluate more than just “logged” expressions.
  • You can add expressions to a watch list and be made aware of them as their value changes.
  • You can see the current scope and its closure(s).
  • While debugging, objects evaluated are “at-runtime”; meaning they don’t suffer the same “asynchronous limbo” that console.log() inflicts.

I encourage you to look up the developer tools of your browser-of-choice, specifically in regards to debugging with breakpoints:

Takeaway
Use the breakpoint debugging feature of your browser’s developer tools to follow the stack; to locate, diagnose, and fix the problematic code.

Bonus Round: Mobile debugging

So all of this is nice, right? But… what about on a phone? Say your bug only shows up on iOS Safari on an iPhone 5? The good news is: it is possible to “attach” mobile devices to an instance of a developer tool for debugging. The bad news is: it’s not super intuitive.
iOS:

  1. On macOS, open Safari
  2. Connect your phone to your computer
  3. Wait for the device to mount (iTunes probably wants to take over, just minimize it)
  4. In mobile Safari, navigate to the website in question
  5. In Safari on desktop, select: Develop > {Phone’s Name} > (Safari) {Website you’re debugging}
  6. Proceed with the Dev Tools as usual

Android:

  1. On your phone, locate and turn on Remote Debugging (fun fact, it’s not always in the same place; but it’s usually in: Settings > Developer Options [sometimes a hidden option] > Remote Debugging [sometimes called USB Debugging] )
  2. On your desktop, open Chrome
  3. Connect your phone to your computer
  4. In desktop Chrome, open Developer Tools
  5. Select:  > More tools > Remote devices (See: Developer Guide) > Settings > Enable USB Devices
  6. You may have to authorize your connection on your device
  7. Select your device from the left menu of the Remote Devices pane
  8. Enter the URL in the “New tab” field and click “Open”
  9. Click “Inspect” next to the newly opened tab
  10. Proceed with the Dev Tools as usual

Windows:

  1. Good luck!?

Takeaway
Debugging on mobile devices is possible. Also, who has a Windows phone?
TL;DR
There are a lot of debugging tricks that you can throw into your development toolbox, including: alert(), console.log(), debugger, breakpoints, and remote USB device debugging. Knowing how, when, and why to use them is an important part of development. Be aware of debugging tactics and use breakpoint debugging to locate and fix issues.

  • Address the earliest bug in the call stack first.
  • Cure the root cause, not the symptoms.
  • Use alert() sparingly.
  • Use console.log() when it makes sense.
  • Use debugger to trigger a hardcoded breakpoint.
  • Use breakpoint debugging to step through the code.
  • Use remote USB device debugging to debug on mobile devices.

 
 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Bryce Taylor

More from this Author

Follow Us
TwitterLinkedinFacebookYoutubeInstagram