11

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.

Community
  • 1
  • 1
Adam Liss
  • 47,594
  • 12
  • 108
  • 150

10 Answers10

7

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.

jkchu
  • 98
  • 10
Jim Burger
  • 4,399
  • 1
  • 24
  • 27
  • 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 Nov 12 '08 at 06:10
6

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
  • 47,594
  • 12
  • 108
  • 150
5

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
  • 2,907
  • 1
  • 19
  • 30
  • 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 Nov 12 '08 at 12:13
2

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
  • 4,107
  • 3
  • 21
  • 25
  • 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 Nov 12 '08 at 06:08
  • 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 Nov 12 '08 at 06:14
  • 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 Nov 12 '08 at 06:31
2

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
  • 496,577
  • 130
  • 894
  • 1,212
  • 1
    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 Nov 12 '08 at 06:20
  • Testing properly should save you from initialization errors anyway. – Jim Burger Nov 12 '08 at 06:50
  • 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 Nov 12 '08 at 07:17
2

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nate
  • 5,237
  • 7
  • 42
  • 52
  • Thanks for some very helpful advice that you've clearly learned the hard way! – Adam Liss Nov 12 '08 at 06:25
  • Curiously, which exception do you trawl for in your event Handlers? – Jim Burger Nov 12 '08 at 06:47
  • @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 Jan 11 '10 at 08:29
2

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.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 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 Nov 12 '08 at 12:50
  • 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 Nov 12 '08 at 12:54
  • Document the limits of values and any other limitation of the routine, but then validate the values anyway. – Jim C Nov 12 '08 at 20:49
2

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
  • 47,594
  • 12
  • 108
  • 150
2

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
  • 4,981
  • 21
  • 25
1

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.

Olof Forshell
  • 3,169
  • 22
  • 28