30
votes

No one's perfect, and no matter what we do, we are going to produce code that has bugs in it from time to time. What are some methods/techniques for reducing the number of bugs you produce, both when writing new software and changing/maintaining existing code?

2

17 Answers 17

58
votes

Avoid fancy coding. The more complicated the code, the more likely there's bugs. Usually on modern systems, clearly written code will be fast and small enough.

Use available libraries. The easiest way to not have bugs writing a utility routine is to not write it.

Learn a few formal techniques for the more complicated stuff. If there's complicated conditions, nail them down with pen and paper. Ideally, know some proof techniques. If I can prove code correct, it's almost always good except for big, dumb, obvious bugs that are easy to fix. Obviously, this only goes so far, but sometimes you can formally reason about small but complicated things.

For existing code, learn how to refactor: how to make small changes in the code, often using an automated tool, that make the code more readable without changing the behavior.

Don't do anything too quickly. Taking a little time up front to do things right, to check what you've done, and to think about what you're doing can pay off big time later.

Once you've written the code, use what you've got to make it good. Unit tests are great. You can often write tests ahead of time, which can be great feedback (if done consistently, this is test-driven development). Compile with warning options, and pay attention to the warnings.

Get somebody else to look at the code. Formal code reviews are good, but they may not be at a convenient time. Pull requests, or similar if your scm doesn't support them allow for asynchronous reviews. Buddy checking can be a less formal review. Pair programming ensures two pairs of eyes look at everything.

4
  • x2 - what Ryan said. Sep 28, 2010 at 22:26
  • 2
    also most languages can be more or less picky. You want it to be as picky as possible.
    – user1249
    Oct 17, 2010 at 9:45
  • 1
    "Learn a few formal techniques for the more complicated stuff."... for instance? Dec 16, 2010 at 19:07
  • 1
    @Yar: I expect something like the systems explained in this book: amazon.com/Verification-Sequential-Concurrent-Programs-Computer/… ; although I have to say that specific book is exceedingly dry and dull, so there are probably much better ones out there (but it's the only one I've read). Jul 4, 2014 at 7:48
30
votes

Unit Tests let you reduce the number of bugs that pop up a second time. If you find a bug in your code, writing a unit test will make sure it doesn't come back up later. (Plus, thinking of all the cases and writing thousands of unit tests up front is hard to get done sometimes)

3
  • 3
    "Thinking of all the cases up front" leads to clean, fully-specified interfaces, which can only be a good thing. Unit tests are only hard to write if you're retrofitting them to code that wasn't designed with testing in mind. Sep 28, 2010 at 17:57
  • 1
    If you can, you should. Unfortunately, most place I've been see unit tests as something that costs more than just "fixing bugs really quick". So, if you can, then you should write tests up front, but if it's not seen as "cost-effective" then writing them alongside bug fixes helps you build it up over time without putting too much of the budget toward just writing unit tests.
    – Ryan Hayes
    Sep 28, 2010 at 18:05
  • 4
    "Testing shows the presence, not the absence of bugs." - E. Dijkstra. That being said, automated tests are definitely a very useful way to maintain and increase software quality.
    – limist
    Sep 28, 2010 at 20:02
9
votes

+1 on both of the unit test comments.

Beyond that, set the highest warning level your compiler offers, and make sure warnings are treated as errors. Bugs often hide in those "erroneous" errors.

Similarly, invest in static analysis tools that run at compile time (I view these as an extra level of compiler warnings).

1
  • +1 for the static analysis comment. It is invaluable to get all that info for free :) Apr 2, 2013 at 4:58
9
votes

In addition to what's been mentioned:

  • Don't ignore error codes - e.g. don't assume that you got a valid result, that a file has been successfully created, etc... Because some day, something will happen.
  • Don't assume that your code will never enter some condition and that therefore "it's safe to ignore that condition".
  • Test your code, then have it tested by someone else. I find I'm the worst person to test my own code.
  • Take a break, then re-read your code and see if you "missed the obvious". Often happens to me.

Lots of other things I'm forgetting at the moment, but the others will surely think of them. :)

2
  • 7
    And if you're so sure that condition X will never happen... use an assert to make sure that when condition X happens, you'll know about it (through an exception, or logging, or whatever). Sep 28, 2010 at 18:08
  • @MetalMikester: Unit tests are good. But with high level languages and good libraries, most of the hard bugs require integration and regression testing to nail.
    – Vector
    Apr 21, 2013 at 2:27
9
votes

I've developed a fairly functional style of programming, despite my primary languages being C++ and Python. I found that if I pass all of the context to a function (or method) that that function needs to do its job, and return the meaningful data that I'm looking for, that my code has become much more robust.

Implicit state is the enemy and in my experience is the #1 source of bugs. This state can be global variables or member variables, but if results are dependent on something that's not passed to the function you're asking for trouble. Clearly it is not feasible to eliminate state, but minimizing it has huge positive effects on program reliability.

I also like to tell my coworkers that every branch (if, for, while, ?:) is a likely bug. I can't say what the manifestation of the bug will be, but the less conditional behavior your code has, the more likely it is to be bug free simply due to the fact that the code coverage during execution will be more consistent.

Go figure, all of these things also have positive effects on performance too. Win!

3
  • In my experience it can quickly become tedious to pass around all the state for every call, if the methods are as small as they should be. This problem can be solved by using many small immutable classes with short object life-times. That way you can store the temporary state as fields and discard the object when you no longer need the state. :-) Jul 4, 2014 at 15:00
  • Another consideration for that case of it becoming tedious is that perhaps you're trying to pass around too much state. :) Jul 18, 2014 at 0:04
  • In many cases that's true but it often isn't. Within some domains you need access to a lot of state, even if you don't have a lot of mutable state. I am currently working on a code generator where I need access to several symbol tables. I don't want to pass them around to each and every method. Jul 21, 2014 at 13:25
8
votes
  • Write less code that does more.
  • Think about the low-level implications and the high-level ramifications
  • Contemplate the abstraction you are creating in your code.
  • Write only the essential complexity if possible.
3
  • I was going to write a big post that sums up as "write less that does more" (IOW know and use the tools available to you). I'll just +1 this instead.
    – Kaz Dragon
    Sep 29, 2010 at 9:08
  • 1
    But be careful not to get fancy code when trying to achieve less code.
    – gablin
    Sep 29, 2010 at 20:23
  • 1
    @gablin: fancy is in the eye of the beholder in many respects. I can write and read code today that I would have been stupefied at 4 years ago. Haskell today is fancy to me. :) Sep 29, 2010 at 21:47
8
votes

A little less technical answer: don't program when you are tired (9h/day are enough), drunk or 'baked'. When I'm tired I don't have the required patience to write clean code.

1
  • 2
    It usually takes most programmers several years to realize this. It's an important point.
    – Jeff Davis
    Feb 7, 2012 at 20:02
7
votes

Write unit tests and integration tests.

5
votes

Some great answers here regarding unit testing and tools. The only thing I can add to them is this:

Involve your testers as early as you can

If you have a test team, don't fall into the trap of treating them as the gatekeepers for your code quality and catching your defects for you. Instead, work with them and involve them as early as possible (on agile projects this will be from the project's beginning, but we can always find ways to involve them earlier if we really try).

  • Find out what their test plan is. Review their test cases with them - are you covering them all with your code?
  • Ask them for their understanding of the requirements. Is it the same as yours?
  • Give them early working builds to do exploratory testing on - you'll be amazed at the improvements they will suggest.

Having a good working relationship with your testers means that you can catch poor assumptions and defects really early on, before they can do any damage. It also means that the testers feel empowered to help with the product design and catch usability issues when there is time to fix them.

4
votes

Static Analysis Tools

Plugins and apps like FindBugs crawl your code and find places where there are potential bugs. Places where variables aren't initialized and used or just crazy things that 9 times out of 10, make it way easier for bugs to arise. Tools like this help me prevent my bonehead moves down the road even if it's not a bug yet.

P.S.: Remember to always research why a tool tells you something is bad. Never hurts to learn (and not everything is right in all situations).

3
votes

Code inspection or other forms of peer review such as pair programming.

Structured code reviews such as Fagan inspection can be at least as effective and efficient as unit testing and has even proved to be better than unit testing in some cases. Inspections can also be used earlier in the software lifecycle and with artifacts other than code.

Peer Reviews in Software by Karl Wiegers is a great book on this subject.

2
votes

In addition to all of the other suggestions here, turn on all possible warnings to the highest level of sensitivity, and treat them as errors. Also use any linting tools the language has.

You would be amazed at how many simple mistakes can be caught by warnings and how many of those simple things translate into real bugs in your code.

2
votes

A lot of good answers here, but a few things I wanted to add. Make sure you actually understand the requirement. I've seen lots of bugs when the user thought the requirement meant X and the programmer thought it meant Y. Push back for clarification on poor or ambiguous requirements. I know we all like to jump in and code but the more time spent up front ensuring understanding, the less rework and bug fixing there will be.

Get to know the business you are supporting, you will often see things in requirements that are missing or need further explanation then. Know that if you do task Y as stated, it will break existing feature Z.

Understand your database structure. Many many bugs are as a result of a query that is syntactically correct, but returns the wrong results. Learn how to recognize when your results look funny. If I am writing a complex reporting query, I always get a technical specialist to review my results before I mark it as ready to go, they will inevitably see something in the data I missed. Then make notes to yourself what they caught that you didn't and remember that the next time you do something simliar.

1
vote

I think the most important technique is take your time. If you feel you need two days to code a new module, but you boss force you to code only in one day... your code will be probable more buggy.

One of the books I read some time ago, said that you shouldn't live with broken windows, because people won't care if anotherone gets broken... Coding is the same, everyone will care about being the first in doing something bad but fast, but none will care about one hell code, with lots of bugs, and very poor design and style.

1
vote

I follow the practice of Test-Code-Test instead of Code-test-code-test. This helps me to think of use-cases and appropriately frame the logic

1
vote

Use code inspection tools like ReSharper or IDEs like IntelliJ IDEA that warn about many copy-and-paste bugs and others by e.g. pointing out variables that "are written to, but never read". Has saved me a lot of time.

1
vote

Surprisingly, the following three very important points have not been mentioned yet:

  • Use assertions liberally. The question you should always be asking yourself is not "should I assert this?" but "is there anything I forgot to assert?"

  • Opt for immutability. (Use final/readonly liberally.) The less mutable state you have, the fewer things can go wrong.

  • Do not optimize prematurely. Many programmers get side tracked with performance concerns, causing them to unnecessarily convolute their code and bastardize their designs without even knowing beforehand whether performance is going to be an issue. First, build your software product the academic way, disregarding performance; then, see if it performs poorly; (It probably won't.) If there are any performance issues, go find the one or two places where you can provide nice and formal algorithmic optimizations that will make your product meet its performance requirements instead of tweaking and hacking your entire code base to squeeze clock cycles here and there.

Not the answer you're looking for? Browse other questions tagged or ask your own question.