DEV Community

Discussion on: Is “Defensive Programming” actually healthy?

Collapse
 
dvddpl profile image
Davide de Paolis

Interesting article ( and very interesting blog btw :-) )
I don't really like the word defensive programming because over the years I see too many bloated methods (even in typed languages like C# and Actionscript) full of null checks, undefined checks for basically everything. and every time I asked " but why... ??" the response was always " Just to be sure " or " better be safe than sorry" or " do you really wanna risk a crash on production !?!?"(the last being pronounced with a horrified face showing disappointment for my being so irresponsible).

But I agree that it is better to make the best out of our coding style and out of the tools we have ( strictly type languages, linters, unit tests) to prevent errors that might happen in the future.
The first thing that I thought when I saw your example was that i would have used a Dictionary/Map to retrieve the right action for the right signal. if there is no mapping then there would be an error. Of course, that would not work at compile time - so the solution suggested here is perfect and elegant.

But when it comes to really be defensive then I sometimes get very paranoid and think of what could happen at runtime. All your type checking works only at compile time: in fact, the elegant solution would be converted to this in simple js:

function respondToTrafficSignal(signal) {
    var mapping = {
        red: "stop",
        yellow: "pause",
        green: "go"
    };
    return mapping[signal];
}

And nothing would prevent your function to be executed passing invalid values. (imagine that the signal is a value coming from the server or from any external API )
Of course with a mapping instead of an if or switch you will get "undefined" rather then "go" but you might end up with unpredictable behaviour anyway. In such case, an error thrown ( and properly handled ) would be better.
So to conclude: as always Defensive Programming: Good? Bad? It depends :-)

Collapse
 
cubiclebuddha profile image
Cubicle Buddha • Edited

Thank you for your kind reply. Yes, I’ve been cooking up the blog articles for many months now. :)

So as for your code I your response, I’m a bit confused about the benefit. Your function returns undefined (as you mentioned). I don’t understand why you feel that’s a good thing. That means that a consumer code needs to either handle the undefined case every time, and if they forget to do so it will be a runtime exception. And that exception would be some kind of unclear “undefined exception.” Even in JS it would be better to throw an error so as to be explicit about the reason why it couldn’t find a match. But there’s a better way with TypeScript where you kind find out months before an error would ever be discovered. The compiler is run on every check in. Why wouldn’t you want the faster feedback of finding out when you check your code in?

Personally, I find that I go much faster if I have a short feedback loop where I can find out what I did wrong. Now, I realize that I’m arguing for static type analysis, which is not everyone’s cup of tea (although the recent Stack Overflow survey showed immense support for TypeScript). But the main point I’d like to make is that it truly is better to avoid bugs in production if you can catch them sooner.

Collapse
 
dvddpl profile image
Davide de Paolis

probably I did not articulate my reply properly.
I totally agree in the benefit of the static type checking. and I definetely want to catch error at compile time.
And i said that i would implement that check not with an IF /ELSE IF nor with a SWITCH, rather with a Mapping like suggested above by @jvanbruegge :

type TrafficLight = "red" | "yellow" | "green";
type TrafficAction = "stop" | "go" | "pause";

type TrafficResponse = {
    [k in TrafficLight]: TrafficAction;
};

function respondToTrafficSignal(signal: TrafficLight): TrafficAction {
    const mapping: TrafficResponse = {
        red: "stop",
        yellow: "pause",
        green: "go"
    };
    return mapping[signal];
}

BUT i would also be even more defensive and check for possible error at runtime in case - at runtime the function is called with an invalid value.
At runtime Typescript does not exist, after you compile all your type checking is gone and what you have is the function I posted.

function respondToTrafficSignal(signal) {
    var mapping = {
        red: "stop",
        yellow: "pause",
        green: "go"
    };
    return mapping[signal];
}

See and play around with this Typescript Playground snippet for comparison
Therefore, if the invocation of the function could be dynamic ( server could respond with blue or user could type in orange ) i would add a catch and a fallback to prevent a runtime error.

Hope i was clearer now :-)

Thread Thread
 
cubiclebuddha profile image
Cubicle Buddha • Edited

Yup yup. Yea the map + the undefined check would be the ideal approach. Sorry I didn’t understand at first. Classic misunderstanding with remote communication. My bad! :)

Oh and as far as the server sending bad or new data types, I have been using a library called TSOA to enforce runtime types at the boundaries. It’s a way of preventing “garbage in garbage out.” It’s pretty cool stuff. There are similar libraries that do runtime checking in the UI too.

Thread Thread
 
jvanbruegge profile image
Jan van Brügge

No, I would not add runtime checks to this. If you call the function with something different than the types specify that's on your own. The caller has to verify that he can call the function