【问题标题】:Avoid output arguments(have no side effects)避免输出参数(没有副作用)
【发布时间】: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 &gt; other.points; this.points++; other.points--; return result; }。调用者根据方法的返回类型及其名称,预计这种方法不会产生副作用,但它会改变两个玩家的状态,这是出乎意料的。
  • JavaScript 方法 Array.splice() 是您不应该做的一个很好的真实示例。它一次做两件事(删除和添加),它有一个糟糕的名字,它修改了接收器数组,但也返回了它,尽管调用者显然已经有了对它的引用,因此导致开发人员认为它返回了一个副本,尽管它修改了数组。
  • 游戏示例是糟糕的编程示例,因为游戏编程的规则集与通用编程非常不同。你最终会得到像class Sword extends Weapon 这样的例子,这与现实无关。
  • 罗伯特·马丁是绝对正确的。我讨厌当我面对参数突变时,因为它迫使我最终几乎必须阅读方法的代码。

标签: java coding-style parameter-passing side-effects


【解决方案1】:

归根结底,这些规则的存在是为了通过消除意外行为来使程序员的生活更轻松。它们帮助人类对程序进行推理。因此,关键是确保您可以查看一行代码并理解它的作用。

如果我,作为一名程序员,请看下面一行:

player1.fight(player2);

我预计player1player2 都会以某种方式发生变异。这也适用于 Arrays.sort(array);,它是语言的一部分。

但是,如果在您的 isAlive() 函数中,您执行了一些突变(可能杀死低于零生命值的玩家),在我看来,是不好的做法,因为我作为程序员,期待一个名为 isFoo()getFoo() 的函数简单地返回有关对象的数据而没有任何变化。

归根结底,当您在一年后回来查看您的代码时,这些指南会让您的生活更轻松,因此,如果您有疑问,请问问自己您期望使用哪种方法执行相同的签名(即我期望public void setAge(int age); 做什么?它是否符合我的期望?)

附言

某些语言(例如 Haskell)根本不允许任何突变。排除 IO 和随机性,函数影响程序的唯一方式是通过其返回值。如果您想了解如何进行“没有副作用的编程”,值得一试。

【讨论】:

    猜你喜欢
    • 2011-06-09
    • 1970-01-01
    • 2021-11-16
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    • 2021-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多