Does protective programming violate the DRY principle?

Disclaimer: I am a layperson who is currently learning to program. Never been part of a project and did not write anything longer than ~ 500 lines.

My question is: does defensive programming violate the Do Not Repeat Principle? Assuming my definition of defensive programming is correct (if the calling function checks the input instead of the opposite), would it be harmful to your code?

For example, this is 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 layman, I don’t know how many simple logical statements count against you in terms of performance, but, of course, defensive programming is not suitable for the program or the soul.

+6
c ++ dry defensive-programming
source share
6 answers

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

Inputs - and thus mainly mean parameters for functions - should be checked by the implementation as a general rule.

The outputs - the result of the return - should basically be trusted by the caller, at least in my opinion.

All this is mitigated by this question: what happens if one side violates the contract? For example, let's say you have an interface:

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

and this contract indicates that an empty string will never be returned (it will be an empty string in the worst case), then this is safe for this:

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

Why? Well, if you are mistaken, and the caller returns zero, then the program will fail. This is really normal. If an object violates his contract, then, generally speaking, the result should be catastrophic.

The risk that you face in excessive defense is that you write a lot of unnecessary code (which can lead to more errors) or that you can actually mask a serious problem by swallowing an exception that you really don't need.

Of course, circumstances can change that.

+6
source share

Violation of the DRY principle is as follows:

 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 have to change it in two places, and there is a chance that we will forget one of them, causing strange behavior . DRY does not mean "do not execute the same code twice", but "do not write the same code twice"

+9
source share

Let me first state that blindly following the principle is idealistic and WRONG. You need to achieve what you want to achieve (say, the security of your application), which, as a rule, is much more important than breaking DRY. Deliberate violations of principles are most often required in GOOD software.

Example: I perform double checks at important stages (for example, LoginService - first check the input once before calling LoginService.Login, and then inside again), but sometimes I tend to delete the external one again after making sure everything works 100% usually with unit tests. It depends.

I would never do double-state checking. On the other hand, PROHIBITING them entirely, as a rule, a few values ​​are worse :)

+4
source share

I think that defensive programming got a bad rap because it does some things that are undesirable that include verbose code and, more importantly, documentation of errors.

Most people seem to agree that the program will run fast when it encounters an error, but these critical systems should never fail and instead go long distances to keep going under the conditions of errors.

There is a problem with this statement, of course, how can a program, even critically important, continue when it is in a contradictory state. Of course, this is actually impossible.

What you want is that the program takes all reasonable steps to work properly, even if there is something strange. At the same time, the program should complain loudly every time it encounters such an odd state. And in the case of an error that cannot be repaired, you should usually avoid issuing the HLT , but it should gracefully stop by safely shutting down your systems or activating some backup system, if any.

+3
source share

In a simplified example, yes, perhaps the second format is preferable.

However, this does not apply to larger, more complex and more realistic programs.

Since you never know in advance where and how "foo" will be used, you need to protect foo by checking the input. If the input is verified by the caller (for example, "main" in your example), then "main" must know the verification rules and apply them.

In real programming, input validation rules can be quite complex. It is not necessary for the caller to know all the validation rules and apply them correctly. Some subscriber somewhere forgets the verification rules or does the wrong ones. Therefore, it is better to check the validity inside "foo", even if it is called again. This transfers the load from the caller to the called party, which frees the caller to think less about the details of "foo" and use it more like an abstract, reliable interface.

If you really have a template where "foo" will be called several times with the same input, I suggest a wrapper function that performs the check once, and an unprotected version that performs the check:

 void RepeatFoo(int bar, int repeatCount) { /* Validate bar */ if (bar != /*condition*/) { //code, assert, return, etc. } for(int i=0; i<repeatCount; ++i) { UnprotectedFoo(bar); } } void UnprotectedFoo(int bar) { /* Note: no validation */ /* do something with bar */ } void Foo(int bar) { /* Validate bar */ /* either do the work, or call UnprotectedFoo */ } 
+1
source share

As Alex said, it depends on the situation, for example, I almost always check the input at each stage of the registration process.

In other places you do not need all this.

However, in the example you indicated, in the second example I assume that you have more than one input, because otherwise it will be redundant, calling the same function 3 times for the same input, which means that you write a condition 3 times. Now this is redundant.

If the ALWAYS input needs to be checked, just include it in the function.

+1
source share

All Articles