Yourstory.com

“Bug-free software is a myth”!

I agree, and like me, i’m sure you would agree if you’ve spent several hours on a code base only to run it and get a syntax error as a greeting message or maybe get False as the print output for the following computation in Python:

import Math
a = Math.sqrt(2)
a = 1.4142… //17 digits in all
a * a == 2
//False

We are not alone fellas. For more than three decades, people and organisations have devoted resources and time towards building great debugging tools and this is because a bug infested code base is almost as good as a none existent code base, it’s as good as you never tried writing a software.

To think that most times these bugs are often due to very little syntax mistakes like skipping the colon(“:”) at the end of a python function declaration or a logical one as simple as equating a list to another list when our original intention was to simply make a copy of the original list, in which case [:] would have been most preferred than the often mistakenly used equality symbol(=).

Every programmer knows that bugs creep around your codes the instant you begin writing them and even though we all consider them as unfriendly, most programmers are often at ease and even happy to announce that they have a bug on their code, i guess it somehow puts us under the more positive psychological state that these things are just normal and to be honest…..they are!

How to solve bugs?

Before we talk about how to confront and tackle bugs, i really feel its important to first disabuse our minds of certain general myths about bugs,.

  1. Bugs Crawl into our programs:

No they do not! at least not like we say or mean it. This is more like a fancy way of saying that you the programmer of the code actually made a mistake! bugs come about because programmers, like every other human, is susceptible to mistakes. Thinking of bugs this way helps you confront them, they are your unwilling creations and not some default creation of the IDE or computer hardware.

2. Bugs Breed

Most people believe that once you find a bug at a point in your code, it automatically breeds more bugs which in turn breeds even more bugs, in essence one bug begets many more others. By no known verifiable means is this theory true, fact remains that if you find one bug in your code, it means you made one mistake and if you find ten, well it means you made ten mistakes as well. In essence, number of bugs is directly proportional to the programmers error count with code quality as constant.

3. The goal of debugging is to eliminate a known single bug

This claim is only half the truth,the more rather complete truth is that the goal of debugging is to make sure that we have a bug-free code.

Debugging: Best practices

Its important to note that no one does debugging instinctively, debugging is a learned skill and like any other, it gets better with consistent practice.

So, what or how should bugs be handled? I’d say, use the tools!

Of all the great debugging tools out there, i still believe that the two most important ones are free and would rightly remain so, they are;

  1. Print() statements in python, Console.log() statements in Javascript.
  2. Reading

Debugging starts whenever a problem exists and as such can be considered a scientific method. Good debuggers have evolved a way of systematically solving the problems that arise with their codes. One of such systematic method is to localize the source of the problem by systematically reducing the search space using the above mentioned tools.

So, when next you encounter a bug in your code, here is what to do:

  1. Study the program text.

Rather than ask yourself why the code doesn’t give you the result that you expect, ask yourself instead why it is giving you the current result that it is giving you. These questions look and sound similar, but if you consider it more closely you would immediately realize how different they are, when you try to understand why a program gives a particular result(even though its not what you expect it to give), you begin to not only understand how the computer interprets your codes but also what you need to do to correct the bug.

Also, always try to ask yourself if a particular bug is part of a family, think of it this way, supposing that i used list’s in say 20 places within my code and then discovered that a bug existed in one of the lists,it would be reasonable at that moment to also test all the other 19 places to see if they work as expected because chances are that i may have repeated the same mistake in more than just one instance of usage of the list.

Finally, ask yourself how you can fix an identified bug. Personally, i often like to think of debugging like searching through a list and one of my favorite algorithmic procedures for often doing this is using the binary search method.

According to Carl Sagan, one reason why exhaustive testing is usually not always possible is because we might be testing our code base against billions of possibilities hence its only logical to seek for what is often called our test suite(some possibility range that is small enough but also large enough to give us some sense of confidence that our code works against all possibilities)-one reason why bugs are a normal part of the software development process.

When i debug, i use the two simple tools mentioned above in a systematic fashion that mimics the binary search method for searching through a list.

In the binary method, we make use of the divide and conquer kind of reasoning by simply finding the center point of our code base and inserting a print() statement at that point, if the output is what we expect, it means that we can simply neglect half of the entire code base and concentrate our efforts in searching for the bug in the lower half, the reasoning here is that since our print() statement gave us a correct output at the middle of our code base, whatever was responsible for the bug was not anywhere in the upper half and must as such be somewhere in the lower half. This process is repeated for the lower half and so i find the middle point again and once again engage my print() tool.

The general idea here is that i simply cut down my problem space to half of what it was previously making debugging fast and systematic,when faced with a non-coding problem i have come to also discover that this method of approach works just as well making debugging an essential life skill to have outside professional work.

This approach is most useful when dealing with logical errors because with such errors, your program runs successfully without throwing up the useful error messages that often characterize syntax errors, logical errors are often super difficult to detect and that’s why a systematic approach to debugging is a crucial skill set to have as a problem solver.

Finally, for the purpose of clarity, below is a python code that is supposed to take in a user input as a list, reverse the formed list, and check to see if the list is a palindrome or not. If it is, the program prints a message confirming that the list is a palindrome(reads same way from left to right and backwards), otherwise it prints “Not a palindrome”.

def isPalindrome():
input_list = [] //user inputs are stored in this list
done = False
while not done:
user_input = input('Please enter a value: ') //prompts user
if user_input == '':
done = True
else:
input_list.append(user_input)
list_copy = input_list
list_copy.reverse()
isPalindrome = (input_list = list_copy)
if isPalindrome:
print(" This is a Palindrome")
else:
print("This is NOT a palindrome")

if for successive prompts we punch in three values that makes our input_list =[ 1, “a”, 1]

our output would be This is a Palindrome

of course, experienced programmers would argue, and rightly so that this is not the most efficient way to solving the palindrome challenge, well for our purpose this works, or did it?

Well, it turns out that even though our code throws no errors, if the values of our input_list were [ 1, 2. 3]

our darling output still claims it to be a palindrome, how so? well, this is exactly the kind of bug that comes about as a result of a failing in logical correctness.

To find out where our bug is, lets simply cut the entire code in half by finding some reasonable half point line and inserting a print() statement there( see below)

def isPalindrome():
input_list = [] //user inputs are stored in this list
done = False
while not done:
user_input = input('Please enter a value: ') //prompts user
if user_input == '':
done = True
else:
input_list.append(user_input)
print(input_list) //half point tool check list_copy = input_list
list_copy.reverse()
isPalindrome = (input_list = list_copy)
print(input_list) //output is [1,2,3]
print(list_copy) //output is [3,2,1]
if isPalindrome:
print(" This is a Palindrome")
else:
print("This is NOT a palindrome")

suppose we typed in the following as in puts 1, 2, 3 the output of that middle print statement is expected to be [1,2,3] and indeed that is what we get. This automatically means that we can neglect the entire top half of the entire code-base, our bug is by no means there!

so, again i divide the bottom half into two halves again and run my test tools again, in this case my code tells me that both lists should be the same, but as you can see from the outputs, they are not!

But wait a second, this should be right since there is a test that should have giving us a “Not a palindrome” output once the tests failed? Well, logically the thinking is correct but its implementation was wrong. Notice that our original intention was to make a duplicate copy of input_list and not equate it to a new list variable. By equating it, the test condition in the subsequent if block always passes as TRUE and hence the reason why we get an affirmative “This is a palindrome” even for inputs that a grade one student knows wouldn’t pass the palindrome test.

How do we then solve the bug?

We simply fix the implementation oversight by making a copy of the input_list and storing it in the list_copy variable like this;

def isPalindrome():
input_list = [] //user inputs are stored in this list
done = False
while not done:
user_input = input('Please enter a value: ') //prompts user
if user_input == '':
done = True
else:
input_list.append(user_input)
list_copy = input_list[:] // BUG FIXED
list_copy.reverse()
isPalindrome = (input_list = list_copy)
if isPalindrome:
print(" This is a Palindrome")
else:
print("This is NOT a palindrome")

The most important takeaway i want to leave you with at this point is that ALL problems, including BUGS can be solved with systematic approaches. This is the essence of computational thinking and it takes practice to master.

By the way, remember when i said in my opening lines that bug-free software's were a myth? ,i was ‘technically’ wrong!

A well debugged software is bug-free!

Once i studied Physics and enjoyed being an on air personality, now i’m a software engineer who enjoys writing about codes and great digital products.