As developers, we spend much of our time debugging apps - often code we didn't even write. Sadly, few developers have ever been taught how to approach debugging - it's something most of us learn through painful experience. The good news is you _can_ learn how to debug effectively, and there's several key techniques and tools you can use for debugging JS and React apps.
AI Generated Video Summary
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.
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.
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.
6. Debugging React and Introduction to Replay
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.
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.