DRY没有单一的代码?

时间:2010-10-27 15:04:16

标签: language-agnostic dry

我想不要重复自己(DRY),但我不能拥有一段代码。例如,这里是使用相同的错误重复3次代码:

class StarWars : Movie
{
   //Calculate "base ^ exponent"
   public float Power(float base, float exponent)
   {
      return (base * exponent);
   }
}

class Customer: Object
{
   //Calculate "base ^ exponent"
   public float Exponential(float base, float exponent)
   {
      return (base ^ exponent);
   }
}

class Student: Person
{
   //Calculate "base ^ exponent"
   public float CalculateExpoential(float base, float exponent)
   {
      return CalculateExponential(2.7182818, exponent * Ln(base));
   }
}

现在理想情况下我会将这个常用函数提取到它自己的帮助器中:

class LibraryOfHelperCode
{
    public static float Exponentiation(float base, float exponent)
    {
       return Exp(2.71828183, base * Ln(exponent));
    }
}

并转换现有代码以使用它:

class StarWars : Movie
{
   //Calculate "base ^ exponent"
   public float Power(float base, float exponent)
   {
      return LibraryOfHelperCode.Exponentiation(base, exponent);
   }
}

class Customer: Object
{
   //Calculate "base ^ exponent"
   public float Exponential(float base, float exponent)
   {
      return LibraryOfHelperCode.Exponentiation(base, exponent);
   }
}

class Student: Person
{
   //Calculate "base ^ exponent"
   public float CalculateExpoential(float base, float exponent)
   {
      return LibraryOfHelperCode.Exponentiation(base, exponent);
   }
}

值是现在我从

中提取了重复的代码
  • 电源
  • 指数
  • CalculateExpoential

成单一功能。这意味着如果有任何错误,它们只需要修复一次。在这种情况下哪个好,因为一个错误:

   public float CalculateExpoential(float base, float exponent)
   {
      //19971012: Oops, should be natrual log of base, not exponent
      return Exp(2.71828183, exponent * Ln(base));    
   }

并在那之后几年:

   public float CalculateExpoential(float base, float exponent)
   { 
      //19990321: Oops, need to handle when exponent is zero
      if (exponent == 0)
         return 1.0;

      //19971012: Oops, should be natrual log of base, not exponent
      return Exp(2.71828183, exponent * Ln(base));    
   }

以及后来:

   public float CalculateExpoential(float base, float exponent)
   { 
      //19990321: Oops, need to handle when exponent is zero
      if (exponent == 0)
         return 1.0;

      //20040523: Another special case
      if (Base = 0.0) && (Exponent > 0.0) then
         return 0.0; // 0**n = 0, n > 0

      //19971012: Oops, should be natrual log of base, not exponent
      return Exp(2.71828183, exponent * Ln(base));    
   }

最后:

   public float CalculateExpoential(float base, float exponent)
   { 
      //20101027: Microsoft just release a method in .NET framework 4.0 that does
      //what we need. Use it:
      return Math.Pow(base, exponent);
   }

每个人都得到了修复。另一方面,我无法保证这些增量修复中的任何一个都不会破坏现有代码。

想象一个人正在打电话:

char ps = Math.Trunc(Exponential(ProblemSize, ProblemComplexity));

并且从未期望价值大于128.他错了。虽然代码错误,但是发生了才能正常工作。

现在我来了修复事情,突然代码因溢出和/或环绕而崩溃。


我今天面临的问题是DRY公共代码的变化会影响它所使用的每个地方。唯一可接受的(polotical)解决方案是为每个使用它的可执行文件/ moduble / namespace /类保留一个库类的副本。

撤消任何干扰。

有没有办法解决这个烂摊子?当我不能重复自己,但继续获得修复和改进,因为它们被添加到单个DRY代码中?


我的意思是......我应该共享代码,但是每次发布都要分支吗?但是,在政治上没有人希望代码都是反向集成的。

5 个答案:

答案 0 :(得分:1)

只有在更改库的界面时,您的程序才会中断。如果更改库的实现会破坏您的程序,那么您可能过于强烈地将程序绑定到库中。该计划不应该依赖于图书馆的内部工作。 如果您不断更改库的界面并破坏项目,则可能需要花费更多时间来设计库。

您还应该为库使用版本控制。针对库的特定分支/版本构建代码。如果库接口发生显着变化并且您不想更新现有项目,请为新接口创建一个新分支,新项目可以使用,而旧项目可以继续使用旧分支。可以针对一个分支编写错误修复并将其合并到另一个分支中。

Git特别擅长这类事情。使用子模块将项目链接到库的特定提交。

答案 1 :(得分:0)

  

唯一可接受的(政治)   解决方案是保留一份副本   每个库的类   可执行/ moduble /命名空间/班   使用它。

DRY是特定软件解决方案的设计原则,但在组装或域边界上并不总是有意义。域驱动设计的方法使用有界域上下文等概念来处理跨程序集和项目的可共享代码问题。

虽然您向我们提供了通用语言的问题,但是没有针对此问题的通用解决方案。


Dan G对组合做了一个很好的观点(使主根对象包含一个子对象,可以实现所需的行为而无需使用实现)。只要有意义,微软的架构指南就会提倡这种方法而不是继承。


如果我能够,我会赞成Meager和Dan G,他们都发表了很好的评论。

答案 2 :(得分:0)

我认为在每个使用此功能的类中都有一个帮助程序类实际上不会消除DRY-ness。即使您确实在任何地方重复包含/声明帮助程序,这使您无需重复任何其他功能。

您所做的任何体系结构更改(例如继承或帮助程序)都必须以某种方式影响使用它们的所有内容,如果继承没有意义,则使用某种Date对象或Helper进行组合是可能是一个很好的方式。

答案 3 :(得分:0)

看起来你的问题不是DRY-ness,而是版本控制依赖关系。所以你所拥有的是你的助手类(带有bug修复)是你的每个主要类的依赖关系。但是,只有一个主类实际引用包含该修复程序的帮助程序类的版本。其他两个类可以自由选择何时升级到改进的帮助程序类(通过依赖管理过程)。

StarWars-1.0.0.jar -> Helpers-1.0.0.jar
Empire-1.0.1.jar -> Helpers-1.0.1.jar
Jedi-1.0.0.jar -> Helpers-1.0.0.jar

帮助团队发布更新以修复内容,其他团队决定何时升级。

你仍然很干,但你管理着变化。

答案 4 :(得分:0)

这里的部分问题似乎是改变需求的正常结果。三段代码依赖于“今天”的共同商业概念。有些事情发生了变化,以至于“今天”不再有一个概念。一些代码需要继续基于银河标准日历的当前理解,并且一些代码现在需要基于本地星系统日历的更灵活的概念(谈论本地化头痛!)。

这只是一件正常事情。换句话说,由单个代码处理的单一责任是现在需要由单独的代码处理的两个职责,因为已经确定了一个新的变更向量。是时候重构了。

Piskvor提出了一种解决问题的方法,这种方法在某些情况下是好的。另一种方法,如果你使用的是IOC容器,或者至少是某种形式的DI,那就是引入一个IHelper接口(希望它从一开始就存在)。然后,您只需要新的接口实现和一些配置,以便为每个系统连接正确的实现。

wllmsaccnt也是正确的,DRY通常不适合您不想创建依赖项的系统边界。事实上,不同的系统拥有不同的产品所有者,这几乎是变革的事实向量,它代表着一个单独的代码库,即使一个人开始作为另一个代码的分支。共享代码库意味着共享所有权,如果这在政治上不现实,那么它在技术上是不合适的。