(Este artigo está disponível em português.)
One of the most frequent categories of bad questions I see on StackOverflow is:
I wrote this program for my assignment and it doesn’t work.
[20 lines of code].
And… that’s it.
If you’re reading this, odds are good it’s because I or someone else linked here from your StackOverflow question shortly before it was closed and deleted. (If you’re reading this and you’re not in that position, consider leaving your favourite tips for debugging small programs in the comments.)
StackOverflow is a question-and-answer site for specific questions about actual code; “I wrote some buggy code that I can’t fix” is not a question, it’s a story, and not even an interesting story. “Why does subtracting one from zero produce a number that is larger than zero, causing my comparison against zero on line 12 to incorrectly become true?” is a specific question about actual code.
So you’re asking the Internet to debug a broken program that you wrote. You’ve probably never been taught how to debug a small program, because let me tell you, what you’re doing now is not an efficient way to get that problem solved. Today is a good day to learn how to debug things for yourself, because StackOverflow is not about to debug your programs for you.
I’m going to assume that your program actually compiles but its action is wrong, and that moreover, you have a test case that shows that it is wrong. Here’s how to find the bug.
First, turn on all compiler warnings. There is no reason why a 20 line program should produce even a single warning. Warnings are the compiler telling you “this program compiles but does not do what you think it does”, and since that is precisely the situation you are in, it behooves you to pay attention to those warnings.
Read them very carefully. If you don’t understand why a warning is being produced, that’s a good question for StackOverflow because it is a specific question about actual code. Be sure to post the exact text of the warning, the exact code that produces it, and the exact version of the compiler you’re using.
If your program still has a bug, obtain a rubber duck. Or if a rubber duck is unavailable, get another computer science undergraduate, it’s much the same. Explain to the duck using simple words why each line of each method in your program is obviously correct. At some point you will be unable to do so, either because you don’t understand the method you wrote, or because it’s wrong, or both. Concentrate your efforts on that method; that’s probably where the bug is. Seriously, rubber duck debugging works. And as legendary programmer Raymond Chen points out in a comment below, if you can’t explain to the duck why you’re executing a particular statement, maybe that’s because you started programming before you had a plan of attack.
Once your program compiles cleanly and the duck doesn’t raise any major objections, if there’s still a bug then see if you can break your code up into smaller methods, each of which does exactly one logical operation. A common error amongst all programmers, not just beginners, is to make methods that try to do multiple things and do them poorly. Smaller methods are easier to understand and therefore easier for both you and the duck to see the bugs.
While you’re refactoring your methods into smaller methods, take a minute to write a technical specification for each method. Even if it is just a sentence or two, having a specification helps. The technical specification describes what the method does, what legal inputs are, what expected outputs are, what error cases are, and so on. Often by writing a specification you’ll realize that you forgot to handle a particular case in a method, and that’s the bug.
If you’ve still got a bug then first double check that your specifications contain all the preconditions and postconditions of every method. A precondition is a thing that has to be true before a method body can work correctly. A postcondition is a thing that has to be true when a method has completed its work. For example, a precondition might be “this argument is a valid non-null pointer” or “the linked list passed in has at least two nodes”, or “this argument is a positive integer”, or whatever. A postcondition might be “the linked list has exactly one fewer item in it than it had on entry”, or “a certain portion of the array is now sorted”, or whatever. A method that has a precondition violated indicates a bug in the caller. A method that has a postcondition violated even when all its preconditions are met indicates a bug in the method. Often by stating your preconditions and postconditions, again, you’ll notice a case that you forgot in the method.
If you’ve still got a bug then learn how to write assertions that verify your preconditions and postconditions. An assertion is like a comment that tells you when a condition is violated; a violated condition is almost always a bug. In C# you can say using System.Diagnostics;
at the top of your program and then Debug.Assert(value != null);
or whatever. Every language has a mechanism for assertions; get someone to teach you how to use them in your language. Put the precondition assertions at the top of the method body and the postconditions before the method returns. (Note that this is easiest to do if every method has a single point of return.) Now when you run your program, if an assertion fires you will be alerted to the nature of the problem, and it won’t be so hard to debug.
Now write test cases for each method that verify that it is behaving correctly. Test each part independently until you have confidence in it. Test a lot of simple cases; if your method sorts lists, try the empty list, a list with one item, two items, three items that are all the same, three items that are in backwards order, and a few long lists. Odds are good that your bug will show up in a simple case, which makes it easier to analyze.
Finally, if your program still has a bug, write down on a piece of paper the exact action you expect the program to take on every line of the program for the broken case. Your program is only twenty lines long. You should be able to write down everything that it does. Now step through the code using a debugger, examining every variable at every step of the way, and line for line verify what the program does against your list. If it does anything that’s not on your list then either your list has a mistake, in which case you didn’t understand what the program does, or your program has a mistake, in which case you coded it wrong. Fix the thing that is wrong. If you don’t know how to fix it, at least now you have a specific technical question you can ask on StackOverflow! Either way, iterate on this process until the description of the proper execution of the program and the actual execution of the program match.
While you are running the code in the debugger I encourage you to listen to small doubts. Most programmers have a natural bias to believe their program works as expected, but you are debugging it because that assumption is wrong! Very often I’ve been debugging a problem and seen out of the corner of my eye the little highlight show up in Visual Studio that means “a memory location was just modified”, and I know that memory location has nothing to do with my problem. So then why was it modified? Don’t ignore those nagging doubts; study the odd behaviour until you understand why it is either correct or incorrect.
If this sounds like a lot of work, that’s because it is. If you can’t do these techniques on twenty line programs that you wrote yourself you are unlikely to be able to use them on two million line programs written by someone else, but that’s the problem that developers in industry have to solve every day. Start practicing!
And the next time you write an assignment, write the specification, test cases, preconditions, postconditions and assertions for a method before you write the body of the method! You are much less likely to have a bug, and if you do have a bug, you are much more likely to be able to find it quickly.
This methodology will not find every bug in every program, but it is highly effective for the sort of short programs that beginner programmers are assigned as homework. These techniques then scale up to finding bugs in non-trivial programs.
Pingback: Iterate through specific rows sql php - CSS PHP
Pingback: php - Mysql query takes a lot of time to execute - CSS PHP
Terrific blog post! I use, or have used, many or all of these methods at one time or another to debug code. They definitely work. I would add one method to the list, although you already touched on it. If possible, developers should use several debugging tools on their code, frequently, sometimes even if the code is not throwing errors or appears to be working correctly. I’m thinking of static and dynamic debuggers like Purify, gdb, valgrind, Allinea, etc. or whatever tools are appropriate for your chosen language. It’s amazing how many times these tools identify (potential) bugs and software defects before they manifest themselves in production code. Look forward to future posts.
Excellent post, i particularly use the rubber duck approach very well. in some cases when all debugging techniques and approaches fail, it’s best to just take a walk and forget about it (very difficult though). the second you come back to the code, the bug would fish itself out 🙂
Pingback: Software Debugging Process | Process Street
if you find yourself thinking – ‘that’s a bug in the compiler’, ‘thats a hardware fault’ or ‘thats a bug in the OS’ – you are wrong. Look again. Once you are really sure and you still think that its compiler, hw or OS, you are still wrong. Look again. Repeat
Well, https://gcc.gnu.org/bugzilla/ would disagree. But yeah, unless you are *really* sure it is a compiler bug and have eliminated every other possibility, it’s probably you.
And ever after I ran into problems with a bad RAM stick once, I recommend doing an overnight Memtest if your dev machine isn’t using ECC and you’re seeing impossible things. If it finds a problem it was worth the time and meanwhile rest your brain.
(Note that if you actually cause the compiler to crash, and emit messages about internal errors, there may actually be a bug in the compiler. This isn’t guaranteed, though.)
If you cause the compiler to crash and emit messages about internal errors, there is a guaranteed bug in the compiler. The compiler should never actually crash no matter what input you feed it.
Agreed. So far in my (not inconsiderable) programming career I have encountered three compiler bugs – all in the same compiler. Two of them caused the compiler to crash with an “Internal Compiler Error”. One of those was caused by invalid source that the compiler didn’t reject. In the third case the compiler generated code that corrupted a pointer causing the program to terminate.
I have yet to see a logic error resulting from a compiler bug.
My favorite way to debug small programs is print statements. If you move the print statements around enough, you will eventually find exactly where the problem lies.
Very true!
The problem is that this is much slower than using the debugger.
Usually you can put a breakpoint and then just
po object.property
.Reduces the compile/run cycle time.
I agree that the debugger is much better. For fast and furious debugging with a print statement, be warned to also flush your stdout buffer after each print when debugging a segmentation fault. Otherwise could go down the wrong rabbit hole.
https://stackoverflow.com/questions/9469790/execution-of-printf-and-segmentation-fault
Another preemptive attack at bugs is — debug before you “run”. After writing new code, fire up your debugger, and step through some test cases (which should include corner cases, as well), inspect intermediate results, check some internal states. You usually will find bugs that would never ever surface, but only if Murphy would’ve been wrong 😉
If finding bugs late in the process is so expensive, why not search for them before the first “run”? Start with a debugger session! I do this all the time, and this way I find more bugs earlier than I ever expected.
It’s quite reasonable to build a good set of test cases Before writing the code.
Exactly! This habit must be inculcated into them as early as possible.
Pingback: Coding Utilities – octavium.org
Pingback: How to fix 'Index was outside the bounds of the array' | How Why What
Warnings are the compiler telling you “this program compiles but does not do what you think it does”
I don’t really understand…I am new to programming but I don’t think that compile-time warning tell you that the program does compile but doesn’t work the way you want it to..aren’t compile-time warning meant for pointing out compiling errors..like typos in code? Or is he talking about some different kind of warning?
Compiler warnings are different from compiler errors. The compiler outputs an error when it *can’t* compile your code — you’ve got a typo; you’re passing the wrong number of arguments, etc., etc.
A *warning* is the compiler telling you: “this code looks suspicious; it’s valid code, but it probably isn’t doing what you think it is”. In C, for example, “if (x = y) { /*…*/ }” is perfectly valid code, but you probably didn’t really mean to type that.
Hi Eric – this is a great reference for beginners and I often link to it from StackOverflow. Unfortunately it seems that the links no longer work with http:// – it seems that https:// is now required, so all my previous links are broken. Is there any way to fix this ? Thanks again for a very useful resource.
Sorry – wrong diagnosis – it looks like the URL changed from https://ericlippert.com/2014/03/05/how-to-debug-small-prgrams/ to https://ericlippert.com/2014/03/05/how-to-debug-small-programs/ – any chance of adding the old link back in, so that it gets forwarded to the new link ? Thanks…
From my perspective, paying attention to what the compiler error warnings and errors actually say is very important (as well as being able to Google that exception if you don’t know exactly what it means or how to solve it). I’ve seen many bad questions where the OP asks about a compile error and the problem is EXACTLY what the compiler says it is.
For example, one question I recently fielded included the following two error messages: first, “The Type or namespace name ‘List’ could not be found (are you missing a using directive or an assembly reference?” Here’s a shocker: he was, in fact, missing a using directive (System.Collections.Generic).
The second compiler error: “No overload method ‘Read Line’ takes 1 argument.” The error: he was trying to pass an argument to Console.ReadLine(), so again the error was exactly what the compiler said.
Short of that, if it compiles without errors or warnings, I always encourage people to just step through the code – pay attention to what happens at each line and make sure that it’s doing what you expected it to. I’ve fixed many bugs like that.
vuvankhangpro@gmail.com
This is, I believe, a good cause for “Warnings are Errors” policy that Joel Spolsky (I believe) once introduced to me. I thought it “meh” at the time (that was years ago), but now I understand that compiler warnings are spots where the compiler moans about code that’s ambiguous. Eliminating ambiguous code eliminates the warnings – and, I’ve found, often also eliminates bugs.
Nobody says to actually make them fail the build, obviously – put down the pitchforks – but it’s helpful to think of them this way. As in – fix them now, and keep fixing the issues (even if it’s a ‘missing’ type cast that still does what you expect it to) until the log is clean – so that they don’t turn into actual errors later.
And as payment for necroing a very old comment thread, here’s a cute (and old) joke: “if ( a or b = null )”. 😉
Pingback: A NICE Approach to Online Dialog – Thoughts on PHP Web Development
Pingback: What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it? | Life View Studio
helpful for noob like me.
Reblogged this on basheerabdulwahab.
Pingback: How to debug small programs – basheerabdulwahab
Pingback: How to debug small programs – Site Title
Great post and really helpful especially for your target audience (and those of us who don’t have to explain it ourselves on SO anymore 🙂 ).
One of the traps that even (or especially) experienced programmers fall into is that they don’t “listen to small doubts”. I’ve had so many cases where assumptions on simple things led to lengthy debugging sessions that could have been a lot shorter had we only checked and double checked our assumptions were correct (like “Did I even compile my code?”).
I’d like to add 2 pieces of advice though (hoping I didn’t just overlook them in your text):
– When using code provided by others (in case of a short program most likely some standard library methods) read the documentation carefully and make sure you understand it. That’s basically a prerequisite to understanding what your code does.
– Before fixing a bug make sure you know how to reproduce it, when possible with different setups. If in some cases you can’t (but you think you should) take note of those as well. After you’ve found and fixed the bug you need to understand why the fix worked (don’t just take fixes you found by trial-and-error or that someone else suggested) and then especially revisit the cases that should have resulted in the bug but didn’t. There’s little worse than that nagging though “why didn’t it work in that case when it clearly shouldn’t have?” since that indicates the existence of additional bugs.
I usually reach for Valgrind before GDB on small test programs, especially if the symptom is some kind of crash. It’s often a faster route to the source of the problem if it involves uninitialized values or mismatched alloc and free.
The debugging process, very well-described in this post, is essentially TDD (test-driven development) backwards. Why do I have such a hard time campaigning for TDD.
Well said, Daniel!
I recently found another method of debugging that the author calls “binary debugging” (http://it.toolbox.com/blogs/tricks-of-the-trade/binary-debugging-21819)
Essentially, put a breakpoint in the middle of your program. If it breaks before, then you know your bug is in the first half of the program. If it breaks after, then it’s in the last half. Put a breakpoint in the middle of the buggy half, repeat the process, and very quickly you know about where the problem is.
It’s trickier in threaded or real-time systems, but this article is geared towards simpler programs that are probably only a single thread.
Personally, I always called it “Binary Search for Bug.” That’s also a great tool for generating a minimal example. More often than not, generating a minimal example pinpoints the problem. But even if it doesn’t, it’s a necessary step before asking for help.
I’ve translated this post into Russian: https://habrahabr.ru/post/339038/
Pingback: [Перевод] Как отлаживать маленькие программы — Malanris.ru
Pingback: [Перевод] Как отлаживать маленькие программы — Портативная колонка jbl charge
Pingback: [Перевод] Как отлаживать маленькие программы — LikeCSS
Pingback: What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it? - ExceptionsHub
Pingback: How to debug small programs – basheerabdulwahab
So what do you do when you have boiled down the code to about 10 lines and it still doesn’t work but NO errors or warnings are produced, as in my case right now.
Print statements?
Even better, use a code visualizer which will display values of all your variables in scope. More importantly, it has the ability to go back and forth in the sequential execution. http://pythontutor.com
If you’re just a beginner and can seem to work your way around proper debugging procedures like using a debugger , just use multiple print statements(printing that variable) at every variable changing statement and even in loops to ensure your loop are running smoothly. But this is only applicable to small programs which can be a couple of lines(50 -100) , now if you’re going more than 100 or beyond , you are bound to use debuggers as those many print statements will be a pain in butt to read!
Pingback: Örebro universitet: Programmering – Automat Etta Nolla
Pingback: Why is this Square Root approximation not working? – Developer FAQs
Pingback: why i got overriding output using arraylist in java? Ask Question-异常分析 - Java full stack learning
A faster way to do a querySelector in the console is with the dollar sign. If you are using an element more than once, it’s worth saving it as a variable.
Pingback: Recursive Function Not Returning Value – PythonCharm
Pingback: c# - Argumento 1: no se puede convertir de 'cadena' a 'int' (Pequeño error simplemente no saben cómo arreglar)
The IDE debugger is great but sometimes i still resort to manually tracking on paper variables and state change – sometimes the act of updating the chart makes clear when something is wrong. Sometimes you’ve just got to get really intimate with your code.
Rubber duck is amazing i didn’t had a rubber duck at the moment so i started to talk with the bottle of water but it still works and it’s amazing thank you for teaching this
You don’t technically need a rubber duck for this – just visualise someone you’re trying to explain this to, and then explain it out loud. It doesn’t catch all issues (especially when there’s a question that needs to be asked but you didn’t think of it), but it falls under the general principle of “If you can’t explain it in simple words, you don’t understand it well enough.”
In this part: ” If you don’t understand why a warning is being produced, that’s a good question for StackOverflow because it is a specific question about actual code.” I would add something like this “But do remember that chances are high that people have posted questions about that specific warning before. Read them first.”
Pingback: Connecting 32 magnetic sensors – tlfong01.blog
GREAT stuff about the rubber duck. And after 20+ years agree wholeheartedly about everything else. I’d also recommend installing and activating one of SonarLint/PMD/FindBugs/other in your IDE to perform static analysis as you code as it’s easier for beginner developers to think through what might not be perfect about their code while writing it than after hitting compile…
Pingback: android - java.lang.NullPointerException: Tenta di richiamare il metodo virtuale 'void java.util.Calendario.setTimeInMillis(long)' di un riferimento oggetto null
Pingback: c# - C++ errore: terminare la chiamata dopo il lancio di un'istanza di 'std::bad_alloc'
How about permalinking to Raymond’s rubber duck comment ?
Seems the comment is not present anymore?
Pingback: arrays - Buscar valores duplicados en una matriz en java
Pingback: Debugging Buddy (Rubber Duck Programming) • Programming is Fun
Pingback: algorithm - ¿Cómo implementar el algoritmo de Luhn?
I’ve created a German Translation: https://entwicklergate.de/t/wie-man-kleine-programme-debuggt/409 Would you kindly allow me to do this? If not, I will, of course, delete it.