views:

68

answers:

2

Occasionally I'll have a situation where I've written some code and, based on its logic, a certain path is impossible. For example:

activeGames = [10, 20, 30]
limit = 4

def getBestActiveGameStat():
    if not activeGames: return None
    return max(activeGames)

def bah():
    if limit == 0: return "Limit is 0"

    if len(activeGames) >= limit:
        somestat = getBestActiveGameStat()
        if somestat is None:
            print "The universe has exploded"
        #etc...

What would go in the universe exploding line? If limit is 0, then the function returns. If len(activeGames) >= limit, then there must be at least one active game, so getBestActiveGameStat() can't return None. So, should I even check for it?

The same also happens with something like a while loop which always returns in the loop:

def hmph():
    while condition:
        if foo: return "yep"
        doStuffToMakeFooTrue()

    raise SingularityFlippedMyBitsError()

Since I "know" it's impossible, should anything even be there?

+2  A: 

IMHO, the first example is really more a question of how catastrophic failures are presented to the user. In the event that someone does something really silly and sets activeGames to none, most languages will throw a NullPointer/InvalidReference type of exception. If you have a good system for catching these kinds of errors and handling them elegantly, then I would argue that you leave these guards out entirely.

If you have a decent set of unit tests, they will ensure with huge amounts of certainty that this kind of problem does not escape the developers machine.

As for the second one, what you're really guarding against is a race condition. What if the "doStuffToMakeFooTrue()" method never makes foo true? This code will eventually run itself into the ground. Rather than risk that, I'll usually put code like this on a timer. If your language has closures or function pointers (honestly not sure about Python...), you can hide the implementation of the timing logic in a nice helper method, and call it this way:

withTiming(hmph, 30) // run for 30 seconds, then fail

If you don't have closures or function pointers, you'll have to do it the long way everywhere:

stopwatch = new Stopwatch(30)
stopwatch.start()
while stopwatch.elapsedTimeInSeconds() < 30
    hmph()
raise OperationTimedOutError()
Chris Jaynes
ah ya python has both of those =). no lisp-style macros, though.
Claudiu
+3  A: 

If len(activeGames) >= limit, then there must be at least one active game, so getBestActiveGameStat() can't return None. So, should I even check for it?

Sometimes we make mistakes. You could have a program error now -- or someone could create one later.

Those errors might result in exceptions or failed unit tests. But debugging is expensive; it's useful to have multiple ways to detect errors.

A quickly written assert statement can express an expected invariant to human readers. And when debugging, a failed assertion can pinpoint an error quickly.

Sutter and Alexandrescu address this issue in "C++ Coding Standards." Despite the title, their arguments and guidelines are are language agnostic.

Assert liberally to document internal assumptions and invariants ... Use assert or an equivalent liberally to document assumptions internal to a module ... that must always be true and otherwise represent programming errors.

For example, if the default case in a switch statement cannot occur, add the case with assert(false).

Andy Thomas-Cramer
I agree that asserts can be a great way to make sure other developers know you weren't forgetting something. For cases like the default in a switch, that can also be a useful way to generate an error instead of just silently doing nothing (with potentially catastrophic results). I do believe that there's a balance, though. For cases like null references, which are very common, and will throw an error anyway, it should be pretty obvious what's going on, and even a simple assert can end up making clean code a little bit less clean.
Chris Jaynes
The most important thing, in my opinion, is to make sure whatever errors do trickle up (asserts or otherwise) are handled politely for the end-user. :)
Chris Jaynes
@Chris Jaynes - An assertion can sometimes fire closer to the source of the problem. If the choice is ever between expressing an invariant explicitly or not expressing it at all because we don't have the time to code a polite handling of the case we expect to never occur, the choice should always be to express the invariant.
Andy Thomas-Cramer