Skip to content

Debugging 101

Learning Objectives

  • Understand what is meant by a "bug" in your application
  • Understand the difference between different types of bugs and how to best tackle them
  • Understand means to mitigate the introduction of bugs in your applications

Introduction

Software development can be aggravating at times. We're a couple weeks into the course now so you'll have had a taste of this already: You're coding along, you're feeling accomplished for how many lines of code you've written but when you've hit the little green button, or opened your page in the browser, something goes wrong.

Styling all over the place. Applications refusing to run. It can be an incredibly frustrating, and sometimes tearful experience trying to figure out what's went wrong.

The unfortunate truth is that within a system where a single semi-colon can mark the difference between a working or broken app, debugging is a naturally common process. The good news is, is that you are not alone in facing these sorts of issues and that dealing with such problems is a skill in itself. One that you can build on with time. While errors can seem sporadic in origin and description, remember that they are intrinsically logical and hence with more experience, you'll become a veteran of handling whatever the console throws at you.

To get better at deling with bugs in your application, there is only one thing you can do:

Get more familiar with what can go wrong so you know how best to deal with it.

Bugs

But what is a bug? Google is telling me that in North America a bug is a "a small insect" but somehow I don't think that's what we're talking about...

"an error in a computer program or system"

Simple and concise. I like it.

So bugs are simply errors in our code. Errors which we likely placed in there ourselves. Of course, that is not to say that every other developer is infallible, as of course we are all human and hence privy to making mistakes. However this early on in your career while your application consists of largely only your own code, this is an assumption we can make.

When we begin to include libraries or packages from other developers, this is when we begin revising this assumption. Do note however that when it comes to making use of another person's code the onus is on you, and that irregardless of how nonsensical someone's product may seem, they can always claim that what's acting up isn't a bug but instead part of a feature.

But before we start to delve into some examples of bugs and how we can spot them, let's take a look at the sort of issues which could pop up in our application in a broad sense.

Note: there is a general term of "functional error" which applies to any deviation of expected vs performed behaviour within our application. This error generally consists of one or many of the following issues at it's base.

Compilation Errors

Complication errors make up the bulk of the errors we face everyday. These are the ones that the IDE (i.e. IntelliJ or VSCode) flag to us with their red squiggly lines. These errors are any which halt your application from being compiled (translated into a lower-lying programming language which is then run by your machine). In simple terms, these errors are any which stop the compiler from understanding your code. This could be spelling errors, other syntactic errors or errors in logic.

Compilation errors stop your application from running

Syntax—the expected, underlying structure of a language. Most often this means opening and closing brackets and semicolons in the right places but for indent-based languages such as Python, this can be level of indentation also. Spelling mistakes will also likely come under this heading. Remember that no matter how complex your code, it won't run properly if you've mis-spelled an identifier!

Use of the wrong type for a variable or constant is also sometimes an issue of syntax.

The IDE is great at spotting these errors and sometimes even providing quick fixes, so it's definitely worth getting familiar with how your platform notifies you of identified errors. Please do note that while the IDE loves to provide a quick fix, these fixes aren't always going to fix the problem as the program doesn't always recognise what it is you're trying to do.

Common Mistakes

for (i = 0; i < 5; i++) {
  System.out.println(i);
}

// forgetting to initialise the variable i in a classic for loop


static int addOneToValue(int initialValue) {
    return initialValue +1;
}

double two = 2;

System.out.println(addOneToValue(two));

// providing the method with a value of the wrong type


if two > 3 {
    System.out.println("wowee!");
}

// no parentheses around the if condition


double three = Two + 1;

// capitalisation of previous variable / call to wrong variable

Runtime Errors

Now, runtime errors are a little different. These errors largely define any deviations in behaviour from what is expected from you app (some of which may not be application breaking). For instance, these sorts of errors may be behind your application returning the wrong value, providing you with an incorrect set of options or even causing your app to stall.

Runtime Errors cause your application to behave differently from expected

The vast majority of these sorts of errors come from failings in logic. Just as with errors within syntax, these kinds of errors can be infuriating as your code may seem to read through logically—often making the mistake difficult to spot. Unlike with syntactic errors which often come with nice error messages pointing you to the right place, errors in logic are best mitigated by having a clear plan to work towards and a running your application often. Nothing feels worse than bashing out a few hundred lines of code, only to find that something, somewhere is acting up.

User Errors

While the world really is your oyster when it comes to building up novel applications, do bear in mind that this level of variation is also available to your users. For every new project and bespoke method within it, there is someone out there who will come at your application from an angle you hadn't expected. Now, this isn't always malicious, indeed many paths around your application and its features may seem unusual to yourself but would be perfectly natural to another. User errors arise when the user interacts with your software in a way which was not envisioned by yourself, either accidentally (such as through a spelling mistake or mis-click) or otherwise (such as simply trying to input a value you didn't expect).

User errors are bundled under Runtime Errors, and hence define a deviation in behaviour for the application

At worst, this sort of error could crash your application. However, the good news is, is that input validation, hence strictly-bound expectations, are easy are implement within your application. We'll see a bit more on forms later in the course when we explore the HTML and JavaScript chapters however even before the inclusion of explicit input validation as discussed there, its worth us focussing on our foundations first.

Planning is vital. We've seen UML diagrams already which are fantastic for representing, for instance, classes in an easy-to-digest form. However, don't allow yourself to be constricted to only this level of diagrammatic planning during the initial stages of your New Application process.

A method can be used in many different ways. Touching on the bases of the SOLID principles and better coding, it is best to create discrete pieces of code with single responsibilities. This doesn't mean, however, that your code should be built to accept one exact input and only that input.

Try to think about the many different ways someone will try to interact with your applications. For instance, if you have created a Command-Line Application which is designed to only accept specific inputs: what happens when someone inputs something else? Also, whenever you are introducing strict numerical bounds to a part of your application, don't forget to consider how the application may behave is the maximal or minimal value is provided.

Exceptions

We're going to cover this topic at a later time but it's worth pointing out at this point that if there is ever a pathway through your application that you can envision but wish to put a block within, you could look at including an exception here or otherwise end execution of this piece of code (by using break or continue within loops).

Rounding Point Errors

This is a bit of an off topic to bring up within a Debugging chapter, however as with the other topics discussed here, awareness leads to understanding which in turn should hopefully lead an easier time when facing a program which isn't behaving as expected. While it may initially sound untrue to say as much, our numbering system as we encounter it in the physical world is analogue. There is little stopping us from simply adding another decimal place to engage a greater level of accuracy. The scope on said accuracy is infinite. When it comes to the digital storage of the same values, however, there is a defined limit we are held to. Rounding point errors occur due to this limit.

We're all familiar with binary numbering systems at this point—the use of 0s and 1s to represent other information. At their core, all computers communicate using this system, which further means that during execution of any code, including numerical calculations, all data is represented by a set of 1s and 0s.

The problem with this system, is that there is a very slight level of inaccuracy introduced during this process. See the example below for how this may manifest:

double a = 0.7;
double b = 0.9;
double x = a + 0.1;
double y = b - 0.1;

System.out.println("x = " + x);
// x = 0.7999999999999999

System.out.println("y = " + y );
// y = 0.8

System.out.println(x == y);
// false

This type of error occurs for numbers which involve a floating point (numbering including fractional amounts). While a very slight deviation from the expected value, this introduction of slight inaccuracy can cause greater issues when precision is paramount—such as within banking and e-commerce.

To mitigate these errors, it's best to make use of the Math.round() method in Java. It is difficult to spot when this sort of error may arise so it is always best to aim for high test coverage for your collected methods, especially those which do require a high level of precision.

Pseudo-code & Testing

Time to labour a point: planning is vital. Pseudo-code is the process of writing out the logical journey of your application within a series of comments in wording closer to plain English. You can include it as part of a greater plan elsewhere however you will most commonly see pseudo-code within the code itself.

If you google "how to pseudo-code" then you'll be returned plenty opinionated articles about how best to go about this process—but really you should feel free to create your own flavour of this activity. Pseudo-coding has one simple aim: to allow you to visualise your application but without having to worry about syntax, hence making the process faster.

But how does this all relate to debugging? Getting ahead of the issue, writing out a detailed plan and hence minimising the number of errors within your application, is in itself a means of debugging. This process can be couple with regular testing and test-driven development to ensure that the number of bugs in your application remain low.

References

Rounding point error example: https://www.geeksforgeeks.org/rounding-off-errors-java/