.NET Zone is brought to you in partnership with:

Mr. Mahal has worked his way through all aspects of software development. He started as a software engineer and learned how to design and architect software systems in different industries. He has been a director of engineering, product management, and professional services; mid-career he worked for a company dedicated to training engineers in collecting requirements and understanding OO and conducted research into why software projects fail; and before starting Accelerated Development he rescued a technology startup in Vancouver as their COO. Accelerated Development is dedicated to the production of high quality software through implementing light weight engineering practices for all size organizations up to Fortune 500. Dalip is a DZone MVB and is not an employee of DZone and has posted 36 posts at DZone. You can read more from them at their website. View Full User Profile

Debuggers are for Losers

07.23.2012
| 30794 views |
  • submit to reddit

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.

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 Points Average Effort
10 1 man month
100 7 man month
1000 84 man month
10000 225 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".

-----------------------------------------------------------
Moo?
Want to see more sacred cows get tipped? Check out:
Published at DZone with permission of Dalip Mahal, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Lukas Eder replied on Mon, 2012/07/23 - 2:26am

This article needs some serious debugging first, before it becomes visually intelligible!

Karol Lalol replied on Mon, 2012/07/23 - 6:15am

Didn't read it all... however it invoked me some memories on old times PHP "programmers" debugging with "print/echo" and yelling "I don't need debugger" :-D

André Pankraz replied on Mon, 2012/07/23 - 7:07am

Would be interesting to see if the programming output of the article author keeps pace with the academic and missionary statements.

Mainframes, environment without debuggers and decision tables, call path logging? Wtf

We have Java and with the JVM come great tools - like a really useable Debugger, Memory Analyzer (Eclipse MAT), Call Recorder etc. etc.

Ignoring them is really crazy and not just as last resort.

matt inger replied on Mon, 2012/07/23 - 7:19am in response to: André Pankraz

Yeah, ignore the debugger, because logging out all your local variables would be more useful in tracking down which of several variables might be causing a NullPointerException on a given line of code.  Not to mention i don't want my code littered with useless log statements.  It pollutes the code, and you should place them in meaningful spots.

 

And the last thing he fails to consider is that while production environments typically sit behind firewalls, the odds are one of the first things that's going to happen is someone is going to at least attempt to reproduce the bug in a local environment before the developer is even involved.  After all, if you can't produce it locally, how are you ever going to test it.

 

Use all the tools at your disposal.  To suggest to someone to ignore a certain tool until a last resort just doesn't make sense. 

Otengi Miloskov replied on Mon, 2012/07/23 - 7:40am

ROFL is true this article needs urgent a debugging session, it is just a mess.

Eyal Golan replied on Mon, 2012/07/23 - 1:25pm

TDD is not one of the defensive programming options?

come on...

it is THE FIRST ONE IMHO.

DbC - and if I break it?

At first I thought that you are going to speak of TDD.

Since I started mastering it, I debug much less. and debugging actually slows me down.

but not using it? not mastering it?

you need to master it before you master anything else.

 

just my two cents here... 

Balázs Bessenyei replied on Mon, 2012/07/23 - 1:43pm

The current Java Debugger API support limited Hot Code replacement and step back through stack frames, code custom execution. Using JRebel there are even more possibilities. 

This is essential to check what-if scenarios in case of large enterprise applications. Where you simply can`t mock the entire system or recompile it after adding additional debug logging or other changes.

I agree that debugging was troublesome and hard in Mainframe era and not so trivial even in today’s C/C++ environments. However this is not the case in modern managed languages, with reflection support.
In Mainframe environment Testing and Logging would have been equally troublesome, because of waiting for the same printer.

Debugging however it is not the only tool, debug and audit logging is still useful and should be used. Even if they not tell you what the problem is, at least they help pinpoint where to test or debug. Developers should produce “tested” (at least run it against the original bug) code before handle it down to testers for verification.

In the article Log4J was mentioned, which is an outdated advice. Log4J is now abandoned by its original author, how has moved on to create SLF4J and Logback, which is a far better solution. Especially, if your project has 3rd party components that are using separate logging framework. SLF4J can migrate them to a common Logging solution without code change.

Actually defects can hide even in tested code. Most of the so called tested code, is just to appease the 100% code coverage rule, without testing anything significant.
In my humble opinion, a code can only be considered somewhat tested. If you have enough test case to cover at least all code paths indicated by NPATH complexity. Cyclomatic complexity is to just to appease the 100% code coverage rule. Fortunately rarely any code out there that warrants this amount of testing effort to enshrine it against any unwanted change.

“While you are spending time in the debugger your project is on hold, it will not progress.” This is true for while you are analyzing the stack trace, the logs the test results and contemplating on the possible causes. If you are using your colleagues then both of you can`t make progress. However in some cases a second opinion can be helpful.

I can exist without ever touching the debugger for several months. However last weekend I had to use it, while I have attempted to optimize a prime factorization code created for one of our in-house programing competition, in this case long sized numbers. It was way faster than creating unit tests (executing the code ran it against the training data so there was no need for new separate test cases) and far more elegant than to just dumping debug statements all over the codebase.

In my conclusion ignoring the debugger as a viable tool is foolish, just because it was once problematic to use. Exclusive reliance on debuggers on the other hand is equally foolish.

Enrique Tron replied on Mon, 2012/07/23 - 3:12pm

When you create new code, it is like poetry, you write it almost without errors... and beautiful by inspiration... only some minor 'slips' like not initializing objects or stuff like that....

BUT

when you have to do some maintenance work, to handle old code, it is another business, checking code that have had more hands on it than someone I know... or trying to find out why some code suddenly stopped working... my best tool is a debugger, it gives you the scope of the variables, the exact flow of the program and the chance to find out how the environment is in the 'real' situation, not just thinking how this should be.... 

I like visual studio's debugger, it really helps, now I am working in an old systema with J2EE, ejbs, and old tomcat, and I really HATE having to fill my code with logs to find out where it's been going thru....


There should be better ways to do it, but generally if I am able to debug the code, and replicate the error as I want, it helps me to find out what is happening and get a solution....

just my opinion....

 

(and yes, you can improve the article format :) 

Sergio Samayoa replied on Mon, 2012/07/23 - 4:22pm

"Debuggers are for loosers" >>> TRUE!

 I can't recall when was the last time I needed a debugger to fix some bug.

 

BTW, I heard Steinmetz's history in different context and until now I know the source of such history. In the one I heard was a loose screw and the detailed invoice said:

Tighten a loose screw: $1

Knowing which screw to tight: $9999 

Kariem Abd El-fattah replied on Mon, 2012/07/23 - 5:12pm

I found at least one grammer problem in your article, can you find it without a debugger? :)

Thanks for the article, but i think debuggers are great tools and ignoring them all together is not the best idea. 

Ken Webster replied on Wed, 2012/07/25 - 4:51pm

Nice article!  I wish more developers paid more attention to defensive programming and keeping code clean and maintainable.  Some of the worst spaghetti code I've seen was written by folks who were glued at the hip to their debuggers (or perhaps they were glued at the hip to their debuggers because they tended to write spaghetti?).  Maintaining code like that was a nightmare and I found myself in the debugger almost constantly.  On the other end of the spectrum, clean defensive code is a joy to work on and I rarely find any need to use a debugger on it.

Debuggers are useful tools, but I think the article makes a good point.  If you are spending a lot of time in a debugger, that's a sign that something is fundamentally wrong with your code.

Constantin Negut replied on Fri, 2012/07/27 - 8:09am

ITHINK YOU DONN'T UNDERSTAND WHAT IS A STATEMENT AND I SUGGEST YOU TO IMPROVE YOUR FUNDAMENTAL KNOWLEDGE.

Rw Greene replied on Mon, 2012/07/30 - 9:18am

 While it is always good to plan and think about the product being produced, saying that debuggers are for losers is somewhat extreme.

 Debuggers are useful for (a) quicking determining the flow of the program and, more importantly, (b) determining the developers misunderstanding which resulted in the defect.  I have spent many hours listening to people try to guess what a program might be doing, when a debugger could determine the exact responses within five minutes. 

Why guess when you can monitor reality in less time than guessing.  Logs and tracing can be used to determine what actions and conditions were in place when a production defect happened.  That information can be used to create the same conditions for a debugger to determine what response and actions the program took for those conditions. 

 Debuggers are essential time and cost reduction tools.

Viktor Nordling replied on Thu, 2012/08/09 - 9:40am

I am sure the article author's code is as clearly strucutred as this article.

Lund Wolfe replied on Sun, 2012/10/28 - 2:08am

Very good article !  Totally agree that it is better to understand the app and use a divide by two method to quickly identify where the problem occurred and narrow it down to the root code defect.  I like using println() statements for this.

Unfortunately, like others have said, I have often spent time maintaining applications that don't make any sense and weren't developed to be simple and elegant.  The applications were built by a debugger programmer and I need a debugger to follow the execution path just to understand how the crazy thing works and where it is failing in this situation.  Yes, the debugger does imply that the code (or critical section containing the bug) is complex and bad but it still has to be understood enough to fix it.

The debugger can also be useful as a sanity check to verify that a section of code works as you expect, whether as a possible cause of the bug, or whether it correctly reflects your mental model of how new developed logic or a fix actually works.

Namers Delux replied on Wed, 2012/10/31 - 1:57am

 The facts of small and big business networking solutions are additional or fewer the same. Significance of effective network fitting service and dependable network security & support services cannot be deprived of for any type of commerce, irrespective of its dimension and level.

http://whitehatsme.com/networking-support-services/

Rickywhore Ricky replied on Mon, 2012/11/12 - 3:00am

The vast age of cases I was competent to illustration out the maker of the imperfectness before my organisation would pic.The warning learned was that the ability to conceive is deprecative to uncovering defects http://www.testbells.com/648-247.html

Jack Jackson replied on Sat, 2013/03/16 - 1:38pm

This is a dumb article, to put it bluntly. Debugging is a tool, one of many tools, no it is not ment to be an end-all or a crutch BUT to say taking away that tool makes you a better programmer is just plain wrong.

Star Khan replied on Mon, 2013/04/29 - 12:59am

I love to tell you that your blog is excellent your site is good .Thanks a lot. his blog is so informative so education and helpful at any kind of help on this blog I am impressed to see this blog laval lawyer

Sadia Sulaman replied on Wed, 2013/05/22 - 1:36pm

 Take that and add in his melancholic falsetto and his new album Total Loss sounds like the résumé of an aspiring film score.

Marketing

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.