Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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
  • Don't repeat yourself
  • Use saboteurs to test your testing
  • Use exceptions for exceptional problems
  • Don't live with broken windows
  • Don't use manual procedures

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

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. :-)

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

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

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.

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.

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.

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.

The systems I write (in C) have high requirements as to performance and reliability. Designing for fail is fine. When it comes to design for safe things become more complicated.

Hardware can always fail and apart from that you have the issue of invalid data entering the system from outside. My solutions in those areas will usually be elaborate (design for safe) and in practically all other areas design for fail by essentially having extremely simple code and no data validation at all.

So you could say that I design for fail when the risks are small and for safe when they are high.

For debugging I sometimes write conditional code which should never be entered which contains a divide by zero or some such so that when processing enters there the debugger will be invoked immediately.

I usually don't initialize local stack variables since the compiler tells me which need to be initialized and which can be omitted completely.

Code reviews are great but not fool-proof. I once spent several hours looking at a small (infinite) loop (embarassing, isn't it?) and couldn't see that the index wasn't incremented. My colleague didn't see it either. When I finally looked at the generated code listing I saw that the loop testing had been optimized to a single unconditional jump and only then did I understand what the problem was.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top