简体   繁体   English

防御性编程是否违反DRY原则?

[英]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. 从未参与过项目,也没有写过超过500行的文章。

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. 再说一次,作为一个外行人,我不知道有多少简单的逻辑陈述对你来说就性能而言是多少,但对于程序或灵魂而言,防御性编程肯定不好。

Violating the DRY principle looks like that: 违反DRY原则看起来像这样:

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" DRY并不意味着“不要两次执行相同的代码”,而是“不要两次写相同的代码”

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. 好吧,如果你错了,并且被调用者返回null,那么程序将崩溃。 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. 当然情况可以改变这一点。

Let me state first that blindly following a principle is idealistic and WRONG. 首先我要说明,盲目遵循一个原则是理想主义和错误的。 You need to achieve what you want to achieve (say, safety of your application), which is usually far more important that violating DRY. 您需要实现您想要实现的目标(例如,您的应用程序的安全性),这通常比违反DRY更重要。 Willful violations of principles are most often necessary in GOOD programming. 在GOOD编程中,最常需要故意违反原则。

An example: I do double checks at important stages (eg LoginService - first validate input once before calling LoginService.Login, and then again inside), but sometimes I tend to remove the outer one again later after I made sure that everything works 100%, usually using unit tests. 一个例子:我在重要阶段进行双重检查(例如LoginService - 在调用LoginService.Login之前首先验证输入一次,然后再在里面再次验证输入),但有时候我确定在确保一切正常工作之后再次删除外部,通常使用单元测试。 It depends. 这取决于。

I'd never get worked up over double condition checks though. 我不会在双重条件检查中解决问题。 FORGETTING them entirely on the other hand is usually multiple magnitudes worse :) 另一方面,完全忘记他们通常是多个级别更糟:)

I think defensive programming gets kind of a bad rap, since it does some things that are kind of undesirable, which include wordy code, and more significantly, papering over errors. 我认为防御性编程会成为一种糟糕的说法,因为它会做一些不受欢迎的事情,其中​​包括罗嗦的代码,更重要的是,应用错误。

Most folks seem to agree that a program should fail fast when it encounters an error, but that mission critical systems should preferably never fail, and instead go to great lengths to keep on going in the face of error states. 大多数人似乎同意程序在遇到错误时应该快速失败,但是关键任务系统应该最好永远不会失败,而是在面对错误状态时不断努力。

There's a problem with that statement, of course, How can a program, even mission critical, continue when it's in an inconsistent state. 当然,这个陈述有一个问题,一个程序,即使是关键任务,如果它处于一个不一致的状态,它怎么能继续。 Of course it can't, really. 当然它不能,真的。

What you want is for the program to take every reasonable step to do the right thing, even if there's something odd going on. 你想要的是让程序采取一切合理的步骤来做正确的事情,即使有一些奇怪的事情发生。 At the same time, the program should complain, loudly , every time it encounters such an odd state. 与此同时,每当遇到如此奇怪的状态时,程序就应该大声抱怨。 And in the event it encounters an error that is unrecoverable, it should usually avoid issuing a HLT instruction, rather it should fail gracefully, shutting down its systems safely or activating some backup system if one is available. 如果遇到无法恢复的错误,通常应该避免发出HLT指令,而应该正常失败,安全地关闭系统或激活某些备份系统(如果有的话)。

In your simplified example, yes, the second format is probably preferable. 在您的简化示例中,是的,第二种格式可能更可取。

However, that doesn't really apply to larger, more complex, and more realistic programs. 但是,这并不适用于更大,更复杂,更现实的程序。

Because you never know in advance where or how "foo" will be used, you need to protect foo by validating the input. 因为您事先不知道“foo”将在何处或如何使用,您需要通过验证输入来保护foo。 If the input is validated by the caller (eg. "main" in your example) then "main" needs to know the validation rules, and apply them. 如果输入由调用者验证(例如,示例中为“main”),则“main”需要知道验证规则并应用它们。

In real-world programming, the input validation rules can be fairly complex. 在实际编程中,输入验证规则可能相当复杂。 It is not appropriate to make the caller know all the validation rules and apply them properly. 使调用者知道所有验证规则并正确应用它们是不合适的。 Some caller, somewhere, is going to forget the validation rules, or do the wrong ones. 某些调用者会在某处忘记验证规则,或者做错误的规则。 So its better to put the validation inside "foo", even if it will get called repeatedly. 所以最好将验证放在“foo”中,即使它会被重复调用。 This shifts the burden from the caller to the callee, which frees the caller to think less about the details of "foo", and use it more as an abstract, reliable interface. 这将负责从调用者转移到被调用者,这使得调用者可以更少地思考“foo”的细节,并将其更多地用作抽象的可靠接口。

If you truly have a pattern where "foo" will get called multiple times with the same input, I suggest a wrapper function that does the validation once, and an unprotected version that side-steps the validation: 如果你真的有一个模式,其中“foo”将使用相同的输入多次调用,我建议一个包装函数执行一次验证,并且一个不受保护的版本支持验证:

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 */
}

Like Alex said, it depends on the situation, for example, I almost always validate input at every stage of the log in process. 像Alex所说,这取决于具体情况,例如,我几乎总是在登录过程的每个阶段验证输入。

In other places, you don't need all that. 在其他地方,你不需要这一切。

However, in the example you gave, I'm assuming, in the second example, that you have more than one input, 'cause otherwise it'll be redundant calling the same function 3 times for the same input which means you'll have to write the condition 3 times. 但是,在你给出的例子中,我假设,在第二个例子中,你有多个输入,'因为否则它将是多余的,为相同的输入调用相同的函数3次,这意味着你将拥有写条件3次。 Now THAT is redundant. 现在这是多余的。

If the input ALWAYS has to be checked just include it in the function. 如果必须检查输入ALWAYS,只需将其包含在函数中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM