2017-12-18 332 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

相關問題