11

总的来说,我经常遇到这种情况。我的一些同事更喜欢非常简单、易于阅读的类,即使这意味着存在一些代码重复,而我会尽我所能避免代码重复,即使这意味着要构建更复杂的架构。最佳做法是什么?我只在 Java 中工作。

4

15 回答 15

15

我总是赞成没有重复代码的解决方案。即使更复杂的架构一开始更难理解,维护的好处也超过了学习曲线。

于 2009-11-19T19:30:40.643 回答
12

虽然两者都是很好的目标,但我认为可读性是拥有可维护代码库的绝对首要要求。我总是更喜欢简单、可读和可维护的,而不是完全消除代码重复。

于 2009-11-19T19:30:50.893 回答
6

最佳实践:如果代码很短,请复制两次,但不要重复。

因此,如果您在 3 个不同的地方复制/粘贴了非常相似的代码片段,请考虑重构。

请记住,重构并不意味着使代码更加复杂。考虑以下:

class IntStack
{
    public int value;
    public IntStack next;
}

class StringStack
{
    public String value;
    public StringStack next;
}

class PersonStack
{
    public Person value;
    pubilc PersonStack Next;
}

每次你想要一个新数据类型的堆栈时,你都需要编写一个新类。复制代码可以正常工作,但是假设您想添加一个新方法,也许是一个返回新堆栈的“推送”方法?好吧,现在你不得不将它添加到无数个不同的地方。或者你可以使用一个通用的对象堆栈,但是你会失去类型安全。泛型将简化架构:

class Stack<T>
{
    public T value;
    public Stack<T> next;
}

凉爽的!

好吧,这个例子怎么样:

class Logger
{
    int logtype;
    public Logger(int logtype) { ... }

    public void Log(string text)
    {
        if (logtype == FILE) { ... }
        elseif (logtype == DATABASE) { ... }
        elseif (logtype == CONSOLE) { ... }
    }

    public void Clear()
    {
        if (logtype == FILE) { ... }
        elseif (logtype == DATABASE) { ... }
        elseif (logtype == CONSOLE) { ... }
    }

    public void Truncate(int messagesToTruncate)
    {
        if (logtype == FILE) { ... }
        elseif (logtype == DATABASE) { ... }
        elseif (logtype == CONSOLE) { ... }
    }
}

好的,所以每次添加方法时,您都必须检查您使用的是哪种记录器。痛苦,并且容易出现错误。通常,您会分解出一个接口(可能使用 Log、Clear 和 Truncate 方法),然后创建三个类(FileLogger、DatabaseLogger、ConsoleLogger)。

更多类 = 更多架构。从长远来看,这是更容易还是更难维护?对于这个例子,我想说代码现在更容易维护,但是 YMMV.

于 2009-11-19T19:47:20.123 回答
4

避免代码重复的主要原因是可维护性。如果一段代码出现在多个地方,当需要更新时,您必须记住在任何地方进行更改。忘记更改一个实例可能会导致问题,您可能不会立即注意到。

于 2009-11-19T19:32:54.787 回答
4

在某些极端情况下,您可以通过复杂的元编程(对 Java 来说不是什么大问题)或过度使用反射来防止代码重复,在这少数情况下,我倾向于允许重复。这是罕见的。只要不是您的技术熟练的开发人员仍然可以理解代码,我就会去消除重复。

我遇到过这样的情况,一个团队包括一两个熟练的开发人员和一群新手,新手试图阻止使用他们一目了然的编码方法。这是必须抵制的。

于 2009-11-19T19:39:44.463 回答
3

我看不出“泛型”与您的问题有什么关系。用单独的类来表示 CollectionOfFoo 和 CollectionOfBar 是微不足道的,显然是错误的,所以这不是你要问的。

您可能必须为每个观点提供一个示例,但您仍然可能会因为主观而被关闭。

于 2009-11-19T19:31:24.227 回答
2

这是一个判断电话。大多数程序员重复代码太多,我认为这导致了热情的开发人员的态度,即消除重复是绝对好的,但事实并非如此。使您的代码易于阅读应该是优先事项,消除重复代码通常对可读性是一件好事,但并非总是如此。

此外,我不会使用具有商业价值的代码作为使用不熟悉的语言特性来学习它们的地方。为此目的创建单独的学习项目。您不希望最终在下班时间被要求去工作以修复由于过于喜欢泛型或任何其他功能而导致的错误。

于 2009-11-19T19:39:25.397 回答
2

避免代码重复总是一件好事。您需要提防的一件事是Premature Generalization

只要你得到几段看起来相似的代码,就没有必要急于求成。在您开发出一个好的模型之前,可以坐下来。

即使让一些你需要概括的事情的例子也可以,特别是当事情有点复杂和/或开放式时。当你有 3-5 个以上的例子而不是一两个例子时,概括起来会更容易。

于 2009-11-19T19:52:49.070 回答
1

不,这两种情况都不可接受。用泛型编写它,但只要它需要的复杂程度。

不要重复代码;您将不得不双重修复错误,双重添加增强功能,双重写入评论,双重写入测试。只要您在该代码库上工作,您创建的每一行代码都是您必须承担的小负担;减轻你的负担。

于 2009-11-19T19:38:19.007 回答
1

取决于许多因素:

  • 复制了多少代码?如果相同的五行出现两次,这并不是什么问题,只要有一些正当的理由。避免过度构建代码,从长远来看,它实际上可能会降低可维护性,因为下一个处理代码的人可能不会欣赏您架构中的所有微妙之处,并将其严重变形。
  • 相同的代码有多少个副本?2 还不错,但 10(十进制)不太好。
  • 为什么代码重复?我遇到了一些“重复”,一旦构建了所有需求,结果证明根本不是重复,只是有点相似。

所以我的答案可能是...

于 2009-11-19T19:48:04.710 回答
0

在给出我的答案之前,我想看一些示例代码来了解问题的真正含义。

在等待的同时,我相信如果你的代码是按照最健全的 OO 原则构建的(例如每个类只做一件事,只做一件事),就不应该有任何代码重复。当然有可能对抽象等发疯,这最终会创建大量无用的类,但我认为这不是问题所在。

于 2009-11-19T19:34:36.593 回答
0

泛型难以阅读的主要原因是没有经验的程序员不熟悉它。这意味着在选择命名和文档时必须非常小心,以使清晰透彻。

对此类核心类进行良好命名非常重要。在选择名称之前,设计师可能希望与同行彻底讨论这一点。

于 2009-11-19T20:50:00.683 回答
0

很好的问题;通用答案可能不适合您的情况。有很多因素决定你的选择

  1. 当前代码质量
  2. 您的产品/项目阶段,早期/成长/成熟
  3. 开发人员的经验和技能
  4. 您的项目/产品上市时间

这些是决定您选择的一些关键因素。以我的经验,我意识到重复的业务逻辑(中间层)代码不是好的做法,但表示层可以有重复的代码。

当我写这篇文章时,我记得文章“缓慢增长等于死亡”。编写高质量且不重复的代码可能需要时间,但这不应该成为您业务的瓶颈。

于 2009-11-20T00:05:25.097 回答
0

通常重复代码的唯一原因是克服语言的弱模板/宏/泛型系统或优化,其中有不同的低级函数名称来处理不同的类型。

在您注意到您不在其中工作的 C++ 中,模板系统允许零开销类型和函数生成,并且可以通过专门化来“自定义”为某些类型参数生成的代码。Java 没有这样的东西,它消除了 C++ 中可用的通用和特定选择避免。

在 Common Lisp 中,可以使用宏和编译器宏来生成类似于 C++ 模板的代码,其中可以针对特定类型自定义基本案例,从而产生不同的代码扩展。

在 Java 中,我反对泛型代码的唯一一次是使用数字类型的内部循环。在这些情况下,为泛型代码中的数字类型支付装箱和拆箱成本是不可接受的,除非您的分析器可以说服您编译器或 HotSpot 足够聪明,可以放弃装箱。

至于可读性,好吧,让你的代码成为你的同事必须面对的模型和挑战。提供教育那些阅读有困难的人。鼓励他们审查它的缺陷。如果他们发现任何问题,您就可以证明只需修复一个唯一版本的好处。

于 2009-11-20T23:43:03.340 回答
0

将其与关系数据库中的规范化进行比较——您可能想要一个存放某种功能或数据的地方,而不是很多地方。它在可维护性和推理代码的能力方面有很大的不同。

于 2009-11-20T23:48:55.463 回答