DRY原理的解释


10

现在,我在编码中正在尝试DRY(不要重复自己)这个概念。我正在创建此函数,但我担心它变得太复杂了,但我尝试遵循DRY原理。

createTrajectoryFromPoint(A a,B b,C c,boolean doesSomething,boolean doesSomething2)

我说过的这个函数需要3个输入参数,然后在给定布尔组合doesSomething和的情况下,该函数会做一些稍有不同的事情doesSomething2。但是我遇到的问题是,随着每个新的布尔参数的添加,此函数的复杂性大大增加。

所以我的问题是,最好是让一堆不同的函数共享很多相同的逻辑(从而违反DRY原理),或者让一个函数在给定多个参数的情况下表现稍有不同,但使其复杂得多(但是保存DRY)?


3
可以将共享/公共逻辑分解为不同公共createTrajectory...功能都调用的私有功能吗?
FrustratedWithFormsDesigner 2012年

可以,但是那些私有函数仍然需要获取这些布尔参数
Albinoswordfish 2012年

2
我认为,鉴于某些具体示例,这将/将容易得多。我的直接反应是,您描绘的二分法并非完全真实-即,这不是唯一的两个选择。顺便说一句,我认为使用a boolean作为参数充其量是最好的。
杰里·科芬


呃,为什么不将条件性事物分解为自己的功能呢?
钻机2012年

Answers:


19

在单个函数/方法中触发不同代码路径的布尔参数是一种可怕的代码味道

您正在执行的操作违反了松散耦合高内聚单一责任原则,而优先原则比DRY 重要得多。

这意味着事物仅在必须(耦合)时才应该依赖于其他事物,并且它们应该做一件事情而只能做一件事情(很好)(凝聚力)。

由于您自己的疏忽,这过于紧密地耦合在一起(所有布尔标志都是一种状态依赖性,这是最严重的状态之一!),并且混合了太多的个人职责(过于复杂)。

无论如何,您所做的都不是DRY的精神。DRY更多关于重复(R代表REPEAT)。避免复制和粘贴是其最基本的形式。您在做什么与重复无关。

您的问题是您对代码的分解不正确。如果您认为将有重复的代码,则应使用适当的参数化其自己的函数/方法,而不是复制和粘贴,而其他代码应以描述性方式命名并委托给核心函数/方法。


好的,这似乎是我写的方式不是很好(我最初的怀疑),但我不确定这'DRY的精神'是什么
-Albinoswordfish


4

您传入布尔值以使函数执行其他操作的事实违反了“单一职责原则”。一个功能应该做一件事。它应该只做一件事,并且应该做得很好。

闻起来好像您需要将其拆分为几个具有描述性名称的较小函数,从而将与这些布尔值相对应的代码路径分开。

完成后,您应该在结果函数中查找通用代码,并将其分解为自己的函数。根据事情的复杂程度,您甚至可以排除一两个类。

当然,这是假设您使用的是版本控制系统,并且您拥有良好的测试套件,因此您可以重构而不必担心会破坏某些内容。


3

为什么在决定做某事或某事2并拥有三个功能(例如:)之前,为什么不创建另一个包含所有功能逻辑的功能:

createTrajectoryFromPoint(A a,B b,C c){...}

dosomething(A a, B b, C c){...}

dosomething2(A a, B b, C c){...}

现在,通过将三个相同的参数类型传递给三个不同的函数,您将再次重复自己,因此您应该定义一个包含A,B,C的结构或​​类。

或者,您可以创建一个包含参数A,B,C和要完成的操作的列表的类。通过向对象注册操作,添加您希望对这些参数(A,B,C)执行的操作(某物,某物2)。然后有一个方法可以调用对象上的所有已注册操作。

public class MyComplexType
{
    public A a{get;set;}
    public B b{get;set;}
    public C c{get;set;}

    public delegate void Operation(A a, B b, C c);
    public List<Operation> Operations{get;set;}

    public MyComplexType(A a, B b, C c)
    {
        this.a = a;
        this.b = b;
        this.c = c   
        Operations = new List<Operation>();
    }

    public CallMyOperations()
    {
        foreach(var operation in Operations)
        {
            operation(a,b,c);
        }
    }
}

要考虑布尔值dosomething和dosomething2的可能值组合,除了基本createTrajectoryFromPoint函数之外,您还需要4个函数,而不是2个。随着选项数量的增加,这种方法无法很好地扩展,甚至命名功能也变得乏味。
JGWeissman

2

DRY可能做得太过分了,最好将单一责任原则(SRP)与DRY结合使用。在函数中添加bool标志以使其执行相同代码的稍有不同的版本可能是您对一个函数执行过多操作的标志。在这种情况下,我建议为您的标志所代表的每种情况创建一个单独的函数,然后当您将每个函数写出时,如果没有可以传递所有标志的情况下可以将公共部分移至私有函数,则应该很明显,如果没有明显的代码段,那么您实际上并没有重复自己,您有几种不同但相似的情况。


1

我通常会通过几个步骤解决此问题,在无法找出进一步的方法时停止。

首先,做你所做的。努力干。如果最终没有毛茸茸的大麻烦,那就大功告成。如果像您一样,您没有重复的代码,但是每个布尔值的值都在20个不同的地方进行了检查,请转到下一步。

其次,将代码分成多个块。每个布尔仅被引用一次(有时,可能两次),以将执行定向到正确的块。有两个布尔值,最终会得到四个块。每个块几乎是相同的。DRY不见了。 不要使每个块成为单独的方法。这样做会更优雅,但是将所有代码放在一个方法中会使进行维护的任何人更容易甚至什至可以看到他们必须在四个位置进行每次更改。借助组织良好的代码和高大的监视器,差异和错误将几乎显而易见。您现在有了可维护的代码它的运行速度将比原始的混乱情况快。

第三,尝试从每个块中获取重复的代码行,并将它们变成漂亮,简单的方法。有时候你什么也做不了。有时候你做不了多少。但是,您所做的每一点都将您带回DRY,并使代码更易于遵循和维护。理想情况下,您的原始方法可能最终没有重复的代码。那时,您可能希望将其拆分为几个没有布尔参数的方法,也可能不需要。现在,调用代码的便利性是主要关注的问题。

由于第二步,我将答案添加到此处已有的大量答案中。我讨厌重复的代码,但是如果这是解决问题的唯一可理解的方法,请以任何人都一眼就能知道您在做什么的方式来进行操作。 使用多个块和仅一种方法。 使块在名称,间距,对齐方式,...所有内容上尽可能相同。差异然后应该在阅读器中跳出来。可能很明显如何以DRY方式重写它,如果没有,则维护起来将相当简单。


0

一种替代方法是用接口参数替换布尔参数,并使用代码来处理重构到接口实现中的不同布尔值。所以你会

createTrajectoryFromPoint(A a,B b,C c,IX x,IY y)

在这里,有IX和IY的实现,它们表示布尔值的不同值。在函数主体中,无论您身在何处

if (doesSomething)
{
     ...
}
else
{
     ...
}

您可以用对IX上定义的方法的调用来替换它,其实现包含省略的代码块。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.