此反模式的名称?字段作为局部变量[关闭]


68

在我正在查看的某些代码中,我看到的东西在道德上等同于以下内容:

public class Foo
{
    private Bar bar;

    public MethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public MethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

从逻辑上讲,该字段bar是局部变量,因为其值永远不会在方法调用之间持久化。但是,由于许多方法都需要类型的对象,因此原始代码作者刚刚创建了一个type字段。FooBarBar

  1. 这显然是不好的,对吧?
  2. 此反模式有名称吗?

60
好吧,那是新的。希望此类不会在多线程上下文中使用,否则您将会遇到有趣的时代!
TMN

8
这真的不常见吗?我在职业生涯中已经看过很多次了。
JSBձոգչ2012年

32
@TMN不是线程安全的,但是更糟糕的是,它甚至都不是可重入的。如果有任何代码以任何方式引起MethodAMethodB导致MethodA或被MethodB调用(并且barbar.{A,B}()被冒犯的情况下再次使用),即使没有并发,您也会遇到类似的问题。

73
我们是否必须为某人可以做的每件事做一个名字?只是称它为坏主意。
卡勒布(Caleb)2012年

74
很难不说这是悬而未决的私人反模式。
psr

Answers:


86

这显然是不好的,对吧?

是的

  • 它使方法不可重入,如果在同一个实例上递归或在多线程上下文中调用它们,则将是一个问题。

  • 这意味着一个呼叫的状态会泄漏到另一个呼叫(如果您忘记重新初始化)。

  • 这使代码难以理解,因为您必须检查以上内容以确保代码实际上在做什么。(与使用局部变量相反。)

  • 它使每个Foo实例都超出其所需。(并想象对N个变量执行此操作...)

此反模式有名称吗?

IMO,这不应该被称为反模式。这只是一种不良的编码习惯/对Java构造的误用/废话。当学生跳过课程和/或没有编程能力时,您可能会在本科生的代码中看到这种情况。如果您在生产代码中看到它,则表明您需要进行更多代码审查...


记录下来,正确的写法是:

public class Foo
{
    public methodA()
    {
        Bar bar = new Bar();  // Use a >>local<< variable!!
        bar.a();
    }

    // Or more concisely (in this case) ...
    public methodB()
    {
        new Bar().b();
    }
}

注意,我还修复了方法和变量名,以符合Java标识符可接受的样式规则。


11
我会说“如果您在生产代码中看到它,则表明您应该重新检查您的招聘过程
Beta

@Beta-也是,尽管您应该能够通过进行严格的代码审查来纠正不良的编码习惯。
斯蒂芬·C

+1了解更多代码评论。尽管人们认为如何,改善招聘实践并不能避免此类问题-招聘是胡扯,但您能做的最好的事情就是给自己装上骰子。
mattnz 2012年

@StephenC“它使方法不可重入,如果在同一个实例上递归或在多线程上下文中调用它们,则将是一个问题。” 。我知道什么是可重入性,但由于方法未使用任何锁,因此在这里看不到要点吗?你能解释一下吗?
极客2012年

@Geek-您过多地关注特定示例。我所谈论的一般情况是,该方法可能是从多个线程或递归调用的。
斯蒂芬·C

39

我称其为变量的不必要的大范围。如果多个线程访问MethodA和MethodB,则此设置还允许竞争条件。


12
具体来说,我认为“可变范围”是这里的问题的名称。这个人需要学习什么是局部变量,以及如何/何时使用它们。积极的模式或一般规则是为每个变量使用最小的可用范围,并仅根据需要扩大范围。要明确的是,此错误不仅是不良样式。编码这样的竞争条件是错误的。
GlenPeterson

30

这是称为的一般模式的特定情况global doorknobbing。盖房子时,很诱人的做法是只买一个门把手,然后把它放在身边。当有人要使用门或橱柜时,他们只要抓住那把全局门把手即可。如果门把手很昂贵,那可能是一个好的设计。

不幸的是,当您的朋友过来并且在两个不同的地方同时使用门把手时,现实就会崩溃。如果门把手价格昂贵,以至于您只能负担得起,那么建立一个安全地允许人们轮到门把手的系统是值得的。

在这种情况下,门把手只是一个参考,因此很便宜。如果门把手是一个实际的对象(并且他们正在重用该对象,而不仅仅是引用),那么作为一个可能就足够昂贵了prudent global doorknob。但是,当它便宜时,就称为cheapskate global doorknob

您的例子cheapskate多种多样。


19

此处的主要问题是并发性-如果Foo被多个线程使用,则您会遇到问题。

除此之外,这只是愚蠢的-局部变量可以很好地管理自己的生命周期。用实例变量替换它们,当它们不再有用时,需要将其无效,这是一个错误提示。


15

这是“范围设定不当”的一种特殊情况,带有“变量重用”的一面。


7

这不是反模式。反模式具有某些特性,使其看起来像是一个好主意,从而导致人们有​​目的地这样做。他们被计划为模式,然后完全错误。

这也是引发辩论的原因,这些事物是某种模式,一种反模式还是一种在某些地方仍然有用的普遍使用不当的模式。

这是错误的。

要添加更多。

该代码是迷信的,或充其量是一种不道德的做法。

迷信是没有明确道理的事情。它可能与真实的事物有关,但是这种连接是不合逻辑的。

货场崇拜做法是您尝试复制从知识渊博的来源学到的东西,但实际上是在复制表面人工制品,而不是复制过程(以巴布亚新几内亚的一名邪教徒的名字命名,飞机控制无线电从竹子里出来,希望让第二次世界大战的日本和美国飞机回来。

在这两种情况下,都没有任何实际的情况。

反模式是一种尝试进行合理改进的尝试,无论是在较小的情况下(该额外的分支来处理必须处理的额外情况,导致产生意大利面条代码),还是在较大的情况下,您非常有意地实施一个模式要么被抹黑,要么被争论(很多人会这样描述单身人士,有些人不包括只写对象(例如,记录对象或只读对象,例如配置设置对象,而有些甚至会谴责那些对象)),否则您将解决错误的问题(当.NET首次出现时,MS建议使用一种模式处理在您同时拥有非托管字段和一次性托管字段的情况下-确实确实可以很好地处理这种情况,但是真正的问题是您已经同一类中的两种类型的字段)。

这样,一个反模式就是一个精通语言,问题域和可用库的聪明人会故意做的事情,它仍然具有(或被认为具有)不利的一面。

由于我们每个人都不是很好地了解一种给定的语言,问题域和可用的库,并且由于每个人从一个合理的解决方案转到另一个解决方案时都可能会错过某些东西(例如,开始在字段中存储一些东西以作好用,然后尝试对其进行重构,但无法完成工作,您最终将得到如问题中的代码),由于我们在学习过程中有时会遗漏某些东西,因此我们在某个时候都创建了一些迷信或易受人欢迎的代码。好消息是,与反模式相比,它们实际上更容易识别和纠正。真正的反模式可以说不是反模式,或者具有一定的吸引力,或者至少有某种方法可以吸引反模式,即使被认为是坏的(太多和太少的层都毫无争议地是坏的,但是避免了一层)导致另一个)。


1
但是人们确实认为这是一个好主意,可能是因为他们认为这是代码重用的一种形式。
JSBձոգչ2012年

初学者似乎常常认为声明两个变量某种程度上需要两倍的空间,因此更喜欢重用变量。这显示了对内存模型的误解(免费存储与堆栈,重用堆栈位置的可能性)以及所有编译器都能够执行的优化。我认为初学者可能因此认为变量重用是个好主意,因此可以将其归类为反模式。
菲利普

这使其成为迷信,而不是反模式。
乔恩·汉娜

3

(1)我会说这不好。

它执行手动整理,以使堆栈可以自动处理局部变量。

由于“新”被称为每个方法调用,因此没有性能上的好处。

编辑:该类的内存占用量较大,因为条形指针始终在该类的生存期内占用内存。如果bar指针是本地的,则它将仅在方法调用的生命周期内使用内存。指针的内存只是沧海一粟,但这仍然是不必要的。

该栏对类中的其他方法可见。不使用bar的方法应该看不到它。

基于状态的错误。在这种特殊情况下,状态基错误应仅在多线程中发生,因为每次都调用“ new”。

(2)我不知道是否有名字,因为实际上是几个问题,而不是一个。
-手动时提供自动清洁服务-
不必要的状态
-寿命更长的状态- 可见
度或范围太高


2

我称它为“流浪的异种动物”。它想被单独留下,但最终却不知道它在哪里或在哪里。因此,正如其他人所说,这是一件坏事。


2

与这里的情况相反,但是尽管我不认为给定的示例像当前形式那样具有良好的编码风格,但在进行单元测试时确实存在该模式

这种相当标准的进行单元测试的方式

public class BarTester
{
    private Bar bar;

    public Setup() { bar = new Bar(); }
    public Teardown() { bar = null; }

    public TestMethodA()
    {
        bar.A();        
    }

    public TestMethodB()
    {
        bar.B();
    }
}

只是对等价于OP的代码的重构

public class BarTester
{
    private Bar bar;

    public TestMethodA()
    {
        bar = new Bar();
        bar.A();
        bar = null;
    }

    public TestMethodB()
    {
        bar = new Bar();
        bar.B();
        bar = null;
    }
}

我绝不会像OP的示例那样编写代码,它会尖叫重构,但我认为该模式既不是无效的,也不是反模式


在那种情况下,夹具将针对每个测试独立实例化,并且不会发生与变量重用或并发相关的问题。在一般情况下(外部单元测试),不清楚是否在每个对象上最多调用一种方法。
菲利普

1
@Philipp-我对重用或并发性问题没有异议,但是OP的问题是关于单元测试中常用的模式。为每个测试实例化夹具的事实是测试框架的实现细节。即使在单元测试中,该模式也仅在使用测试框架时才是安全的,因为应该使用它。在生产代码中使用此模式时,同样的推理也适用。
Lieven Keersmaekers 2012年

有没有必要设置barnull,JUnit的,无论如何,每个测试方法创建一个新的测试实例。

2

该代码气味被Beck / Fowler称为“重构中的临时字段”。

http://sourcemaking.com/refactoring/temporary-field

根据主持人的要求进行编辑以添加更多解释:您可以在上面引用的URL或本书的印刷版中阅读有关代码气味的更多信息。由于OP一直在寻找这种特殊的反图案/代码气味的名称,因此我认为引用已有10多年历史的文献中从未提及的参考文献会有所帮助,尽管我完全意识到自己迟到了。这个问题的游戏,因此答案不太可能被投票或接受。

如果不是太个人宣传,我还将在我即将举行的Pluralsight课程“重构基础”中引用并解释这种特殊的代码味道,我预计该课程将于2013年8月发布。

通过增加更多的价值,让我们讨论如何解决此特定问题。有几种选择。如果该字段确实仅由一种方法使用,则可以将其降级为局部变量。但是,如果有多种方法使用该字段进行通信(代替参数),则这是“重构”中描述的经典情况,可以通过用参数替换该字段来纠正,或者使用该代码的方法非常接近与此相关,提取类可用于将方法和字段拉出自己的类,其中该字段始终处于有效状态(可能在构造期间设置),并表示此新类的状态。如果要进行参数传递,但传递的值太多,另一种选择是引入新的参数对象,


重要的是要注意,提取类重构通常需要临时字段。但是意识到这一点的人可能不会在此中间状态下签入代码。

1

我想说的是,这确实缺乏凝聚力,我可能会给它起“虚假凝聚力”的名字。我会用这个词(或者如果我从某个地方拿到它而忘了它,“借用”)这个词,因为该类似乎具有凝聚力,因为它的方法似乎在成员字段上起作用。但是,实际上它们并非如此,这意味着表面的凝聚力实际上是错误的。

至于它是否不好,我想说的很清楚,我认为Stephen C在解释原因方面做得很好。但是,无论是否值得将其称为“反模式”,我认为它和任何其他编码范例都可以使用标签,因为这样通常会使通信更容易。


1

除了已经提到的问题外,在没有自动垃圾收集的环境(例如C ++)中使用这种方法还会导致内存泄漏,因为临时对象在使用后无法释放。(虽然这似乎有些牵强,但有些人只是将'null'更改为'NULL'并为代码编译感到高兴。)


1

在我看来,这就像是对Flyweight模式的可怕误用。不幸的是,我已经认识到一些开发人员,他们不得不以不同的方法多次使用相同的“事物”,并且只是出于误导性尝试将其拉到私有领域,以节省内存或提高性能,或者进行其他奇怪的伪优化。在他们看来,他们正在尝试解决类似的问题,因为Flyweight只能解决他们实际上不需要解决的问题。


-1

我遇到过很多次,通常是在刷新以前由VBA For Dummies当作第一个标题的人编写的代码,然后继续使用他们偶然使用的任何语言编写一个相对较大的系统时。

要说这确实是一种糟糕的编程习惯是正确的,但这可能是由于没有互联网和同行评审的资源而导致的,例如我们今天所享有的资源,这些资源可能会指导原始开发人员走“更安全”的道路。

虽然它实际上不值得使用“ antipattern”标签,但是很高兴看到关于如何重新编码OP的建议。


1
欢迎使用Stack Exchange编程器,感谢您输入第一个答案。似乎您投了反对票,这可能是因为FAQ 程序员 programs.stackexchange.com/faq中列出的原因。该常见问题解答为做出有效(并获得投票赞成)的答案提供了很好的建议。有时人们会很友善地添加关于否决票的注解,以某种方式指出错误,重复,无证据的意见,或者重复先前答案的我。我们中的大多数人都投下反对票,关闭或删除答案,所以请不要担心。这只是入门的一部分。欢迎。
DeveloperDon
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.