总是避免Java中的in-out参数?

时间:2012-03-12 19:43:24

标签: java refactoring parameter-passing

毫无疑问,输入输出参数会导致代码混乱,因为它们可能会增加意外/不可预测的副作用。

所以,许多优秀的程序员都说:

  

避免使用in-out参数来更改可变方法参数。希望保持参数不变。

对于一个完美主义程序员,他希望自己的代码最干净,最容易理解,这种“规则”是否必须适用于所有情况?

例如,假设一种将元素添加到简单列表的基本方法,有两种方法:

第一种方式(使用输入输出参数):

private void addElementsToExistingList(List<String> myList){
  myList.add("Foo");
  myList.add("Bar");
}

和来电者:

List<String> myList = new ArrayList<String>();
//.......Several Instructions (or not) .....
addElementsToExistingList(myList);

没有输出参数的第二种方式:

private List<String> addElementsToExistingList(List<String> originalList){
      List<String> filledList = new ArrayList<String>(originalList); //add existing elements
      filledList.add("Foo");
      filledList.add("Bar");
      return filledList; 
    }

和来电者:

List<String> myList = new ArrayList<String>();
//.......Several Instructions (or not) .....
myList.addAll(addElementsToExistingList(myList));

第二种方式的优点:

未修改参数=&gt;没有新代码阅读器出现意外副作用的风险。

第二种方式:

非常详细且非常不易读......

当然,你会告诉我,对于像这个简单的代码,第一种方法真的更方便。

但是,如果我们不考虑任何概念/代码的难度,那么对于任何读者(无论是否为初学者),我都认为第二种方式更具逻辑性和显而易见性。

但是,它违反了CQS原则,认为“命令”方法具有带有潜在的void返回(但是因为它是惯例而允许)副作用和具有返回类型且没有副作用的“查询”方法。

那么,激励程序员应该采用什么?两个混合代码案例?或者保持“法律”期望始终避免进出参数......

(当然,添加Element的方法被命名用于表示示例,并且在实际代码中将是一个错误的名称选择。)

5 个答案:

答案 0 :(得分:5)

我认为法律应该是:

  

使用更简单的方法,但始终始终广泛记录方法的行为。

你的第二个例子是一个非常好的案例,如果没有文档,你会有一个保证的错误:方法的名称是addElementsToExistingList,但方法不会向现有列表添加元素 - 它创建一个新的。一个反直觉和误导性的名字,至少可以说......

答案 1 :(得分:3)

还有第三种方式。将List<String>包装到知道如何向自身添加元素的类中:

class ElementList {
   private List<String> = new ArrayList<String>();

   public void addElements(Element... elements);
}

我喜欢这种方法,因为它使List实现保密。如果有人将不可变列表传递给您的方法或者是否修改了参数,则不必担心。代码更简单。像addElementsToExistingList这样的长方法名称是代码气味,一个对象试图做另一个对象应该做的事情。

答案 2 :(得分:2)

在变异作为参数的对象时,您应始终记录文档,否则会对调用者产生意外的副作用。在第一种情况下,我同意其他评论说方法名称是足够的文档。

在第二个示例中,myList中已存在的元素似乎添加了两次。实际上你可以完全删除addElementsToExistingList方法的参数并将其重写为:

private List<String> getElements() {
   List<String> filledList = new ArrayList<String>();
   filledList.add("Foo");
   filledList.add("Bar");
   return filledList; 
}

List<String> myList = new ArrayList<String>();
//.......Several Instructions (or not) .....
myList.addAll(getElements());

请注意,此代码与第二个示例不同,因为元素只添加了一次,但我认为这实际上就是您的意图。这是我通常喜欢的风格。这个代码比第一个例子更容易理解和更灵活,而无需添加额外的代码(它可能会略微降低性能,但这通常不是一个问题)。除了将元素列表添加到现有集合之外,getElements()的客户端现在还可以使用元素列表执行其他操作。

答案 3 :(得分:1)

只要记录参数,就可以更改/改变参数。当然,方法名称为“addElementsToExistingList”,还有什么人应该期待的?但是,正如之前有人指出的那样,您的第二个实现会返回一个副本而不会修改原始副本,因此方法名称现在具有误导性。你的第一种方式是一种完全可以接受的做事方式。唯一的其他改进是可能在返回时添加一个true / false值,如果只将所有元素添加到列表中,则表示为true。

答案 4 :(得分:0)

在你的例子的情况下,名称清楚 - “addElementsToExistingList”对我来说似乎很明显暗示你要......呃..你知道。但是你的担忧可以用一个不太明显的名字来证明。

例如,在ruby中,这通常使用命名约定来处理

"a".upcase =&gt;给你变量的大写,保持原始不变 "a".upcase! =&gt;改变原始变量