Friday, April 19, 2024 06:12

Table of contents >> Debugging And Error Handling > First steps in Debugging: understanding common errors

First steps in Debugging: understanding common errors

As I said many times, debugging is the process of correcting errors in the codes of your programs. That is only partially true. Debugging is also about understanding the error and understanding why the bug was there in the first place. Today’s lesson will be about this part of debugging.

Many beginner programmers, eager to start coding as soon as possible, often skip the debugging lessons in almost all programming languages, without realizing that 2 hours spent learning the process of debugging can help them save hundreds of hours of error mayhem in combating errors later on. And they inadvertently end up on programming forums, desperate after spending days of trying in vain to fix their patch over patch program, asking questions that a prior debugging lesson would have rendered useless, or simply getting to the bottom of all programming despair: “It’s not working, and I don’t know why! Can you write it for me??”. It may come as a surprise to them that most experienced programmers frown upon such requests and they often just criticize the request, or simply jump to the next question shaking their heads hopelessly, even if they know the answers. The reason for this is not that programmers become some anti-social alien creatures, dreaming codes and formulas and looking from above to the rest of puny mortals out there. Seasoned programmers probably started skipping debugging tutorials and went through the same hell of bugs hunting in the dark as well. What is different about them is the fact that they soon understood that the right way of learning how to write robust programs and fix the errors that appear is not about asking for codes on forums and being happy that they managed to trick the others into writing the code for them, because that is just as futile as trying to carry water with a nest. The next time they would encounter an error, they will be just as clueless, wasting some more nerves and time of others. Instead, they realized that the problem is the nest itself, which needs to be fixed or replaced with another object, better suited for caring water. They understood that the problem is about them, and they are the ones that need to stop asking for others to correct their codes, and actually start learning the right way of fixing the errors themselves.

In other words, give a man a fish and he will eat a day. Teach a man how to fish, and he will eat for a lifetime. And for the life of me I can’t understand why not a single university is offering courses on debugging. Enough saying that anyone who learns to debug correctly is head and shoulders above their class mates. Anyway, lets get started.

If you still remember from an earlier lesson, I was saying that a program can exist in three states at any given time: design time (when we write our codes), runtime (when we compile and run our codes) and debug time (enabled either automatically, when an exception occurs, or manually, when we specifically enter the debug mode, as we will learn later).

The first step in the process of debugging is identifying the place where the error occurs, the piece of code that generates the error. Fortunately, unless very special cases, Visual Studio already does a wonderful job at this, and it will stop the execution and highlight the row that produces the error in our codes. You’ve probably seen it already a few times:

NullReferenceException

The next thing that you should always do is have the Locals and Autos tabs opened. I know we never talked about them before, but they are critical in the process of debugging. If they are not already opened, you can do so by going to the Debug menu, Windows submenu, and choosing Autos and Locals. Be aware that these menus are only available when you are in debugging mode, of which we will learn later how to enable.

Autos and Locals in Visual Studio

When you are in debugging mode, these two windows will appear as panels at the bottom part of Visual Studio:

These two panels let you see the variables values in the current scope, in real time. You will later learn that just having a quick look at the values of your variables in these tabs can most of the times hint you in the right direction of solving your errors. Just as in the example above, when I tried to run that code, the compiler stopped the execution, entered debugging mode and threw an exception of type System.NullReferenceException. This already offers me a small hint about what is wrong with my codes, and if it doesn’t, a quick search on Google for the error name can offer me this hint: “a *null reference* exception”. Well, that must mean that it is something related to null, or more precise, to something being null, when it shouldn’t. Cause, of course, trying to use a null value is impossible, it will generate an error, the exact error we received. So, Visual Studio helped me pinpoint the location of my error, by stopping the execution and highlighting that error line with yellow, then it gave me a general idea about what the error is. This already narrowed the complexity of the problem by orders of magnitude. It is only enough to have a quick look at the Locals and Autos tabs and see the values of my variables, and I already have that “Aha!” moment: my text variable is null, as shown in the picture, and I am trying to use the ToUpper() method on a null value, on a value that doesn’t exist! So, always keep an eye on these two tabs, it might save you a lot of time by quickly seeing that your variable is 0, when it should have been 158.

You should also know that a similar functionality can be obtained just by hovering your mouse over your variables (again, remember that this only happens in the debugging mode, and only in the current scope of the execution, in other words, in the scope where the error occurred). So, I can either have a look at the Autos and Locals tabs, or I can just hover with the mouse over my variables, until the tooltip updates. Like that, I can quickly see that my text variable has a null value:

Lets take another common type of error, that many beginners often encounter: Object {name} is not set to the instance of an object. God, if only you’d know how much this error haunted me when I was just learning the Object Oriented Programming concepts. And I still remember being so annoyed and clueless about what it meant! Lets take an example:

See anything wrong with the code above? Well, this is exactly the kind of code that will get you in the trouble of the above error. The good thing is that we already learned how to correct these kind of errors. Yes, yes, we really did!

Remember the lesson about classes and instantiation? I was saying there that when we are working with objects (classes), we never use the blueprint itself, but we create copies out of it (unless we want to create static objects). Starting to see the problem in the code above? No? well, let’s think it logically. We declared an object of type Button. Nothing special until here. But did we actually instantiate it? Did we create the copy of the Button blueprint? No! The myButton variable was never set to an instance of something, and it is still null! True, it is a variable of type Button, but its not a copy of a button! So, lets actually create this copy:

Run the code above and see the exception going away! Why? Because we told the compiler that we don’t want to alter the original Button class itself, but to create a copy of it and manipulate this copy, which is completely allowed.

Let’s take another very common type of error among the beginners, stack overflow. Consider the following code:

Any peculiarity about it? We haven’t learned about the Heap and the Stack memory areas, but we don’t even have to. Having a quick look at the above code tells us something VERY wrong: method one calls method two, which calls method one, which calls method to, etc, etc, you get the point. That is an infinite calling loop. You just need to know for now that whenever we are calling a function or a method, that call is stored in list in a special memory area called the Stack. But this Stack memory has a very small size, it cannot record an infinite number of calls in its list. Hence, it will overflow, and we will end up with that exception. So, get this error and and know that somewhere, somehow, some piece of your code is making too many calls to some method.

Cannot apply mathematical operation to string. Here is an error that beginners encounter whenever they try to work with numbers and mathematics. As you should already know, most of the times we will get the input from our users as text. So, even if we use Console.Read(), even if we take the Text value from a textbox control in a GUI application, we are still getting that value as text! Even if the user presses the key “5”, that 5 is read as the letter 5, not the number 5! I know this is sometimes rather hard to grasp, but the text 5 and the number 5 are two different things. Because:

By using strings, we end up concatenating (joining) them, by using numbers, we end up doing arithmetical operations. So, remember, you ALWAYS have to convert your strings or user input into numbers, if you want to apply math on it. I know you might be thinking “what a stupid thing to do, to have separate meaning for the text and number 5!”, but answer a single question: what if the user types “yogi bear” instead of “5” in your accounting program input? Is that still valid for your arithmetic? It is not the user’s fault that the keyboard allows him or her to type both numbers and letters.. So, bottom line, always convert your numerical strings into numbers, after making sure first that they are actually text representation of numbers (and not yogi bears)!

The object {name} does not exist in the given context. Another error that made me pull my hairs out when I was a beginner, when in reality it was actually such a simple concept! This error is all about scope and the lifetime of our variables:

Just as we learned in the scope lesson, a variable is only alive as long as the block in which it was declared is still alive too. If the execution left it, the variable is gone. In this example just because we made a picnic.basket doesn’t mean our Bear knows anything about it because he wasn’t part of that class. It’s a sad thing when you aren’t invited to the picnic!

Not all code paths return a value. This one is another simple one. Even the error itself is self descriptive, yet it still troubles a lot of beginners. In the lesson functions, I was saying that a function must always return a value, of the type that was declared in the signature of the function. In other words, if we have:

In the example, myFunction‘s signature says that it must return an int. And we did so. But what would happen if we did it like this?

Would we still be OK? Well, NO! What if the dayOfWeek variable is NOT Monday? What’s the value returned in that case? None… And since the function MUST return an int value, that generates an error. You must always make sure that a function does return a value, no matter the conditions of your calculations.

Index was outside the bounds of the array/collection. We already talked about this error when we learned about arrays. And you should already know what I am about to say: if we have a collection, any type of collection, with, say, 5 elements, and we try to access the 7th element, how would that be even possible? Always remember the bounds of your collections when you receive this error, and always remember that most indices usually start at 0, not 1!

So, after talking about very generic debugging stuff and common error types, in the next lesson we will actually start learning everything in  depth.

Tags:

Leave a Reply



Follow the white rabbit