14

人们普遍认为,复制和粘贴编程是一个坏主意,但是如果您有两个函数或代码块,它们确实需要在几个方面有所不同,从而使概括它们变得非常混乱,那么处理这种情况的最佳方法是什么

如果代码基本相同,除了一些细微的变化,但那些细微的变化不是很容易通过添加参数、模板方法或类似的东西分解出来的东西怎么办?

更一般地说,你有没有遇到过这样的情况,你会承认一点点复制和粘贴编码是真正合理的。

4

14 回答 14

28

问这个关于你的函数的问题

“如果这个小要求发生变化,我是否必须同时更改两个功能才能满足它?”

于 2008-12-30T23:44:23.417 回答
14

当然有时也可以接受。这就是人们保留片段文件的原因。但是如果你经常剪切和粘贴代码,或者多于几行,你应该考虑把它变成一个子程序。为什么?因为你必须改变一些东西,这样,你只需要改变一次。

如果您有可用的宏,则中间情况是使用宏。

于 2008-12-30T23:44:19.410 回答
12

我听说有人说将复制和粘贴一次(将代码重复限制为最多两个实例),因为除非您在三个或更多地方使用代码,否则抽象不会得到回报。() 我自己,我试着把它养成一个好习惯,一看到需要就进行重构。

于 2008-12-31T00:59:24.500 回答
6

是的,正如你所说的那样;微小但难以考虑的变化。如果情况确实如此,请不要鞭打自己。

于 2008-12-30T23:43:12.610 回答
5

Re 是可以接受的:

是的。当细分市场略有不同并且您正在使用一次性系统(系统存在时间很短且不需要维护)时。否则,通常最好将共性提取出来。

看起来相似但不完全相同的细分:

如果差异在数据中,则通过提取函数并将数据中的差异用作参数进行重构(如果要传递的数据太多作为参数,请考虑将它们分组为对象或结构)。如果不同之处在于函数中的某个过程,则使用命令模式或抽象模板进行重构。如果即使使用这些设计模式仍然难以重构,那么您的函数可能会尝试自己处理许多职责。

例如,如果您的代码段在两个段中不同 - diff#1 和 diff#2。在 diff#1 中,你可以有 diff1A 或 diff1B,而对于 diff#2,你可以有 diff2A 和 diff2B。

如果 diff1A 和 diff2A 总是在一起,而 diff1B 和 diff2B 总是在一起,那么 diff1A 和 diff2A 可以包含在一个命令类或一个抽象模板实现中,而 diff1B 和 diff2B 则包含在另一个中。

但是,如果有几种组合(即 diff1A & diff2A、diff1A & diff2B、diff1B & diff2A、diff1B & diff2B),那么您可能需要重新考虑您的功能,因为它可能试图自己处理太多的责任。

重新 SQL 语句:

使用逻辑(if-else、loops)动态构建 SQL 会牺牲可读性。但是创建所有 SQL 变体将很难维护。所以半途而废,使用 SQL 段。将共性提取为 SQL 段,并使用这些 SQL 段作为常量创建所有 SQL 变体。

例如:

private static final String EMPLOYEE_COLUMNS = " id, fName, lName, status";

private static final String EMPLOYEE_TABLE = " employee";

private static final String EMPLOYEE_HAS_ACTIVE_STATUS = " employee";

private static final String GET_EMPLOYEE_BY_STATUS =
  " select" + EMPLOYEE_COLUMNS + " from" + EMPLOYEE_TABLE + " where" + EMPLOYEE_HAS_ACTIVE_STATUS;

private static final String GET_EMPLOYEE_BY_SOMETHING_ELSE =
  " select" + EMPLOYEE_COLUMNS + " from" + EMPLOYEE_TABLE + " where" + SOMETHING_ELSE;
于 2008-12-31T00:55:59.937 回答
4

正如 Martin Fowler 所建议的,

做一次就好了

做两次,开始闻起来。

做三次,是时候重构了。


编辑:在回答评论时,建议的来源是唐罗伯茨:

三击,你重构

Martin Fowler 在重构第 2 章的“三法则”一节(第 58 页)中描述了这一点。

于 2008-12-31T02:52:52.227 回答
3

在我公司的代码库中,我们有一系列大约 10 条左右的大毛 SQL 语句,具有高度的通用性。所有的陈述都有一个共同的核心,或者至少有一个核心只有一两个词的区别。然后,您可以将 10 条语句分成 3 或 4 组,将共同的附属词添加到核心,同样每个附属词中可能有一两个不同的词。无论如何,将这 10 个 SQL 语句视为维恩图中的集合,并且有很大的重叠。

我们选择以避免任何重复的方式对这些语句进行编码。因此,有一个函数(技术上是 Java 方法)来构建语句。它需要一些参数来解释共同核心中的一两个词的差异。然后,它需要一个仿函数来构建附属物,当然,它也被参数化,用更多的参数来表示细微的差异,用更多的仿函数来表示更多的附属物,等等。

该代码的巧妙之处在于没有一条 SQL 是重复的。如果您需要修改 SQL 中的某个子句,只需在一处修改它,所有 10 个 SQL 语句都会相应地修改。

但是人是难以阅读的代码。确定在给定情况下将执行什么 SQL 的唯一方法是使用调试器单步执行并在 SQL 完全组装后打印出它。并且弄清楚生成子句的特定函数如何适应更大的图景是令人讨厌的。

自从写了这篇文章后,我经常想知道我们是否会更好地剪切和粘贴 SQL 查询 10 次。当然,如果我们这样做,对 SQL 的任何更改可能必须在 10 个地方发生,但注释可以帮助我们指出要更新的 10 个地方。

让 SQL 易于理解并且集中在一个地方的好处可能会超过剪切和粘贴 SQL 的缺点。

于 2008-12-31T00:03:36.017 回答
2

绝对没有..

:)

您可以发布有问题的代码,看看它比看起来更容易

于 2008-12-30T23:44:16.163 回答
2

如果这是唯一的方法,那就去做吧。通常(取决于语言),您可以使用可选参数满足对同一函数的微小更改。

最近,我在 PHP 脚本中有一个 add() 函数和一个 edit() 函数。它们几乎都做了同样的事情,但 edit() 函数执行的是 UPDATE 查询而不是 INSERT 查询。我只是做了类似的事情

function add($title, $content, $edit = false)
{
    # ...
    $sql = edit ? "UPDATE ..." : "INSERT ...";
    mysql_unbuffered_query($sql);
}

效果很好——但在其他时候需要复制/粘贴。不要使用一些奇怪的、复杂的路径来防止它。

于 2008-12-30T23:53:16.880 回答
2
  1. 好的代码是可重用的代码。
  2. 不要重新发明轮子。
  3. 示例的存在是有原因的:帮助您学习,最好是更好地编码。

你应该复制和粘贴吗?谁在乎!重要的是为什么要复制和粘贴。我不想在这里对任何人进行哲学化,但让我们实际考虑一下:

是因为懒惰吗?“胡说八道,我以前做过这个……我只是更改了几个变量名……完成了。”

如果在您复制和粘贴之前它已经是很好的代码,那不是问题。否则,你会因为懒惰而延续糟糕的代码,这会在路上咬你的屁股。

是因为不懂吗?“该死......我不明白那个函数是如何工作的,但我想知道它是否可以在我的代码中工作......”它可能!这可能会立即节省您的时间,因为您有压力,因为您在上午 9 点有截止日期,并且您正盯着凌晨 4 点左右的时钟而红着眼睛

当你回到它时,你会理解这段代码吗?即使你评论它?不是真的 - 在数千行代码之后,如果您在编写代码时不理解代码在做什么,那么您将如何理解几周、几个月后的代码?尝试学习它,尽管有其他的诱惑。输入它,这将有助于将其提交到记忆中。你输入的每一行,问问自己那行在做什么,以及它如何促成该功能的整体目的。即使你没有从内而外地学习它,你至少有机会在以后再学习它时认出它。

那么 - 复制和粘贴代码?如果你意识到你正在做的事情的影响,那很好。除此以外?不要这样做。此外,请确保您拥有复制和粘贴的任何第 3 方代码的许可证副本。似乎是常识,但你会惊讶有多少人不这样做。

于 2009-01-02T12:07:09.413 回答
1

我避免像瘟疫一样剪切和粘贴。它甚至比它的堂兄克隆和修改还要糟糕。如果遇到像您这样的情况,我随时准备使用宏处理器或其他脚本来生成不同的变体。根据我的经验,一个单一的事实是非常重要的。

不幸的是,由于换行符、表达式、语句和参数的令人讨厌的引用要求,C 宏处理器不能很好地用于此目的。我讨厌写作

#define RETPOS(E) do { if ((E) > 0) then return; } while(0)

但引用是必要的。尽管有缺陷,我还是经常使用 C 预处理器,因为它不会向工具链添加其他项,因此不需要更改构建过程或 Makefile。

于 2008-12-30T23:49:14.390 回答
0

我很高兴这个被标记为主观的,因为它确实是!这是一个过于模糊的示例,但我想如果您有足够的重复代码,您可以将这些部分抽象出来并保持不同的部分不同。不复制粘贴的重点是,您最终不会拥有难以维护和脆弱的代码。

于 2008-12-30T23:46:03.307 回答
0

最好的方法(除了转换成通用函数或使用宏)是放注释。如果你注释代码从哪里复制到哪里,共性是什么,差异,以及这样做的原因......那么你会没事的。

于 2008-12-30T23:56:55.733 回答
0

如果您发现您的功能大部分相同,但在不同的场景中需要稍作调整,那么问题出在您的设计上。使用多态性和组合而不是标志或复制粘贴。

于 2008-12-30T23:57:59.477 回答