Episode 7

Debugging Skills

00:00:00
/
00:40:03

December 21st, 2022

40 mins 3 secs

Your Host

About this Episode

Let's talk debugging skills! The conversation starts with some fun stories and experiences regarding debugging in general, and then panelists share thoughts about how to find bugs, lessons learned struggling through debugging, and how the way we structure code has a great deal of impact on debugging.

Transcript:

MIKE: Welcome to another episode of our Acima Development Podcast. We have, I think, an exciting discussion today. It's relevant to anybody doing development. We're going to be talking about debugging skills. Before going to that, let's go around and do some quick introductions. I'm Mike. I'll be hosting today. I've been a software developer for over 20 years, a Rubyist for most of that. Ramses, do want to introduce yourself?

RAMSES: Hey, everyone. I've been a familiar voice on this podcast for a while now. I've been a Rubyist developer for about a year and a half and a professional developer, I guess now, for about six months, and at Acima for almost four years now.

MIKE: Thank you. Afton.

AFTON: Hi, this is Afton again. I am also hitting my four-year mark this week here at Acima. And I've been in Ruby and Ruby on Rails my whole career.

MIKE: Thank you. Zsolt.

ZSOLT: Hello. I'm Zsolt. I've been doing hobbyist development for a little over ten years. I have been a Rubyist for about three months of that so far, very wonderful thing.

MIKE: Great. I think that we've got a range of experience levels here, which will be really great for the discussion. I wanted to start the discussion with, let's say, a couple of stories. The first one is not my own. I had a friend who had a professor [laughs] who told me this story. This friend got the story from the professor. He had a professor who said...when he was teaching the class, he taught them the way you find the last blue wolf in Ireland.

And the way you find the last blue wolf in Ireland is that you build a wall across Ireland. And then you stand up on the wall at night, and you listen for which side of the wall the wolf howls on. And then when you figure out which side that is, you build a wall through the middle of that side of Ireland. And then you stand up on the wall at night, and you listen, and when you hear the wolf howl, you know it's on that side of the wall. And then you build the wall through that section and do the same thing. And after a few iterations of this, you've found your wolf because you've cut down the problem size by half every time. That's my first story.

[laughter]

The second one is more of a personal one. I've got a window well that has regularly flooded for years, and it's just been a persistent problem. Luckily, the basement is unfinished or mostly unfinished, but it's a hassle. When it floods, we have to peel up the...we got some foam floor, and we peel it off the ground and have to mop up the floor and scoop up the water and put it in a bucket and dump it. It's a pain. [laughs] And I've had to do this often, particularly when the rain comes from that side of the house.

And I've tried all sorts of things to solve the problem. First thing I did was dug out some extra dirt in the window well. I thought, well, if I add some capacity to the window, well, the problem is that it's not big enough. Well, it didn't help. And I noticed that there's a drain pipe in there. It's a perforated pipe that goes down into the ground and connects to some others that go to a drain that comes into a sump with a sump pump. It drains the foundation, so we don't get flooding.

And the pipe, I thought, well, you know, this pipe is really long. It's hanging up at the top. Maybe some kids shoved some stuff in the end of it because it's just perforated. Maybe water can't get in the top. So I shortened the pipe, didn't help at all. [chuckles] We still got flooding. I used some caulk, and I sealed the edges of the window well. I thought, well, maybe water is pouring in from the side when it rains heavily from that side, and if I seal the joints, that will help. And the same problem still happened.

Earlier this year, I got a pipe auger or a pipe snake, like, long ones, so I could really run it down there, 50-foot long. And I attached a drill to the end, and I put it in there. And I thought, okay, there's probably some debris in there because it's just draining really slowly. So I ran in there, and I ran it in there, bzzzzzz, spun it around for a while. And I pulled out a little bit of mud and a little bit of grass, and not enough to matter.

[sighs] What is up with this? As I was standing there looking at the window well, kind of scratching my head, I noticed that there is a drain coming down from the roof into some drain pipe that ran out through under the lawn and drained over into a low spot that drains out to the road. And I noticed that where that drain pipe came out, the grass had grown over it, so it was plugged over. And I thought, oh, I should do something to fix that, you know, I bet the water is not able to get down there very well. I should have thought a little harder about this.

So I went over and took a little shovel and cut out around the end so the water could drain out there. And I took a hose, and I thought, okay, I'm going to test this. And I took the hose over to the end of the drain pipe coming off the roof. And I noticed that where that drain pipe met the pipe that goes under the ground, they'd become dissociated. The ground drain pipe had loosened over time. It had been washed out a little behind and fallen off and slipped over to the side.

So the drain pipe coming off the roof wasn't going into the drain from the ground at all, and if it had, it was plugged at the end. So any water that was coming down there, and it was about three feet away from the window well, would have immediately just started flooding the area next to the house, gone straight down to the window well, and poured inside.

After all these years of trying to figure out what was wrong with the window well, I realized the problem wasn't the window well at all. The water was coming off the roof and pouring straight into the window well. And there's no amount of drainage [laughs] that would have helped to solve that problem. The problem is it was just dumping water in every time it rained.

I took the pipe that went underneath, connected it to the drain pipe, stuck a couple of rocks under it, just some pebbles nearby, to brace it so that it was high enough. And presumably, the problem is solved with a couple of little pebbles. Had I looked around the window well ten years ago when this problem started happening, I would have saved myself a lot of trouble.

A couple of things I want to point out from this story, additional tools did not solve the problem. You know, I cut the pipe. I used the caulk to seal the joints, used the pipe auger, you know, I kept on escalating the tools I used to solve the problem. None of that solved it. What solved my problem was better observation of the situation and, in particular, of the context. Looking bigger than just at the window well and seeing the context that it was in allowed me to see that I was targeting the wrong problem. If I had looked a little bigger, I'd have seen what the underlying cause was for all the troubles I was having.

I thought through these stories ahead of time since we're talking about debugging because I think they'll really inform our discussion talking about software and how to find the problems that we have. With that, [laughs] any thoughts from the rest of you about...well, you can start with the stories or about debugging in general, your thoughts about how to find bugs given this discussion so far.

AFTON: That's such a good story. It brought a quote to mind from The Pragmatic Programmer I've been listening to. And I'm going to botch it. And I'm not even 100% sure it's related, but it's what came into my mind. [laughs] So it was saying that novices tend to try to fix problems by adding more correct code, or writing something to be more correct, or a new workable code. And experts go in and try to remove the problematic code. [laughs] So, I mean, first of all, you tried to add all these new solutions in, and then, in the end, you had something that was existing. You just had to refactor. I don't know how that works in -- [laughs]

MIKE: Oh yeah, refactor, exactly.

AFTON: Yeah. [laughs] That was a good quote.

MIKE: The direction I've thought about this is I see a lot of people lean hard into their debuggers. And debuggers can be a useful tool. So I want to be careful here. I don't want to suggest that you should never use a debugger. That being said, a debugger is a sophisticated, complicated tool. And if you've got a complicated, hard-to-think-about problem, then adding something complicated and hard to think about as part of your solution may be problematic.

And I have not, in my career, seen that debuggers have tended, you know, the fanciest of the debugger has not gone very far to helping people solve problems. For myself, I honestly don't use debuggers very often. I print out to the screen, and that's worked in every language I've ever worked in. It works in C; it works in Java; it works in Python; it works in JavaScript; it works in Ruby. If you print something out, and they've all got tools to do it, whether you're doing your console.log() or your puts, or your System.out.println(), if you can print something out to your screen while your program is running, you get that context.

And you can put it where you think the problem is. You can put it on one side of the problem, going back to that blue wolf in Ireland. You can put it on one side of the problem and the other side of the problem, and you can isolate it, split the problem in half, figure which side it's happening. And such a simple tool means you don't really have to think about it; instead, you're just trying to identify which side of the wall the problem is on, cutting out half the problem that way.

You know, which side is it on? It gives you that visibility in a way that leaning really hard and trying to find all the things in one specific spot might not give you, you know, looking with a larger scope to cut the problem in half rather than getting hyperfocused on one spot.

I'm going to say debuggers could be a solution; you know, there are some pipes that need an auger to be cleaned out. [laughs] But, in general, it's the awareness that you need to be concerned about, and if you get that with the debugger, great. But, you know, if you can just print something out to your screen, that probably will work just about as well and maybe even better because you don't have to think about it as much.

ZSOLT: Right. The interesting thing is that, for years, I've been told that print debugging is a terrible practice and you shouldn't do it. And I'm like, you know what? I'm going to respectfully disagree with that and do it anyway.

MIKE: [laughs]

ZSOLT: Because it works very well for me. But I've also recently started using more actual debugging tools, and I do see where their uses stem from. But I do agree that you don't want to eat a bowl of cereal with an industrial food processor; I don't know.

MIKE: [laughs] You can use that to process the cereal and avoid the chewing, but it might not be fun.

ZSOLT: Exactly.

MIKE: Really well said. I've seen developers who are really, really good, and they often have minimal tools. They know the tools they use really well. I'm not trying to suggest that we shouldn't use tools, but they use them judiciously. [laughs] They learn that the tools don't necessarily solve the problem. It's the problem-solving that solves the problem. Ramses, what's been your experience with debugging?

RAMSES: I'm pretty simple with debugging. I'll use a lot of print or put statements in combination with regular debuggers. If it's JavaScript, I'll just use a debugger. If it's Ruby, I'll use byebug or binding.pry, just so that I can do a quick analysis of whatever objects or logic I'm trying to investigate.

MIKE: Makes sense. I want to talk a little bit about the first story that I shared about finding the last blue wolf in Ireland. It's a silly story, and sometimes the best ones are like that because they stick. [laughs] They don't have to be very serious, but they stick in your head. If you can cut up the scope of a problem in half repeatedly, it gets much smaller very quickly. It ends up taking the time relative to the base two logarithm of the data you're looking at.

And the code that we look at is not generally tremendously big. And we might be working with millions of lines. Even if you have a million lines of code, you cut that in half once, and you're down to 500,000. That's a large size, but after you've gone through 20 iterations of cutting it in half, you've gotten to a pretty small problem set. It really doesn't take very long to cut things down when you cut them in half every time.

But I think a lot of times we neglect that idea when we're looking for problems. We think, well, I know where the problem is, so I'm going to look really, really close there and do a lot of looking there and don't look next to the window well. There's another story that I've heard that I was sharing yesterday that I've seen shared a number of times, but I think is great.

You got a drunk guy who's looking for his keys, and somebody notices he's looking for his keys under the streetlight. He's like, "What are you doing?" He's like, "Well, I'm looking for my keys." And he's like, "Oh, so you dropped them here?" And he's like, "No, I dropped them somewhere over there." "Oh, why are you looking here?" "Well, the light's here. I can see here." [laughs]

And it makes us laugh out loud when you think about somebody doing that. But we totally do that; we look where we can see or where we think the problem might be. And if we spend too much time looking where there's a lot of visibility rather than expanding the scope of what we're looking at, find some way to add visibility to other parts of our code, then we're likely to waste a lot of effort looking where there really isn't a problem. And instead, thinking, well, what can I do to look at the broader scope and eliminate where those problems are?

ZSOLT: I also have something to add to that. In the same vein, it's also important to not over-isolate because then you'll just end up confusing yourself even more. Because there have been several instances where I have cut out too much, and then I just didn't understand how I got from not working to not working in a different way. I'm like, this is giving me even more unintended results. What's happening, you know?

MIKE: That's interesting. And how do you go about fixing that?

ZSOLT: So when I find that I've, I guess, you could say that I've overscoped the problem, then I will try to take a step back and then rethink my strategy for isolation of the problem. Because sometimes I'll be like, all right, so I have this set of functions that do these things, and then I'll take a look at one function that I think might be causing the issue. And then, if it's not that, I have to re-scope to another thing.

MIKE: Okay, so you hyperfocus on the problem is here in this function, and you realize, wait, maybe it's not.

ZSOLT: Exactly, because you start seeing that, wait, this is actually what I expected, or something else is wrong, you know?

MIKE: Yeah, absolutely. But you suggested something, and you said, but I think the problem is this function. A lot of times, if you're going through a series of functions, just something as simple as logging in each of those functions and to find out where the problem starts is really helpful. And you don't necessarily have to have it in all the places. You start by putting some sort of statement halfway through and figure out which side of that logging statement the problem happened on. You've cut down a lot of scope, that binary search.

Cutting the problem in half every time is just tremendously powerful, I think. It's not just powerful in debugging; it's how indexes work. And it's the reason you can search your database and get answers back quickly [laughs] instead of having to search every row. The internet wouldn't work without that kind of algorithm.

So I've asked Ramses and Zsolt specifically about some of their experience. Afton, I know that you were self-taught for a while as well. I'm curious what kind of lessons you learned while you were struggling through debugging alone during that time. I suspect you found some interesting lessons about how to track down tricky bugs.

AFTON: I was building a new project, and most of my struggles were how do I implement a new thing? How do I address a new problem? So, learning to research and going and finding answers was a big thing that I spent a lot of time doing and getting better at. But my own codebase at the time wasn't; I don't know, I'm not sure that debugging...because isn't debugging kind of fundamentally existing stuff, existing code, the problems in it, right?

MIKE: New code has problems, too. Just because it compiles doesn't mean it works.

AFTON: [laughs] Yeah, I'm not thinking about my learning stage and any scenarios really about debugging. I will say I've been thinking about how I go about debugging now. I typically find an entry point where, okay, the bug is being identified as this kind of process issue, or it's this feature-related. So I find the entry point, find what UI kicks off what processes, and go start at the controller.

And, I'll even, in my notes, outline as I'm going through the code; okay, the first entry point is here. This is what the code is doing, and either this can happen or this can happen. And what is the next step? And so I step through, you know, service objects and other methods and functions and just document this is what the code says it's doing. And I'll throw in my binding.pry at each new entry point to ensure that the data that I'm getting at that moment is what is expected.

So stop at each point, look at the data. Is this what the code is expecting? Okay, if so, great, move on to the next step. And work my way down to the point where, okay, it's expecting this value or this attribute and can find something that is inconsistent or unexpected or not present but should be. That's usually pretty effective for me, and creating an outline in my notes helps me gain this big-picture context, going from broader and narrowing as I go. So I think that's kind of a way of cutting in half, cutting in half.

This cutting in half thing I was trying to think, how do you do that? How do you start with a problem and cut it in half? How do you know you're cutting in the right place? How do you know what the boundaries are in order to cut it in half? [laughs] I mean, you have to see the borders of your zone in order to cut it in half. Those are kind of the questions that were running through my head, and I'm like, do I do something like that? And how do you know that you're doing that effectively?

MIKE: And as you were talking, I was envisioning pipes again (Talking about pipes today.), and I thought about sewage lines. They have access points, right? Once every block or so. When something's blocked up, they can go into each of those access points and find and check which one was blocked. They can't necessarily check everywhere in between. But there are those boundaries that you're talking about where you can check if things flowed correctly in between.

AFTON: Right. [chuckles]

MIKE: And, if you have a wide area, we'll start by checking some points in the middle, and you might be nowhere near the right spot. But you at least know that things were flowing there. And then you go downstream and say, well is it flowing here? It's your idea of you got to drill down through all this area. Well, you got to start somewhere, and maybe you don't know it. And maybe you're going to start at the wrong place.

That's the beautiful thing about binary search, though, is you can start at the wrong place, and that's okay. [laughs] Is it right here? And if it is, well, then you know it's farther down the pipe. It's farther down the call chain. And if it's broken there, well, then it was farther up the call chain. That's the idea, is your idea of this call chain. Again, I'm visualizing it as pipes again, but it's just calls, right? You got calls from function to function to function.

You can figure out, you know, start somewhere and see if it's right there, if it's correct, you know, if everything is flowing properly at that point, and if it is, well, then the problem is probably downstream from there, farther down the call chain. And if it's incorrect, well, then it's farther up the call chain.

AFTON: Right. Yeah, no, that makes sense. That's really good. Yeah, so I think that's essentially what I'm doing then.

MIKE: Yeah. I think these metaphors...so I like to throw out a lot of them because they capture ideas, I think, better than talking about the actual thing often does. Because if you can visualize something, if you can easily grasp it, then you can relate it to the problem and better understand that problem. As humans, we talk in symbols all the time. Words themselves are symbols representing ideas. They have great utility that we don't have to hold up an apple to communicate about an apple.

I can say the word apple, and that is a symbol that represents the idea of an apple, and communicate with you that way. It's not the thing itself but a token or a representation of that idea that allows us to talk in a more abstract way. Stories are a great way to abstract ideas to think about them in a higher level way than getting lost in the weeds as we sometimes do.

I want to make another observation about debugging. How easy is it to debug code that is sprawling and complicated, methods hundreds of lines long or functions (I'm using those synonymously.) methods or functions hundreds of lines long with lots of branching logic? Thoughts?

AFTON: That is very difficult [laughs] because there are so many things that can go wrong, and all in one place, supposedly returning one result is not easy. [laughs]

MIKE: No. And you think, well, it's just code, right? It does the same thing. The broader structural organization shouldn't matter; I mean, it's still just code. I say that, but I know it's not true. The way we structure our code has a great deal of impact on debugging. And I found that sometimes for tricky problems, the best way to debug the problem is not to but rather to restructure the code, refactor that code. Sometimes it's remarkable how much difference that makes.

To share another story, my son was doing some Minecraft modding a number of years ago, and he came to me for some help. And he said, "Yeah, my code is not working. Can you tell me why?" And I looked at it. And it was this deeply nested function. I think it was nested like ten levels deep or more, and the code wasn't formatted cleanly at all. I mean, there were double curly braces, and the indentation was inconsistent from line to line.

I looked at that, and I said, "You know, I really want to help you, but I can't." If you can go and just clean up your syntax, get your curly braces aligned, and your code indented, that's all I ask." I said, "Just get your indentation right and your braces in alignment so that I can see the structure of your code because right now, I can't. It's all over the place." He was young. He can be excused. [laughs] "It's just not lined up. I can't see it. I can't understand what's going on here because it's not organized."

And I didn't ask him to refactor the function. I didn't ask him to break it up into separate functions at all, just to order the code. He came back to me a couple of hours later, kind of with his tail between his legs, and said, "Well, I did what you asked, and the code works now."

AFTON: [laughs]

MIKE: All he did was clean up the syntax. And somehow, he had gotten some braces misaligned so that there was an if block somewhere that shouldn't have been. And by cleaning that up, without even noticing that he had fixed it, he had fixed his problem. That moment has really stuck with me because I remember, oh, wow, all he did was clean up the syntax. I'm not saying that cleaning up your syntax is always going to solve your problems, but it says something.

I mean, debugging is a human endeavor. It's something that we have to do to make better sense of the code. And things that we can do to better wrap our heads around what's going on are going to allow us to find the problem better. And that applies, first, not just to debugging but building the code; anything that we can do to easier think about the problem is going to be likely to help us move faster, not slower.

It's not working against you to organize your code well. It's actually helping you work faster and more easily. It may feel like it's a constraint that I can't just quickly get all the ideas out of my head. I understand that idea. But I would argue pretty strongly that organizing those ideas as you get them out, even though it's difficult, even though it's a constraint, even though it's hard to do that when you just want to get it out, is actually going to help you work faster.

It's similar to the idea of writing an outline of a paper before you just start banging away on the keyboard. Getting that organization in place is extremely helpful to being able to better express your ideas because it gives you kind of a map. It allows you to not have to worry about everything at once but to be able to understand what's going on elsewhere and then not think about it. It allows you to better wrap your head around the global context so that you can forget about it and focus on the local context. That's really powerful. It's a tool that allows you to use your brain.

One thing you said, Afton, I think goes along with that. I'm talking about taking notes and organizing an outline. Well, you said you do that when you're looking to solve a problem. You write things down, and you outline what the problem is so that you can see the overall scale of what's going on and then focus in on the parts that actually matter.

AFTON: Yeah, that helps a lot. And what you've been saying goes right along with our team convention of creating single-purpose methods and single-purpose service objects in order to take complexity out of one class and divvy it up into small, understandable, manageable pieces. Because, like you were saying, if the piece is small and we can hold it all in our head at once, it's going to be a lot easier to debug, to understand, and to utilize. So yeah, our team works really hard to do that. And that does aid in easier debugging, single-purpose methods, and classes.

MIKE: You've spent a lot of time working with a team that cares about those kinds of things. I've not always had that luxury. When you're working with sprawling code with a class that has thousands of lines, and hundreds of functions, and multiple different workflows that pass through the same class, they overlap each other. You're not sure which one is deprecated or how much they cross over or not. [laughs] Sometimes they touch each other, sometimes they don't. It is really hard to figure out what's going on.

Sometimes the best way to debug is to rebuild. I say rebuild; starting from scratch is often not tenable. Most of us who are in the industry are working on applications that are in production. And we can't just throw out this thing of value that we've built and start over. So, somehow, we need to improve it in place while being a running system. People talk about upgrading the airplane while it's flying; we've got to do that sort of thing.

But refactoring can be done. You can take something big and messy and replace it with something tidy in place, in particular, if you have written your code to be highly decoupled so that everything around it has a single entry point into that code and is able to, you know, just call that method. As long as it gets the same thing back, you can completely redo everything inside.

Now, maybe your problem is that it's not decoupled, and you've got a couple of things that are closely coupled and entangled with each other. Well, then, your problem is to figure out where to divide those. That's a whole different discussion that we should probably have some time. But by getting those divided so that they are decoupled, and there's a clear boundary there, it's likely to help you to solve the problem because now you've isolated the two components, and you can fix the problem where it is rather than having to deal with its repercussions everywhere else.

I'm just spending some time talking about this idea of debugging via refactoring. I think it's not something that we always talk about when we're talking about debugging, but I think it's a real thing. And sometimes, it's the most useful tool that we have. Finding problems in the code through isolating by using a binary search, following the call chain, cutting the problem in half every time is very useful. And it may even get you to the problem quite quickly and allow you to solve it there locally.

But if you're often having problems in the piece of code, that suggests a bigger problem than just a quick patch is going to fix. There can be larger structural problems that are leading to those bugs. I've seen this many times where there's code that's just problematic all the time, and it's generally because it's too complicated. And by complicated, I'm going to...let me define that. And it goes back to what you were saying, Afton. It's usually when things are doing more than one purpose, serving more than one purpose.

So if you've got a function that does multiple things or does something with a number of side effects, well, now you've got lots of things happening in one place, and you have to think about all of them going on at once, and that's hard to do. It's hard to make sure that all of the combinations are covered. One of the best ways to solve that is to disentangle the multiple things that are happening in that code into single-purpose components. Depending on the language you're in, there are different ways you could have those components, functions, classes, modules of some sort. The important part is that they have a single purpose.

Once you've done that, well, your code is still doing the same thing. So by some measures, the complexity is identical. But by disentangling the components, they both become more reusable. But they also become easier to comprehend and are less likely to have unexpected side effects when they interact with each other, unexpected interactions because they don't have any interactions other than through their public interfaces. And removing the opportunity for accidental interactions is what I mean by removing complexity. I think, oftentimes, we can do that. And that should be one of our primary focuses of refactoring. And that leads to code that is less likely to be buggy and may also be the most important thing we can do to debug problems.

I'm thinking about a particular problem that a team worked on a year or two ago. There were two workflows. I'm not going to get into the details, but the overall workflow for one object and the overall workflow for a related thing. They were entangled with each other and presented as one overall workflow. The problem was having both of those together meant that there were a whole lot of intermediate states. You think of a state machine, and there are a whole lot of states you have to accommodate because it was the combinations you have to accommodate, all of the combinations of all of the states of those two different things.

And by extracting one of the things out into its own thing and giving it its own workflow, we were able to dramatically simplify the number of states in each of the things because now we didn't have to account for the combinations. Each one just had to think about its own set of states. And there was a perennial series of bugs that was happening in one of these two things. And by just extracting them, the problem went away and hasn't been back since [laughs] because we disentangled them. The key to debugging wasn't just to fix that local problem, which would have patched that one little spot, but it would have left all the other problems of entangled state, and disentangling them was the real solution.

AFTON: Yeah, I'm pretty sure I remember what you're referencing. And I just remember whenever someone had to go in and make changes in that code; it was like a nightmare of, oh, good luck in there, you know. [laughter] But then someone new had to go in and try to make sense of this complicated system, which was more likely to create more bugs because it was so complex and hard to change that it was going to likely produce more bugs when changes were made. So yeah, extracting those out made the scope of the one feature easier to understand and, therefore, easier to change, which helps mitigate the likeliness of bugs. [laughs]

MIKE: Yeah, and I think the engineer when he was going in there, he was going in there specifically to solve a bug, and he said, "You know what? I'm not going to be able to solve this bug because the concept is not well enough disentangled." There's this concept that wasn't a concrete object. And by having it fuzzily spread around within the other object, it didn't have its own representation.

Going back to this idea of abstracting ideas, by giving something its own representation, there's a place to put everything. I worked with a developer once who said, "You know, you always need to create a new class, so you have a place to put stuff so that there's a place for everything." And I think that was really wise. By creating this new class, this new representation of the idea, there was a place to put the code that needed to be there.

The idea is you could give it its own database table easily, for example, and then have all of the attributes persisted cleanly and separately. And by being able to represent that idea separately, we're able to persist the state in a way that we couldn't previously because it was all entangled. And it was able to solve the problem because the idea could be represented. Before, there were key ideas that weren't even saved. They were just inferred, and sometimes the inferences couldn't be accurately made. Having that disentangled allowed us to see that, oh, wait, we need to be able to save this idea. And we couldn't see that before until it was disentangled.

ZSOLT: I do agree about the importance of making sure that you have discrete chunks of code, I guess you could say, to ensure that you don't have weird conflicts like that. Because I've had several scenarios like that in my personal experience where I had everything in a handful of functions, and that was not good. And then, I separated everything out, and things started working significantly better. And I'm like, oh, maybe that's smart. Maybe I should do that more often.

MIKE: [laughs] It just sort of magically starts working better, right?

ZSOLT: Yeah. It's kind of weird when your code is written right that it works right.

[laughter]

MIKE: Yeah, there's an uncanny parallel between code organization and code working. Again, it's not what we always talk about when we're talking about debugging, but I think it's an important thing to recognize and think about.

I think there's maybe one idea we've touched on but haven't dug into as much as we could, which is this idea of looking outside the local scope to see the context a bit more. There have been a number of times when I have been working on a problem, and I got some output. And I said, "That's just not possible. I don't understand. It's impossible." And I've learned that as soon as I think that, I need to recognize, yep, that is impossible, which means I'm looking at the problem wrong.

If an impossible thing is happening, it means I'm looking too closely. And I need to step back and think about what context this code could be operating in that could make this impossible thing happen. It means that there is some input or some state that I was not accounting for that is causing the problem. I talked about the drain pipe that was flooding my window well. I wasn't accounting for that. I was assuming that all of the problems were just coming from water pouring off the wall into the window well. And it just seemed impossible that so much water could come up in there.

Well, it was impossible, given the scope of where I was looking. And I needed to stop, take a step back, and think, where am I not looking? What am I not looking at? And that moment of pause where you stop and say, well, this doesn't seem possible; I need to take a step back and figure out what I'm not looking at, I think is one of the most valuable things that we can learn when we're debugging.

ZSOLT: The only way that you will have an impossible problem is if you're impossibly scoped because everything is either defined or undefined behavior. If you're getting something that doesn't match either of those, then your scope is wrong.

MIKE: Well, that's interesting: if you're impossibly scoped. Yeah, I think that's a really interesting idea you're saying. When you see something impossible, it means that your scope is wrong. The behavior you're saying is defined within this scope is really not the key source of the problem.

ZSOLT: Right.

MIKE: Interesting. Yeah, I think that's very well said. If you're seeing something that seems impossible, then it's almost certainly a scope problem. The problem is the scope, not the code necessarily you're looking at.

ZSOLT: Right. I deal with a lot of unmanaged code like C, and C++, and Assembly. And you get a lot of issues like that where if your scope is wrong, everything's just going to be really confusing.

MIKE: Yeah, that's really interesting. It seems to dovetail really neatly with what I was saying. When you see those impossible problems, it's evidence that your scope is wrong. You're looking too closely. And you need to step back and look at the context, well, let me look a little bit bigger. What is the environment of this problem? And maybe the environment isn't what I thought it was.

ZSOLT: As they say, you're missing the forest for the trees.

MIKE: Oh, that's great. Any other thoughts about this idea of scope or context of a problem and the impossible results, how to identify that moment to step back and look elsewhere?

ZSOLT: Sort of to touch back on what was previously discussed, a pretty solid way of making sure that you don't get into these impossible situations is to just make sure that you're writing your code in a cohesive way that doesn't cause these conflicts to happen in the first place. If you build a building out of garbage material, then that building is going to fall apart.

But if you build a building out of solid materials, it's much more likely to last longer. And the same thing can be applied to code. If you write code like crap, then it's not going to last very long because you're going to see that it's going to start falling apart at the seams. But if you write solid, well-documented code, then it's significantly easier to debug. It's significantly easier to maintain in the future.

MIKE: Thank you.

RAMSES: It's really interesting. I've been thinking about that a lot lately on how to reduce the number of bugs that are introduced by writing cleaner and more concise code.

MIKE: Yeah, it's interesting that by making your code properly modular, large groups of bugs just never happen in the first place.

ZSOLT: Somebody once told me that the best way to debug your code is to ensure that bugs don't happen in the first place.

MIKE: [laughs] I will say that in the real world, we don't always have the code we wish we had, [laughs] whether it be our own fault or that of others. But one of the things we've talked about of reducing the scope of the problem and then also looking back outward and seeing the problem in context, looking for what the true source of the problem is rather than getting too focused on right where we're at is likely to help you even when you're working with code that isn't ideal.

And then, if it's persistently a problem, you can avoid those bugs by refactoring. That's just part of the work we do. We improve the code that we're working on and leave it better for that iteration but also for ourselves in the future.

I think that ties up the discussion we've had nicely. We've talked about the various ways to address the problems by filtering away things that aren't relevant, by expanding our scope to find things that we hadn't looked for, and by improving the code so that the problems are easier to find. I think that's a great discussion on debugging. We'll leave it there. Thank you, everybody, for being here today. And we'll see you next time.