代码之家  ›  专栏  ›  技术社区  ›  sharptooth

代码复制的有效原因是什么?[关闭]

  •  20
  • sharptooth  · 技术社区  · 15 年前

    我现在正在复习一个非常古老的C++项目,在那里看到大量的代码复制。

    例如,有一个类有5个MFC消息处理程序,每个处理程序包含10行相同的代码。或者,对于非常具体的字符串转换,这里和那里都有一个5行代码片段。在这些情况下,减少代码重复根本不是问题。

    但我有一种奇怪的感觉,我可能误解了一些事情,而这种重复最初是有原因的。

    复制代码的有效原因是什么?

    20 回复  |  直到 6 年前
        1
  •  14
  •   John MacIntyre    15 年前

    当我第一次开始编程的时候,我写了一个应用程序,在那里我有一堆类似的功能,我用一个简洁的20-30行函数来包装…我为自己写了这么一段优雅的代码而自豪。

    不久之后,客户在非常具体的情况下更改了流程,然后一次、一次、一次、一次、一次……(很多次)我优雅的代码变成了一个非常困难,黑客,小车和高维护混乱。

    一年后,当我被要求做一些非常类似的事情时,我故意决定忽略dry。我把基本的过程放在一起,生成了所有重复的代码。重复的代码被记录下来,我保存了用于生成代码的模板。当客户要求特定的条件改变时(比如,如果x=y^z+b,那么1+2=3.42),这是小菜一碟。维护和更换非常容易。

    回想起来,我可能已经用函数指针和谓词解决了其中的许多问题,但是利用当时的知识,我仍然相信在这个特定的情况下,这是最好的决定。

        2
  •  26
  •   Sam    15 年前

    关于这个的一个好消息是 large scale c++ software design John Lakos。

    他在代码复制方面有很多优点,在那里它可能有助于或阻碍一个项目。

    最重要的一点是在决定删除重复或重复代码时询问:

    如果此方法在将来发生更改,我是要更改复制方法中的行为,还是需要保持原样?

    毕竟,方法包含(业务)逻辑,有时您需要为每个调用者更改逻辑,有时不需要。视情况而定。

    归根结底,一切都是为了维护,而不是为了漂亮的资源。

        3
  •  16
  •   JaredPar    15 年前

    懒惰,这是我唯一能想到的原因。

    更严重的是。我能想到的唯一有效原因是在产品周期的最后阶段进行更改。这些计划往往会经受更多的审查,而最小的变化往往具有最高的成功率。在这种有限的情况下,通过代码重复更改比重构较小的更改更容易。

    我的嘴里还留着不好的味道。

        4
  •  14
  •   Daniel MoÅ¡mondor    11 年前

    除了缺乏经验外,出现重复代码的原因还有:

    没有时间进行适当的重构

    我们中的大多数人都是在现实世界中工作的,在现实世界中,真实的约束迫使我们快速地转向现实问题,而不是思考代码的正确性。所以我们复制粘贴并继续。对于我来说,如果我以后看到代码被重复了几次,这就意味着我必须花更多的时间在代码上,并将所有实例聚合到一个实例上。

    由于语言约束,代码的泛化不可能/不“漂亮”

    假设在一个函数的深处,有几个语句在同一重复代码的实例之间有很大的不同。例如:我有一个为视频绘制二维缩略图数组的函数,它嵌入了每个缩略图位置的计算。为了计算命中测试(从单击位置计算缩略图索引),我使用了相同的代码,但没有绘制。

    你不确定是否会有泛化

    首先复制代码,然后观察它将如何演变。因为我们正在编写软件,所以我们可以允许对软件进行“尽可能晚”的修改,因为所有东西都是“软”的,并且是可变的。

    如果我还记得别的东西,我会再加一点。


    稍后添加…

    循环展开

    在编译器像爱因斯坦和霍金那样聪明的时候,你必须展开循环或者内联代码才能更快。循环展开将使您的代码被复制,而且速度可能快了几个百分点,但无论如何,IT编译器并没有为您做这件事。

        5
  •  12
  •   Liran Orevi    15 年前

    您可能希望这样做,以确保一部分中的未来更改不会无意中更改另一部分。例如,考虑

    Do_A_Policy()
    {
      printf("%d",1);
      printf("%d",2);
    }
    
    Do_B_Policy()
    {
      printf("%d",1);
      printf("%d",2);
    }
    

    现在,您可以使用以下函数来防止“代码重复”:

    first_policy()
    {
    printf("%d",1);
    printf("%d",2);
    }
    
    Do_A_Policy()
    {
    first_policy()
    }
    
    Do_B_Policy()
    {
    first_policy()
    }
    

    但是,还有一个风险是,其他程序员可能希望更改do-a-u策略()。 并且通过更改第一个_policy()来实现,并且会导致更改do_b_policy()的副作用,这是程序员可能不知道的副作用。 因此,这种“代码复制”可以作为一种安全机制,防止将来程序中发生这种更改。

        6
  •  6
  •   cwap    15 年前

    有时域方面的方法和类没有共同点,但实现方面的方法和类看起来非常相似。在这些情况下,随着未来的变化,代码复制通常会更好,这样就不会将这些实现分支到不相同的地方。

        7
  •  4
  •   Tobias Langner    15 年前

    我能想到的正当理由是:如果代码变得越来越复杂,以避免重复。基本上,这就是你在几个方法中做几乎相同的事情的地方——但并不完全相同。当然,然后您可以重构并添加特殊参数,包括指向必须修改的不同成员的指针。但是新的重构方法可能会变得过于复杂。

    示例(伪代码):

    procedure setPropertyStart(adress, mode, value)
    begin
      d:=getObject(adress)
      case mode do
      begin
        single: 
           d.setStart(SingleMode, value);
        delta:
           //do some calculations
           d.setStart(DeltaSingle, calculatedValue);
       ...
    end;
    
    procedure setPropertyStop(adress, mode, value)
    begin
      d:=getObject(adress)
      case mode do
      begin
        single: 
           d.setStop(SingleMode, value);
        delta:
           //do some calculations
           d.setStop(DeltaSingle, calculatedValue);
       ...
    end;
    

    您可以以某种方式重构方法调用(setxxx),但根据语言的不同,这可能很困难(尤其是继承)。这是代码重复,因为每个属性的主体大部分都是相同的,但是很难重构出公共部分。

    简而言之,如果重构的方法是更复杂的因素,那么我将使用代码复制,尽管它是“邪恶的”(并且将继续邪恶)。

        8
  •  3
  •   GManNickG    15 年前

    我能看到的唯一“有效”的事情就是当这些代码行不同时,然后通过随后的编辑收敛到相同的代码行。我以前也有过这样的经历,但没有太频繁。

    当然,现在是时候将这段代码分解成新功能了。

    也就是说,我想不出任何合理的方法来证明重复代码的合理性。看看为什么不好。

    这很糟糕,因为一个地方的变化需要多个地方的变化。这会增加时间,有可能出现错误。通过分解它,您可以将代码维护在单个工作位置。毕竟,当你写一个程序时,你不会写两次,为什么一个函数会有什么不同呢?

        9
  •  3
  •   Pascal MARTIN    15 年前

    对于这种代码复制(很多行重复很多次),我会说:

    • 要么懒惰 (您只需在这里和那里粘贴一些代码,而不必担心它可能对应用程序的其他部分产生的任何影响——我想,编写一个新函数并在两个地方使用它可能会产生一些影响)
    • 或者不知道什么好的做法 (重新使用代码,在不同的函数/方法中分离不同的任务)

    不过,从我通常看到的情况来看,可能是第一个解决方案:-(

    我见过的最好的解决方案是:让开发人员从维护一些旧的应用程序开始,当他们被雇用时——这将教会他们这种事情不好……他们会 理解 为什么,这是最重要的部分。

    将代码拆分为多个函数,以正确的方式重新使用代码,所有这些通常都是有经验的——或者您没有雇佣合适的人员;—)

        10
  •  2
  •   jmq ryanshow    15 年前

    很久以前,当我使用图形编程时,在某些特殊情况下,您将使用重复的代码,这样可以避免代码中生成的低级JMP语句(它可以通过避免跳转到标签/函数来提高性能)。这是一种优化和执行伪“内联”的方法。

    不过,在这种情况下,我不认为这就是为什么他们这么做,呵呵。

        11
  •  2
  •   orip    15 年前

    如果不同的任务是偶然相似的,那么在两个地方重复相同的操作不一定是重复的。如果一个地方的行动发生了变化,那么其他地方的行动也有可能发生变化吗?那么这就是您应该避免或重构的重复。

    而且,有时——即使逻辑是重复的——减少重复的成本也太高了。这可能发生,尤其是当它不只是代码复制:例如,如果你有一个数据的记录与特定字段重复在不同的地方自己(DB表定义,C++类,基于文本的输入),通常的方式来减少这种重复是代码生成。这会增加解决方案的复杂性。几乎总是这样,这种复杂性会有回报,但有时不会——这是你要做的权衡。

        12
  •  2
  •   Paddy    15 年前

    我不知道代码重复的很多好理由,但与其直接着手重构,不如只重构那些您实际更改的代码片段,而不是修改一个您还没有完全理解的大型代码库。

        13
  •  1
  •   AndersK    15 年前

    听起来原作者要么经验不足,要么时间紧迫。大多数经验丰富的程序员将重用的东西组合在一起,因为以后维护的次数会减少——一种懒惰的形式。

    唯一需要检查的是是否有任何副作用,如果复制的代码访问一些全局数据,可能需要进行一些重构。

    编辑: 早在编译器很糟糕,优化器甚至更糟糕的时候,由于编译器中的一些错误,人们可能不得不做这样的一个技巧来绕过一个错误。也许是那样?老年多大?

        14
  •  1
  •   Everyone    15 年前

    对于大型项目(代码库大到GB的项目),很有可能 失去 现有的API。这通常是由于文档不足,或者程序员无法定位原始代码,从而导致代码重复。

    归根结底就是懒惰,或者糟糕的复习练习。

    编辑:

    另一种可能性是,在这些方法中可能有一些额外的代码,这些代码在过程中被删除了。

    你看过文件上的修订历史了吗?

        15
  •  1
  •   Ignacio Soler Garcia    15 年前

    所有的答案看起来都是正确的,但我认为还有另一种可能性。 也许有性能方面的考虑,因为您所说的内容提醒我“内联代码”。内联调用它们的函数总是更快的。 也许你看到的代码已经预先处理过了?

        16
  •  1
  •   Stephen C    15 年前

    当复制的代码由源代码生成器生成时,我对它没有任何问题。

        17
  •  1
  •   Ron Warholic    15 年前

    我们发现迫使我们复制代码的是我们的像素操作代码。我们处理非常大的图像,函数调用开销消耗了我们每像素时间的30%。

    复制像素操作代码让我们以代码复杂性为代价加快了20%的图像遍历速度。

    这显然是一个非常罕见的情况,最终它使我们的源代码显著膨胀(300行函数现在是1200行)。

        18
  •  0
  •   Asaph    15 年前

    没有充分的理由进行代码复制。

    Refactor Mercilessly 设计模式。

    最初的程序员不是急于赶上最后期限,就是懒惰。请随意重构和改进代码。

        19
  •  0
  •   varnie    15 年前

    在我看来,没有地方可以进行代码复制。例如,看看 this wikipedia article

    或者,让我们参考拉里·沃尔的引文:

    “我们将鼓励您培养程序员的三大美德:懒惰、不耐烦和傲慢。”

    很明显,代码复制与“懒惰”没有任何关系。 哈哈;)

        20
  •  0
  •   Turing Complete    14 年前

    由于存在“策略模式”,因此没有复制代码的有效理由。没有一行代码必须被复制,其他的都是史诗般的失败。