Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does defensive programming violate the DRY principle?

Disclaimer: I am a layperson currently learning to program. Never been part of a project, nor written anything longer than ~500 lines.

My question is: does defensive programming violate the Don't Repeat Yourself principle? Assuming my definition of defensive programming is correct (having the calling function validate input instead of the opposite), wouldn't that be detrimental to your code?

For instance, is this bad:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    foo(input); //doesn't the extra logic
    foo(input); //and potentially extra calls
    foo(input); //work against you?
}   

compared to this:

int main()
{
    if (input == /*condition*/)
    {
        foo(input);
        foo(input);
        foo(input);
    }
}

Again, as a layperson, I don't know how much simple logic statements count against you as far as performance goes, but surely defensive programming is not good for the program or the soul.

like image 429
jkeys Avatar asked Jun 07 '09 05:06

jkeys


2 Answers

Violating the DRY principle looks like that:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    if (input == /*condition*/)
    {
       foo(input);
       foo(input);
       foo(input);
    }
}

as you can see, the problem is that we have the same check twice in the program, so if the condition changes, we must modify it at two places, and chances are that we forget one of them, causing strange behaviour. DRY doesn't mean "don't execute the same code twice", but "don't write the same code twice"

like image 185
Erich Kitzmueller Avatar answered Oct 21 '22 04:10

Erich Kitzmueller


It all comes down to the contract the interface provides. There are two different scenarios for this: inputs and outputs.

Inputs--and by that I basically mean parameters to functions--should be checked by the implementation as a general rule.

Outputs--being return results--should be basically trusted by the caller, at least in my opinion.

All of this is tempered by this question: what happens if one party breaks the contract? For example, lets say you had an interface:

class A {
  public:
    const char *get_stuff();
}

and that contract specifies that a null string will never be returned (it'll be an empty string at worst) then it's safe to do this:

A a = ...
char buf[1000];
strcpy(buf, a.get_stuff());

Why? Well, if you're wrong and the callee returns a null then the program will crash. That's actually OK. If some object violates its contract then generally speaking the result should be catastrophic.

The risk you face in being overly defensive is that you write lots of unnecessary code (which can introduce more bugs) or that you might actually mask a serious problem by swallowing an exception that you really shouldn't.

Of course circumstances can change this.

like image 39
cletus Avatar answered Oct 21 '22 06:10

cletus