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?
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.
+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).
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. :)
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!
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.
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).
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.
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.
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.
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.
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.