Using the power of programming, anyone with the skills can build any virtual experience their imagination can hold. The building itself is massive fun, until bugs show up. Bugs are every programmer’s nightmare, and up to 50% of the software engineer’s time is spent fixing them.Why are bugs worth writing about? First, each year they cost the software industry billions of dollars to fix. Then, there is the fact that instead of fixing bugs, the man-hours invested in bug fixing could be channelled to developing new and innovative products.
For the initiated, the thought of bugs can be highly demotivating factor. Some people have quit a promising programming career early because of this menace. Seasoned programmers know this too well.It is clear that nobody wants bugs, and they are best avoided from the outset. It is important to write code and design architecture to ensure that debugging becomes unnecessary. Code should be presented such that it is obvious how exactly it works. That said, the stark reality remains that it is impossible to eliminate bugs and debugging completely.
Master Debugging to Improve at Programming
One big slice of becoming a better programming is to master the elusive art of debugging. The goal is to remain in the proverbial flow. Programmers and bugs are like oil and water – there is no love lost. No one wishes to spend time with bugs, whether by design or chance.How then can the programmer subdue bugs while spending as minimal time as possible? It’s all captured in the idea of becoming lean. Lean avoids waste, and since time is a most priceless resource, proper debugging is lean debugging.
Validate Riskiest Assumptions Early
A bug, by any definition or standards, is persona non grata. It is a phenomenon we would rather live without. However, bugs happen for an unknown reason. Knowing the reason makes fixing the problem a breeze. A good chunk of the time is first spent uncovering this reason.While resolving a bug, the process involves making natural assumptions. These assumptions include trusting a compiler not to make a mistake and assuming we made no typos. We might even assume that a particular class or function is working exactly to specification. This leads us to the question of how we might speed up validating our assumption and getting the bug fixed in record time.
How to Validate Assumptions for Debugging Purposes
Systematic debugging often necessitates a top-to-bottom approach. Without having the faintest idea where the bug lives within your application (probably one that you have hosted already), all hope is not lost. You can simply begin by assuming – blindly – the bug lives in a relatively large area of your codebase, proceeding to validate if it does or not. This is known as divide and conquer in some contexts.A programmer can search for bugs in time or in space. Using Git commits as a scenario, to search in time for a bug is to seek to answer this question – at what git commit did this bug emerge? This asks a when question. Conversely, searching for a bug in space would ask, “where is the exact root of this bug in my codebase?”. It is a where question.
Tools to Hunt Bugs in Time and Space
To search your codebase in time, git bisect is a tool of choice. With it, you can tag one state of your codebase as clean, implying it is free of bugs. You can also tag another state as unclean, implying that a bug occurs. Once tagging is done, you can search recursively for states between these two defined states until you find the particular git commit which introduced the bug.Bug hunting with the git bisect tool is much easier where your commits represent atomic changes. This is the reason git and its best practices are powerful for teamwork, version control and debugging your own code. Even when you are working solo, as is likely the case on your hosted web application armed with vital tools like search engine optimisation and so forth, consider using git and making clear commits.
For searching bugs in space, the majority of programmers have done binary search using commented code. Simply comment out or bypass half the codebase. Do this recursively until you can pinpoint the exact file, function, or line the bug is taking refuge at.What we have described above is many times far from simple. A bug could cut across several modules, and need understanding of the whole control flow and state of the modules involved. Some cases are different however – they point to a significant flaw in your codebase. Worse still, the bug might be external to your codebase.
Sometimes binary search does not work, necessitating more creative approaches. Brainstorming opens you to all sorts of possibilities, including suspecting an issue with your IDE or other tool, version mismatch of libraries, date and time libraries sensitive to computer settings, the bug is in an external resource or a remote service, or even bit flipping in the hard drive.In validating any assumptions, the issue could also be in the method of validation. This is common with typos, where you believe you have validated the absence of typos, yet there might be one. Validating any assumption twice using different methods is a good way around this. Pair programming on a team could have at least two people validate the same assumption, using separate approaches. The goal should always be to validate the assumption of the problem, not of the solution.
The Bug is not an Enemy
Making the bug an enemy will not cure a programmer’s woes. Programming is really about understand correctly, not typing and building. The bulk of programming time is spent understanding how all parts interconnect. The smaller fraction of the time I actual keyboard typing.Tackle your lack of understanding first, then you can easily embrace the bug when it shows up. As obvious as this is, in the middle of debugging, you are more likely to antagonise the bug. Consciously decide to avoid that. Understanding everything upfront makes bug fixing a simple task.
How To Understand Better, Faster!
Many programmers are geniuses, no doubt. Other programmers need to run a program a few times, experimenting with it to discover some properties of its functionality. Debuggers make it easier to understand programs, just as ad hoc tools like logs and printing debugging strings to the console greatly complement them.Debuggers give deep into specific portions of code, while logs and console prints offer a keen overview of the program. This is useful in validating assumptions that span several modules.
Endeavour to build tools that improve your grasp of the codebase. Your creativity is important here. Such out-of-the-box thinking has gifted developers open source reactive programming tools. You’ll find reason to do this if you remember bugs cost our industry billions of dollars per year.Bug Prevention
Having established that programming is understanding, bug prevention is thus mostly linked with proper architecture. This makes it easier to reason about a program’s functionality. There are several programming models and architectures, along with ongoing debate as to which are best, but some practices are worth following.A typical paradigm is to keep different levels of abstraction distinct.
The simple getDollarString() function checks that a given number is not an integer, returning a price label in Dollars with two decimal digits, else it returns a price label with a whole value. This function has no issues working, except that the condition in the if-clause is working on a lower level of abstraction than the rest of the control flow. This function should only express the above logic, but it delves into details not vital to its purpose.The function might be a source of bugs and an impediment to future debugging because while debugging, you will likely look at getDollarString() from the angle of one level of abstraction – it checks if the value is a decimal, to use the decimal format, else it uses the integer format. The result is that you dismiss the lower levels of abstraction, assuming things go according to your plan now. This is where the issue lies – letting an assumption slip away unchecked.
We can instead, refactor the function so it uses other functions which hide those lower level instructions. This makes the assumption validation process happen while debugging. When inspecting getIntegerDollarString()or any other function, it will be a breeze to check if it only does what it says it does.Each function clarifies what level of abstraction it operates within, so there’s no need to mentally switch between levels of abstraction when inspecting the function, lending a focus to your mind, so you know where to find bugs.Programming practices that keep bugs away consider other programmers and your ability to properly interpret the code in the future. Easy to scan, read and change code, that is obvious eventually yields fewer bugs.
Preventing bugs in code requires writing code that appears easy to any programmer. Fixing bugs requires deep understanding of your code. This understanding must be on an exact level, then enumerate and validate any assumptions, while caring to build debugging tools where necessary.