views:

903

answers:

28

I don't mean external tools. I think of architectural patterns, language constructs, habits. I am mostly interested in C++

+1  A: 

Using an IDE like IntelliJ that inspects my code as I write it and flags dodgy code as I write it.

duffymo
Unfortunately it only tells you about syntax errors or suspicious constructs; a compiler would tell you about those. It takes humans to spot the rest.
MarkR
It doesn't read minds or write code for you, but it's far better than a static code inspection tool like FindBugz that checks after that fact. Better to get the feedback immediately.
duffymo
+28  A: 

Automated Unit Testing .

P.K
I've found this better for making sure you don't break already existing functionality, more then helping you avoid bugs.
windfinder
@windfinder: I mostly agree. But if you code tests that stick to specification before coding, you can find bugs related to spec conformity
neuro
Who gets accurate, complete specifications?
duffymo
@duffy: I agree too :) But you can have (almost) complete and (almost) accurate specs for part of the softare. For example, it is the case when you design a custom 'protocol' between subsystem.
neuro
+15  A: 

I find the following rather handy.

1) ASSERTs.
2) A debug logger that can output to the debug spew, console or file.
3) Memory tracking tools.
4) Unit testing.
5) Smart pointers.

Im sure there are tonnes of others but I can't think of them off the top of my head :)

Goz
Yes, asserts are a life-saver when used properly.
zvrba
+3  A: 

Model-View-Controller, and in general anything with contracts and interfaces that can be unit-tested automatically.

fbonnet
+4  A: 

I find many problems before i start testing at all using

asserts

RED SOFT ADAIR
Another vote for the king of debug.
Pod
+1 but I believe it's quite tricky to trigger a runtime assertion without running (testing) your code...
bltxd
As you seem to have noticed: I distinguish "running" from "testing" here. Many assertions fail immediately when you run new functions the first time. Testing in contrast here means thorough testing.
RED SOFT ADAIR
+17  A: 

Code Review, Unit Testing, and Continuous Integration may all help.

Yuval F
+19  A: 

There's an oft-unappreciated technique that I like to call The QA Team that can do wonders for weeding out bugs before they reach production.

It's been my experience (and is often quoted in textbooks) that programmers don't make the best testers, despite what they may think, because they tend to test to behaviour they already know to be true from their coding. On top of that, they're often not very good at putting themelves in the shoes of the end user (if it's that kind of app), and so are likely to neglect UI formatting/alignment/usability issues.

Yes, unit testing is immensely important and I'm sure others can give you better tips than I on that, but don't neglect your system/integration testing. :)

..and hey, it's a language independent technique!

Xiaofu
+1: Isn't "avoid or resolve bugs" what QA is for?
S.Lott
Yes! And it can be so hard to get programmers to realize that they are not doing a good job of testing their own code.
Nathan
@nathan : so true
neuro
You can get developers to test other people's code, that works ok. Except for the UI usability, perhaps.
Zan Lynx
+5  A: 

Testing it with actual, realistic data from the start. And testing is necessary not only while writing the code, but it should start early in the design phase. Find out what your worst use cases will be like, and make sure your design can handle it. If your design feels good and elegant even against these use cases, it might actually be good.

Automated tests are great for making sure the code you write is correct. However, before you get to writing code, you have to make sure you're building the right things.

af
+1 I strongly agree. Testing against simulators or idealised data is fine as a start, but that should not be all even in unit testing. And I see requirements go unfulfilled or misinterpreted far too often, so perhaps "talk to the business side more" should be another piece of advice. It's all very well delivering flawless code, but not if it's the wrong solution.
Xiaofu
+14  A: 

RAII to avoid resource leakage errors.

Vinko Vrsalovic
RAII to avoid *resource* leakage errors.
avakar
Well, true. Although I would argue the most commonly leaked resource is memory. Thanks for the correction though :)
Vinko Vrsalovic
+10  A: 

I use thinking.

Sam
This is indeed rarely used and commonly underestimated technique.
Michael Krelin - hacker
also can't be automated.
TheMachineCharmer
@david: it is but the programs behind it are tightly-coupled and of low cohesion and racing to deadlock.
Sam
+3  A: 

I find peer progamming tends to help avoid alot of the silly mistakes, and alot of the time generates discussions which uncover flaws. Plus with someone free to think about the why you are doing something, it tends to make everything cleaner.

Irfy
+8  A: 

Reducing variables scope to as narrow as possible. Less variables in outer scope - less chances to plant and hide an error.

sharptooth
One of the best ones here. Greatly underestimated.
Vulcan Eager
+5  A: 

Learning functional programming helps somehow. HERE
Learn you a haskell for great good.

TheMachineCharmer
Wow, that 2nd link is an *excellent* Haskell tutorial. Intuitive, entertainingly written and barely a trace of FP smugness. There's even a sidebar written by The Fonz! Aaay!
j_random_hacker
+1  A: 

Unit Testing followed by Continious Integration.

Finglas
+1  A: 

Book suggestions: "Code Complete" and "Release it" are two must-read books on this topic.

nikie
As well as "Clean Code".
Pukku
+12  A: 
  1. Strive for simplicity and conciseness.
  2. Never leave cases where your code behavior is undefined.
  3. Look for opportunities to leverage the type system and have the compiler check as much as possible at compile time. Templates and code generation are your friends as long as you keep your common sense.
  4. Minimize the number of singletons and global variables.
  5. Use RAII !
  6. Use assertions !
  7. Automatic testing of some nominal and all corner cases.
  8. Avoid last minute changes like the plague.
bltxd
+1 for them all, but especially (2). In my experience, for anything longer than a 5-line Perl script, this always pays off. Whenever you can take another 10 minutes and convince yourself that your function handles all possible cases including error states, seize it! You just slammed the door closed on a maze of windy passages that you won't have to wander through again. (This isn't always possible of course, e.g. with UI code.)
j_random_hacker
Glad to see you gave simplicity the top spot. It's sad to see that this appears to be lost on the majority of programmers (the voting pattern on this question illustrates this nicely). +1
Dan Moulding
put links in to references on these topics, and this might get more votes!
Ape-inago
The plague was a last minute change? I guess that explains THAT bug!
Kaz Dragon
+6  A: 

I found that, the more is done and checked at compile time, the less can possibly go wrong at run-time. So I try to leverage techniques that allow stricter checking at compile-time. That's one of the reason I went into template-meta programming. If you do something wrong, it doesn't compile and thus never leaves your desk (and thus never arrives at the customer's).

sbi
+1. It's unfortunate that C++ makes metaprogramming so painful, and that there are still classes of errors that should be compile-time-preventable but aren't yet (e.g. avoiding object slicing), but hopefully things will evolve.
j_random_hacker
99% of things have a solution. Object slicing is avoidable by enforcing "non-leaf class is abstract" and/or all base class copy constructors/copy assignment operators be protected.
Richard Corden
Good points Richard, but I still crave a solution that doesn't rely so heavily on programmer discipline.
j_random_hacker
+4  A: 

I agree with many of the other answers here.

Specific to C++, the use of 'const' and avoiding raw pointers (in favor of references and smart pointers) when possible has helped me find errors at compile time.

Also, having a "no warnings" policy helps find errors.

David Coufal
+1  A: 

In addition to the already mentioned things I believe that some features introduced with C++0x will help avoiding certain bugs. Features like strongly-typed enums, for-in loops and deleteing standard functions of objects come to mind.

In general strong typing is the way to go imho

lnz
+1  A: 

Coding style consistency across a project.

Not just spaces vs. tab issues, but the way that code is used. There is always more than one way to do things. When the same thing gets done differently in different places, it makes catching common errors more difficult.

mLewisLogic
+1  A: 

It's already been mentioned here, but I'll say it again because I believe this cannot be said enough:

Unnecessary complexity is the arch nemesis of good engineering.

Keep it simple. If things start looking complicated, stop and ask yourself why and what you can do to break the problem down into smaller, simpler chunks.

Dan Moulding
A: 

all kinds of 'trace'.

lovespring
+3  A: 

Requirements.

From my experience, having full and complete requirements is the number one step in creating bug-free software. You can't write complete and correct software if you don't know what it's supposed to do. You can't write proper tests for software if you don't know what it's supposed to do; you'll miss a fair amount of stuff you should test. Also, the simple process of writing the requirements helps you to flesh them out. You find so many issues and problems before you ever write the first line of code.

Rob K
+1  A: 

Hire someone that test/validate your software.

We have a guy that use our software before any of our customer. He finds bugs that our automated tests processes do not find, because he thinks as a customer not as a software developper. This guy also gives support to our customers, because he knows very well the software from the customer point of view. INVALUABLE.

neuro
A: 

Something not mentioned yet - when there's even semi-complex logic going on, name your variables and functions as accurately as you can (but not too long). This will make incongruencies in their interactions with each other, and with what they're supposed to be doing stand out better. The 'meaning', or language-parsing part of your brain will have more to grab on to. I find that with vaguely named things, your brain sort of glosses over what's really there and sees what is /supposed to/ be happening rather than what actually is.

Also, make code clean, it helps to keep your brain from getting fuzzy.

JDonner
+1  A: 

Code reviews; I've personally found lots of bugs in my colleagues' code and they have found bugs in mine.

Code reviews, early and often, will help you to both understand each others' code (which helps for maintenance), and spot bugs.

The sooner you spot a bug the easier it is to fix. So do them as soon as you can.

Of course pair programming takes this to an extreme.

MarkR
A: 

Test-driven development combined with pair programming seems to work quite well on keeping some bugs down. Getting the tests created early helps work out some of the design as well as giving some confidence should someone else have to work with the code.

JB King
A: 

Creating a string representation of class state, and printing those out to console. Note that in some cases single line-string won't be enough, you will have to code small printing loop, that would create multi-line representation of class state. Once you have "visualized" your program in such a way you can start to search errors in it. When you know which variable contained wrong value in the end, it's easy to place asserts everywhere where this variable is assigned or modified. This way you can pin point the exact place of error, and fix it without using the step-by-step debugging (which is rather slow way to find bugs imo).

Just yesterday found a really nasty bug without debugging a single line:

vector<string> vec;
vec.push_back("test1");
vec.push_back(vec[0]); // second element is not "test1" after this, it's empty string

I just kept placing assert-statements and restarting the program, until multi-line representation of program's state was correct.

AareP