人们普遍认为复制和粘贴编程是一个坏主意,但是处理这样一种情况的最佳方法是,你有两个功能或代码块,真正做需要不同只是通过几种方式使它们变得非常混乱?
如果代码基本相同,除了一些细微的变化,但是那些微小的变化并不是通过添加参数,模板方法或其他类似的东西很容易解决的问题怎么办? / p>
更一般地说,您是否遇到过这样一种情况:您承认一点点复制粘贴编码是真的有道理。
答案 0 :(得分:28)
提出有关您的职能的问题
“如果这个小要求发生变化,我是否必须更改这两个功能才能满足它?”
答案 1 :(得分:14)
当然有时可以接受。这就是人们保留片段文件的原因。但是,如果您经常剪切和粘贴代码,或者使用多行代码,那么您应该考虑将其作为子例程。为什么?因为你必须改变一些东西,这样,你只需要改变一次。
中间情况是使用宏,如果你有这样的话。
答案 2 :(得分:12)
我听过有人说会复制和粘贴一次(限制重复代码到最多两个实例),因为除非你在三个或更多地方使用代码,否则抽象不会得到回报。 ()我自己,一旦看到需要,我就试着让它成为重构的好习惯。
答案 3 :(得分:6)
是的,就像你说的那样;较小但很难因素的变化。如果情况确实如此,请不要鞭挞自己。
答案 4 :(得分:5)
是。当细分市场略有不同时,您正在使用一次性系统(系统存在的时间非常短,不需要维护)。否则,通常更好地提取共性。
如果差异在数据中,则通过提取函数并使用数据中的差异作为参数来重构(如果要将数据作为参数传递太多,请考虑将它们分组到对象或结构中)。 如果差异在函数的某个进程中,则使用命令模式或抽象模板进行重构。如果即使使用这些设计模式仍然很难重构,那么你的功能可能会试图自己处理很多责任。
例如,如果您的代码段在两个段中有所不同 - 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),那么你可能想重新考虑你的功能,因为它可能试图处理太多责任本身。
使用逻辑(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;
答案 5 :(得分:3)
在我公司的代码库中,我们有一系列大约10个大毛茸茸的SQL语句,具有高度的通用性。所有的陈述都有一个共同的核心,或者至少是一个只有一两个字的核心。然后,您可以将10个语句分组为3或4个分组,这些分组将共同的附属物添加到核心,同样可能在每个附属物中有一个或两个不同的单词。无论如何,将10个SQL语句视为维恩图中具有显着重叠的集合。
我们选择对这些语句进行编码,以避免重复。因此,有一个函数(技术上,一个Java方法)来构建语句。它需要一些参数来解释共同核心中的一两个差异。然后,它需要一个仿函数来构建附属物,当然这也是参数化的更多参数,用于较小的差异,更多的仿函数用于更多的附属物,等等。
代码很聪明,因为没有SQL重复。如果您需要修改SQL中的子句,只需在一个位置修改它,并相应地修改所有10个SQL语句。
但是人是难以阅读的代码。关于确定给定案例将要执行什么SQL的唯一方法是使用调试器并在完成汇编后打印出SQL。并且弄清楚生成子句的特定函数如何适应更大的图像是令人讨厌的。
自写这篇文章以来,我经常想知道我们是否会更好地将SQL查询剪切和粘贴10次。当然,如果我们这样做,对SQL的任何更改都可能必须在10个位置发生,但是注释可以帮助我们指向10个更新位置。
将SQL理解为一体的好处可能会超过剪切和粘贴SQL的缺点。
答案 6 :(得分:3)
正如Martin Fowler所说,
做一次,很好。
做两次,开始闻起来。
做三次,时间到refactor。
三次罢工,你重构。
Martin Fowler在重构第2章“三规则”(第58页)一节中描述了这一点。
答案 7 :(得分:2)
:)
您可以发布有问题的代码,看看它比它看起来更容易
答案 8 :(得分:2)
如果这是唯一的方法,那就去吧。通常(取决于语言),您可以使用可选参数来满足对同一函数的微小更改。
最近,我在PHP脚本中有一个add()函数和一个edit()函数。它们都做了几乎相同的事情,但edit()函数执行了UPDATE查询而不是INSERT查询。我只是做了像
这样的事情function add($title, $content, $edit = false)
{
# ...
$sql = edit ? "UPDATE ..." : "INSERT ...";
mysql_unbuffered_query($sql);
}
工作得很好 - 但还有其他时候需要复制/粘贴。不要使用一些奇怪的,复杂的路径来阻止它。
答案 9 :(得分:2)
你应该复制粘贴吗?谁在乎!重要的是为什么你要复制和粘贴。我不是试图在这里对任何人进行哲学思考,但让我们实际考虑一下:
是否出于懒惰? “Blah blah,我之前已经做过了......我只是改变了一些变量名。完成了。”
如果在复制和粘贴它之前它已经是好的代码,那就没问题了。否则,你会因为懒惰而使蹩脚的代码永久存在,这将使你的屁股在路上受伤。
是因为你不明白吗? “该死......我不明白这个功能是如何工作的,但我想知道它是否适用于我的代码......”它可能!这可能会让您节省时间,因为当您感到压力过大,截止时间是上午9点,而且您正盯着凌晨4点左右的时钟注视红色。
返回时会不会理解这段代码?即使你评论它?不是真的 - 在成千上万行代码之后,如果您在编写代码时不明白代码的作用,您将如何理解几周,几个月之后回归它?尽管有其他诱惑,尝试学习它。输入它,这将有助于将其提交到内存。您键入的每一行,询问自己该行正在做什么以及它如何有助于该功能的整体目的。即使你没有在里面学习它,你可能有机会至少在你以后再回来时认出它。
那么 - 复制和粘贴代码?如果你意识到你正在做的事情的含义,那很好。除此以外?不要这样做。此外,请确保您拥有复制和粘贴的任何第三方代码的许可证副本。似乎是常识,但你会惊讶于有多少人没有。
答案 10 :(得分:1)
我避免像瘟疫一样剪切和粘贴。它比表兄克隆和修改更糟糕。如果遇到像你这样的情况,我总是准备使用宏处理器或其他脚本来生成不同的变体。根据我的经验,单点事实非常重要。
不幸的是,由于对换行符,表达式,语句和参数的引用要求很烦人,C宏处理器并不是很好。我讨厌写作
#define RETPOS(E) do { if ((E) > 0) then return; } while(0)
但引用是必要的。我经常使用C预处理器,尽管它有缺陷,因为它不会在工具链中添加另一个项目,因此不需要更改构建过程或Makefile。
答案 11 :(得分:0)
我很高兴这个被标记为主观,因为它肯定是!这是一个过于模糊的例子,但我想如果你有足够的代码重复,你可以抽出这些部分并保持不同的部分不同。不是复制粘贴的重点是你不会最终得到难以维护和脆弱的代码。
答案 12 :(得分:0)
最好的方法(除了转换为常用函数或使用宏)是将注释放入。如果你评论代码的复制位置,共同点是什么,差异,以及执行它的原因......那你就没事了。
答案 13 :(得分:0)
如果您发现您的功能大致相同,但在不同的情况下需要稍微调整,那么您的设计就是问题所在。使用多态和组合而不是标记或复制粘贴。