【问题标题】:How to avoid side-effects (Javascript) [closed]如何避免副作用(Javascript)[关闭]
【发布时间】:2021-07-30 16:43:01
【问题描述】:

我目前正在构建一个二十一点游戏,我的一个课程叫做“Deck”。这个套牌需要做两件事:

  1. 返回一组其他对象可以使用的卡片(这相当于分发这些卡片)
  2. 从牌堆中取出这些牌(一旦发牌,就应该将其取出)。

目前,我通过创建两个方法实现了这两个功能:selectCards()removeIndexFromDeck。我首先调用selectCards(),它返回选定的卡片,但也调用removeIndexFromDeck()

这种方法是否违反最佳做法?看起来我的selectCards() 函数既有返回值又有副作用。

如果它确实违反了最佳做法,我将如何更改这些方法,但要确保我仍然能够归还所选卡片并将它们从牌组中移除。

谢谢!

class Deck {
  constructor() {
    this.deck = [];
    ['♦', '♣', '♥', '♠'].forEach(suit => {
      ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'].forEach(value => {
        this.deck.push(`${value}${suit}`);
      });
    });
  }

  selectCards(numCards) {
    let selectedCards = [];

    while (numCards > 0) {
      let randIndex = Math.floor(Math.random() * this.deck.length);
      selectedCards.push(this.deck[randIndex]);
      this.removeIndexFromDeck(randIndex);
      numCards -= 1;
    }

    return selectedCards;
  }

  removeIndexFromDeck(index) {
    this.deck.splice(index, 1);
  }
}

【问题讨论】:

  • "这种方法是否违反最佳实践?" ¯\_(ツ)_/¯ 您要建模什么?人们与之互动的实际物理甲板?您移除的卡片似乎无处可去。有了物理甲板,他们最终会回去。你只是想模拟更抽象的甲板交互吗?您可以将卡片标记为已消失,或者不要尝试将它们视为真正“将它们拉出”。
  • select 听起来好像以某种方式将它们标记为选中。我将方法命名为removeRandomCards。但除此之外,是的,具有返回值和副作用的方法很好。 CQS 并不适用于所有事物。

标签: javascript function oop encapsulation side-effects


【解决方案1】:

确实,您通常应该避免同时改变和返回值,但是好的规则总是有例外的。甚至本机 Java Script 也有例外,Array#pop 可能是最广为人知的一个。然而,许多人会同意pop 非常有用,因为它目前有效。此外,您在脚本中调用的Array#splice 会改变并返回信息。

除非您想放弃 OOP 并转向函数式编程,否则这种模式很好。我只是确保方法的名称尽可能少地对这种双重效果产生怀疑。出于这个原因,我会打电话给selectCards 而不是extractCardspullCards。这更强烈地暗示了套牌已经变异。

我还建议实现shuffle 方法,而不是在选择卡片时使用随机索引。如果您支持私有属性,则将deck 数组定义为私有,以便对外界隐藏打乱后的内容。

这就是我的意思(没有私人):

class Deck {
  constructor() {
    this.deck = Array.from('♦♣♥♠', suit =>
      ['A','2','3','4','5','6','7','8','9','10','J','Q','K'].map(
        value => `${value}${suit}`
      )
    ).flat();
  }

  shuffle() { // mutates the deck, much like Array#sort mutates an array
    let deck = this.deck;
    for (let i = deck.length - 1; i > 0; i--) {
      let j = Math.floor(Math.random() * (i + 1));
      let temp = deck[i];
      deck[i] = deck[j];
      deck[j] = temp;
    }
  }

  extractCards(numCards) {  // Better name. Mutates & returns. 
    // Perform a controlled Array#splice
    if (typeof numCards !== "number" || numCards <= 0) throw "Invalid argument";
    if (this.deck.length < numCards) throw "Deck does not have enough cards for this operation";
    return this.deck.splice(-numCards);
  }
}

let deck = new Deck();
deck.shuffle();
console.log(...deck.extractCards(4));

【讨论】:

  • 知道了。在这种情况下,使用单独的 removeIndexFromDeck() 方法是否有意义?还是只在 selectCards() 方法中发生突变就可以了。
  • 这取决于您在其他情况下是否也需要removeIndexFromDeck。如果您真的没有其他用途,您确实可以在 selectCards 方法中执行此操作。如果您保留它,我不会在其名称中重复 Deck,因为这在课堂上很清楚。也许你可以称它为removeCardAt
  • this.removeIndexFromDeck(randIndex); 通常是不好的做法,这是一种代码味道,可能会意外更改数据或整个类。它没有返回值,它正在从使用this.deck 的任何其他人那里拉出地毯。我不认为pop() 是“规则例外”的示例,有很多很棒的方法可以以不可变的方式操作数组。
  • @AndyRay,关于这个话题的意见总是不同的。这就是我们选择函数式编程的原因。然而,我不同意不基于不可变范式的代码结果是“一般” 不好的做法。在某个思想流派中,这可能是不好的做法。但我不会走得更远。您也知道,不可变的工作方式有其自身的缺点。
  • 如果你切换行的顺序 selectedCards.pushthis. removeIndexFromDeck 程序将会失败,这对我来说非常可怕。那里有一个隐含的/神奇的顺序,你不能从自己的方法中看到。或者,如果您选择将this.deck.length 从循环中拉出到一个变量中,它将失败,而且原因可能并不明显,因为数据在幕后被操纵。以这种方式编程通常是不好的做法。我也同意这个问题应该作为一个意见问题结束。
【解决方案2】:

如果您使用类,这通常意味着存在副作用,因为数据是类的一部分。 Aka 你得到的方法没有返回值(通常意味着它们有副作用),它们会改变类状态。您可以做的最接近的事情是尽可能多地创建方法返回新的牌组值,而不是使用变异/副作用数组方法,然后在您的父方法中执行this.deck = removeIndexFromDeck(index) 来限制您的位置有副作用。

如果你放弃类,转而创建操作数据的函数,并将 Deck 仅视为数据而不是智能类,则函数本身将不会产生副作用。

您还可以在进行突变时使用返回新的Deck 类实例的模式,这意味着Deck 中的数据将始终是不可变的。不过,在这种情况下,我没有看到这样做的好处。

【讨论】:

  • 在这种情况下,如果我们说在使用类时改变值是很常见的,那么使用 removeFromIndex() 方法还有意义吗?或者我可以将它作为 selectCards() 方法的一部分包含在内吗?
  • 为了封装你的数据层,我认为有一个 const removeFromDeck = (deck, index) =&gt; {...} 的函数是很好的,这样使用卡片组的人就不需要知道卡片组是如何操作的。在实现中,我还会有一堆数组/对象/集合实用程序函数,你可以在任何地方使用——比如 removeFromArray,它们返回输入数据的不可变副本。这通常称为“数据抽象”ocf.berkeley.edu/~shidi/cs61a/wiki/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-16
  • 2016-07-30
  • 1970-01-01
  • 2019-07-02
  • 2021-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多