About Dalip Mahal

Dalip is a principal consultant at Accelerated Development and has worked his way through all aspects of software development. He started as a software engineer and worked his way through engineering and product management.

Debuggers are for Losers

Code defects are not planned; they are accidents that result from an inability to understand execution flow under different input conditions. But just as airbags are the last line of defense in a car, a debugger should be the last line of defense for a programmer. Defensive driving reduces or eliminates reliance on airbags.  Defensive driving is proactive, it is using all available data to predict and avoid problems.

The alternative to defensive driving is to drive inattentively and treat driving like you are in a
demolition derby. Situations exist where hardware and software debuggers are essential and have no substitutes.  But most of the time the debugger is not the best tool to find and remove defects.
 
 
 
 
 

Debuggers will not fix the defect for you. 

Debuggers are not like washing machines where you can throw in your code and some soap, go get some coffee and watch TV, and then 30 minutes later the defect is removed. You can only fix a defect when you understand the root cause of the problem. Remember, removing a defect does not advance your project; your project will be on hold until you resolve the problem.  You are better served by making sure the defect never gets into your code rather than learning to become efficient at getting rid of them. The worst programmers spend 25 times more time in the debugger than the best programmers.  (link)

Inspiration, not Perspiration

A defect is an observation of program behavior that deviates from a specific requirement (no requirements = no defect ).  A developer familiar with the code pathways can find defects by simply reasoning about how the observation could arise.


At McGill, my year was the last one that had to take mainframe programming.  Debuggers did not exist on the mainframe, you would simply submit your code for execution in the batch system, get a code dump if there was a defect, and send the listing and the dump to the printer. The nature of mainframes in university was that you would queue up at the high speed printer with the 10—15 other people all waiting for their print-outs.


Not having anything else to do, I would think about how my program could have failed – and in the vast majority of cases I was able to figure out the source of the defect before my listing would print. The lesson learned was that the ability to think is critical to finding defects. There is no substitute for the ability to reason through a defect; however, you will accelerate the process of finding defects if you lay down code pathways as simply as possible.  In addition, if there are convoluted sections of code you will benefit by rewriting them, especially if they were written by someone else.

Not There When You Want Them

Even if you think the debugger is the best invention since sliced bread most of us can’t use a debugger on a customer’s production environments.

There are so many production environments that are closed off behind firewalls on computers with minimal O/S infrastructure.  Most customers are not going to let you mess around with their production environments and it would take an act of god to get the debugger symbolic information on those computers. In those environments the typical solution is to use a good logging system (i.e. Log4J, etc).  The placement of the logging calls will be at the same locations that you would throw exceptions – but since this is a discussion of debuggers and not debugging we will discuss logging at another time.
 
 
 

Debuggers and Phantom Problems


I worked for one client where enabling the compiler for debugging information actually caused intermittent errors to pop up.  The program would crash randomly for no apparent reason and we were all baffled as to the source of the problem. Interestingly enough, the problem was only happening in our development environment and not in the production environment. Our performance in development was quite slow and I suspected that our code files with symbolic information were potentially the source of the problem.  I started compiling the program without the symbolic information to see if we could get a performance boost. To my surprise, not only did we get a performance boost but the random crashes vanished.  With further investigation we discovered that the symbolic information for the debugger was taking up so much RAM that it was causing memory overflows.

Debuggers and Productivity


Research in developer productivity has show that the most productive developers are at least 10x faster than everyone else. If you work with a hyper-productive individual then ask them how much time they spend in the debugger, odds are they don’t…
 


Do you know who Charles Proteus Steinmetz was?  He was a pioneer in electrical engineering and retired from GE in 1902.  After retirement, GE called him because one of the very complex systems that he had built was broken and GE’s best technicians could not fix it.  Charlie came back as a consultant; he walked around the system for a few minutes then took out some chalk and made an X on a part (he correctly identified the malfunctioning part). Charlie then submitted a $10,000 bill to GE, who was taken aback by the size of the bill (about $300,000 today).  GE asked for an itemized invoice for the consultation and Charles sent back the following invoice:

Making the chalk mark          $1
Knowing where to place it      $9999

Debuggers are Distracting

As Brian W. Kernighan and Rob Pike put it in their excellent book “The Practice of Programming

As personal choice, we tend not to use debuggers beyond getting a stack trace or the value of a variable or two. One reason is that it is easy to get lost in details of complicated data structures and control flow; we find stepping through a program less productive than thinking harder and adding output statements and self-checking code at critical places. Clicking over statements takes longer than scanning the output of judiciously-placed displays. It takes less time to decide where to put print statements than to single-step to the critical section of code, even assuming we know where that is. More important, debugging statements stay with the program; debugging sessions are transient. A debugger is like the Sirens of Greek mythology, it is very easy to get mesmerized and distracted by all the data being displayed by the debugger.

Despite a modern debugger’s ability to show an impressive amount of context at the point of execution, it is just a snap shot of a very small portion of the code.  It is like trying to get an image of a painting where you can only see a small window at a time.  Above we show 6 snapshots of a picture over time and it is hard to see the big picture.

The above snap shots are small windows on the Mona Lisa, but it is hard to see the bigger picture from small snap shots.   To debug a problem efficiently you need to have a higher level view of the picture to assess what is going on (effectiveness). If you have to find defects in code that you have not written then you will still be better off analyzing the overall structure of the code (modules, objects, etc) than simply single-stepping through code in a debugger hoping to get lucky.

Defects occur when we have not accounted for all the behavioral pathways that will be taken through the code relative to all the possible data combinations of the variables.

Some common defects categories include:

  • Uninitialized data or missing resources (i.e. file not found)
  • Improper design of code pathways
  • Calculation errors
  • Improper persistent data adjustment (i.e. missing data)

The point is that there are really only a few ways that defects behave.  If you understand those common behaviors then you can structure your code to minimize the chances of them happening.
 
 
 

Defensive Programming


The same way that we have defensive driving there is the concept of defensive programming, i.e. “The best defense is a good offense”. My definition of defensive programming is to use techniques to construct the code so that the pathways are simple and easy to understand. Put your effort into writing defect free code, not on debugging code with defects. In addition, if I can make the program aware of its context when a problem occurs then it should halt and prompt me with an error message so that I know exactly what happened. There are far too many defensive programming techniques to cover them all here, so let’s hit just a few that I like (send me your favorite techniques email ):

  • Proper use of exceptions
  • Decision Tables
  • Design by contract
  • Automated tests

Proper Use of Exceptions

One poor way to design a code pathway is the following comment: // Program should not get here. Any developer who writes a comment like this should be shot.After all, what if you are wrong and the program really gets there?  This situation exists in most programs especially for the default case of a case/switch statement. Your comment is not going to stop the program and it will probably go on to create a defect or side effect somewhere else. It could take you a long time to figure out what the root cause of the eventual defect; especially since you may be thrown off by your own comment and not consider the possibility of the code getting to that point. Of course you should at least log the event, but this article is about debuggers and not debugging – so let’s leave the discussion about logging until the end of the article. At a minimum you should replace the comment with the following: throw new Exception( “Program should not get here!”); Assuming that you have access to exceptions.  But even this is not going far enough; after all, you have information about the conditions that must be true to get to that location.  The exception should reflect those conditions so that you understand immediately what happened if you see it. For example, if the only way to get to that place in the code is because the Customer object is inconsistent then the following is better: throw new InconsistentCustomerException( “New event date preceeds creation date for Customer ” + Customer.getIdentity() ); If the InconsistentCustomerException is ever thrown then you will probably have enough information to fix the defect as soon as you see it.

Decision Tables

Improper design of code pathways has other incarnations, such as not planning enough code pathways in complex logic.  i.e. you build code with 7 code pathways but you really need 8 pathways to handle all  inputs.  Any data that requires the missing 8th pathway will turn into a calculation error or improper persistent data adjustment and cause a problem somewhere else in the code.

When you have complex sections of code with multiple pathways then create a decision table to help you to plan the code. Decision tables have been around since the 1960s, they are one of the best methods for pro-actively designing sections of code that are based on complex decisions. However, don’t include the decision table in a comment unless you plan to keep it up to date (See Comments are for Losers).
 
 
 
 
 

Design by Contract

The concept of design by contract (DbC) was introduced in 1986 by the Eiffel programming language.  I realize that few people program in Eiffel, but the concept can be applied to any language.  The basic idea is that every class function should have pre-conditions and post-conditions that are tested on every execution.
 

The pre-conditions are checked on the entry to the function and the post-conditions are checked on the exit to the function.  If any of the conditions are violated then you would throw an exception.

DbC is invasive and adds overhead to every call in your program.  Let’s see what can make this overhead worth it.  When we write programs, the call stack can be quite deep.
 
 
 

Let’s assume we have the following call structure; as you can see H can be reached through different call paths (i.e. through anyone calling F or G). Let’s assume that the call flow from A has a defect that needs to be fixed in H, clearly that will affect the call flow from B. If the fix to H will cause a side effect to occur in F, then odds are the DbC post conditions of F (or B) will catch the problem and throw an exception. Contrast this with fixing a problem with no checks and balances.  The side effect could manifest pretty far away from the problem was created and cause intermittent problems that are very hard to track down.
 
 

DbC via Aspect Oriented Programming

Clearly very few of us program in Eiffel.  If you have access to Aspect Oriented Programming (AOP) then you can implement DbC via AOP.  Today there are AOP implementations as a language extension or as a library for many current languages (Java, .NET, C++, PHP, Perl, Python, Ruby, etc).

Automated Tests

Normal application use will exercise few code pathways in day to day application use.  For every normal code pathway there will be several alternative pathways to handle exceptional processing, and this is where most defects will be found. Some of these exceptions do not happen very often because it will involve less than perfect input data – do you really want those exceptions to happen for the first time on a remote customer system for which you have few troubleshooting tools? If you are using Test Driven Development (TDD) then you already have a series of unit tests for all your code.  The idea is good, but does not go far enough in my opinion. Automated tests should not only perform unit tests but also perform tests at the integration level.  This will not only test your code with unusual conditions at the class level but also at the integration level between classes and modules.  Ultimately these tests should be augmented with the use of a canonical database where you can test data side effects as well.

The idea is to build up your automated tests until the code coverage gets in the 80%+ category.  Getting to 80%+ means that your knowledge of the system pathways will be very good and the time required for defect fixing should fall dramatically. Why 80%+? (See Minimum Acceptable Code Coverage)

If you can get to 80%+ it also means that you have a pretty good understanding of your code pathways.  The advantage to code coverage testing is that it does not affect the runtime performance of your code and it improves your understanding of the code. All your defects are hiding in the code that you don’t test.

Reducing Dependence on Debuggers

If you have to resort to a debugger to find a defect then take a few minutes after you solve the problem and ask, “Was there a faster way?“.  What did you learn from using the debugger? Is there something that you can do to prevent you (or someone else) having to use the debugger down this same path again?  Would refactoring the code or putting in a better exception help? The only way to resolve a defect is by understanding what is happening in your code.  So the next time you have a defect to chase down, start by thinking about the problem.  Spend a few minutes and see if you can’t resolve the issue before loading up the debugger.

Another technique is to discuss the source of a defect with another developer.  Even if they are not familiar with your code they will prompt you with questions that will quite often help you to locate the source of the defect without resorting to the debugger.

In several companies I convinced the developers to forgo using their debuggers for a month.  We actually deleted the code for the debuggers on everyones system.  This drastic action had the effect of forcing the developers to think about how defects were caused.  Developers complained for about 2 weeks, but within the month their average time to find a defect had fallen over 50%. Try to go a week without your debugger, what have you got to lose?

After all, the next graph shows how much effort we spend trying to remove defects, the average project ends up spending 30% of the total time devoted to defect removal. Imagine what a 50% boost in efficiency will do for your project? The yellow zone sketches out where most projects fall.

Function PointsAverage Effort
101 man month
1007 man month
100084 man month
10000225 man year

Conclusion

There will definitely be times where a debugger will be the best solution to finding a problem.  While you are spending time in the debugger your project is on hold, it will not progress. Some programs have complex and convoluted sections that generate many defects.  Instead of running through the sections with the debugger you should perform a higher level analysis of the code and rewrite the problematic section. If you find yourself going down the same code pathways multiple times in the debugger then you might want to stop and ask yourself, “Is there a better way to do this?” As Master Card might say, “Not using the debugger… priceless“.
 
 
 
 
Make no mistake, I am the biggest “Loser” of them all. I believe that I have made every mistake in the book at least once
 

Reference: Debuggers are for Losers from our JCG partner Dalip Mahal at the Accelerated Development blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

6 Responses to "Debuggers are for Losers"

  1. Zed says:

    Flames incoming…

  2. Vivek Na says:

    ORLY?
    This is like saying ABS brakes are for losers, because a good driver doesn’t need one, he brakes like Michael Schumacher everytime (for the record even Michael does better with ABS anyway!).
    Take an unknown 75000 line open source codebase and fix a visible bug in it without using a debugger.

    Debugging is a skill just like writing good defensive code. Let he who has never written buggy code cast the first stone at debuggers, even Knuth hasn’t made bug free code in one iteration.

    Think of debugging as testing a system, but not completely black box, the programmer is emulating a more smart tester, who tests not only external behaviours but internal states of the system based on localized deduction of what the system is doing. You don’t need to understand the whole system at all.
    A lot of electronics is fixed this way – you have a broken TV, the repairman checks voltages at known points and isolates the burnt out components – he has absolutely no idea how the entire system works.

    A number of projects which I have done involved debugging at the machine level… There is no way I could have fixed those bugs by trying to “understand the system and root causes”. Besides some bugs are so low level you can absolutely not find them by inspection.

    Did anyone ever know that CUDA C++ compiler in 2010 did not generate the code call constructors for arrays of objects? No frigging way anyone could find out this without stepping through code and it wasnt even a bug in the code, but one in the compiler.

  3. Mike says:

    What a ridiculous article. The author suggests that the best way to debug your apps is to not use a debugger but instead stare at your code until you can divine where the bug is. He also suggests that the best way to write applications is to write them with no bugs in them in the first place. Holy shit, I have been doing it wrong all these years.

    A debugger is a tool that every developer should know how to use. The only losers are the people that don’t bother to learn how to use them, or even worse won’t use one out of unreasoned nonsense articulated by this atrocious article.

    • Hikari . says:

      What a ridiculous article. The author suggests that the best way to
      debug your apps is to not use a debugger but instead stare at your code
      until you can divine where the bug is. He also suggests that the best
      way to write applications is to write them with no bugs in them in the
      first place. Holy shit, I have been doing it wrong all these years

      This statement is ridiculous. He’s not saying to just develop softwares with no bugs in first place. He’s saying to use “defensive programming” techniques that reduce the risk of creating bugs in first place, and to use logging to help identify the ones that still happen, instead of dumbly relying on tools that won’t even be available in production environment.

      If you have fewer bugs and logs to show what’s happening when they show up, it will be way easier to replicate them in dev environment and fix them. Or… you can beg your customer to install junk debuggers in his system!

    • Mike, writing code correctly in the first place is the first line of defense for any developer. Two methodologies that place heavy emphasis on code and module planning are the Personal Software Process (PSP) and the Team Software Process (TSP).
      – PSP can raise productivity by 21.2% and quality by 31.2%
      – TSP can raise productivity by 20.9% and quality by 30.9%
      Together you can raise code quality by about 63%, so it seems like it is worth trying. :-)

      As for staring at code until you divine where the bug is, code inspections (not code walk throughs) are the most effective way of identifying defects. Without code inspections you will rarely find more than 80% of the defects of a system, with code inspections teams have achieved 97% defect removal. If you add design inspections the results are even better.
      – Code inspections can raise productivity by 20.8% and quality by 30.8%
      – Design inspections can raise productivity by 16.9% and quality by 24.7%

      I understand that the common belief is that a debugger is the primary tool to improve code quality. But simply because developers “believe” that the debugger is the only method of defect removal does not make it true. For those who are interested, the percentages above have been verified over 18,000 projects,

  4. disqus_vJr03uADRN says:

    what if a bug gets in by mistake — nobody is perfect ..
    debuggers are always handy for going straight to the root cause of a problem (tracking it down)
    there’ll always be a role for a debugger — nobody is perfect

Leave a Reply


× 4 = twelve



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close