2017-12-18 331 views
7
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语句中使用这样的函数,该语句包含它递增的变量,则条件将与顺序有关。

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

+4

为了便于阅读,我尽量避免这种副作用 - 但如果您提供更详细的使用案例可能会有帮助 – msrd0

+3

该代码几乎没有实际用途。它在很多方面都很糟糕,它无法描述。这就像你从来没有听说过(1)抽象。 ... (2)继承。 ... (3)多态性。调用一个函数来完成一项工作,在需要时调用它,该函数应该返回一个外部一致的答案。 –

+3

@JonGoodwin我没有看到这与继承或多态性有什么关系。 – Bergi

回答

12

无论您是否更改条件中使用的变量,条件都与订单有关。您用作示例的两个if语句是不同的,不管您是否使用myFunction()都会有所不同。它们等同于:

if (myFunction()) { 
    if (a === 2) { 
    alert("Hello, world!") 
    } 
} 

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

在我看来,在代码中坏的做法是不是你改变条件的操作数的条件内在价值的事实,但事实证明,您的应用程序状态暴露和处理内函数甚至不接受这个状态改变变量作为参数。我们通常尝试将函数与代码范围之外的代码隔离开来,并使用它们的返回值来影响代码的其余部分。全局变量占用时间的90%是一个糟糕的想法,随着代码库越来越大,它们倾向于创建难以追踪,调试和解决的问题。

+2

为了补充说明,条件的顺序依赖有时可用于优化。我们称之为短路。我建议阅读[这个问题]的答案(https://stackoverflow.com/questions/9344305/what-is-short-circuiting-and-how-is-it-used-when-programming-in-java)for短路评估的快速解释。 –

6

这是不好的做法,有以下原因:

  • 代码远比构建良好的代码的可读性。如果代码稍后由第三方检查,这非常重要。

  • 如果myfunction稍后更改,代码流是完全不可预知的,并且可能需要重大文档更新。

  • 小而简单的更改会对代码的执行产生严重影响。

  • 它看起来业余。

+0

这非常含糊。如果你提出了改进的具体建议,你的答案会更好。 – TinkerTenorSoftwareGuy

5

如果你不得不问,这不是一个好的做法。是的,对于您提到的原因,这是一个不好的做法:更改逻辑操作的操作数顺序不应影响结果,因此通常应避免条件中的副作用。特别是当它们隐藏在一个函数中时。

函数是纯粹的(只读取状态并执行一些逻辑)或者它是否突变状态应该从其名称中显而易见。您有几种选择来解决这个代码:

  • 把函数调用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()) … 
    
+0

我认为其实只有你的第一个建议是好的。 (把函数调用之前的if) 另外两个仍然混合状态变化的条件测试。谓词不应该改变状态。 –

+1

@TiagoCoelho我同意第一个是最好的,但我不会像说“永远”一样绝对。当然'changeAndTest'不再是一个纯谓词,但是有一些模式(特别是循环),在某种情况下你可以像'regex.match(string)'或'i - '那样做事情,而我不会看到在编写'const ok = changeAndTest();如果(好)... ...超过更简洁的变体而没有临时变量。 – Bergi

3

代码呈现更多然后一个不好的做法,实际上是:

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. 使用具有明确名称的常量。 (好吧,这可能会更好,但考虑到这个例子,请忍受我)。

+0

我不认为使用函数调用作为谓词作为条件语句的一部分是没有问题的,只要函数真的是纯函数即可。仅将本地符号引入作为存储函数结果的地方似乎要糟糕得多。 – Pointy

+0

在我看来,在条件语句中使用函数调用是令人困惑的。我更喜欢将结果分配给一个描述性名称的变量。例如: 'const hasFuel = driveCar(1000); if(!hasFuel){ refill(); }' –

+0

这并不需要OOP来解决根本问题。在你打破OOP之前,先看看问题的核心。原始问题可以通过函数式编程来解决。添加架构是一个单独的问题。 – TinkerTenorSoftwareGuy

5

MyFunction违反了原则Tell, Don't Ask

MyFunction改变的东西的状态下,从而使其成为一个命令。如果MyFunction成功或以某种方式无法增加a,则不应该返回true或false。它被赋予了一份工作,它必须试图成功,或者如果它发现现在工作是不可能的,它应该抛出异常。

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

一般来说,查询应不表现出副作用(可以观察到的,即不改变的东西)。一个好的查询可以像计算一样对待,因为对于相同的输入,它应该产生相同的输出(有时被描述为“幂等”)。

知道这些指南可以帮助您和其他人了解代码的原因也很重要。代码可以引起混淆,。对代码的混淆是对错误的孵化。

像Trier-Doer模式那样有很好的模式可以像你的代码示例一样使用,但是每个阅读它的人都必须理解通过名称和结构发生了什么。

+0

对于moveNext()数据库函数,您将遇到非常困难的时刻。它操作内部指针并在每次调用时返回不同的结果。 – danny117

1

改变内容的函数。这个世界到底是什么?这个函数必须改变东西,并在每次调用时返回不同的值。

考虑一副扑克牌的dealCard函数。它处理卡1-52。每次调用它都会返回一个不同的值。

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

/*我们只能假定阵列卡是洗牌*/

/*为简便起见,我们假设在甲板上是无限的,在52 */

不循环
+0

我的意思是,我不想让我的厨房重新排列,因为我打了电话。但是,如果我有一个清洁服务的安排,他们说他们将在15分钟内结束,这是一个不同的故事。如果在这种情况下“a”属于“dealCards”,并且每个人都可以通过调用该方法来改变'a',那么这没有问题。 Ruby数组shuffle只是给你一个全新的数组,这个数组已经被混洗了,所以它甚至不会让你感到困惑。 – Greg

+0

Greg。更多依赖订单的功能。 getNationalDebt( “USA”);时间(); IKEA( “厨房”); – danny117

+0

所有这些应该是我可以推理和可靠测试的可预测,可靠程序的输入。 – Greg

相关问题