Video Summary and Transcription
Debugging JavaScript is a crucial skill that is often overlooked in the industry. It is important to understand the problem, reproduce the issue, and identify the root cause. Having a variety of debugging tools and techniques, such as console methods and graphical debuggers, is beneficial. Replay is a time-traveling debugger for JavaScript that allows users to record and inspect bugs. It works with Redux, plain React, and even minified code with the help of source maps.
1. Introduction to Debugging JavaScript
This is Mark Ericsson, and today I'm excited to talk to you about debugging JavaScript. Fair warning, I've probably got about 40 minutes of content and like 20 minutes to get through it, so I'm going to go a little bit fast. A couple of quick things about myself. I am a senior frontend engineer at Replay, where we're building a time-traveling debugger for JavaScript.
This is Mark Ericsson, and today I'm excited to talk to you about debugging JavaScript. Fair warning, I've probably got about 40 minutes of content and like 20 minutes to get through it, so I'm going to go a little bit fast.
The slides all are already up on my blog, blog.isquaredsoftware.com. Feel free to pop that open and follow along, or take a look at it later.
A couple of quick things about myself. I am a senior frontend engineer at Replay, where we're building a time-traveling debugger for JavaScript. We'll talk more about that in a few minutes. I answer questions anywhere there is a text box on the internet, I collect any link that looks potentially useful, I write extremely long blog posts, and I am a Redux maintainer, but most people know me as that guy with the Simpsons avatar.
This is not a Redux talk, but I do have one Redux-related piece of news. Two days ago while I was at the airport, I published Redux Toolkit 2.0 Beta. We have a number of things we're trying to accomplish in 2.0, most of it has to do with modernizing the package formatting and the contents of the JavaScript. We've removed a couple deprecated APIs. We have several new APIs we've added. The biggest thing I would ask is that we would really like people to try it out in your applications right now, see how it works, give us feedback, tell us what things broke, tell us if the APIs are working, so that we can move forwards towards 2.0 final. I don't have an actual timeline. I'm hoping within the next few months, hopefully, if things go well.
2. Principles of Debugging
Debugging is the process of finding problems in your program and trying to figure out what's going on. As programmers, we spend a lot of time trying to figure out why the code we wrote is not working. I think the biggest problem is that our industry does not teach people how to debug. Every problem has a cause and a reason, and it should be possible to figure out why something is broken. It's important to understand what the system is supposed to be doing and to be able to reproduce an issue. Debugging with a plan and not panicking when encountering errors are also crucial.
All right. Let's talk debugging. Debugging is the process of finding problems in your program and trying to figure out what's going on. In other words, why is it broken? And how do we fix it?
Now, as programmers, we spend a lot of time doing things other than just writing code. We are communicating with our team. We are doing planning, design, discussions, code review. We spend a lot of time trying to figure out why the code we wrote is not working. And yet a lot of developers are not actually comfortable doing this. And I've thought about this a bit. I think the biggest problem is that our industry does not teach people how to debug. How many of you have a comp sci degree and yet never had a course in debugging? To me, debugging is an absolutely critical skill for developers. And the good news is it is something you can learn and get better at.
So, let's look at some core principles of debugging. Now, the title of the talk is debugging JavaScript. These principles are universal. You can apply them to any language and frankly you can apply them outside of programming as well. And the first is that every problem has a cause and a reason. And it should be possible to figure out why this thing is broken. Now, just because there is a cause doesn't mean it's going to be easy to figure out. And there's a lot of things that can make that more complicated. But it is possible. Another is that it's very important to understand what the system is supposed to be doing. If a bug is something is wrong with the system, you have to know what it was supposed to do in the first place to see that behavior is incorrect. Another principle is that reproducing an issue is absolutely key. For one, you need to be able to figure out what area of the code is broken, and that often requires a trial and error process of making this thing crash over and over and over but it's also important because once you think you have a fix, you need to be able to try the same steps and verify that it actually works right. You also need to be able to debug with a plan. This is basically the scientific method. Don't just go changing random variables and hoping it's somehow going to make it better. You need to be very careful and intentional about the changes you make, try one thing at a time, see if the behavior changes actually match what you expect it to be and try to narrow down where and why something is going wrong. Another issue is that errors provide useful information and yet people often panic when they see that gigantic stack trace.
3. Debugging Principles
I've seen pictures of a react stack trace that is minified and you can't read anything or a Java J2EE stack trace that's 500 lines long and your code is two lines somewhere in the middle. Don't panic, try to understand it. Debugging is twice as hard as writing a program. Try to write code that is clear and easy to understand. The steps for debugging include understanding the problem, reproducing the issue, figuring out why it's happening, identifying the root cause, finding the best solution, fixing it, and documenting the process. Use the right tool for the job.
I've seen pictures of a react stack trace that is minified and you can't read anything or a Java J2EE stack trace that's 500 lines long and your code is two lines somewhere in the middle. Now, somewhere in there, there is useful information, your code, your file, line 37, somewhere, but the errors tell you something about what's going wrong. Don't panic, try to understand it.
And yes, literally just Googling your error is a good first step. There's a very famous quote from one of the adventures of the UNIX operating system in the C language where he says, debugging is twice as hard as writing a program. So if you really clever code, you're probably not smart enough to figure it out yourself. So try to write code that is clear and easy to understand so that some time later you or one of your teammates or that intern five years from now can understand what was happening.
In general, the steps for debugging look something like this. First you have to understand what is the description? Someone filed a bug report. What are they actually even trying to say is wrong? Second, reproduce the issue. You have to be able to find reproducible steps that make the error happen. Open up the app, click this tab, click that button, kaboom. Then, and this is the hard part, try to figure out why it's happening. A good tactic is kind of like a binary search. We have a whole application. Can I narrow it down to half the app? A quarter? An eighth? Work your way, try to narrow down where in the code this is going on. Once you actually think you know what's happening and you've identified some of the symptoms, don't stop there. Keep going and try to figure out if there's a deeper root cause problem that's happening. Once you know what's going wrong, then you can try to figure out what the best solution is for trying to fix it. And this is where constraints come in. Maybe it's a one line fix. Maybe you think you need to rewrite the entire subsystem, but you don't have time because your team is already overloaded. Maybe the code is really complicated. Try to figure out what is the right amount of effort needed to make the appropriate fix. Then, actually fix it. And ideally add more tests and checks so that this doesn't happen in the future. And finally, try to document as much as possible, whether it's in the commit message, the PR, the issue, leave information for people for later so they understand what went wrong and how you fixed it. A couple other tips. Use the right tool for the job. There are many different tools to use for debugging.
4. Debugging Tools and Techniques
The more tools you have in your toolbox, the better equipped you are to try to solve problems. Be willing to look underneath the hood and understand what's going on inside. Don't be afraid. Take your time. Think through it. It is something you can figure out. There are times you just need to walk away, take a breath, get some sleep, come back the next day. Print statements are very easy to add. They show you changes to the system over time. Graphical debuggers help you focus on a specific piece of code and inspect the contents of the running program. Different languages have different kinds of print statements available. You can have timestamps. You can have different levels of logging.
We'll talk about some of them in just a second. The more tools you have in your toolbox, the better equipped you are to try to solve problems. Another is that we use many different libraries and packages and frameworks. We often treat them as black boxes. Be willing to look underneath the hood and understand what's going on inside. Because often understanding that behavior makes it possible to see what the real problem is.
Another is just don't be afraid. You see that giant error stack trace, or it's a piece of the system you've never worked on before. You might panic, especially if you're new to the team. Don't be afraid. Take your time. Think through it. It is something you can figure out. And this is something I struggle with. It is very easy to get caught up and chasing it. I'm this close to fixing it. I'm this close to fixing it. And get stuck. There are times you just need to walk away, take a breath, get some sleep, come back the next day. There have been times I fixed five minutes the next morning after being stuck for hours the previous day.
All right. I'm going to have to keep on going. There's a lot of debate about should I use print statements or graphical debuggers? And I say, why not both? They are both wonderful tools. Print statements are very easy to add. They show you changes to the system over time. Graphical debuggers help you focus on a specific piece of code and go through it step by step and inspect the contents of the running program. These are both great tools to have in your toolbox. Different languages have different kinds of print statements available. You can have timestamps. You can have different levels of logging.
5. Debugging JavaScript Tools
In JavaScript, debugging is mostly done with console methods or logging libraries like Winston. The console API allows you to print objects as tables and group messages. One common confusion is that expanded objects or arrays show their contents at the time of expansion, not when they were logged. JavaScript libraries are often distributed as source files, and graphical debuggers have similar commands and buttons. Breakpoints, variable scopes, call stack functions, and step buttons are common features. Examples include Chrome DevTools and VS Code.
In JavaScript, this is mostly done with the console methods or you might have a logging library like Winston. There are different methods for different levels. The console API also has things that let you do like print objects as a table, group messages together. One common problem I see is that people log an object or an array, and sometime later they expand it and the contents look different. It actually shows you what it contained at the time that you expanded it, not at the time that you logged it in the first place. This confuses a lot of people.
Also, most JavaScript libraries are distributed as source files on disk in node modules. You can edit those yourself, but, like, try to remember to remove the log statements later. Most graphical debuggers have the same kinds of commands and buttons inside. Break points let you pause at a certain point in the program. Usually you would set those by left-clicking on a line number. There's usually panels that show the contents of variables in scope, something that shows you the call stack functions, and some buttons that let you step forward, step in, step out. As a couple examples, the Chrome DevTools have all those commands. You've got the break points and the scope and the call stack on the right side. You've got a break point marker over there on the left line. VS Code has basically all the same buttons, just in different places. These are all very common pieces to every IDE and every debugger.
6. Debugging React and Introduction to Replay
The biggest thing is to understand how React mental model works with components and data flow. Trace the data back to where you found it. Use the React DevTools and Redux DevTools extensions to inspect and debug your code. My day job is working for Replay, where we're building a true time traveling debugger for JavaScript. Replay allows you to record a bug once and use time travel debugging to understand what actually happened.
Let's see, let's skip past some of this. A couple tips for debugging React. The biggest thing is to understand how React mental model works with components and data flow. React renders components, parents pass data as props to their children, children pass data back to their props via callback functions. If your UI, if the stuff on the screen is wrong, either your data was incorrect or the rendering logic was wrong, if the rendering logic is right, look at the data. Where did it come from? It came from the parent, it came from Redux, it came from Apollo. Trace the data back to where you found it.
Also be sure to use the React DevTools. The React DevTools browser extension will show you the component tree, it allows you to select a component, inspect it, and look at its prop, state, and hooks. Similarly, with Redux, it's very important to understand the Redux data flow, you dispatch actions, reducers update the state, the UI re-renders. Similarly, there is a Redux DevTools extension that shows you the history of the dispatched actions. For each action, you can inspect the contents of the action, the contents of the state and the diff of the state. As a result of that, these are all very valuable tools to have in your toolbox.
Okay. We might actually pull this off about on time. All right. So corporate job sales pitch. My day job is working for Replay. I mentioned this. And we're building a true time traveling debugger for JavaScript. It's kind of ironic, because the original sales pitch for Redux was time travel debugging, and it's that. Replay is that like times a million. One of the hardest parts of debugging is you have to be able to reproduce the issue. And sometimes that can take a lot of steps. And you're paused at a break point, you step, you sit, whoops, I went one line too far. And now you have to stop the program and restart it and get all the way back to the point where you were. And it's a pain. Or the bug only happens on that one QA developer's machine on a Tuesday in February or something like that. So the idea of replay is that we let you record a bug once. And then use time travel debugging to understand what actually happened in that.
7. Replay UI and Debugging Workflow
The basic workflow involves downloading our versions of Firefox or Chrome, recording the bug, uploading it to the cloud, and opening it in the replay UI for inspection. The UI allows you to jump to any line of code, see how many times it ran, add console logs, and use step debugging. Collaboration features enable comments and sharing of print statements. Replay is free for open source and individuals. In the live demo, the replay UI showcases a recording of a Redux app, allowing users to navigate through different points in time and inspect the code using DevTools mode. Hit counts provide insights into code execution, helping identify unexpected behavior. The console-like interface allows for further investigation.
The basic workflow, you download our versions of Firefox or Chrome, record the bug, make it happen once, upload it to the cloud, open up the recording in the UI, and now you can inspect the recording at any point in time. You can jump to any line of code, you can see how many times it ran, you can add console logs and print statements after the fact, and you can use step bugging to inspect things.
There's also collaboration where you can add comments, have your teammates see what was going on, and even share the print statements that you've added. Replay is free for open source and individuals. Paid for companies where we're a startup, we're trying to actually make money.
All right. Now, the fun part. We get to do a live demo. Come on, Wi-Fi. Okay. Good. First step. So, this is the replay UI. This is a recording I made quite a while back of the example from the Redux Fundamentals tutorial. So, we've got the viewer. I can see what the application looked like at the time it was recorded. I can jump to a couple of different points in time and see what the UI looked like. But I can flip over to DevTools mode. And this is basically like the Firefox Browser DevTools in a browser, because that's actually where our codebase started.
So, this is a Redux app. I'm probably interested in the reducers. So let's go find the todo's slice. The first thing I noticed when I opened this is in addition to the line numbers, we've got all these hit counts. And those are telling me how many times each line of code ran during the recording. And this starts to tell you useful information, like maybe I expected the line to run five times, but it actually ran like 100 times. That's not good. Or maybe I expected it to go into the if statement, but it didn't. Why? So, I can look at the code and I can see that, in this example, the todo toggled reducer ran, it looks like, three times. Okay, so now I'm curious about what was going on inside of that. So I can click this plus sign and you notice over here on the right we have what looks like your typical browser console.
8. Debugging with Replay
You can evaluate print statements, jump to a point in time, inspect values, and navigate the code. Replay provides a list of events and allows jumping to the corresponding code. It also shows the DOM and React component tree at specific points in time. This revolutionary debugging tool saves time and is highly recommended. Check out the slides and additional resources on the blog.
And you notice we've got three messages there. And that's because that line of code ran three times, and it's evaluating the message at each line. So what if I want to say, like, here's the name of the function, and I want to see what the action object was at each of those. So I've edited the print statement, and it's evaluating it. There's the string and there's the entire action object. And I can expand it and take a look.
Okay, what if I want to jump to a point in time? I could, for example, click on one of these, and now I'm paused just like a normal step debugger inside this function, and I can look at the values that are in scope, I can hover over them, I can inspect them, and we've got the let's see we've got a whole stack down here somewhere. So we can see that this was triggered from some kind of react quick handler, and we're inside the Redux code.
Another thing we've got is there's a list of all the different times I pressed a key or I clicked, and if I hover over this, there's a jump to code button. What does that do? Well, it just jumped me right to the line of code where I dispatched where I ran a click, there's my on click prop handler, and now I can start inspecting the code even further. We've even got the DOM tree at that point in time, and the React component tree at that point in time. And so now I can go through the entire recording, I can see what happened, jump back and forth, inspect the system, and I only had to make the recording once. I am having a ton of fun building this application, and I truly believe it is a revolutionary change in how we debug. So please check it out, it will save you a ton of time. I wish I'd had it years ago. All right. That's all I've got, and I actually basically finished on time. That's impressive. Like I said, the slides are up on my blog, I've got links to a bunch of additional resources about debugging. Please come by and say hi, ask questions about Redux or replay or debugging or whatever. Thank you very much.
9. Replay Compatibility and Usage
Does replay work with Redux or plain React? Replay works with everything, recording anything that runs in the browser. The biggest point of friction with replay is the need to pause and make the recording separately. Replay has limited capability to obfuscate data, but a better version is planned. Console logs and debuggers are both used for different purposes. Replay can be used with minified code.
Tons of questions, where do we start? Here we go. Does replay only work with Redux or also plain React? Replay works with everything. Replay works by recording the browser talking to the operating system. So literally anything that runs in the browser, replay has recorded it. We do have some React-specific integrations, like the dev tools, but it doesn't matter if you're using React, Vue, Angular, vanilla JS, whatever, if it ran in the browser we with the exceptions of WebGL and audio we can't do those yet.
This is a fun one. Is this the new dev tools and can we just fully replace the old one? Yes, no, maybe. The honestly the biggest point of friction with replay right now is that you have to pause and make the recording and then open it up separately. When I'm working on a feature, I'm still using the browser dev tools to investigate that as I'm writing the code. Once I've actually written something then using replay to investigate what happened afterwards is way easier. Makes sense.
This is very interesting. Can replay obfuscate sensitive or confidential data before sending the replay to cloud? I think we have a very limited capability to obfuscate right now. I think a better version of that is on our road map for later this year, early next, I think. Cool. I'd be very excited for that. Hopefully. Hopefully you don't hold me to that. Nope.
This is a good one. Do you console log? I console log, I debug. Like I said, it depends on what the situation is. Logs are great for seeing step by step what happened in my application, GUI debugging, debuggers are good for drilling down on one particular piece, so I do both. I'm a big console log fan. You and a lot of other people. I know. It's just for the quick ones, you know? We answered that one. Can replay be used with minified code? Absolutely, yes.
10. Using Replay with Minified Code
Replay can be used with minified code. It records what happened in the browser, regardless of the build mode. Source maps are important for replay as they allow us to show the original code alongside the recorded minified code.
It's just for the quick ones, you know? We answered that one. Can replay be used with minified code? Absolutely, yes. So same thing I said a minute ago, replay records what happened in the browser. It doesn't matter if it's a development mode build, production mode build, whatever. And in fact, at replay, yesterday at the JS Nation, Jesselyn Yeen gave a great talk about debugging and she mentioned source maps which tell the debugger here's what the minified code looked like. Here's what the original code looked like. And we love source maps at replay. So replay also relies on source maps. If your app had source maps alongside, it records the minified code that ran but we then use the source maps to show you the original application.
Comments