虚假代码重复


56

通常的本能是删除您在代码中看到的所有代码重复。但是,我发现自己处于一种重复的幻象之中

为了更详细地描述这种情况:我正在开发一个Web应用程序,并且大多数视图基本上是相同的-它们显示用户可以滚动并从中选择的项目列表,包含所选项目的第二个列表以及“保存” ”按钮以保存新列表。

在我看来,这个问题很容易。但是,每个视图都有其自己的怪癖-有时您需要重新计算某些内容,有时您必须存储一些其他数据,等等。这些我通过在主逻辑代码中插入回调挂钩来解决。

这么多,它是越来越少维护的,因为我需要为所有基本功能提供回调的观点之间的微小差异,主要逻辑开始看起来像回调调用的一个巨大的序列。最后,我没有节省任何时间或代码,因为每个视图都有自己执行的代码-全部在回调中。

问题是:

  • 差异是如此之细,以至于代码在所有视图中看起来几乎都是相似的
  • 这么多的差异,当你看细节,代码是不是有一点相像

我应该如何处理这种情况?
具有完全由回调调用组成的核心逻辑是否是一个好的解决方案?
还是我应该复制代码并降低基于回调的代码的复杂性?


38
通常,我认为让复制最初开始很有帮助。一旦有了一些示例,就可以轻松了解常见和不常见的情况,并找到共享这些共同部分的方法。
温斯顿·埃韦特

7
很好的问题-要考虑的一件事不仅是代码的物理重复,还包括语义重复。如果一个代码的更改必然意味着相同的更改将在其他代码之间重复,则该部分可能是重构或抽象的候选对象。有时您可以归一化到您实际陷入困境的地步,因此我还将考虑将重复视为语义上不同的实际含义-尝试重复数据删除的后果可能会抵消这些实际含义。
Ant P

请记住,您只能重用执行相同操作的代码。如果您的应用在不同的屏幕上执行不同的操作,则将需要不同的回调。关于它,如果不是,不是,而是。
corsiKa

13
我个人的经验法则是:如果我在一个地方更改了代码,如果我没有在其他地方进行完全相同的更改,那是否是错误?如果是这样,那是一种糟糕的重复。如果不确定,请选择目前可读性更高的一种。在您的示例中,行为上的差异是故意的,而不是错误,因此可以进行一些重复。
Ixrec

您可能有兴趣阅读有关面向方面的编程的信息。
本杰克逊

Answers:


53

最终,您必须对是否组合相似代码以消除重复进行判断

似乎有一种不幸的趋势,将“不要重复自己”这样的原则作为必须始终死记硬背的规则。实际上,这些不是通用规则,而是可以帮助您考虑和开发良好设计的准则。

作为生活中的一切,您必须考虑收益与成本。多少重复的代码将被删除?该代码重复多少次?编写更通用的设计将花费多少精力?您将来可能会开发多少代码?等等。

在不知道您的特定代码的情况下,尚不清楚。也许有一种更优雅的方法来删除重复项(例如LindaJeanne建议的方法)。或者,也许根本就没有足够的真实重复来保证抽象。

对设计的关注不足是一个陷阱,但也要提防过度设计。


我认为,您对“不幸的趋势”的评论和盲目遵循准则的评论是当场的。
Mael 2015年

1
@Mael您是说如果将来不再维护此代码,就没有充分的理由来获得正确的设计吗?(没有犯罪,只是想知道你怎么想的事情)
斑点

2
@Mael当然,我们可以认为这只是一个不幸的转折!:D但是,我认为编写代码时,我们应该像对待其他人一样严格对待自己(编写代码两周后阅读自己的代码时,我认为自己就是另一个人)。
斑点

2
@ user61852,那么您将非常不喜欢The Codeless Code
RubberDuck

1
@ user61852,哈哈-但如果它完全取决于(信息不是在给定的问题)?很少有事比确定性没有太大帮助。

43

请记住,DRY与知识有关。这两段代码看起来是相似,相同还是完全不同并不重要,重要的是,在这两个代码中都可以找到有关您系统的相同知识

一条知识可能是事实(“与目标值的最大允许偏差为0.1%”),或者可能是您过程的某些方面(“此队列永远不会包含三个以上的项目”)。实际上,它是源代码中编码的任何一条信息。

因此,当您确定是否应删除重复项时,请询问其是否是知识重复项。如果不是这样,则可能是偶然的重复,将其提取到某个常见位置会在以后要创建类似组件(其中明显重复的部分不同)时引起问题。


12
这个!DRY的重点是避免重复更改
Matthieu M.

这很有帮助。

1
我认为DRY的重点是确保有一些代码没有两位应该表现相同,但没有。问题不是重复工作,因为必须两次应用代码更改,真正的问题是何时需要两次应用代码更改,但不是。
gnasher729

3
@ gnasher729是的,这就是重点。如果两段代码具有重复的知识,那么您希望当其中之一需要更改时,另一段代码也需要更改,从而导致您所描述的问题。如果它们有偶然的重复,那么当一个需要更改时,另一个可能很需要保持不变。在这种情况下,如果您提取了一个通用方法(或其他方法),那么您现在将
面临

1
同样是必不可少的重复意外重复,请参见Ruby中的意外Doppelgänger我将代码干燥的内容,现在很难使用。发生了什么?。偶然的重复也出现在上下文边界的两侧。简介:仅合并重复项(如果对它们的客户而言,可以同时修改这些依赖项)。
埃里克

27

您是否考虑过使用策略模式?您将拥有一个View类,其中包含由多个视图调用的通用代码和例程。View类的子级将包含特定于那些实例的代码。它们都将使用您为View创建的通用接口,因此差异将被封装且一致。


5
不,我没有考虑过。感谢您的建议。通过快速阅读有关策略模式的信息,似乎可以找到我想要的东西。我一定会进一步调查。
Mael 2015年

3
模板方法模式。您也可以考虑
Shakil 2015年

5

变革的潜力是什么?例如,我们的应用程序有8个不同的业务领域,每个领域有4个或更多用户类型的潜力。根据用户类型和区域自定义视图。

最初,这是使用相同的视图完成的,并在此进行了一些检查,以确定是否应显示不同的内容。随着时间的流逝,某些业务领域已决定做完全不同的事情。最后,我们基本上将每个业务区域的每个功能迁移到一个视图(在ASP.NET MVC的情况下为局部视图)。并非所有业务领域都具有相同的功能,但是如果一个人想要另一个拥有的功能,则该领域将拥有自己的视图。对于代码的理解以及可测试性而言,它不再那么麻烦。例如,对一个区域进行更改不会导致对另一个区域进行不必要的更改。

如@ dan1111所述,它可以归结为判断。随着时间的流逝,您可能会发现它是否有效。


2

一个问题可能是您只为单个功能级别提供了一个接口(理论接口,而不是语言功能):

A(a,b,c) //a,b,c are your callbacks or other dependencies

而不是取决于需要多少控制权的多个级别:

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

据我了解,您仅公开高级界面(A),隐藏了实现细节(此处还有其他内容)。

隐藏实现细节有好处,而您刚刚发现了一个缺点-控制是有限的,除非您为直接使用低级接口可能会为每件事显式添加的功能。

因此,您有两个选择。您要么只使用低级接口,要么使用低级接口,因为高级接口的维护工作量太大,或者公开高低级接口。唯一明智的选择是提供高层和低层接口(以及两者之间的所有内容),假设您要避免使用冗余代码。

然后,当您编写另一本书时,请查看到目前为止已编写的所有可用功能(无数种可能性,取决于您决定哪些可以重复使用)并将它们组合在一起。

在几乎不需要控制的地方使用单个对象。

当需要进行一些怪异操作时,请使用最低级别的功能。

它也不是很黑白。也许您的高水平课程可以合理地涵盖所有可能的用例。也许用例变化如此之大,以至于最低级别的原始功能就足够了。由您自己来找到余额。


1

已经有其他有用的答案。我会加我的。

复制不好,因为

  1. 它使代码混乱
  2. 它会使我们对代码的理解混乱,但最重要的是
  3. 因为如果你改变的东西在这里,你也必须改变的东西存在,你可以忘掉/引入错误/ ... ...,这是很难永远不会忘记。

因此,重点是:您并不是为了消除重复或因为有人说重复很重要而消除了重复。之所以这样做,是因为您想减少错误/问题。以您的情况看来,如果您在视图中更改了某些内容,则可能不需要在所有其他视图中更改完全相同的行。因此,您具有明显的重复,而不是实际的重复。

另一个重要的观点是,永远不要像Joel所说的那样,仅从原则上重写某些目前仅在原则上起作用的东西(您可能已经听说过他。。。)。因此,如果您的意见有效,请逐步进行改进,不要沦为“任何软件公司都可能犯的最严重的战略错误”。

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.