人们普遍认为复制和粘贴编程是一个坏主意,但是处理这样一种情况的最佳方法是,你有两个功能或代码块,真正需要不同只是通过几种方式使它们变得非常混乱?

如果代码基本相同,除了一些微小的变化,但是那些微小的变化并不是通过添加参数,模板方法或其他类似的东西很容易解决的问题怎么办? / p>

更一般地说,您是否遇到过这样一种情况,即您承认一点点复制粘贴编码是真的有道理。

有帮助吗?

解决方案

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

其他提示

提出有关您的职能的问题

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

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

中间情况是使用宏,如果你有这样的话。

是的,就像你说的那样;较小但很难因素的变化。如果情况确实如此,请不要鞭挞自己。

Re是切入和过去可以接受的:

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

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),那么你可能想重新考虑你的功能,因为它可能试图处理太多责任本身。

Re SQL语句:

使用逻辑(if-else,循环)动态构建SQL会牺牲可读性。但是创建所有SQL变体将很难维护。所以遇到中途并使用SQL Segments。 将共性作为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;

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

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

代码很聪明,因为没有SQL重复。如果您需要修改SQL中的子句,只需在一个位置修改它,并相应地修改所有10个SQL语句。

但是人是难以阅读的代码。关于确定给定案例将要执行什么SQL的唯一方法是使用调试器并在完成汇编后打印出SQL。并且弄清楚生成子句的特定函数如何适应更大的图像是令人讨厌的。

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

将SQL理解为一体的好处可能会超过剪切和粘贴SQL的缺点。

正如Martin Fowler所说,

做一次,很好。

做两次,开始闻起来。

做三次,是时候重构


编辑:在回答评论时,建议的来源是Don Roberts:

三次罢工,你重构

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

绝对NEEEVER ..

:)

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

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

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

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

工作得很好 - 但还有其他时候需要复制/粘贴。不要使用一些奇怪的,复杂的路径来阻止它。

  1. 好的代码是可重用的代码。
  2. 不要重新发明轮子。
  3. 存在的例子有一个原因:帮助你学习,理想的代码更好。
  4. 你应该复制粘贴吗?谁在乎!重要的是为什么你要复制和粘贴。我不是试图在这里对任何人进行哲学思考,但让我们实际考虑一下:

    是否出于懒惰? “Blah blah,我之前已经做过了......我只是改变了一些变量名。完成了。”

    如果在复制和粘贴它之前它已经是好的代码,那就没问题了。否则,你会因为懒惰而使蹩脚的代码永久存在,这将使你的屁股在路上受伤。

    是因为你不明白吗? “该死......我不明白这个功能是如何工作的,但我想知道它是否适用于我的代码......”它可能!这可能会让您节省时间,因为当您感到压力过大,截止时间是上午9点,而且您正盯着凌晨4点左右的时钟注视红色。

    返回时会不会理解这段代码?即使你评论它?不是真的 - 在成千上万行代码之后,如果您在编写代码时不明白代码的作用,您将如何理解几周,几个月之后回归它?尽管有其他诱惑,尝试学习它。输入它,这将有助于将其提交到内存。您键入的每一行,询问自己该行正在做什么以及它如何有助于该功能的整体目的。即使你没有在里面学习它,你可能有机会至少在你以后再回来时认出它。

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

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

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

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

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

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

最好的方法(除了转换为常用函数或使用宏)是将注释放入。如果你评论代码的复制位置,共同点是什么,差异,以及执行它的原因......那你就没事了。

如果您发现您的功能大致相同,但在不同的情况下需要稍微调整,那么您的设计就是问题所在。使用多态和组合而不是标记或复制粘贴。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top