修改传入参数是否是反模式?[关闭]


60

我正在用Java编程,并且我总是使转换器像这样:

public OtherObject MyObject2OtherObject(MyObject mo){
    ... Do the conversion
    return otherObject;
}

在新的工作场所中,模式是:

public void MyObject2OtherObject(MyObject mo, OtherObject oo){
    ... Do the conversion
}

对我来说,这有点臭,因为我习惯于不更改传入的参数。此传入参数更改是反模式吗?它有一些严重的缺点吗?


4
正确的答案将是针对特定语言,因为那些按值传递参数可将其有效地转换为局部变量的参数。
Blrfl 2014年

1
第二种模式有时用作重用对象的效率度量。假设这oo是一个传递给方法的对象,而不是设置为新对象的指针。是这样吗?如果这是Java的很可能是,如果它的C ++它可能不是
理查德发麻

3
是的,这看起来像过早的优化。我建议您一般使用第一种形式,但是如果遇到性能瓶颈,则可以使用第二种形式并带有注释来证明其合理性。发表评论会阻止您自己和其他人查看该代码,并使同一问题再次弹出。
敏锐的肯恩

2
当函数也返回值(例如成功/失败)时,第二种模式很有用。但是,这并没有真正的“错误”。这实际上取决于您要在哪里创建对象。
GrandmasterB

12
我不会以相同的方式命名实现这两种不同模式的函数。
Casey

Answers:


70

这不是反模式,这是一个坏习惯。

反模式和纯粹的不良做法之间的区别在这里:反模式定义

根据Bob叔叔的《清洁规范》,您展示的新工作场所风格是不道德的做法,是残留的或在OOP之前的时间。

参数最自然地解释为函数的输入。

强迫您检查功能签名的任何事情都等同于重复。这是一种认知障碍,应避免。在面向对象编程之前的日子里,有时必须要有输出参数。但是,输出参数的许多需求在OO语言中消失了


7
我不明白您链接到的定义为什么不能将其视为反模式。
塞缪尔·埃德温·沃德

7
我想这是因为“我们不通过创建新对象来节省CPU /内存,而是应该回收已有的对象并将其作为arg传递”的基本原理对于具有严重OOP背景的每个人来说显然是错误的,因此这可能永远不会构成一种模式-因此,按照定义,我们无法将其视为反模式...
vaxquis

1
如果输入参数是需要修改的大量对象的集合,该怎么办?
史蒂夫·钱伯斯

尽管我也更喜欢选项1,但如果OtherObject是接口呢?只有呼叫者(希望)知道它想要什么具体类型。
user949300

@SteveChambers我认为在这种情况下,类或方法的设计不好,应该对其进行重构以避免这种情况。
piotr.wittchen

16

引用罗伯特·C·马丁(Robert C. Martin)的著名著作“清洁代码”:

应避免输出参数

函数应包含少量参数

第二种模式违反了两个规则,尤其是“输出参数”。从这个意义上讲,它比第一种模式差。


1
我会说它违反了第一个,很多参数都意味着凌乱的代码,但是我认为两个参数完全可以。我认为有关干净代码的规则意味着如果您需要5或6个传入数据,则您确实想在单个方法中做太多事情,因此您应该重构以增加代码的可读性和OOP范围。
CsBalazsHungary 2014年

2
无论如何,我怀疑这不是严格的法律,而是强烈的建议。我知道它不适用于基本类型,如果它是项目中的一种广泛使用的模式,那么初级将获得通过int的想法,并期望它像Objects一样进行更改。
CsBalazsHungary 2014年

27
无论清理代码有多正确,如果不解释为什么要避免这些问题,我认为这不是一个有价值的答案。它们不是落在石板上的诫命,理解很重要。可以通过在书中提供推理摘要和章节参考来改进此答案
Daenyth 2014年

3
好了,地狱,如果一些人在一本书中写道它,它必须是任何可能的情况下很好的建议。无需思考。
Casey

2
@emodendroket但是老兄,那个家伙!
皮埃尔·阿洛德

15

第二个习惯用法可以更快,因为调用者可以在长循环中重用一个变量,而不是每次迭代都创建新实例。

我一般不会使用它,但是例如。在游戏编程中它占有一席之地。例如,看一下JavaMonkey的Vector3f,许多操作允许传递应该修改的实例并作为结果返回。


12
好吧,我可以同意这是否是瓶颈,但是无论我在哪里工作,瓶颈通常都是效率低下的算法,而不是克隆或创建对象。我对游戏开发知之甚少。
CsBalazsHungary 2014年

4
@CsBalazsHungary我认为,当关注对象创建时的问题往往与内存分配和垃圾收集有关,这很可能是算法复杂性之后的下一个瓶颈级别(尤其是在有限内存环境中,例如智能手机等) 。
JAB 2014年

JMonkey也是我经常看到的地方,因为它通常对性能至关重要。我从来没有听说过它叫做“ JavaMonkey”
Richard Tingle

3
@JAB:JVM的GC已针对临时对象进行了专门调整。收集大量非常短命的对象是微不足道的,在许多情况下,只需一次移动指针就可以收集整个短暂的世代。
Phoshi 2014年

2
实际上,如果必须创建一个对象,它将不会提高性能;如果必须对代码块进行严格的速度优化,则应使用基元和本机数组;因此,我认为此答案中的陈述不成立。
vaxquis 2014年

10

我认为这些不是两个等效的代码段。在第一种情况下,您必须创建otherObject。您可以在第二个中修改现有实例。两者都有它的用途。代码气味会优先于另一种。


我知道它们并不等效。您说得对,我们正在坚持这些工作,因此将有所作为。因此,您说的是它们都不是反模式的,只是用例的问题?
CsBalazsHungary 2014年

@CsBalazsHungary我会说是的。从您提供的一小段代码来看。
欣快2014年

我猜想如果您有一个传入的原始参数,它将变得更加严重,它当然不会被更新,因此就像@claasz写道:除非有必要,否则应避免使用它。
CsBalazsHungary 2014年

1
@CsBalazsHungary在原始参数的情况下,该功能甚至无法工作。因此,与更改参数相比,您遇到的问题更糟。
2014年

我想说一个初级将陷入尝试使原始类型成为输出参数的陷阱。因此,我想如果可能的话,应该首选第一个转换器。
CsBalazsHungary 2014年

7

它确实取决于语言。

在Java中,第二种形式可能是反模式,但是某些语言对待传递参数的方式有所不同。在阿达和VHDL例如,代替通通过值或参考,参数可以具有模式inoutin out

修改in参数或读取out参数是错误的,但是对参数的任何更改in out都将传递回调用方。

因此,Ada中的两种形式(这些也是合法的VHDL)是

function MyObject2OtherObject(mo : in MyObject) return OtherObject is
begin
    ... Do the conversion
    return otherObject;
end MyObject2OtherObject;

procedure MyObject2OtherObject(mo : in MyObject; oo : out OtherObject) is
begin
    ... Do the conversion
    oo := ... the conversion result;
end MyObject2OtherObject;

两者都有其用途。一个过程可以在多个Out参数中返回多个值,而一个函数只能返回一个结果。因为第二个参数的目的已在过程声明中明确说明,所以不能反对这种形式。我倾向于使用该功能以提高可读性。但是在某些情况下该过程会更好,例如,调用者已经创建了对象。


3

它确实看起来确实很臭,而且在没有看到更多上下文的情况下,无法确定地说。这样做可能有两个原因,尽管两者都有其他选择。

首先,这是实现部分转换的简洁方法,如果转换失败,则将结果保留为默认值。也就是说,您可能有以下内容:

public void ConvertFoo(Foo from, Foo to) {
    if (can't convert) {
        return;
    }
    ...
}

Foo a;
Foo b = DefaultFoo();
ConvertFoo(a, b);
// If conversion fails, b is unchanged

当然,通常这是使用异常处理的。但是,即使出于某种原因需要避免出现异常,也有更好的方法可以做到这一点-TryParse模式是一种选择。

另一个原因是,这可能纯粹出于一致性原因,例如,它是公共API的一部分,无论出于何种原因,该方法都用于所有转换函数(例如具有多个输出的其他转换函数)。

Java不能很好地处理多个输出-它不能像某些语言一样具有仅输出参数,也不能像其他语言那样具有多个返回值-但即使如此,您仍然可以使用返回对象。

一致性原因相当la脚,但可悲的是,这可能是最常见的。

  • 也许您工作场所(或代码库)的样式警察来自非Java背景并且不愿进行更改。
  • 您的代码可能是这种风格更惯用的语言的移植。
  • 您的组织可能需要在不同语言之间保持API的一致性,这是最低分母的样式(虽然很愚蠢,但即使对于Google也会发生)。
  • 或者,该样式在遥远的过去更有意义,并演变为当前形式(例如,它本来可以是TryParse模式,但是一些善意的前任在发现根本没有检查任何值之后删除了返回值)。

2

第二种模式的优势在于,它迫使调用者对所创建的对象承担所有权和责任。毫无疑问,该方法是创建对象还是从可重用池中获取对象。呼叫者知道他们对新对象的生命周期和处置负责。

这种方法的缺点是:

  1. 不能用作工厂方法,调用者必须知道所需的确切子类型OtherObject并预先构造它。
  2. 如果这些参数来自,则不能与在其构造函数中需要参数的对象一起使用MyObjectOtherObject在不知情的情况下必须是可构造的MyObject

答案基于我在c#中的经验,我希望逻辑可以转换为Java。


我认为,随着您提到的责任,它还会创建对象的“难以看清”的生命周期。在复杂的方法系统中,我希望直接修改对象,而不是开始研究我们正在传递的所有方法。
CsBalazsHungary 2014年

这就是我的想法。一种原始的依赖注入
Lyndon White

另一个好处是,OtherObject是抽象的还是接口。该函数不知道要创建哪种类型,但调用者可能会创建。
user949300 '16

2

鉴于语义的松散- myObjectToOtherObject同样可能意味着您将一些数据从第一个对象移到了第二个对象,或者将其完全重新转换,第二种方法似乎更合适。

但是,如果方法名是Convert(如果我们看“ ...进行转换”部分,应该是),那么我会说第二种方法没有任何意义。您不会将一个值转换为另一个已经存在的值IMO。

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.