仅使用功能集中通用代码是否是一种好习惯?


20

我经常遇到这个问题。例如,我目前正在编写一个读取函数和一个写入函数,它们都检查是否buf为NULL指针以及mode变量是否在一定范围内。

这是代码重复。可以通过将其移入其自身的功能来解决。但是我应该吗?这将是一个非常贫乏的功能(不会做很多事情),而是局部化的(所以不是通用的),并且不能很好地独立运行(除非您知道它的位置,否则无法确定您的需求)用过的)。另一个选择是使用宏,但我想在这篇文章中谈谈功能。

那么,您应该使用类似这样的功能吗?优缺点都有什么?


2
单行的东西不仅仅需要在多个地方使用,还需要转移到一个单独的功能中。

1
好问题。我已经想过很多次了。
rdasxy 2011年

Answers:


31

这是对函数的极大利用。

这将是一个非常贫乏的功能(不会做太多)...

非常好。函数只能做一件事。

相当本地化...

使用OO语言,将其设为私有。

(因此不是通用)

如果处理多个案件, 就是通用。而且,泛化不是函数的唯一用途。实际上,它们的存在是为了(1)避免您多次编写同一代码,而且(2)将代码分解成较小的块以使其更具可读性。在这种情况下,它既实现了(1)又实现了(2)。但是,即使仅从一个位置调用了函数,它也可能对(2)有所帮助。

并且不能很好地独立运行(除非您看到它的使用位置,否则无法弄清楚它的用途)。

为此起一个好名字,它本身就很好。“ ValidateFileParameters”之类的。现在靠自己很好。


6
好货要点。我要补充一点(3),当您可以在某个位置进行修复时,可以防止您在整个代码库中寻找重复的错误。代码重复会很快导致维护麻烦。
deadalnix

2
@deadalnix:好点。在撰写本文时,我意识到我的观点是过分简化。编写和简化调试无疑是将事物分解为功能的好处(对单独的功能进行单元测试的能力也是如此)。
克拉莫伊(Kramii)恢复莫妮卡(Monica)2011

11

因此,这完全应该是一个功能。

if (isBufferValid(buffer)) {
    // ...
}

更具可读性和可维护性(如果检查逻辑发生更改,则只需在一个地方进行更改)。

此外,这类事情很容易内联,因此您甚至不必担心函数调用开销。

让我问你一个更好的问题。怎么做不是一个好习惯?

做正确的事。:)


4
如果isBufferValid很简单,return buffer != null;那么我认为您正在那里损害可读性。
pdr

5
@pdr:在那种简单的情况下,只有当您有控制狂的想法时,它才会损害可读性,而且真的非常非常想知道代码如何检查缓冲区是否有效。因此,在那些简单的情况下,它是主观的。
Spoike

4
@pdr我不知道它如何以任何标准损害可读性。你从关怀免除其他开发者关于如何你正在做的事情,并专注于什么你在干什么。isBufferValid(在我的书中)绝对比更具可读性buffer != null,因为它可以更清楚地传达目的。再说一次,更不用说它在这里也可以避免重复。您还需要什么?
Yam Marcovic

5

IMO,每当代码片段使代码更具可读性时,都值得将代码片段移至其自身的功能,而不管该功能是非常简短还是仅使用一次。

当然,常识也有一些限制。例如,您不想让WriteToConsole(text)主体简单化的方法Console.WriteLine(text)。但是在可读性方面犯错误是一个好习惯。


2

通常,最好使用函数来消除代码中的重复项。

但是,它可能太过分了。这是一个判断电话。

以您的空缓冲区检查为例,我可能会说以下代码很清楚,即使在某些地方使用了相同的模式,也不应将其提取到单独的函数中。

if (buf==null) throw new BufferException("Null read buffer!");

如果将错误消息作为通用null检查函数的参数包括在内,并且还考虑了定义该函数所需的代码,则用以下代码代替它不是节省净LOC的费用:

checkForNullBuffer(buf, "Null read buffer!");

另外,调试时必须深入研究该函数以查看其功能,这意味着该函数调用对用户而言不太“透明”,因此可以认为其可读性/可维护性较低。


可以说,您的例子更多地说明了该语言缺乏合同支持,而不是真正需要重复。但是总的来说好点。
deadalnix11 2011年

1
您有点错过了我所看到的观点。具有跨过或不得不考虑每次调试的逻辑是什么要找的。如果我想知道它是如何完成的,请检查一次。每次都必须这样做简直是愚蠢的。
Yam Marcovic

如果重复出现错误消息(“空读取缓冲区”),则应绝对消除该重复。
凯文·克莱恩

好吧,这只是一个示例,但是大概在每个函数调用站点上您都会收到一条不同的消息-例如,“空读取缓冲区”和“空写入缓冲区”。除非您希望在每种情况下都使用相同的日志/错误消息,否则您将无法对其进行重复数据删除。……
mikera 2011年

-1 Preconditions.checkNotNull是一种好习惯,而不是坏习惯。无需包含字符串消息。google-collections.googlecode.com/svn/trunk/javadoc/com/google/...
ripper234

2

集中代码通常总是一个好主意。我们必须尽可能地重复使用代码。

但是,重要的是要注意如何做到这一点。例如,当您具有执行compute_prime_number()或check_if_packet_is_bad()的代码时,这很好。很有可能功能性算法本身将会发展,这将是有益的。

但是,任何重复散文的代码都没有资格立即集中。这不好。您可能会在函数内部隐藏任意代码行,只是为了隐藏代码,随着时间的流逝,当应用程序的多个部分开始使用时,它们都必须与该函数的所有被调用方的需求保持兼容。

这是您应该问的一些问题

  1. 您所创建的函数是否具有其固有的含义,或者仅仅是几行?

  2. 哪个其他上下文需要使用相同的功能?在使用此功能之前,您是否可能需要稍微概括一下API?

  3. 当您引发异常时,对应用程序(不同部分)的期望是什么?

  4. 可以看到功能发展的场景是什么?

您还应该检查是否已经有类似的东西。我已经看到很多人总是倾向于重新定义其宏MIN,MAX,而不是搜索已经存在的宏。

本质上,问题是:“这个新功能真的值得重复使用吗?还是仅仅是复制粘贴?” 如果是第一个,那就去吧。


1
好吧,如果重复的代码没有它自己的含义,则您的代码告诉您它需要重构。因为发生重复的地方可能也没有自己的意思。
deadalnix

1

应该避免代码重复。每次您期望它时,都应避免代码重复。如果您没想到,请应用3的规则:在同一段代码重复3次之前重构,在重复2次后对注释进行注释。

什么是代码重复?

  • 在代码库中多个位置重复的例程。该鸟嘌呤必须比简单的函数调用更复杂(否则,分解将无济于事)。
  • 一个非常常见的任务,甚至是一个琐碎的任务(通常是测试)。这将改善代码中的封装和语义。

考虑以下示例:

if(user.getPrileges().contains("admin")) {
    // Do something
}

变成

if(user.isAdmin()) {
    // Do something
}

您改进了封装(现在可以透明地将条件更改为管理员)和代码的语义。如果以检查用户是否为管理员的方式发现了错误,则无需扔掉整个代码库并在任何地方进行修复(有放弃该代码并在应用程序中出现安全漏洞的风险)。


1
顺便说一句示例说明了得墨meter耳定律。
2011年

1

DRY旨在简化代码操作。您刚刚谈到了该原则的一个要点:不是要减少代码中的令牌数量,而是要为语义上等效的代码创建单点修改。听起来您的支票总是具有相同的语义,因此应将其放入函数中,以防您需要对其进行修改。


0

如果发现重复,则应该找到一种集中化方法。

函数是一种好方法(也许不是最好的方法,但这取决于语言)即使您所说的函数贫乏,也不意味着它会保持这种状态。

如果您还必须检查其他东西怎么办?

您是否要查找所有必须添加额外支票或仅更改功能的位置?


...另一方面,如果您极有可能永远不会在其中添加任何内容,那该怎么办?在这种情况下,您的建议还是最佳的行动方案吗?
EpsilonVector

1
@EpsilonVector我的经验法则是,如果必须更改一次,则最好重构它。因此,在您的情况下,我将保留它,如果必须对其进行更改,它将成为一个功能。
Thanos Papathanasiou

0

如果满足以下条件,这几乎总是好的:

  • 提高可读性
  • 在有限范围内使用

在更大的范围内,您必须仔细权衡重复与依赖之间的权衡。范围限制技术的示例:隐藏在专用部分或模块中而不公开显示。

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.