views:

380

answers:

9

I just came across this question about initializing local variables. Many of the answers debated simplicity/readability vs. robustness. As a developer of (remotely deployed) embedded systems, I always favor robustness and tend to follow several seemingly conflicting rules:

  • Handle every error to the best of your ability in a way that allows the device to continue running.

  • Design the code to fail as soon as possible after a programming or fatal error occurs.

We've all been trained to validate input to prevent the device from breaking as a result of user (or other external) input; always assume data may be invalid and test it accordingly.

What other specific practices do you follow to ensure robustness? Examples are helpful, but I'm also interested in techniques that are universally applicable.

+2  A: 

I try to use Design by contract as much as possible. But I find that it's rarely practical in my field of work.

Jonas
It's difficult for enforce this when a single developer is responsible, but we try to define our interfaces first. If nothing else, it makes it easy to write test cases and develop modules independently.
Adam Liss
DBC is also best used with a language that natively supports it, which is not often the case in many peoples production environments.
Jim Burger
Sadly, I find that DbC is only useful in some cases and for finding some problems. Right now I program GUIs and the program can't really fail because of input errors then. But I do find that DbC is good for documenting how the code should be used, even without native support.
Jonas
+6  A: 

I'm a fan of the techniques described in "The Pragmatic Programmer". I also use TDD, rather than DBC as I find it more flexible and productive. For example some of the techniqes described in 'pragprog' include:

  • Test Often. Test Early. Test Automatically
  • Dont Repeat Yourself
  • Use Sabotuers to Test your Testing
  • Use Exceptions for exceptional problems
  • Dont live with broken windows
  • Dont use manual procedures

They all seem like common sense, but its amazing how quickly teams deviate from these underpinning principles when faced with deadlines.

Jim Burger
I'm lobbying to adopt TDD and Evidence-Based Scheduling, as they both force us to work with reality, rather than assumptions. Thanks for the pointers.
Adam Liss
+2  A: 

I'm a fan of not initializing local variables. They should be set when needed. Otherwise, programmers reading your code could be confused as in "hmm why is this 0 at the beginning...". If you don't initialize it, it's clear it's not used yet.

Johannes Schaub - litb
I'll take that chance--and mitigate it with a comment--as I've spent far too much time chasing "ricochet" bugs that resulted from uninitialized variables.
Adam Liss
Testing properly should save you from initialization errors anyway.
Jim Burger
I agree. Do not initialize variables to values that are never used. An unused initial value is unnecessary code, which can cause confusion.
Jason Hernandez
FxCop will warn about this.
JBRWilkinson
+2  A: 

I implement a variety of methods to prevent and recover from errors:

1) Handle all exceptions. As you stated, "handle every error". If a user clicks a button on a form, there should be no possibility of the application just disappearing ("poof") from an unhandled exception. For that reason, I wrap event handlers with a generic try catch.

2) Log errors with full stack trace. When I rethrow an exception I always create a new one and add the caught exception as an inner exception. My logging code unwraps the messages recursively which gives a little more detail than I'd have otherwise.

3) Decide whether your classes are going to be reusable or not. If not document it. Consider implementing an interface in your code, something like IRestartable or IReusable. Any object not implementing it must be thrown away after it's used once.

4) Never assume thread safety. I've learned the hard way that .NET is extremely multi-threaded. Many events are handled on arbitrary threads. A given app written in .NET could have many simultaneous threads executing and not have a single line of code explicitly creating one.

5) Keep variable scope as narrow as possible. Instantiate objects near where they are used instead of in a large block at the beginning of a method. You'll potentially shorten the life of the objects and you won't forget about unneeded or reused variables sitting in a huge block at the top of the class or method.

5) Simple, but I still see it happening. Avoid globals like the plague. I've seen code with hundreds of unused/reused variables. It was a mess to figure out and refactor.

Nate
Thanks for some very helpful advice that you've clearly learned the hard way!
Adam Liss
Curiously, which exception do you trawl for in your event Handlers?
Jim Burger
@Jim, If the purpose of the try/catch is to prevent an unhandled exception, I catch System.Exception. If I'm trying to filter out specific exceptions because I either want to ignore them or do something else, I put those first. The point is, that I don't want an unhandled exception making the program crash when a user presses a button...I know some people claim catching System.Exception is bad form, I don't *always* agree.
Nate
Amen about Number 5!!
C Johnson
+5  A: 

Sounds like you already have these two down:
Fail Fast. http://en.wikipedia.org/wiki/Fail-fast
Fail Safe. http://en.wikipedia.org/wiki/Fail-safe

These three probably serve me better than any other:

Avoid state whenever possible. Create and use immutable objects- they are easier to test and less likely to betray you.

Don't write unnecessary code. This one is hard. Check out the recent bloom of articles relating "The Elements of Style" by Strunk and White to programming.

Ask yourself every 10 minutes or so: "Is this dumb?" Be honest. This one is harder.

-Jason

Jason Hernandez
Thanks for the good advice. My mentor is fond of saying, "You know your software is starting to mature when you improve it by _removing_ code."
Adam Liss
+2  A: 

I like to... document limit values in (java)doc. (can a parameter be empty ? be null ?)

That way, when my code is used (or when I used my own code), I know what I can expect. Simple recommendation, but so rarely implemented ;)

That documentation also include a clear separation of static and runtime exceptions. That way, if the program must fail as soon as possible in order to improve robustness, I know if it fails because of a foreseen exception (static, must be dealt with at coding time trough a catch or a re-throw), or if it is because of incorrect parameters (runtime exceptions, only detected during the application lifetime).

If both types of exception are clearly documented (especially when it come to limit values for parameters), the overall robustness is easier to enforce.

VonC
I'm still amazed that such simple techniques are so rarely used, especially when you consider they're essentially "free," and the cost of _not_ using them can ruin a company. And they say engineers aren't usually gamblers! :-)
Adam Liss
Yeap... still the number of times I see javadoc for public functions (without even going as far as a careful documentation of limit values or exception) is quite limited (sigh).
VonC
Document the limits of values and any other limitation of the routine, but then validate the values anyway.
Jim C
+6  A: 

I'm fond of the second pair of eyes method: after I've written and tested some critical code, I'll sometimes ask a coworker to review it specifically with the intent of looking for ways to break it.

It's fun to bring out people's creativity this way. :-)

Adam Liss
Code review rocks +1
Jim Burger
Code reviews are mandatory at my work.
C Johnson
+2  A: 

When it's possible, I ask the opinion of someone who specializes in something else. That often uncovers one or more entirely new ways of breaking things.

Adam Liss
+2  A: 

Being paranoid is a survival trait for programmers. Constantly ask yourself how can this fail? Then try to figure out how to prevent that failure.

Jim C