我目前正在审查一个非常古老的C ++项目,并在那里看到很多代码重复。
例如,有一个包含5个MFC消息处理程序的类,每个消息处理程序包含10行相同的代码。或者,每个地方都有一个5行代码段用于非常具体的字符串转换。在这些情况下,减少代码重复不是问题。
但我有一种奇怪的感觉,我可能会误解某些东西,并且最初有这种重复的原因。
复制代码的正当理由是什么?
答案 0 :(得分:26)
关于这一点的好读物是John Lakos的large scale c++ software design。
他对代码重复有许多好处,它可能有助于或阻碍项目。
最重要的一点是询问何时决定删除重复或重复的代码:
如果此方法将来发生变化,我是否要更改重复方法中的行为,还是需要保持原样?
毕竟,方法包含(业务)逻辑,有时你会想要改变每个调用者的逻辑,有时候不是。取决于具体情况。
最后,所有这些都是关于维护,而不是关于漂亮的来源。
答案 1 :(得分:16)
懒惰,这是我能想到的唯一原因。
更严肃的说明。我能想到的唯一有效理由是产品周期结束时的变化。这些往往受到更多的审查,最小的变化往往具有最高的成功率。在这种有限的情况下,通过改进代码重复更容易,而不是重构更小的更改。
仍然在我嘴里留下了不好的味道。
答案 2 :(得分:14)
当我第一次开始编程的时候,我写了一个应用程序,其中我有一些类似的功能,我用一个整齐的小20-30行函数包裹起来...我为自己编写这样一个优雅的片段感到非常自豪码。
不久之后,客户端在非常具体的情况下改变了这个过程,然后再次,然后再次,然后又一次又一次地再次......(很多次)我优雅的代码变成了一个非常困难的,hackish ,越野车,&高维护混乱。
一年后,当我被要求做一些非常相似的事情时,我故意决定忽略DRY。我把基本过程放在一起,并生成了所有重复的代码。记录了重复的代码,我保存了用于生成代码的模板。当客户要求特定的条件变更时(例如,如果x == y ^ z + b然后1 + 2 == 3.42),这是一块蛋糕。维护和维护难以置信。变化
回想起来,我可能已经用函数指针和谓词解决了许多这些问题,但是使用我当时的知识,我仍然相信这个特定情况,这是最好的决定。
答案 3 :(得分:14)
除了缺乏经验之外,还有可能出现重复代码出现的原因:
没时间正确重构
我们大多数人都在一个真实世界中工作,在这个世界里,真正的约束迫使我们快速转向真正的问题,而不是考虑代码的优点。所以我们复制并粘贴并继续前进。对我来说,如果我后来看到代码重复了几次,那就是我需要花费更多时间并将所有实例汇总到一个的标志。
由于语言限制,代码的推广不可能/不“漂亮”
让我们说,在函数内部,您有几个语句,这些语句在实例与相同重复代码的实例之间存在很大差异。例如:我有一个为视频绘制2d缩略图数组的功能,它嵌入了每个缩略图位置的计算。为了计算命中测试(从点击位置计算缩略图索引)我使用相同的代码,但没有绘画。
您不确定是否会进行泛化
首先重复代码,然后观察它将如何演变。由于我们正在编写软件,因此我们可以“尽可能晚地”修改软件,因为所有内容都是“软”且可更改的。
如果我还记得别的东西,我会添加更多。
稍后添加......
循环展开
在爱因斯坦和霍金合并之前,编译器很聪明,你必须展开循环或内联代码才能更快。循环展开将使您的代码重复,并且可能会快几个百分点,编译器无论如何都不会为您执行此操作。
答案 4 :(得分:12)
您可能希望这样做以确保一个部分中的未来更改不会无意中更改另一部分。例如考虑
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_Policy() 并且将通过更改first_policy()来实现,并且会导致更改Do_B_Policy()的副作用,这是程序员可能不知道的副作用。 所以这种“代码重复”可以作为一种安全机制来对抗这种未来的程序变化。
答案 5 :(得分:6)
有时候,域名方面的方法和类没有任何共同之处,但实现方式看起来很相似。在这些情况下,进行代码重复通常会更好,因为未来的更改通常不会将这些实现分支到不同的实现。
答案 6 :(得分:4)
我能想到的正当理由:如果代码变得更复杂以避免重复。基本上这就是你在几种方法中做几乎相同的地方 - 但不完全一样。当然 - 您可以重构并添加特殊参数,包括指向必须修改的不同成员的指针。但是新的重构方法可能会变得太复杂。
示例(伪代码):
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) - 但是根据语言可能很难(尤其是继承)。它是代码重复,因为每个属性的大部分主体都是相同的,但是很难重构公共部分。
简而言之 - 如果重构的方法是更复杂的因素,我会使用代码重复,尽管它是“邪恶的”(并且会保持邪恶)。
答案 7 :(得分:3)
我能看到的唯一“有效”的东西是当这些代码行不同时,然后通过后续编辑收敛到同一个东西。我之前遇到过这种情况,但不经常发生。
当然,这是在将这个常见的代码段分解为新功能的时候。
那就是说,我想不出任何合理的方法来证明重复的代码。看看它为什么不好。
这很糟糕,因为一个地方的变化需要多个地方的变化。这是增加的时间,有可能出现错误。通过将其分解出来,您可以将代码保存在一个工作位置。毕竟,当你编写一个程序时,你不会写两次,为什么函数会有所不同呢?
答案 8 :(得分:3)
对于那种代码重复(很多次重复的行),我会说:
可能是我通常看到的第一个解决方案: - (
我见过的最好的解决方案:让你的开发人员开始维护一些旧应用程序,当他们被雇用时 - 他们会告诉他们这种事情并不好......他们会理解为什么,这是最重要的部分。
将代码拆分为多个功能,以正确的方式重复使用代码,以及所有经常带来经验的代码 - 或者您没有聘请合适的人员; - )
答案 9 :(得分:2)
如果不同的任务偶然相似,则在两个地方重复相同的操作不一定是重复的。如果一个地方的行动发生变化,他们是否应该在其他地方改变?然后这是重复的,你应该避免或重构。
此外,有时 - 即使重复逻辑 - 减少重复的成本也太高。这可能发生,特别是当它不仅仅是代码重复时:例如,如果你有一些数据记录,某些字段在不同的地方重复(DB表定义,C ++类,基于文本的输入),通常的方法是减少这种情况重复是代码生成。这增加了解决方案的复杂性。几乎总是,这种复杂性得到了回报,但有时却没有 - 这是你做出的权衡。
答案 10 :(得分:2)
很久以前,当我以前做图形编程时,在某些特殊情况下,您会以这种方式使用重复代码来避免代码中生成的低级JMP语句(它会通过避免跳转到标签来提高性能/功能)。这是一种优化和伪“内联”的方法。
但是,在这种情况下,我不认为这就是为什么他们这样做,呵呵。
答案 11 :(得分:2)
我不知道代码重复有很多好的理由,但不是首先跳到重构,最好只重构那些实际更改的代码,而不是改变你的大代码库。还没完全明白。
答案 12 :(得分:1)
在大型项目(代码库大到GB的项目)上,很可能丢失现有的API。这通常是由于文档不足或程序员无法找到原始代码;因此重复代码。
归结为懒惰或糟糕的复习练习。
编辑:
另一种可能性是,在这些方法中可能存在其他代码,这些代码在此过程中被删除。
您是否查看过该文件的修订历史记录?
答案 13 :(得分:1)
听起来原作者既没有经验,也没有按时受到压力。大多数有经验的程序员将重复使用的东西捆绑在一起,因为以后会有更少的维护 - 一种懒惰的形式。
你唯一应该检查的是,是否有任何副作用,如果复制的代码访问某些全局数据,可能需要进行一些重构。
编辑:当编译器很糟糕的时候,优化器甚至更加糟糕可能会发生由于编译器中的一些错误,可能不得不做这样的技巧以绕过一个bug 。也许是这样的?多大了?
答案 14 :(得分:1)
所有答案看起来都是正确的,但我认为还有另一种可能性。 也许有性能考虑,因为你说的东西让我想起“内联代码”。内联函数调用它们总是更快。 也许您看到的代码已经预先处理好了?
答案 15 :(得分:1)
当源代码生成器生成重复代码时,我没有任何问题。
答案 16 :(得分:1)
我们发现强迫我们复制代码的东西是我们的像素操作代码。我们使用非常大的图像,并且函数调用开销正在耗费大约每像素时间的30%。
复制像素操作代码使代码复杂度降低了20%的图像遍历速度。
这显然是一种非常罕见的情况,最终它使我们的源显着膨胀(300线功能现在是1200线)。
答案 17 :(得分:0)
答案 18 :(得分:0)
以我的拙见,没有代码重复的地方。例如,请查看this wikipedia article
或者,让我们参考Larry Wall的引文:
“我们将鼓励你发展程序员的三大优点:懒惰,急躁和傲慢。”
很明显,代码重复与“懒惰”无关。 哈哈;)
答案 19 :(得分:0)
由于存在“策略模式”,因此重复代码没有正当理由。不必复制任何一行代码,其他一切都是史诗般的失败。