使用在条件内更改内容的函数,从而使情况取决于订单依赖性是不好的


var a = 1;
function myFunction() {
    ++a;
    return true;
}
// Alert pops up.
if (myFunction() && a === 2) {
    alert("Hello, world!");
}
// Alert does not pop up.
if (a === 3 && myFunction()) {
    alert("Hello, universe!");
}

https://jsfiddle.net/3oda22e4/6/

myFunction递增变量并返回某些东西。如果我在包含其增量变量的if语句中使用类似的函数,则条件将取决于订单。

这样做是好还是坏的,为什么?

条件是否取决于订单,是否更改条件中使用的变量或不是。如果您使用myFunction(),则用作示例的语句是不同的,这两个是不同的。它们等同于:

if (myFunction()) {
   if (a === 2) {
     alert("Hello, world!")
   }
}
// Alert does not pop up.
if (a === 3) {
   if (myFunction()) {
     alert("Hello, universe!")
   }
}

我认为,代码中的不良习惯不是您在条件内更改条件的操作数的事实,而是您的应用程序状态在函数中被暴露和操纵,甚至不接受此状态在变化的函数中变量作为参数。我们通常会尝试将功能与范围之外的代码隔离,并使用其返回值来影响代码的其余部分。全局变量是一个错误的想法的90%,随着您的代码库变得越来越大,它们往往会产生难以追踪,调试和解决的问题。

这是不良习惯,出于以下原因:

  • 该代码远不如结构良好的代码读取。如果稍后由第三方检查代码,这将非常重要。

  • 如果以后更改了myfunction,则代码流是完全不可预测的,可能需要大量文档更新。

  • 小而简单的更改可能会对执行代码产生巨大影响。

  • 看起来很业余。

如果您必须问,那不是一个好习惯。是的,这是您提到的确切原因的不良习惯:更改逻辑操作的操作数的顺序不应影响结果,因此通常应避免条件下的副作用。特别是当它们隐藏在功能中时。

该函数是纯净的(仅读取状态并执行一些逻辑)还是从其名称中显而易见的状态应该是显而易见的。您有几个修复此代码的选项:

  • 将功能调用放在if之前:

    function tryChangeA() {
        a++;
        return true;
    }
    var ok = tryChangeA();
    if (ok && a == 2) … // alternatively: if (a == 2 && ok)
    
  • 使if内的突变显式:

    function testSomething(val) {
        return true;
    }
    if (testSomething(++a) && a == 2) …
    
  • 将逻辑放入所谓的函数中:

    function changeAndTest() {
        a++;
        return a == 2;
    }
    if (changeAndTest()) …
    

MyFunction违反了一个称为告诉的原则,不要问。

MyFunction改变了某物的状态,从而使其成为命令。如果MyFunction成功或某种程度上无法递增a,则不应返回True或False。它得到了一份工作,它必须尝试成功,或者如果发现目前不可能做出工作,则应引发例外。

在if语句的谓词中, MyFunction用作查询。

一般而言,查询不应表现出副作用(即不改变可以观察到的内容)。一个好的查询可以像计算一样对待,因为对于相同的输入,它应该产生相同的输出(有时被描述为" iDempotent")。

也重要的是要知道这些是帮助您和其他人有关该代码的指南。可以引起混乱的代码, will 。关于代码的混乱是错误的孵化场。

有诸如Trier-doer模式之类的良好模式可以像您的代码示例一样使用,但是每个人都必须通过名称和结构来了解正在发生的事情。

该代码实际上比一个不好的做法更多:

var a = 1;
function myFunction() {
    ++a; // 1
    return true;
}
if (myFunction() && a === 2) { // 2, 3, 4
    alert("Hello, world!")
}
if (a === 3 && myFunction()) { // 2, 3, 4
    alert("Hello, universe!")
}
  1. 在不同范围中突变一个变量。这可能是一个问题,也可能不是问题,但通常是。

  2. if语句条件中调用功能。这本身不会引起问题,但并不是很干净。将该功能的结果分配给变量,可能具有描述性名称是一个更好的做法。这将帮助阅读代码的任何人了解您想在if语句中检查什么。顺便说一句,函数始终返回true

  3. 使用一些魔术数字。想象一下其他人阅读该代码,这是大型代码库的一部分。这些数字是什么意思?一个更好的解决方案是用命名良好的常数替换它们。

  4. 如果要支持更多消息,则需要添加更多条件。更好的方法是使此配置。

我将按照以下方式重写代码:

const ALERT_CONDITIONS = { // 4
  WORLD_MENACE: 2,
  UNIVERSE_MENACE: 3,
};
const alertsList = [
  {
    message: 'Hello world',
    condition: ALERT_CONDITIONS.WORLD_MENACE,
  },
  {
    message: 'Hello universe',
    condition: ALERT_CONDITIONS.UNIVERSE_MENACE,
  },
];

class AlertManager {
  constructor(config, defaultMessage) {
    this.counter = 0; // 1
    this.config = config; // 2
    this.defaultMessage = defaultMessage;
  }
  incrementCounter() {
    this.counter++;
  }
  showAlert() {
    this.incrementCounter();
    let customMessageBroadcasted = false;
    this.config.forEach(entry => { //2
      if (entry.condition === this.counter) {
        console.log(entry.message);
        customMessageBroadcasted = true; // 3
      }
    });
    if (!customMessageBroadcasted) {
      console.log(this.defaultMessage)
    }
  }
}
const alertManager = new AlertManager(alertsList, 'Nothing to alert');
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
  1. 具有精确函数的类,它使用其自身的内部状态,而不是依赖某些可以位于任何地方的变量的函数。是否使用班级,这是选择问题。可以以不同的方式完成。

  2. 使用配置。这意味着您想添加更多消息,您根本不必触摸代码。例如,想象一下来自数据库的配置。

  3. 您可能会注意到,这会在功能的外部范围中变量,但是在这种情况下,它不会引起任何问题。

  4. 使用带有清晰名称的常数。(嗯,可能会更好,但是以我的例子为例)。

更改内容的函数。世界也是什么?此函数必须每次调用时都会更改内容并返回不同的值。

考虑一台扑克牌的DealCard功能。它处理卡1-52。每次称呼它都应返回一个不同的值。

function dealCard() {
    ++a;
    return cards(a);
}

/*我们只假设数组卡被改组 */

/*为了简洁起见,我们将假设甲板是无限的,并且在52*/

上不循环

相关内容

最新更新