【发布时间】:2020-05-05 03:37:50
【问题描述】:
我正在阅读 Robert C. Martin 的“清洁代码”,但我无法完全理解第 44 到 45 页的“没有副作用”和“输出参数”部分。
在“没有副作用”部分中指出,传递给方法参数的更改被视为副作用,不应进行。
在“输出参数”部分中,声明方法的参数不应该被改变。如果必须更改某些状态,方法应该只更改其所属对象的状态。
我确实理解,如果指定此行为的方法被没有完全意识到这一点的客户调用,副作用和输出参数可能会导致令人困惑的行为和错误。此外,这在多线程环境中工作时也会出现问题。
但关于“清洁代码”一书是基于 Java 示例编写的,我感到很困惑。
让我们看一个例子。 假设我们有一个播放器类:
public class Player {
private int healthPoints;
private boolean alive = true;
public Player(int healthPoints) {
if(healthPoints < 1) {
throw new IllegalArgumentException();
}
this.healthPoints = healthPoints;
}
public boolean isAlive() {
return this.alive;
}
public void fight(Player otherPlayer) {
//do some fighting which will change this instance
// but also the otherPlayer instance
}
}
如果我们现在调用以下函数:
player1.fight(player2);
这将改变 player1 的状态以及 player2 的状态。 在大多数情况下,Robert C. Martin 不鼓励这样做。但实际上,我经常看到这种模式。大多数 Java 程序真正在突变和对象上进行了更改,超出了它们的创建范围。
如果我们创建一个改变双方玩家的战斗,情况会更糟,因为现在另一个对象在其方法中改变了两个参数:
battle.fight(player1, player2)
我在这里错过了什么吗?什么时候可以在方法中改变参数(在 Java 中)?我前段时间问了一个非常相似的问题(Is mutating object-parameters in a method(in Java) a bad practice?)。
【问题讨论】:
-
你错过了 Bob 叔叔的一个关键点:那些是指导方针,而不是硬性规定。必要时弯曲它们。毕竟,这正是你的工作:决定何时使用哪种技术。至于提出的问题:可以给
Battle-objects 两个Player-properties,然后在Battle-object 上调用.fight()。这样,Battle-object 只会改变它自己的状态,即它的Players。 -
您应该将其视为一般准则,而不是绝对规则。方法的返回类型及其名称对于不欺骗调用者非常重要。你绝对应该避免的事情是
boolean isStrongerThat(Player other) { boolean result = this.points > other.points; this.points++; other.points--; return result; }。调用者根据方法的返回类型及其名称,预计这种方法不会产生副作用,但它会改变两个玩家的状态,这是出乎意料的。 -
JavaScript 方法 Array.splice() 是您不应该做的一个很好的真实示例。它一次做两件事(删除和添加),它有一个糟糕的名字,它修改了接收器数组,但也返回了它,尽管调用者显然已经有了对它的引用,因此导致开发人员认为它返回了一个副本,尽管它修改了数组。
-
游戏示例是糟糕的编程示例,因为游戏编程的规则集与通用编程非常不同。你最终会得到像
class Sword extends Weapon这样的例子,这与现实无关。 -
罗伯特·马丁是绝对正确的。我讨厌当我面对参数突变时,因为它迫使我最终几乎必须阅读方法的代码。
标签: java coding-style parameter-passing side-effects